/******************************************************************************* * * Copyright (c) 2004-2012 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Winston Prakash * * *******************************************************************************/ package hudson.cli; import hudson.FilePath; import hudson.FilePath.FileCallable; import hudson.Util; import hudson.model.Hudson; import hudson.model.Hudson.MasterComputer; import hudson.remoting.Callable; import hudson.remoting.Channel; import hudson.remoting.VirtualChannel; import hudson.util.Secret; import org.springframework.dao.DataAccessException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.Properties; import org.eclipse.hudson.jna.NativeUtils; import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; /** * Represents the authentication credential store of the CLI client. * * <p> This object encapsulates a remote manipulation of the credential store. * We store encrypted user names. * * @author Kohsuke Kawaguchi * @since 1.351 */ public class ClientAuthenticationCache implements Serializable { /** * Where the store should be placed. */ private final FilePath store; /** * Loaded contents of the store. */ private final Properties props = new Properties(); public ClientAuthenticationCache(Channel channel) throws IOException, InterruptedException { store = (channel == null ? MasterComputer.localChannel : channel).call(new Callable<FilePath, IOException>() { public FilePath call() throws IOException { File home = new File(System.getProperty("user.home")); return new FilePath(new File(home, ".hudson/cli-credentials")); } }); if (store.exists()) { props.load(store.read()); } } /** * Gets the persisted authentication for this Hudson. * * @return {@link Hudson#ANONYMOUS} if no such credential is found, or if * the stored credential is invalid. */ public Authentication get() { Secret userName = Secret.decrypt(props.getProperty(getPropertyKey())); if (userName == null) { return Hudson.ANONYMOUS; // failed to decrypt } try { UserDetails u = HudsonSecurityEntitiesHolder.getHudsonSecurityManager().getSecurityRealm().loadUserByUsername(userName.toString()); return new UsernamePasswordAuthenticationToken(u.getUsername(), u.getPassword(), u.getAuthorities()); } catch (AuthenticationException e) { return Hudson.ANONYMOUS; } catch (DataAccessException e) { return Hudson.ANONYMOUS; } } /** * Computes the key that identifies this Hudson among other Hudsons that the * user has a credential for. */ private String getPropertyKey() { String url = Hudson.getInstance().getRootUrl(); if (url != null) { return url; } return Secret.fromString("key").toString(); } /** * Persists the specified authentication. */ public void set(Authentication a) throws IOException, InterruptedException { Hudson h = Hudson.getInstance(); // make sure that this security realm is capable of retrieving the authentication by name, // as it's not required. UserDetails u = h.getSecurityRealm().loadUserByUsername(a.getName()); props.setProperty(getPropertyKey(), Secret.fromString(u.getUsername()).getEncryptedValue()); save(); } /** * Removes the persisted credential, if there's one. */ public void remove() throws IOException, InterruptedException { if (props.remove(getPropertyKey()) != null) { save(); } } private void save() throws IOException, InterruptedException { final NativeUtils nativeUtils = NativeUtils.getInstance(); store.act(new FileCallable<Void>() { public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { f.getParentFile().mkdirs(); OutputStream os = new FileOutputStream(f); try { props.store(os, "Credential store"); } finally { os.close(); } // try to protect this file from other users, if we can. Util.chmod(f, 0600, false, null); return null; } }); } }