Skip to content

Commit 3eeba9c

Browse files
authored
Merge pull request #77 from jenkinsci/jenkins-cli-token
[JENKINS-27045] Jenkins CLI --username/--password options
2 parents 120f8ec + e5858c3 commit 3eeba9c

File tree

4 files changed

+85
-7
lines changed

4 files changed

+85
-7
lines changed

.mvn/jvm.config

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xmx256m -XX:MaxPermSize=128m -Djava.awt.headless=true

LICENSE.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ The MIT License
22

33
Copyright (c) 2011 Michael O'Cleirigh
44
Copyright (c) 2013-2014 Sam Kottler
5-
Copyright (c) 2015 Sam Gleske
5+
Copyright (c) 2015-2017 Sam Gleske
6+
Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors - https://github.com/jenkinsci/jenkins
67

78
Permission is hereby granted, free of charge, to any person obtaining a copy
89
of this software and associated documentation files (the "Software"), to deal

src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java

+79-3
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,21 @@ of this software and associated documentation files (the "Software"), to deal
3333
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
3434
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
3535
import hudson.Extension;
36+
import hudson.FilePath;
3637
import hudson.ProxyConfiguration;
3738
import hudson.Util;
39+
import hudson.cli.CLICommand;
3840
import hudson.model.Descriptor;
3941
import hudson.model.User;
42+
import hudson.security.AbstractPasswordBasedSecurityRealm;
43+
import hudson.security.CliAuthenticator;
4044
import hudson.security.GroupDetails;
4145
import hudson.security.SecurityRealm;
4246
import hudson.security.UserMayOrMayNotExistException;
4347
import hudson.tasks.Mailer;
4448
import hudson.util.Secret;
4549
import jenkins.model.Jenkins;
50+
import jenkins.security.MasterToSlaveCallable;
4651
import jenkins.security.SecurityListener;
4752
import org.acegisecurity.Authentication;
4853
import org.acegisecurity.AuthenticationException;
@@ -64,6 +69,7 @@ of this software and associated documentation files (the "Software"), to deal
6469
import org.apache.http.impl.client.HttpClients;
6570
import org.apache.http.util.EntityUtils;
6671
import org.jfree.util.Log;
72+
import org.kohsuke.args4j.Option;
6773
import org.kohsuke.github.GHEmail;
6874
import org.kohsuke.github.GHMyself;
6975
import org.kohsuke.github.GHOrganization;
@@ -77,15 +83,16 @@ of this software and associated documentation files (the "Software"), to deal
7783
import org.springframework.dao.DataAccessException;
7884
import org.springframework.dao.DataRetrievalFailureException;
7985

80-
import javax.annotation.Nonnull;
81-
import javax.annotation.Nullable;
86+
import java.io.Console;
8287
import java.io.IOException;
8388
import java.net.InetSocketAddress;
8489
import java.net.Proxy;
8590
import java.util.Arrays;
8691
import java.util.HashSet;
8792
import java.util.Set;
8893
import java.util.logging.Logger;
94+
import javax.annotation.Nonnull;
95+
import javax.annotation.Nullable;
8996

9097
/**
9198
*
@@ -95,7 +102,7 @@ of this software and associated documentation files (the "Software"), to deal
95102
* This is based on the MySQLSecurityRealm from the mysql-auth-plugin written by
96103
* Alex Ackerman.
97104
*/
98-
public class GithubSecurityRealm extends SecurityRealm implements UserDetailsService {
105+
public class GithubSecurityRealm extends AbstractPasswordBasedSecurityRealm implements UserDetailsService {
99106
private static final String DEFAULT_WEB_URI = "https://github.com";
100107
private static final String DEFAULT_API_URI = "https://api.github.com";
101108
private static final String DEFAULT_ENTERPRISE_API_SUFFIX = "/api/v3";
@@ -485,6 +492,57 @@ public UserDetails loadUserByUsername(String username)
485492
});
486493
}
487494

