|
@@ -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; |
|
|
} |
|
|
} |