Skip to content

Commit 7e13146

Browse files
Wadecksamrocketman
authored andcommitted
[JENKINS-47113] Populate the authorities after a successful authentication 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.
1 parent 6602105 commit 7e13146

File tree

7 files changed

+649
-3
lines changed

7 files changed

+649
-3
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2017, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package org.jenkinsci.plugins;
25+
26+
import hudson.Extension;
27+
import hudson.model.User;
28+
import hudson.model.UserProperty;
29+
import hudson.model.UserPropertyDescriptor;
30+
import hudson.util.Secret;
31+
import org.jenkinsci.Symbol;
32+
33+
import javax.annotation.Nonnull;
34+
35+
/**
36+
* Remembers the access token used to connect to the Github server
37+
*
38+
* @since TODO
39+
*/
40+
public class GithubAccessTokenProperty extends UserProperty {
41+
private final Secret accessToken;
42+
43+
public GithubAccessTokenProperty(String accessToken) {
44+
this.accessToken = Secret.fromString(accessToken);
45+
}
46+
47+
public @Nonnull Secret getAccessToken() {
48+
return accessToken;
49+
}
50+
51+
@Extension
52+
@Symbol("githubAccessToken")
53+
public static final class DescriptorImpl extends UserPropertyDescriptor {
54+
@Override
55+
public boolean isEnabled() {
56+
// does not show elements in /<user>/configure/
57+
return false;
58+
}
59+
60+
@Override
61+
public UserProperty newInstance(User user) {
62+
// no default property
63+
return null;
64+
}
65+
}
66+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,8 @@ public GithubOAuthUserDetails getUserDetails(String username) throws IOException
412412

413413
public GrantedAuthority[] getGrantedAuthorities(GHUser user) {
414414
List<GrantedAuthority> groups = new ArrayList<GrantedAuthority>();
415+
groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY);
416+
415417
try {
416418
GHPersonSet<GHOrganization> orgs;
417419
if(myRealm == null) {
@@ -444,7 +446,7 @@ public GrantedAuthority[] getGrantedAuthorities(GHUser user) {
444446
GHTeam team = entry.getValue();
445447
if (team.hasMember(user)) {
446448
groups.add(new GrantedAuthorityImpl(orgLogin + GithubOAuthGroupDetails.ORG_TEAM_SEPARATOR
447-
+ team));
449+
+ team.getName()));
448450
}
449451
}
450452
} catch (IOException | Error ignore) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2017, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package org.jenkinsci.plugins;
25+
26+
import hudson.model.User;
27+
import org.jfree.util.Log;
28+
29+
import javax.annotation.CheckForNull;
30+
import javax.annotation.Nonnull;
31+
import java.io.IOException;
32+
33+
public class GithubSecretStorage {
34+
35+
private GithubSecretStorage(){
36+
// no accessible constructor
37+
}
38+
39+
public static boolean contains(@Nonnull User user) {
40+
return user.getProperty(GithubAccessTokenProperty.class) != null;
41+
}
42+
43+
public static @CheckForNull String retrieve(@Nonnull User user) {
44+
GithubAccessTokenProperty property = user.getProperty(GithubAccessTokenProperty.class);
45+
if (property == null) {
46+
Log.debug("Cache miss for username: " + user.getId());
47+
return null;
48+
} else {
49+
Log.debug("Token retrieved using cache for username: " + user.getId());
50+
return property.getAccessToken().getPlainText();
51+
}
52+
}
53+
54+
public static void put(@Nonnull User user, @Nonnull String accessToken) {
55+
Log.debug("Populating the cache for username: " + user.getId());
56+
try {
57+
user.addProperty(new GithubAccessTokenProperty(accessToken));
58+
} catch (IOException e) {
59+
Log.warn("Received an exception when trying to add the GitHub access token to the user: " + user.getId(), e);
60+
}
61+
}
62+
}

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ public HttpResponse doFinishLogin(StaplerRequest request)
379379
if (u == null) {
380380
throw new IllegalStateException("Can't find user");
381381
}
382+
383+
GithubSecretStorage.put(u, accessToken);
384+
382385
u.setFullName(self.getName());
383386
// Set email from github only if empty
384387
if (!u.getProperty(Mailer.UserProperty.class).hasExplicitlyConfiguredAddress()) {
@@ -398,6 +401,10 @@ public HttpResponse doFinishLogin(StaplerRequest request)
398401
}
399402

400403
SecurityListener.fireAuthenticated(new GithubOAuthUserDetails(self.getLogin(), auth.getAuthorities()));
404+
405+
// While LastGrantedAuthorities are triggered by that event, we cannot trigger it there
406+
// or modifications in organizations will be not reflected when using API Token, due to that caching
407+
// SecurityListener.fireLoggedIn(self.getLogin());
401408
} else {
402409
Log.info("Github did not return an access token.");
403410
}
@@ -477,6 +484,14 @@ public Authentication authenticate(Authentication authentication)
477484
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
478485
GithubAuthenticationToken github = new GithubAuthenticationToken(token.getCredentials().toString(), getGithubApiUri());
479486
SecurityContextHolder.getContext().setAuthentication(github);
487+
488+
User user = User.getById(token.getName(), false);
489+
if(user != null){
490+
GithubSecretStorage.put(user, token.getCredentials().toString());
491+
}
492+
493+
SecurityListener.fireAuthenticated(new GithubOAuthUserDetails(token.getName(), github.getAuthorities()));
494+
480495
return github;
481496
} catch (IOException e) {
482497
throw new RuntimeException(e);
@@ -621,10 +636,22 @@ public UserDetails loadUserByUsername(String username)
621636
throw new UsernameNotFoundException("Using org*team format instead of username: " + username);
622637
}
623638

639+
User localUser = User.getById(username, false);
640+
624641
Authentication token = SecurityContextHolder.getContext().getAuthentication();
625642

626643
if (token == null) {
627-
throw new UserMayOrMayNotExistException("Could not get auth token.");
644+
if(localUser != null && GithubSecretStorage.contains(localUser)){
645+
String accessToken = GithubSecretStorage.retrieve(localUser);
646+
try {
647+
token = new GithubAuthenticationToken(accessToken, getGithubApiUri());
648+
} catch (IOException e) {
649+
throw new UserMayOrMayNotExistException("Could not connect to GitHub API server, target URL = " + getGithubApiUri(), e);
650+
}
651+
SecurityContextHolder.getContext().setAuthentication(token);
652+
}else{
653+
throw new UserMayOrMayNotExistException("Could not get auth token.");
654+
}
628655
}
629656

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

0 commit comments

Comments
 (0)