@@ -33,16 +33,21 @@ of this software and associated documentation files (the "Software"), to deal
import com.thoughtworks.xstream.io.HierarchicalStreamReader ;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter ;
import hudson.Extension ;
import hudson.FilePath ;
import hudson.ProxyConfiguration ;
import hudson.Util ;
import hudson.cli.CLICommand ;
import hudson.model.Descriptor ;
import hudson.model.User ;
import hudson.security.AbstractPasswordBasedSecurityRealm ;
import hudson.security.CliAuthenticator ;
import hudson.security.GroupDetails ;
import hudson.security.SecurityRealm ;
import hudson.security.UserMayOrMayNotExistException ;
import hudson.tasks.Mailer ;
import hudson.util.Secret ;
import jenkins.model.Jenkins ;
import jenkins.security.MasterToSlaveCallable ;
import jenkins.security.SecurityListener ;
import org.acegisecurity.Authentication ;
import org.acegisecurity.AuthenticationException ;
@@ -64,6 +69,7 @@ of this software and associated documentation files (the "Software"), to deal
import org.apache.http.impl.client.HttpClients ;
import org.apache.http.util.EntityUtils ;
import org.jfree.util.Log ;
import org.kohsuke.args4j.Option ;
import org.kohsuke.github.GHEmail ;
import org.kohsuke.github.GHMyself ;
import org.kohsuke.github.GHOrganization ;
@@ -77,15 +83,16 @@ of this software and associated documentation files (the "Software"), to deal
import org.springframework.dao.DataAccessException ;
import org.springframework.dao.DataRetrievalFailureException ;
import javax.annotation.Nonnull ;
import javax.annotation.Nullable ;
import java.io.Console ;
import java.io.IOException ;
import java.net.InetSocketAddress ;
import java.net.Proxy ;
import java.util.Arrays ;
import java.util.HashSet ;
import java.util.Set ;
import java.util.logging.Logger ;
import javax.annotation.Nonnull ;
import javax.annotation.Nullable ;
/**
*
@@ -95,7 +102,7 @@ of this software and associated documentation files (the "Software"), to deal
* This is based on the MySQLSecurityRealm from the mysql-auth-plugin written by
* Alex Ackerman.
*/
public class GithubSecurityRealm extends SecurityRealm implements UserDetailsService {
public class GithubSecurityRealm extends AbstractPasswordBasedSecurityRealm implements UserDetailsService {
private static final String DEFAULT_WEB_URI = " https://github.com" ;
private static final String DEFAULT_API_URI = " https://api.github.com" ;
private static final String DEFAULT_ENTERPRISE_API_SUFFIX = " /api/v3" ;
@@ -485,6 +492,57 @@ public UserDetails loadUserByUsername(String username)
});
}
@Override
protected GithubOAuthUserDetails authenticate (String username , String password ) throws AuthenticationException {
try {
GithubAuthenticationToken github = new GithubAuthenticationToken (password, getGithubApiUri());
if (username. equals(github. getPrincipal())) {
SecurityContextHolder . getContext(). setAuthentication(github);
return github. getUserDetails(username);
}
} catch (IOException e) {
throw new RuntimeException (e);
}
throw new BadCredentialsException (" Invalid GitHub username or personal access token: " + username);
}
@Override
public CliAuthenticator createCliAuthenticator (final CLICommand command ) {
return new CliAuthenticator () {
@Option (name = " --username" ,usage = " GitHub username to authenticate yourself to Jenkins." )
public String userName;
@Option (name = " --password" ,usage = " GitHub personal access token. Note that passing a password in arguments is insecure." )
public String password;
@Option (name = " --password-file" ,usage = " File that contains the personal access token." )
public String passwordFile;
public Authentication authenticate () throws AuthenticationException , IOException , InterruptedException {
if (userName == null ) {
// return command.getTransportAuthentication(); // no authentication parameter. fallback to the transport
return Jenkins . ANONYMOUS ;
}
if (passwordFile != null ) {
try {
password = new FilePath (command. channel, passwordFile). readToString(). trim();
} catch (IOException e) {
throw new BadCredentialsException (" Failed to read " + passwordFile, e);
}
}
if (password == null ) {
password = command. channel. call(new InteractivelyAskForPassword ());
}
if (password == null ) {
throw new BadCredentialsException (" No GitHub personal access token specified." );
}
GithubSecurityRealm . this . authenticate(userName, password);
return new GithubAuthenticationToken (password, getGithubApiUri());
}
};
}
@Override
public String getLoginUrl () {
return " securityRealm/commenceLogin" ;
@@ -686,4 +744,22 @@ static Jenkins getJenkins() {
private static final Logger LOGGER = Logger . getLogger(GithubSecurityRealm . class. getName());
private static final String REFERER_ATTRIBUTE = GithubSecurityRealm . class. getName()+ " .referer" ;
/**
* Asks for the password.
*/
private static class InteractivelyAskForPassword extends MasterToSlaveCallable<String ,IOException > {
public String call () throws IOException {
Console console = System . console();
if (console == null ) {
return null ; // no terminal
}
char [] w = console. readPassword(" GitHub Personal Access Token: " );
if (w== null ) {
return null ;
}
return new String (w);
}
private static final long serialVersionUID = 1L ;
}
}