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 27, 2017
1 parent 120f8ec commit 3f64c19adf34802b989717e7c73068959d2c1f74
@@ -0,0 +1 @@
-Xmx256m -XX:MaxPermSize=128m -Djava.awt.headless=true
@@ -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;
}
}
@@ -25,7 +25,7 @@ of this software and associated documentation files (the "Software"), to deal
package org.jenkinsci.plugins;

import org.jenkinsci.plugins.GithubSecurityRealm.DescriptorImpl;
import org.junit.Rule;
import org.junit.ClassRule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

@@ -35,8 +35,8 @@ of this software and associated documentation files (the "Software"), to deal

public class GithubSecurityRealmTest {

@Rule
public final JenkinsRule rule = new JenkinsRule();
@ClassRule
public final static JenkinsRule rule = new JenkinsRule();

@Test
public void testEquals_true() {

0 comments on commit 3f64c19

Please sign in to comment.