495+
@Override
496+
protected GithubOAuthUserDetails authenticate(String username, String password) throws AuthenticationException {
497+
try {
498+
GithubAuthenticationToken github = new GithubAuthenticationToken(password, getGithubApiUri());
499+
if(username.equals(github.getPrincipal())) {
500+
SecurityContextHolder.getContext().setAuthentication(github);
501+
return github.getUserDetails(username);
502+
}
503+
} catch (IOException e) {
504+
throw new RuntimeException(e);
505+
}
506+
throw new BadCredentialsException("Invalid GitHub username or personal access token: " + username);
507+
}
508+
509+
@Override
510+
public CliAuthenticator createCliAuthenticator(final CLICommand command) {
511+
return new CliAuthenticator() {
512+
@Option(name="--username",usage="GitHub username to authenticate yourself to Jenkins.")
513+
public String userName;
514+
515+
@Option(name="--password",usage="GitHub personal access token. Note that passing a password in arguments is insecure.")
516+
public String password;
517+
518+
@Option(name="--password-file",usage="File that contains the personal access token.")
519+
public String passwordFile;
520+
521+
public Authentication authenticate() throws AuthenticationException, IOException, InterruptedException {
522+
if(userName == null) {
523+
//return command.getTransportAuthentication(); // no authentication parameter. fallback to the transport
524+
return Jenkins.ANONYMOUS;
525+
}
526+
if(passwordFile != null) {
527+
try {
528+
password = new FilePath(command.channel, passwordFile).readToString().trim();
529+
} catch (IOException e) {
530+
throw new BadCredentialsException("Failed to read " + passwordFile, e);
531+
}
532+
}
533+
if(password == null) {
534+
password = command.channel.call(new InteractivelyAskForPassword());
535+
}
536+
537+
if(password == null) {
538+
throw new BadCredentialsException("No GitHub personal access token specified.");
539+
}
540+
GithubSecurityRealm.this.authenticate(userName, password);
541+
return new GithubAuthenticationToken(password, getGithubApiUri());
542+
}
543+
};
544+
}
545+
488546
@Override
489547
public String getLoginUrl() {
490548
return "securityRealm/commenceLogin";
@@ -686,4 +744,22 @@ static Jenkins getJenkins() {
686744
private static final Logger LOGGER = Logger.getLogger(GithubSecurityRealm.class.getName());
687745

688746
private static final String REFERER_ATTRIBUTE = GithubSecurityRealm.class.getName()+".referer";
747+
748+
/**
749+
* Asks for the password.
750+
*/
751+
private static class InteractivelyAskForPassword extends MasterToSlaveCallable<String,IOException> {
752+
public String call() throws IOException {
753+
Console console = System.console();
754+
if(console == null) {
755+
return null; // no terminal
756+
}
757+
char[] w = console.readPassword("GitHub Personal Access Token: ");
758+
if(w==null) {
759+
return null;
760+
}
761+
return new String(w);
762+
}
763+
private static final long serialVersionUID = 1L;
764+
}
689765
}

src/test/java/org/jenkinsci/plugins/GithubSecurityRealmTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ of this software and associated documentation files (the "Software"), to deal
2525
package org.jenkinsci.plugins;
2626

2727
import org.jenkinsci.plugins.GithubSecurityRealm.DescriptorImpl;
28-
import org.junit.Rule;
28+
import org.junit.ClassRule;
2929
import org.junit.Test;
3030
import org.jvnet.hudson.test.JenkinsRule;
3131

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

3636
public class GithubSecurityRealmTest {
3737

38-
@Rule
39-
public final JenkinsRule rule = new JenkinsRule();
38+
@ClassRule
39+
public final static JenkinsRule rule = new JenkinsRule();
4040

4141
@Test
4242
public void testEquals_true() {

0 commit comments

Comments
 (0)