Skip to content
Permalink
Browse files

[JENKINS-27045] Jenkins CLI --username/--password

options.

Here's an example:

    java -jar jenkins-cli.jar -s http://localhost:8080 -noKeyAuth who-am-i \
      --username samrocketman --password-file ./personal-access-token

Unfortunately, Jenkins core prevents Jenkins CLI LoginCommand from
working correctly because it only stores the Username and mishandles
creating the authentication token.  This is a [bug in core][1].

[1]: https://github.com/jenkinsci/jenkins/blob/jenkins-2.47/core/src/main/java/hudson/cli/ClientAuthenticationCache.java#L65-L75
  • Loading branch information
samrocketman committed Feb 26, 2017
1 parent 120f8ec commit b751298fe57873592d6ded8eda0eb6098c4bccd4
Showing with 78 additions and 3 deletions.
  1. +78 −3 src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java
@@ -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,58 @@ public UserDetails loadUserByUsername(String username)
});
}

@Override
protected GithubOAuthUserDetails authenticate(String username, String password) throws AuthenticationException {
//UserDetails u = loadUserByUsername(username);
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.");
}
UserDetails d = authenticate(userName, password);
return new GithubAuthenticationToken(password, getGithubApiUri());
}
};
}

@Override
public String getLoginUrl() {
return "securityRealm/commenceLogin";
@@ -686,4 +745,20 @@ 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;
}
}

0 comments on commit b751298

Please sign in to comment.