Skip to content

Commit

Permalink
[JENKINS-47113] Populate the authorities after a successful authentic…
Browse files Browse the repository at this point in the history
…ation to Github (#87)

This change stores a GitHub token in a user property for reuse by other
authorization method.  Specifically, the token in which the user authorized for 
Jenkins to collect consenting through OAuth.
  • Loading branch information
Wadeck authored and samrocketman committed Dec 7, 2017
1 parent 6602105 commit 7e13146
Show file tree
Hide file tree
Showing 7 changed files with 649 additions and 3 deletions.
66 changes: 66 additions & 0 deletions src/main/java/org/jenkinsci/plugins/GithubAccessTokenProperty.java
@@ -0,0 +1,66 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins;

import hudson.Extension;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import hudson.util.Secret;
import org.jenkinsci.Symbol;

import javax.annotation.Nonnull;

/**
* Remembers the access token used to connect to the Github server
*
* @since TODO
*/
public class GithubAccessTokenProperty extends UserProperty {
private final Secret accessToken;

public GithubAccessTokenProperty(String accessToken) {
this.accessToken = Secret.fromString(accessToken);
}

public @Nonnull Secret getAccessToken() {
return accessToken;
}

@Extension
@Symbol("githubAccessToken")
public static final class DescriptorImpl extends UserPropertyDescriptor {
@Override
public boolean isEnabled() {
// does not show elements in /<user>/configure/
return false;
}

@Override
public UserProperty newInstance(User user) {
// no default property
return null;
}
}
}
Expand Up @@ -412,6 +412,8 @@ public GithubOAuthUserDetails getUserDetails(String username) throws IOException

public GrantedAuthority[] getGrantedAuthorities(GHUser user) {
List<GrantedAuthority> groups = new ArrayList<GrantedAuthority>();
groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY);

try {
GHPersonSet<GHOrganization> orgs;
if(myRealm == null) {
Expand Down Expand Up @@ -444,7 +446,7 @@ public GrantedAuthority[] getGrantedAuthorities(GHUser user) {
GHTeam team = entry.getValue();
if (team.hasMember(user)) {
groups.add(new GrantedAuthorityImpl(orgLogin + GithubOAuthGroupDetails.ORG_TEAM_SEPARATOR
+ team));
+ team.getName()));
}
}
} catch (IOException | Error ignore) {
Expand Down
62 changes: 62 additions & 0 deletions src/main/java/org/jenkinsci/plugins/GithubSecretStorage.java
@@ -0,0 +1,62 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins;

import hudson.model.User;
import org.jfree.util.Log;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.IOException;

public class GithubSecretStorage {

private GithubSecretStorage(){
// no accessible constructor
}

public static boolean contains(@Nonnull User user) {
return user.getProperty(GithubAccessTokenProperty.class) != null;
}

public static @CheckForNull String retrieve(@Nonnull User user) {
GithubAccessTokenProperty property = user.getProperty(GithubAccessTokenProperty.class);
if (property == null) {
Log.debug("Cache miss for username: " + user.getId());
return null;
} else {
Log.debug("Token retrieved using cache for username: " + user.getId());
return property.getAccessToken().getPlainText();
}
}

public static void put(@Nonnull User user, @Nonnull String accessToken) {
Log.debug("Populating the cache for username: " + user.getId());
try {
user.addProperty(new GithubAccessTokenProperty(accessToken));
} catch (IOException e) {
Log.warn("Received an exception when trying to add the GitHub access token to the user: " + user.getId(), e);
}
}
}
30 changes: 28 additions & 2 deletions src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java
Expand Up @@ -379,6 +379,9 @@ public HttpResponse doFinishLogin(StaplerRequest request)
if (u == null) {
throw new IllegalStateException("Can't find user");
}

GithubSecretStorage.put(u, accessToken);

u.setFullName(self.getName());
// Set email from github only if empty
if (!u.getProperty(Mailer.UserProperty.class).hasExplicitlyConfiguredAddress()) {
Expand All @@ -398,6 +401,10 @@ public HttpResponse doFinishLogin(StaplerRequest request)
}

SecurityListener.fireAuthenticated(new GithubOAuthUserDetails(self.getLogin(), auth.getAuthorities()));

// While LastGrantedAuthorities are triggered by that event, we cannot trigger it there
// or modifications in organizations will be not reflected when using API Token, due to that caching
// SecurityListener.fireLoggedIn(self.getLogin());
} else {
Log.info("Github did not return an access token.");
}
Expand Down Expand Up @@ -477,6 +484,14 @@ public Authentication authenticate(Authentication authentication)
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
GithubAuthenticationToken github = new GithubAuthenticationToken(token.getCredentials().toString(), getGithubApiUri());
SecurityContextHolder.getContext().setAuthentication(github);

User user = User.getById(token.getName(), false);
if(user != null){
GithubSecretStorage.put(user, token.getCredentials().toString());
}

SecurityListener.fireAuthenticated(new GithubOAuthUserDetails(token.getName(), github.getAuthorities()));

return github;
} catch (IOException e) {
throw new RuntimeException(e);
Expand Down Expand Up @@ -621,10 +636,22 @@ public UserDetails loadUserByUsername(String username)
throw new UsernameNotFoundException("Using org*team format instead of username: " + username);
}

User localUser = User.getById(username, false);

Authentication token = SecurityContextHolder.getContext().getAuthentication();

if (token == null) {
throw new UserMayOrMayNotExistException("Could not get auth token.");
if(localUser != null && GithubSecretStorage.contains(localUser)){
String accessToken = GithubSecretStorage.retrieve(localUser);
try {
token = new GithubAuthenticationToken(accessToken, getGithubApiUri());
} catch (IOException e) {
throw new UserMayOrMayNotExistException("Could not connect to GitHub API server, target URL = " + getGithubApiUri(), e);
}
SecurityContextHolder.getContext().setAuthentication(token);
}else{
throw new UserMayOrMayNotExistException("Could not get auth token.");
}
}

GithubAuthenticationToken authToken;
Expand All @@ -639,7 +666,6 @@ public UserDetails loadUserByUsername(String username)
* Always lookup the local user first. If we can't resolve it then we can burn an API request to Github for this user
* Taken from hudson.security.HudsonPrivateSecurityRealm#loadUserByUsername(java.lang.String)
*/
User localUser = User.getById(username, false);
if (localUser != null) {
return new GithubOAuthUserDetails(username, authToken);
}
Expand Down

0 comments on commit 7e13146

Please sign in to comment.