Merge "Fix OAuth callback URL for non-root Gerrit canonical URL"
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java index 9210fc2..0d66026 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
@@ -18,8 +18,10 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -44,7 +46,7 @@ import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope; public class GitHubLogin { - private static final Logger LOG = LoggerFactory.getLogger(GitHubLogin.class); + private static final Logger log = LoggerFactory.getLogger(GitHubLogin.class); private static final List<Scope> DEFAULT_SCOPES = Arrays.asList( Scope.PUBLIC_REPO, Scope.USER_EMAIL); private static final int YEARS = 365; @@ -65,9 +67,10 @@ @Getter protected GitHub hub; + protected GHMyself myself; + private transient OAuthProtocol oauth; - private GHMyself myself; private SortedSet<Scope> loginScopes; private final GitHubOAuthConfig config; @@ -79,6 +82,14 @@ } } + public Set<String> getMyOrganisationsLogins() throws IOException { + if (isLoggedIn()) { + return hub.getMyOrganizations().keySet(); + } else { + return Collections.emptySet(); + } + } + @Inject public GitHubLogin(final OAuthProtocol oauth, final GitHubOAuthConfig config) { this.oauth = oauth; @@ -91,7 +102,7 @@ try { myself = hub.getMyself(); } catch (Throwable e) { - LOG.error("Connection to GitHub broken: logging out", e); + log.error("Connection to GitHub broken: logging out", e); logout(); loggedIn = false; } @@ -111,13 +122,13 @@ return true; } - LOG.debug("Login " + this); + log.debug("Login " + this); if (OAuthProtocol.isOAuthFinal(request)) { - LOG.debug("Login-FINAL " + this); + log.debug("Login-FINAL " + this); login(oauth.loginPhase2(request, response)); if (isLoggedIn()) { - LOG.debug("Login-SUCCESS " + this); + log.debug("Login-SUCCESS " + this); response.sendRedirect(OAuthProtocol.getTargetUrl(request)); return true; } else { @@ -126,7 +137,7 @@ } } else { this.loginScopes = getScopes(getScopesKey(request, response), scopes); - LOG.debug("Login-PHASE1 " + this); + log.debug("Login-PHASE1 " + this); oauth.loginPhase1(request, response, loginScopes); return false; } @@ -142,6 +153,7 @@ } public GitHub login(AccessToken authToken) throws IOException { + log.debug("Logging in using access token {}", authToken.access_token); this.token = authToken; this.hub = GitHub.connectUsingOAuth(authToken.access_token); this.myself = hub.getMyself();
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java index ab04304..14cce5c 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
@@ -55,6 +55,7 @@ public final int fileUpdateMaxRetryCount; public final int fileUpdateMaxRetryIntervalMsec; public final Config gerritConfig; + public final String oauthHttpHeader; @Inject public GitHubOAuthConfig(@GerritServerConfig Config config) @@ -62,6 +63,7 @@ this.gerritConfig = config; httpHeader = config.getString("auth", null, "httpHeader"); + oauthHttpHeader = config.getString("auth", null, "httpExternalIdHeader"); gitHubUrl = dropTrailingSlash( Objects.firstNonNull(config.getString(CONF_SECTION, null, "url"), GITHUB_URL));
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java new file mode 100644 index 0000000..bffb7a1 --- /dev/null +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
@@ -0,0 +1,93 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.oauth; + +import java.io.IOException; +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gerrit.common.Nullable; +import com.google.gerrit.reviewdb.client.AccountExternalId; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.AccountState; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken; + +@Singleton +public class IdentifiedUserGitHubLoginProvider implements + UserScopedProvider<GitHubLogin> { + private static final Logger log = LoggerFactory.getLogger(IdentifiedUserGitHubLoginProvider.class); + private static final String EXTERNAL_ID_PREFIX = "external:" + + OAuthWebFilter.GITHUB_EXT_ID; + + private final Provider<IdentifiedUser> userProvider; + private OAuthProtocol oauth; + private GitHubOAuthConfig config; + private AccountCache accountCache; + + @Inject + public IdentifiedUserGitHubLoginProvider( + final Provider<IdentifiedUser> identifiedUserProvider, + final OAuthProtocol oauth, final GitHubOAuthConfig config, + final AccountCache accountCache) { + this.userProvider = identifiedUserProvider; + this.oauth = oauth; + this.config = config; + this.accountCache = accountCache; + } + + @Override + public GitHubLogin get() { + IdentifiedUser currentUser = userProvider.get(); + return get(currentUser.getUserName()); + } + + @Override + @Nullable + public GitHubLogin get(String username) { + try { + AccessToken accessToken = newAccessTokenFromUser(username); + if (accessToken != null) { + GitHubLogin login = new GitHubLogin(oauth, config); + login.login(accessToken); + return login; + } else { + return null; + } + } catch (IOException e) { + log.error("Cannot login to GitHub as '" + username + + "'", e); + return null; + } + } + + private AccessToken newAccessTokenFromUser(String username) { + AccountState account = accountCache.getByUsername(username); + Collection<AccountExternalId> externalIds = account.getExternalIds(); + for (AccountExternalId accountExternalId : externalIds) { + String key = accountExternalId.getKey().get(); + if (key.startsWith(EXTERNAL_ID_PREFIX)) { + return new AccessToken(key.substring(EXTERNAL_ID_PREFIX.length())); + } + } + + return null; + } +}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java index 4eca364..f546753 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java
@@ -47,6 +47,7 @@ private static final org.slf4j.Logger log = LoggerFactory .getLogger(OAuthWebFilter.class); public static final String GERRIT_COOKIE_NAME = "GerritAccount"; + public static final String GITHUB_EXT_ID = "github_oauth:"; private final GitHubOAuthConfig config; private final Random retryRandom = new Random(System.currentTimeMillis()); @@ -89,7 +90,9 @@ if (ghLogin != null && ghLogin.isLoggedIn()) { httpRequest = new AuthenticatedHttpRequest(httpRequest, config.httpHeader, - ghLogin.getMyself().getLogin()); + ghLogin.getMyself().getLogin(), + config.oauthHttpHeader, + GITHUB_EXT_ID + ghLogin.getToken().access_token); } if (OAuthProtocol.isOAuthFinalForOthers(httpRequest)) {
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/UserScopedProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/UserScopedProvider.java new file mode 100644 index 0000000..93f474d --- /dev/null +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/UserScopedProvider.java
@@ -0,0 +1,22 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.oauth; + +import com.google.gerrit.common.Nullable; +import com.google.inject.Provider; + +public interface UserScopedProvider<T> extends Provider<T> { + @Nullable T get(String username); +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java index d4c0b27..21926c4 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java
@@ -16,11 +16,32 @@ import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.webui.TopMenu; +import com.google.gerrit.server.account.GroupBackend; import com.google.inject.AbstractModule; +import com.google.inject.TypeLiteral; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import com.googlesource.gerrit.plugins.github.group.GitHubGroupBackend; +import com.googlesource.gerrit.plugins.github.group.GitHubGroupMembership; +import com.googlesource.gerrit.plugins.github.group.GitHubGroupsCache; +import com.googlesource.gerrit.plugins.github.group.GitHubOrganisationGroup; +import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; +import com.googlesource.gerrit.plugins.github.oauth.IdentifiedUserGitHubLoginProvider; +import com.googlesource.gerrit.plugins.github.oauth.UserScopedProvider; public class GuiceModule extends AbstractModule { @Override protected void configure() { + bind(new TypeLiteral<UserScopedProvider<GitHubLogin>>() {}).to( + IdentifiedUserGitHubLoginProvider.class); + + install(GitHubGroupsCache.module()); + DynamicSet.bind(binder(), TopMenu.class).to(GitHubTopMenu.class); + DynamicSet.bind(binder(), GroupBackend.class).to(GitHubGroupBackend.class); + + install(new FactoryModuleBuilder() + .build(GitHubOrganisationGroup.Factory.class)); + install(new FactoryModuleBuilder() + .build(GitHubGroupMembership.Factory.class)); } }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroup.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroup.java new file mode 100644 index 0000000..4f5947c --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroup.java
@@ -0,0 +1,41 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.group; + +import lombok.Getter; + +import com.google.gerrit.common.data.GroupDescription.Basic; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; + +public abstract class GitHubGroup implements Basic { + public static final String UUID_PREFIX = "github:"; + public static final String NAME_PREFIX = "github/"; + + @Getter + protected final UUID groupUUID; + + @Getter + protected final String url; + + GitHubGroup(UUID groupUUID, String url) { + this.groupUUID = groupUUID; + this.url = url; + } + + @Override + public String getEmailAddress() { + return ""; + } +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java new file mode 100644 index 0000000..b6fd5cf --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java
@@ -0,0 +1,125 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.group; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.googlesource.gerrit.plugins.github.group.GitHubOrganisationGroup.NAME_PREFIX; +import static com.googlesource.gerrit.plugins.github.group.GitHubOrganisationGroup.UUID_PREFIX; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.gerrit.common.data.GroupDescription.Basic; +import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.GroupBackend; +import com.google.gerrit.server.account.GroupMembership; +import com.google.gerrit.server.project.ProjectControl; +import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; +import com.googlesource.gerrit.plugins.github.oauth.UserScopedProvider; + +public class GitHubGroupBackend implements GroupBackend { + private static final Logger log = LoggerFactory + .getLogger(GitHubGroupBackend.class); + private final GitHubGroupMembership.Factory ghMembershipProvider; + private final GitHubGroupsCache ghOrganisationCache; + + @Inject + GitHubGroupBackend( + UserScopedProvider<GitHubLogin> ghLogin, + GitHubGroupMembership.Factory ghMembershipProvider, + GitHubGroupsCache ghOrganisationCache) { + this.ghMembershipProvider = ghMembershipProvider; + this.ghOrganisationCache = ghOrganisationCache; + } + + @Override + public boolean handles(UUID uuid) { + return uuid.get().startsWith(UUID_PREFIX); + } + + @Override + public Basic get(UUID uuid) { + checkArgument(handles(uuid), "{} is not a valid GitHub Group UUID", + uuid.get()); + return GitHubOrganisationGroup.fromUUID(uuid); + } + + @Override + public Collection<GroupReference> suggest(String name, ProjectControl project) { + if (!name.startsWith(NAME_PREFIX)) { + return Collections.emptyList(); + } + String orgNamePrefix = name.substring(NAME_PREFIX.length()); + return listByPrefix(orgNamePrefix); + } + + public Set<GroupReference> listByPrefix(String orgNamePrefix) { + try { + log.debug("Listing user's organisations starting with '{}'", + orgNamePrefix); + + String[] namePrefixParts = orgNamePrefix.toLowerCase().split("/"); + String orgNamePrefixLowercase = + namePrefixParts.length > 0 ? namePrefixParts[0] : ""; + String teamNameLowercase = + namePrefixParts.length > 1 ? namePrefixParts[1] : ""; + + Set<String> ghOrgs = ghOrganisationCache.getOrganizationsForCurrentUser(); + log.debug("Full list of user's organisations: {}", ghOrgs); + + Builder<GroupReference> orgGroups = + new ImmutableSet.Builder<GroupReference>(); + for (String organizationName : ghOrgs) { + if (organizationName.toLowerCase().startsWith(orgNamePrefixLowercase)) { + GroupReference teamGroupRef = + GitHubOrganisationGroup.groupReference(organizationName); + + if ((orgNamePrefixLowercase.length() > 0 && orgNamePrefix + .endsWith("/")) || teamNameLowercase.length() > 0) { + for (String teamName : ghOrganisationCache.getTeamsForCurrentUser(organizationName)) { + if (teamName.toLowerCase().startsWith(teamNameLowercase)) { + orgGroups.add(GitHubTeamGroup.groupReference(teamGroupRef, + teamName)); + } + } + } else { + orgGroups.add(teamGroupRef); + } + } + } + return orgGroups.build(); + } catch (ExecutionException e) { + log.warn("Cannot get GitHub organisations matching '" + orgNamePrefix + + "'", e); + } + + return Collections.emptySet(); + } + + @Override + public GroupMembership membershipsOf(IdentifiedUser user) { + return ghMembershipProvider.get(user.getUserName()); + } +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupMembership.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupMembership.java new file mode 100644 index 0000000..b93c138 --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupMembership.java
@@ -0,0 +1,65 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.group; + +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; +import com.google.gerrit.server.account.GroupMembership; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +public class GitHubGroupMembership implements GroupMembership { + private final Set<UUID> groups; + + public interface Factory { + GitHubGroupMembership get(@Assisted String username); + } + + @Inject + GitHubGroupMembership(GitHubGroupsCache ghOrganisationCache, + @Assisted String username) { + this.groups = + new ImmutableSet.Builder<UUID>().addAll( + ghOrganisationCache.getGroupsForUser(username)).build(); + } + + @Override + public boolean contains(UUID groupId) { + return groups.contains(groupId); + } + + @Override + public boolean containsAnyOf(Iterable<UUID> groupIds) { + return !intersection(groupIds).isEmpty(); + } + + @Override + public Set<UUID> intersection(Iterable<UUID> groupIds) { + ImmutableSet.Builder<UUID> groups = new ImmutableSet.Builder<>(); + for (UUID uuid : groupIds) { + if (contains(uuid)) { + groups.add(uuid); + } + } + return groups.build(); + } + + @Override + public Set<UUID> getKnownGroups() { + return groups; + } +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java new file mode 100644 index 0000000..d02d4ff --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java
@@ -0,0 +1,176 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.group; + +import static java.util.concurrent.TimeUnit.MINUTES; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.kohsuke.github.GHTeam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.cache.CacheModule; +import com.google.inject.Inject; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Named; +import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; +import com.googlesource.gerrit.plugins.github.oauth.UserScopedProvider; + +@Singleton +public class GitHubGroupsCache { + private static final Logger log = LoggerFactory + .getLogger(GitHubGroupsCache.class); + private static final String ORGS_CACHE_NAME = "groups"; + protected static final long GROUPS_CACHE_TTL_MINS = 60; + public static final String EVERYONE_TEAM_NAME = "Everyone"; + + public static class OrganisationLoader extends + CacheLoader<String, Multimap<String, String>> { + private static final Logger log = LoggerFactory + .getLogger(OrganisationLoader.class); + private final UserScopedProvider<GitHubLogin> ghLoginProvider; + + @Inject + public OrganisationLoader(UserScopedProvider<GitHubLogin> ghLoginProvider) { + this.ghLoginProvider = ghLoginProvider; + } + + @Override + public Multimap<String, String> load(String username) throws Exception { + Multimap<String, String> orgsTeams = HashMultimap.create(); + GitHubLogin ghLogin = ghLoginProvider.get(username); + if (ghLogin == null) { + log.warn("Cannot login to GitHub on behalf of '{}'", username); + return orgsTeams; + } + + try { + loadOrganisationsAndTeams(username, orgsTeams, ghLogin); + } catch (FileNotFoundException teamsNotFound) { + log.warn( + "Cannot access teams for user '{}': falling back to list of public organisations", + username); + loadOrganisations(username, orgsTeams, ghLogin); + } + + log.debug("GitHub user '{}' belongs to: {}", username, orgsTeams); + return orgsTeams; + } + + private void loadOrganisationsAndTeams(String username, Multimap<String, String> orgsTeams, + GitHubLogin ghLogin) throws IOException { + log.debug("Getting list of organisations/teams for user '{}'", username); + Map<String, Set<GHTeam>> myOrganisationsLogins = + ghLogin.getHub().getMyTeams(); + for (Entry<String, Set<GHTeam>> teamsOrg : myOrganisationsLogins + .entrySet()) { + orgsTeams.put(teamsOrg.getKey(), EVERYONE_TEAM_NAME); + for (GHTeam team : teamsOrg.getValue()) { + orgsTeams.put(teamsOrg.getKey(), team.getName()); + } + } + } + + private void loadOrganisations(String username, + Multimap<String, String> orgsTeams, GitHubLogin ghLogin) throws IOException { + log.debug("Getting list of public organisations for user '{}'", username); + Set<String> organisations = ghLogin.getMyOrganisationsLogins(); + for (String org : organisations) { + orgsTeams.put(org, EVERYONE_TEAM_NAME); + } + } + } + + public static Module module() { + return new CacheModule() { + @Override + protected void configure() { + cache(ORGS_CACHE_NAME, String.class, + new TypeLiteral<Multimap<String, String>>() {}).expireAfterWrite( + GROUPS_CACHE_TTL_MINS, MINUTES).loader(OrganisationLoader.class); + bind(GitHubGroupsCache.class); + } + }; + } + + private final LoadingCache<String, Multimap<String, String>> orgTeamsByUsername; + private final Provider<IdentifiedUser> userProvider; + + @Inject + GitHubGroupsCache( + @Named(ORGS_CACHE_NAME) LoadingCache<String, Multimap<String, String>> byUsername, + Provider<IdentifiedUser> userProvider) { + this.orgTeamsByUsername = byUsername; + this.userProvider = userProvider; + } + + Set<String> getOrganizationsForUser(String username) { + try { + return orgTeamsByUsername.get(username).keySet(); + } catch (ExecutionException e) { + log.warn("Cannot get GitHub organisations for user '" + username + "'", e); + return Collections.emptySet(); + } + } + + Set<String> getOrganizationsForCurrentUser() throws ExecutionException { + return orgTeamsByUsername.get(userProvider.get().getUserName()).keySet(); + } + + Set<String> getTeamsForUser(String organizationName, String username) { + try { + return new ImmutableSet.Builder<String>().addAll( + orgTeamsByUsername.get(username).get(organizationName)).build(); + } catch (ExecutionException e) { + log.warn("Cannot get Teams membership for organisation '" + + organizationName + "' and user '" + username + "'", e); + return Collections.emptySet(); + } + } + + Set<String> getTeamsForCurrentUser(String organizationName) { + return getTeamsForUser(organizationName, userProvider.get().getUserName()); + } + + public Set<UUID> getGroupsForUser(String username) { + ImmutableSet.Builder<UUID> groupsBuilder = new ImmutableSet.Builder<>(); + for (String org : getOrganizationsForUser(username)) { + groupsBuilder.add(GitHubOrganisationGroup.uuid(org)); + + for (String team : getTeamsForUser(org, username)) { + groupsBuilder.add(GitHubTeamGroup.uuid( + GitHubOrganisationGroup.uuid(org), team)); + } + } + return groupsBuilder.build(); + } +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubOrganisationGroup.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubOrganisationGroup.java new file mode 100644 index 0000000..7dca217 --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubOrganisationGroup.java
@@ -0,0 +1,61 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.group; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.gerrit.common.Nullable; +import com.google.gerrit.common.data.GroupDescription.Basic; +import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +public class GitHubOrganisationGroup extends GitHubGroup implements Basic { + public interface Factory { + GitHubOrganisationGroup get(@Assisted("orgName") String orgName, + @Assisted("orgUrl") @Nullable String orgUrl); + } + + private final String orgName; + + @Inject + GitHubOrganisationGroup(@Assisted("orgName") String orgName, + @Assisted("orgUrl") @Nullable String orgUrl) { + super(uuid(orgName), orgUrl); + this.orgName = orgName; + } + + @Override + public String getName() { + return NAME_PREFIX + orgName; + } + + public static GitHubOrganisationGroup fromUUID(UUID uuid) { + checkArgument(uuid.get().startsWith(UUID_PREFIX), "Invalid GitHub UUID '" + + uuid + "'"); + return new GitHubOrganisationGroup(uuid.get().substring( + UUID_PREFIX.length()), null); + } + + public static UUID uuid(String orgName) { + return new AccountGroup.UUID(UUID_PREFIX + orgName); + } + + public static GroupReference groupReference(String orgName) { + return new GroupReference(uuid(orgName), NAME_PREFIX + orgName); + } +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubTeamGroup.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubTeamGroup.java new file mode 100644 index 0000000..cd53736 --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubTeamGroup.java
@@ -0,0 +1,55 @@ +// Copyright (C) 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.github.group; + +import com.google.gerrit.common.Nullable; +import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +public class GitHubTeamGroup extends GitHubGroup { + public interface Factory { + GitHubTeamGroup get(@Assisted GitHubOrganisationGroup orgGroup, + @Assisted String teamName, @Nullable String teamUrl); + } + + private final GitHubOrganisationGroup orgGroup; + private final String teamName; + + @Inject + GitHubTeamGroup(@Assisted GitHubOrganisationGroup orgGroup, + @Assisted String teamName, @Nullable String teamUrl) { + super(uuid(orgGroup.getGroupUUID(), teamName), teamUrl); + this.orgGroup = orgGroup; + this.teamName = teamName; + } + + @Override + public String getName() { + return orgGroup.getName() + "/" + teamName; + } + + public static UUID uuid(UUID orgUUID, String teamName) { + return new AccountGroup.UUID(orgUUID.get() + "/" + teamName); + } + + public static GroupReference groupReference(GroupReference orgReference, + String teamName) { + return new GroupReference(uuid(orgReference.getUUID(), teamName), + orgReference.getName() + "/" + teamName); + } +}