diff --git a/pom.xml b/pom.xml
index ab9a76a1..2446e494 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,7 @@
org.jenkins-ci.plugins
github-api
- 1.58
+ 1.67
diff --git a/src/main/java/org/jenkinsci/plugins/GithubAuthenticationToken.java b/src/main/java/org/jenkinsci/plugins/GithubAuthenticationToken.java
index 673d59d1..3535d542 100644
--- a/src/main/java/org/jenkinsci/plugins/GithubAuthenticationToken.java
+++ b/src/main/java/org/jenkinsci/plugins/GithubAuthenticationToken.java
@@ -27,7 +27,6 @@ of this software and associated documentation files (the "Software"), to deal
package org.jenkinsci.plugins;
import java.io.IOException;
-import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -43,6 +42,8 @@ of this software and associated documentation files (the "Software"), to deal
import com.google.common.cache.CacheBuilder;
import hudson.security.SecurityRealm;
import java.util.Collection;
+
+import org.jenkinsci.plugins.GithubOAuthUserDetails;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.providers.AbstractAuthenticationToken;
@@ -56,14 +57,14 @@ of this software and associated documentation files (the "Software"), to deal
/**
* @author mocleiri
- *
+ *
* to hold the authentication token from the github oauth process.
- *
+ *
*/
public class GithubAuthenticationToken extends AbstractAuthenticationToken {
/**
- *
+ *
*/
private static final long serialVersionUID = 1L;
private final String accessToken;
@@ -71,9 +72,9 @@ public class GithubAuthenticationToken extends AbstractAuthenticationToken {
private final String userName;
private final GitHub gh;
private final GHMyself me;
-
+
/**
- * Cache for faster organization based security
+ * Cache for faster organization based security
*/
private static final Cache> userOrganizationCache =
CacheBuilder.newBuilder().expireAfterWrite(1,TimeUnit.HOURS).build();
@@ -103,9 +104,17 @@ public GithubAuthenticationToken(String accessToken, String githubServer) throws
this.userName = this.me.getLogin();
authorities.add(SecurityRealm.AUTHENTICATED_AUTHORITY);
- for (String name : gh.getMyOrganizations().keySet())
- authorities.add(new GrantedAuthorityImpl(name));
- }
+ Map> myTeams = gh.getMyTeams();
+ for (String orgLogin : myTeams.keySet()) {
+ LOGGER.log(Level.FINE, "Fetch teams for user " + userName + " in organization " + orgLogin);
+ authorities.add(new GrantedAuthorityImpl(orgLogin));
+ for (GHTeam team : myTeams.get(orgLogin)) {
+ authorities.add(new GrantedAuthorityImpl(orgLogin + GithubOAuthGroupDetails.ORG_TEAM_SEPARATOR
+ + team.getName()));
+ }
+ }
+
+ }
/**
* Necessary for testing
@@ -146,12 +155,12 @@ public String getPrincipal() {
/**
* For some reason I can't get the github api to tell me for the current
* user the groups to which he belongs.
- *
+ *
* So this is a slightly larger consideration. If the authenticated user is
* part of any team within the organization then they have permission.
- *
+ *
* It caches user organizations for 24 hours for faster web navigation.
- *
+ *
* @param candidateName
* @param organization
* @return
@@ -240,55 +249,90 @@ public Boolean call() throws Exception {
}
}
- private static final Logger LOGGER = Logger
- .getLogger(GithubAuthenticationToken.class.getName());
+ private static final Logger LOGGER = Logger
+ .getLogger(GithubAuthenticationToken.class.getName());
- public GHUser loadUser(String username) throws IOException {
- if (gh != null && isAuthenticated())
- return gh.getUser(username);
- else
- return null;
- }
+ public GHUser loadUser(String username) {
+ try {
+ if (gh != null && isAuthenticated())
+ return gh.getUser(username);
+ } catch (IOException e) {
+ LOGGER.log(Level.FINEST, e.getMessage(), e);
+ }
+ return null;
+ }
- public GHOrganization loadOrganization(String organization)
- throws IOException {
+ public GHOrganization loadOrganization(String organization) {
+ try {
+ if (gh != null && isAuthenticated())
+ return gh.getOrganization(organization);
+ } catch (IOException e) {
+ LOGGER.log(Level.FINEST, e.getMessage(), e);
+ }
+ return null;
+ }
- if (gh != null && isAuthenticated())
- return gh.getOrganization(organization);
- else
- return null;
+ public GHRepository loadRepository(String repositoryName) {
+ try {
+ if (gh != null && isAuthenticated()) {
+ return gh.getRepository(repositoryName);
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING,
+ "Looks like a bad GitHub URL OR the Jenkins user does not have access to the repository{0}",
+ repositoryName);
+ }
+ return null;
+ }
- }
+ public GHTeam loadTeam(String organization, String team) {
+ try {
+ GHOrganization org = loadOrganization(organization);
+ if (org != null) {
+ return org.getTeamByName(team);
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.FINEST, e.getMessage(), e);
+ }
+ return null;
+ }
- public GHRepository loadRepository(String repositoryName) {
+ /**
+ * @since 0.21
+ */
+ public GithubOAuthUserDetails getUserDetails(String username) {
+ GHUser user = loadUser(username);
+ if (user != null) {
+ List groups = new ArrayList();
try {
- if (gh != null && isAuthenticated()) {
- return gh.getRepository(repositoryName);
- } else {
- return null;
+ for (GHOrganization ghOrganization : user.getOrganizations()) {
+ String orgLogin = ghOrganization.getLogin();
+ LOGGER.log(Level.FINE, "Fetch teams for user " + username + " in organization " + orgLogin);
+ groups.add(new GrantedAuthorityImpl(orgLogin));
+ try {
+ if (!me.isMemberOf(ghOrganization)) {
+ continue;
+ }
+ Map teams = ghOrganization.getTeams();
+ for (String team : teams.keySet()) {
+ if (teams.get(team).hasMember(user)) {
+ groups.add(new GrantedAuthorityImpl(orgLogin + GithubOAuthGroupDetails.ORG_TEAM_SEPARATOR
+ + team));
+ }
+ }
+ } catch (IOException ignore) {
+ LOGGER.log(Level.FINEST, "not enough rights to list teams from " + orgLogin, ignore);
+ continue;
+ } catch (Error ignore) {
+ LOGGER.log(Level.FINEST, "not enough rights to list teams from " + orgLogin, ignore);
+ continue;
+ }
}
- } catch(FileNotFoundException e) {
- LOGGER.log(Level.WARNING, "Looks like a bad github URL OR the Jenkins user does not have access to the repository{0}", repositoryName);
- return null;
} catch(IOException e) {
- LOGGER.log(Level.WARNING, "Looks like a bad github URL OR the Jenkins user does not have access to the repository{0}", repositoryName);
- return null;
+ LOGGER.log(Level.FINE, e.getMessage(), e);
}
- }
-
- public GHTeam loadTeam(String organization, String team) throws IOException {
- if (gh != null && isAuthenticated()) {
-
- GHOrganization org = gh.getOrganization(organization);
-
- if (org != null) {
- Map teamMap = org.getTeams();
-
- return teamMap.get(team);
- } else
- return null;
-
- } else
- return null;
- }
+ return new GithubOAuthUserDetails(user, groups.toArray(new GrantedAuthority[groups.size()]));
+ }
+ return null;
+ }
}
diff --git a/src/main/java/org/jenkinsci/plugins/GithubOAuthGroupDetails.java b/src/main/java/org/jenkinsci/plugins/GithubOAuthGroupDetails.java
index 490759a5..144b5517 100644
--- a/src/main/java/org/jenkinsci/plugins/GithubOAuthGroupDetails.java
+++ b/src/main/java/org/jenkinsci/plugins/GithubOAuthGroupDetails.java
@@ -4,6 +4,7 @@
package org.jenkinsci.plugins;
import org.kohsuke.github.GHOrganization;
+import org.kohsuke.github.GHTeam;
import hudson.security.GroupDetails;
@@ -13,24 +14,42 @@
*/
public class GithubOAuthGroupDetails extends GroupDetails {
- private final GHOrganization org;
-
- public GithubOAuthGroupDetails(GHOrganization org) {
- super();
- this.org = org;
- }
-
- /* (non-Javadoc)
- * @see hudson.security.GroupDetails#getName()
- */
- @Override
- public String getName() {
- if (org != null)
- return org.getLogin();
- else
- return null;
- }
-
-
+ private final GHOrganization org;
+ private final GHTeam team;
+ static final String ORG_TEAM_SEPARATOR = "*";
+
+ /**
+ * Group based on organization name
+ * @param org
+ */
+ public GithubOAuthGroupDetails(GHOrganization org) {
+ super();
+ this.org = org;
+ this.team = null;
+ }
+
+ /**
+ * Group based on team name
+ * @param ghTeam
+ */
+ public GithubOAuthGroupDetails(GHTeam team) {
+ super();
+ this.org = team.getOrganization();
+ this.team = team;
+ }
+
+ /* (non-Javadoc)
+ * @see hudson.security.GroupDetails#getName()
+ */
+ @Override
+ public String getName() {
+ if (team != null)
+ return org.getLogin() + ORG_TEAM_SEPARATOR + team.getName();
+ if (org != null)
+ return org.getLogin();
+ return null;
+ }
+
+
}
diff --git a/src/main/java/org/jenkinsci/plugins/GithubOAuthUserDetails.java b/src/main/java/org/jenkinsci/plugins/GithubOAuthUserDetails.java
index 57fccc94..47287470 100644
--- a/src/main/java/org/jenkinsci/plugins/GithubOAuthUserDetails.java
+++ b/src/main/java/org/jenkinsci/plugins/GithubOAuthUserDetails.java
@@ -1,9 +1,10 @@
/**
- *
+ *
*/
package org.jenkinsci.plugins;
import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.userdetails.User;
import org.acegisecurity.userdetails.UserDetails;
import org.kohsuke.github.GHUser;
@@ -11,71 +12,12 @@
* @author Mike
*
*/
-public class GithubOAuthUserDetails implements UserDetails {
-
- private final GHUser user;
-
- /**
- *
- */
- public GithubOAuthUserDetails(GHUser user) {
- this.user = user;
- }
-
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#getAuthorities()
- */
- @Override
- public GrantedAuthority[] getAuthorities() {
- return new GrantedAuthority [] {};
- }
-
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#getPassword()
- */
- @Override
- public String getPassword() {
- return null;
- }
-
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#getUsername()
- */
- @Override
- public String getUsername() {
- return user.getLogin();
- }
-
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#isAccountNonExpired()
- */
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#isAccountNonLocked()
- */
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
+public class GithubOAuthUserDetails extends User implements UserDetails {
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#isCredentialsNonExpired()
- */
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
+ private static final long serialVersionUID = 1L;
- /* (non-Javadoc)
- * @see org.acegisecurity.userdetails.UserDetails#isEnabled()
- */
- @Override
- public boolean isEnabled() {
- return true;
- }
+ public GithubOAuthUserDetails(GHUser user, GrantedAuthority[] authorities) {
+ super(user.getLogin(), "", true, true, true, true, authorities);
+ }
}
diff --git a/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java b/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java
index 42480f58..144d7c7c 100644
--- a/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java
+++ b/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java
@@ -60,6 +60,7 @@ of this software and associated documentation files (the "Software"), to deal
import org.apache.http.util.EntityUtils;
import org.jfree.util.Log;
import org.kohsuke.github.GHOrganization;
+import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GHUser;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Header;
@@ -89,7 +90,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 {
+public class GithubSecurityRealm extends SecurityRealm 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";
@@ -448,7 +449,7 @@ public HttpResponse doFinishLogin(StaplerRequest request)
u.addProperty(new Mailer.UserProperty(self.getEmail()));
}
- fireAuthenticated(new GithubOAuthUserDetails(self));
+ fireAuthenticated(new GithubOAuthUserDetails(self, auth.getAuthorities()));
}
else {
Log.info("Github did not return an access token.");
@@ -600,27 +601,18 @@ public UserDetails loadUserByUsername(String username)
}
try {
-
- GroupDetails group = null;
- try {
- group = loadGroupByGroupname(username);
- } catch (DataRetrievalFailureException e) {
- LOGGER.config("No group found with name: " + username);
- } catch (UsernameNotFoundException e) {
- LOGGER.config("No group found with name: " + username);
- }
-
- if (group != null) {
- throw new UsernameNotFoundException ("user("+username+") is also an organization");
+ GithubOAuthUserDetails userDetails = authToken.getUserDetails(username);
+ if (userDetails == null)
+ throw new UsernameNotFoundException("Unknown user: " + username);
+
+ // Check the username is not an homonym of an organization
+ GHOrganization ghOrg = authToken.loadOrganization(username);
+ if (ghOrg != null) {
+ throw new UsernameNotFoundException("user(" + username + ") is also an organization");
}
- user = authToken.loadUser(username);
-
- if (user != null)
- return new GithubOAuthUserDetails(user);
- else
- throw new UsernameNotFoundException("No known user: " + username);
- } catch (IOException e) {
+ return userDetails;
+ } catch (Error e) {
throw new DataRetrievalFailureException("loadUserByUsername (username=" + username +")", e);
}
}
@@ -642,14 +634,26 @@ public GroupDetails loadGroupByGroupname(String groupName)
throw new UsernameNotFoundException("No known group: " + groupName);
try {
- GHOrganization org = authToken.loadOrganization(groupName);
-
- if (org != null)
- return new GithubOAuthGroupDetails(org);
- else
- throw new UsernameNotFoundException("No known group: " + groupName);
- } catch (IOException e) {
- throw new DataRetrievalFailureException("loadGroupByGroupname (groupname=" + groupName +")", e);
+ int idx = groupName.indexOf(GithubOAuthGroupDetails.ORG_TEAM_SEPARATOR);
+ if (idx > -1 && groupName.length() > idx + 1) { // groupName = "GHOrganization*GHTeam"
+ String orgName = groupName.substring(0, idx);
+ String teamName = groupName.substring(idx + 1);
+ LOGGER.config(String.format("Lookup for team %s in organization %s", teamName, orgName));
+ GHTeam ghTeam = authToken.loadTeam(orgName, teamName);
+ if (ghTeam == null) {
+ throw new UsernameNotFoundException("Unknown GitHub team: " + teamName + " in organization "
+ + orgName);
+ }
+ return new GithubOAuthGroupDetails(ghTeam);
+ } else { // groupName = "GHOrganization"
+ GHOrganization ghOrg = authToken.loadOrganization(groupName);
+ if (ghOrg == null) {
+ throw new UsernameNotFoundException("Unknown GitHub organization: " + groupName);
+ }
+ return new GithubOAuthGroupDetails(ghOrg);
+ }
+ } catch (Error e) {
+ throw new DataRetrievalFailureException("loadGroupByGroupname (groupname=" + groupName + ")", e);
}
}