Merge branch 'stable-3.8' into stable-3.9 * stable-3.8: GitHubOAuthConfigTest: Reformat the code using google-java-format Build github plugin against the v3.8.0-rc5 Gerrit API PullRequestCreateChange: Remove unused import Set plugin version to 3.4.7 Fire git ref update events for all imported refs PluginVelocityRuntimeProvider: Fix warning flagged by error prone Fix default scopes resolution Change-Id: Ifd589f9099f27ad59790e44f4cbca3f39327a767
diff --git a/README.md b/README.md index 5a95019..c497a89 100644 --- a/README.md +++ b/README.md
@@ -78,13 +78,13 @@ Gerrit 3.3 is distributed for Java 11 only. However, the source code is compatible with Java 8 assuming you build it from the source repository by yourself. -The GitHub plugin can be built for Java 8 by using the `javaVersion=1.8` Maven +The GitHub plugin can be built for Java 17 by using the `javaVersion=1.17` Maven parameter. Example: git clone https://gerrit.googlesource.com/plugins/github cd github - mvn -DjavaVersion=1.8 install + mvn -DjavaVersion=17 install ### singleusergroup plugin @@ -136,9 +136,10 @@ * GitHub Integration * GitHub URL: [https://github.com]: <confirm> -* Use GitHub for Gerrit login? [Y/n] Y +* GitHub API URL: [https://api.github.com]: <confirm> * ClientId []: <provided client id from previous step> * ClientSecret []: <provided client secret from previous step> +* Gerrit OAuth implementation [http/?]: <http or oauth> ### Receiving Pull Request events to automatically import @@ -195,4 +196,4 @@ refs/for/foo, refs/meta/bar ``` -More information on Gerrit magic refs can be found [here](https://gerrit-review.googlesource.com/Documentation/intro-user.html#upload-change) \ No newline at end of file +More information on Gerrit magic refs can be found [here](https://gerrit-review.googlesource.com/Documentation/intro-user.html#upload-change)
diff --git a/github-oauth/pom.xml b/github-oauth/pom.xml index 75b9c09..d027b61 100644 --- a/github-oauth/pom.xml +++ b/github-oauth/pom.xml
@@ -21,7 +21,7 @@ <parent> <groupId>com.googlesource.gerrit.plugins.github</groupId> <artifactId>github-parent</artifactId> - <version>3.8.0</version> + <version>3.9.0-rc5</version> </parent> <artifactId>github-oauth</artifactId> <name>Gerrit Code Review - GitHub OAuth login</name> @@ -71,35 +71,35 @@ <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> - <version>3.0.1</version> + <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> - <version>4.1.0</version> + <version>6.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>20.0</version> + <version>32.1.2-jre</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.kohsuke</groupId> <artifactId>github-api</artifactId> - <version>1.116</version> + <version>1.316</version> </dependency> <dependency> <groupId>com.infradna.tool</groupId> <artifactId>bridge-method-injector</artifactId> - <version>1.18</version> + <version>1.29</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> - <version>4.4</version> + <version>4.5.2</version> <scope>provided</scope> </dependency> <dependency>
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CanonicalWebUrls.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CanonicalWebUrls.java new file mode 100644 index 0000000..faca0f9 --- /dev/null +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CanonicalWebUrls.java
@@ -0,0 +1,54 @@ +// Copyright (C) 2023 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 static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.GERRIT_OAUTH_FINAL; +import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.GITHUB_PLUGIN_OAUTH_SCOPE; + +import com.google.common.base.CharMatcher; +import com.google.common.base.MoreObjects; +import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class CanonicalWebUrls { + private final GitHubOAuthConfig oauthConf; + private final HttpCanonicalWebUrlProvider canonicalWebUrlProvider; + + static String trimTrailingSlash(String url) { + return CharMatcher.is('/').trimTrailingFrom(url); + } + + @Inject + CanonicalWebUrls( + GitHubOAuthConfig oauthConf, HttpCanonicalWebUrlProvider canonicalWebUrlProvider) { + this.oauthConf = oauthConf; + this.canonicalWebUrlProvider = canonicalWebUrlProvider; + } + + public String getScopeSelectionUrl() { + return getCannonicalWebUrl() + + MoreObjects.firstNonNull(oauthConf.scopeSelectionUrl, GITHUB_PLUGIN_OAUTH_SCOPE); + } + + String getOAuthFinalRedirectUrl() { + return getCannonicalWebUrl() + GERRIT_OAUTH_FINAL; + } + + private String getCannonicalWebUrl() { + return trimTrailingSlash(canonicalWebUrlProvider.get()); + } +}
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 ece944d..02889a1 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
@@ -38,7 +38,9 @@ import org.kohsuke.github.GHMyself; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; -import org.kohsuke.github.HttpConnector; +import org.kohsuke.github.HttpException; +import org.kohsuke.github.connector.GitHubConnector; +import org.kohsuke.github.internal.GitHubConnectorHttpConnectorAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +65,9 @@ private SortedSet<Scope> loginScopes; private final GitHubOAuthConfig config; - private final HttpConnector httpConnector; + private final CanonicalWebUrls canonicalWebUrls; + private final VirtualDomainConfig virtualDomainConfig; + private final GitHubConnector gitHubConnector; public GHMyself getMyself() throws IOException { if (isLoggedIn()) { @@ -72,17 +76,35 @@ return null; } - public Set<String> getMyOrganisationsLogins() throws IOException { + public Set<String> getMyOrganisationsLogins(String username) throws IOException { if (isLoggedIn()) { - return getHub().getMyOrganizations().keySet(); + try { + return getHub().getMyOrganizations().keySet(); + } catch (HttpException httpException) { + if (!httpException.getMessage().contains("You need at least")) { + throw httpException; + } + log.info( + "Cannot access organizations for user '{}': falling back to list of public" + + " organisations", + username); + + return getHub().getUserPublicOrganizations(username).keySet(); + } } return Collections.emptySet(); } @Inject - public GitHubLogin(GitHubOAuthConfig config, GitHubHttpConnector httpConnector) { + public GitHubLogin( + GitHubOAuthConfig config, + CanonicalWebUrls canonicalWebUrls, + VirtualDomainConfig virutalDomainConfig, + GitHubHttpConnector httpConnector) { this.config = config; - this.httpConnector = httpConnector; + this.canonicalWebUrls = canonicalWebUrls; + this.virtualDomainConfig = virutalDomainConfig; + this.gitHubConnector = GitHubConnectorHttpConnectorAdapter.adapt(httpConnector); } public boolean isLoggedIn() { @@ -107,12 +129,13 @@ response.sendRedirect(OAuthProtocol.getTargetUrl(request)); } } else { - Set<ScopeKey> configuredScopesProfiles = config.scopes.keySet(); + Set<ScopeKey> configuredScopesProfiles = virtualDomainConfig.getScopes(request).keySet(); String scopeRequested = getScopesKey(request, response); if (Strings.isNullOrEmpty(scopeRequested) && configuredScopesProfiles.size() > 1) { - response.sendRedirect(config.getScopeSelectionUrl(request)); + response.sendRedirect(canonicalWebUrls.getScopeSelectionUrl()); } else { - this.loginScopes = getScopes(MoreObjects.firstNonNull(scopeRequested, "scopes"), scopes); + this.loginScopes = + getScopes(request, MoreObjects.firstNonNull(scopeRequested, "scopes"), scopes); log.debug("Login-PHASE1 " + this); state = oauth.loginPhase1(request, response, loginScopes); } @@ -141,7 +164,7 @@ return new GitHubBuilder() .withEndpoint(config.gitHubApiUrl) .withOAuthToken(token.accessToken) - .withConnector(httpConnector) + .withConnector(gitHubConnector) .build(); } @@ -178,15 +201,15 @@ return null; } - private SortedSet<Scope> getScopes(String baseScopeKey, Scope... scopes) { - HashSet<Scope> fullScopes = new HashSet<>(scopesForKey(baseScopeKey)); + private SortedSet<Scope> getScopes(HttpServletRequest req, String baseScopeKey, Scope... scopes) { + HashSet<Scope> fullScopes = new HashSet<>(scopesForKey(req, baseScopeKey)); fullScopes.addAll(Arrays.asList(scopes)); return new TreeSet<>(fullScopes); } - private List<Scope> scopesForKey(String baseScopeKey) { - return config.scopes.entrySet().stream() + private List<Scope> scopesForKey(HttpServletRequest req, String baseScopeKey) { + return virtualDomainConfig.getScopes(req).entrySet().stream() .filter(entry -> entry.getKey().name.equals(baseScopeKey)) .map(entry -> entry.getValue()) .findFirst()
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 19eebf2..300a945 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
@@ -13,14 +13,14 @@ // limitations under the License. package com.googlesource.gerrit.plugins.github.oauth; +import static com.googlesource.gerrit.plugins.github.oauth.CanonicalWebUrls.trimTrailingSlash; import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.KeyConfig.PASSWORD_DEVICE_CONFIG_LABEL; -import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSortedMap; import com.google.gerrit.extensions.client.AuthType; -import com.google.gerrit.httpd.CanonicalWebUrl; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; import com.google.inject.Inject; @@ -35,17 +35,16 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.SortedMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; import lombok.Getter; import org.eclipse.jgit.lib.Config; @Singleton public class GitHubOAuthConfig { private final Config config; - private final CanonicalWebUrl canonicalWebUrl; public static final String CONF_SECTION = "github"; public static final String CONF_KEY_SECTION = "github-key"; @@ -67,10 +66,11 @@ public final String httpHeader; public final String gitHubOAuthUrl; public final String gitHubOAuthAccessTokenUrl; + public final String scopeSelectionUrl; public final boolean enabled; - @Getter public final Map<ScopeKey, List<OAuthProtocol.Scope>> scopes; - @Getter public final List<ScopeKey> sortedScopesKeys; + @Getter public final SortedMap<ScopeKey, List<OAuthProtocol.Scope>> scopes; + @Getter public final Map<String, SortedMap<ScopeKey, List<OAuthProtocol.Scope>>> virtualScopes; public final int fileUpdateMaxRetryCount; public final int fileUpdateMaxRetryIntervalMsec; @@ -83,9 +83,8 @@ private final Optional<String> cookieDomain; @Inject - protected GitHubOAuthConfig(@GerritServerConfig Config config, CanonicalWebUrl canonicalWebUrl) { + protected GitHubOAuthConfig(@GerritServerConfig Config config) { this.config = config; - this.canonicalWebUrl = canonicalWebUrl; httpHeader = Preconditions.checkNotNull( @@ -106,6 +105,7 @@ Preconditions.checkNotNull( config.getString(CONF_SECTION, null, "clientSecret"), "GitHub `clientSecret` must be provided"); + scopeSelectionUrl = config.getString(CONF_SECTION, null, "scopeSelectionUrl"); oauthHttpHeader = config.getString("auth", null, "httpExternalIdHeader"); gitHubOAuthUrl = gitHubUrl + GITHUB_OAUTH_AUTHORIZE; @@ -115,10 +115,7 @@ enabled = config.getString("auth", null, "type").equalsIgnoreCase(AuthType.HTTP.toString()); cookieDomain = Optional.ofNullable(config.getString("auth", null, "cookieDomain")); scopes = getScopes(config); - sortedScopesKeys = - scopes.keySet().stream() - .sorted(Comparator.comparing(ScopeKey::getSequence)) - .collect(Collectors.toList()); + virtualScopes = getVirtualScopes(config); fileUpdateMaxRetryCount = config.getInt(CONF_SECTION, "fileUpdateMaxRetryCount", 3); fileUpdateMaxRetryIntervalMsec = @@ -152,36 +149,29 @@ currentKeyConfig = currentKeyConfigs.get(0); } - public String getOAuthFinalRedirectUrl(HttpServletRequest req) { - return req == null - ? GERRIT_OAUTH_FINAL - : trimTrailingSlash(canonicalWebUrl.get(req)) + GERRIT_OAUTH_FINAL; + private SortedMap<ScopeKey, List<Scope>> getScopes(Config config) { + return getScopesInSection(config, null); } - public String getScopeSelectionUrl(HttpServletRequest req) { - String canonicalUrl = req == null ? "" : trimTrailingSlash(canonicalWebUrl.get(req)); - return canonicalUrl - + MoreObjects.firstNonNull( - config.getString(CONF_SECTION, null, "scopeSelectionUrl"), GITHUB_PLUGIN_OAUTH_SCOPE); + private Map<String, SortedMap<ScopeKey, List<Scope>>> getVirtualScopes(Config config) { + return config.getSubsections(CONF_SECTION).stream() + .collect(Collectors.toMap(k -> k, v -> getScopesInSection(config, v))); } - private Map<ScopeKey, List<Scope>> getScopes(Config config) { - return config.getNames(CONF_SECTION, true).stream() + private SortedMap<ScopeKey, List<Scope>> getScopesInSection(Config config, String subsection) { + return config.getNames(CONF_SECTION, subsection, true).stream() .filter(k -> k.startsWith("scopes")) .filter(k -> !k.endsWith("Description")) .filter(k -> !k.endsWith("Sequence")) .collect( - Collectors.toMap( + ImmutableSortedMap.toImmutableSortedMap( + Comparator.comparing(ScopeKey::getSequence), k -> new ScopeKey( k, - config.getString(CONF_SECTION, null, k + "Description"), - config.getInt(CONF_SECTION, k + "Sequence", 0)), - v -> parseScopesString(config.getString(CONF_SECTION, null, v)))); - } - - private String trimTrailingSlash(String url) { - return CharMatcher.is('/').trimTrailingFrom(url); + config.getString(CONF_SECTION, subsection, k + "Description"), + config.getInt(CONF_SECTION, subsection, k + "Sequence", 0)), + v -> parseScopesString(config.getString(CONF_SECTION, subsection, v)))); } private List<Scope> parseScopesString(String scopesString) {
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 index 5449347..63414b2 100644 --- 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
@@ -36,23 +36,20 @@ ExternalId.SCHEME_EXTERNAL + ":" + OAuthWebFilter.GITHUB_EXT_ID; private final Provider<IdentifiedUser> userProvider; - private final GitHubOAuthConfig config; private final AccountCache accountCache; - private final GitHubHttpConnector httpConnector; private final OAuthTokenCipher oAuthTokenCipher; + private final Provider<GitHubLogin> gitHubLoginProvider; @Inject public IdentifiedUserGitHubLoginProvider( + Provider<GitHubLogin> gitHubLoginaprovider, Provider<IdentifiedUser> identifiedUserProvider, - GitHubOAuthConfig config, - GitHubHttpConnector httpConnector, AccountCache accountCache, OAuthTokenCipher oAuthTokenCipher) { this.userProvider = identifiedUserProvider; - this.config = config; this.accountCache = accountCache; - this.httpConnector = httpConnector; this.oAuthTokenCipher = oAuthTokenCipher; + this.gitHubLoginProvider = gitHubLoginaprovider; } @Override @@ -67,7 +64,7 @@ try { AccessToken accessToken = newAccessTokenFromUser(username); if (accessToken != null) { - GitHubLogin login = new GitHubLogin(config, httpConnector); + GitHubLogin login = gitHubLoginProvider.get(); login.login(accessToken); return login; }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java index b93837b..035f1ce 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
@@ -155,6 +155,7 @@ private static SecureRandom randomState = newRandomGenerator(); private final GitHubOAuthConfig config; + private final CanonicalWebUrls canonicalWebUrls; private final Gson gson; private final Provider<HttpClient> httpProvider; @@ -231,6 +232,7 @@ @Inject public OAuthProtocol( GitHubOAuthConfig config, + CanonicalWebUrls canonicalWebUrls, PooledHttpClientProvider httpClientProvider, /* * We need to explicitly tell Guice which Provider<> we need as this class @@ -239,6 +241,7 @@ */ GsonProvider gsonProvider) { this.config = config; + this.canonicalWebUrls = canonicalWebUrls; this.httpProvider = httpClientProvider; this.gson = gsonProvider.get(); } @@ -256,7 +259,7 @@ + "?client_id=" + config.gitHubClientId + getURLEncodedParameter("&scope=", scopesString) - + getURLEncodedParameter("&redirect_uri=", config.getOAuthFinalRedirectUrl(req)) + + getURLEncodedParameter("&redirect_uri=", canonicalWebUrls.getOAuthFinalRedirectUrl()) + getURLEncodedParameter("&state=", state); }
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 0bca570..20b80bd 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
@@ -131,7 +131,7 @@ String user = myself.getLogin(); updateSecureConfigWithRetry( - ghLogin.getHub().getMyOrganizations().keySet(), user, ghLogin.getToken().accessToken); + ghLogin.getMyOrganisationsLogins(user), user, ghLogin.getToken().accessToken); } }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/VirtualDomainConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/VirtualDomainConfig.java new file mode 100644 index 0000000..a6b2b17 --- /dev/null +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/VirtualDomainConfig.java
@@ -0,0 +1,38 @@ +// Copyright (C) 2023 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.inject.Inject; +import com.google.inject.Singleton; +import java.util.List; +import java.util.Optional; +import java.util.SortedMap; +import javax.servlet.http.HttpServletRequest; + +@Singleton +public class VirtualDomainConfig { + private final GitHubOAuthConfig oauthConfig; + + @Inject + VirtualDomainConfig(GitHubOAuthConfig oauthConfig) { + this.oauthConfig = oauthConfig; + } + + public SortedMap<ScopeKey, List<OAuthProtocol.Scope>> getScopes(HttpServletRequest req) { + String serverName = req.getServerName(); + return Optional.ofNullable(oauthConfig.virtualScopes.get(serverName)) + .orElse(oauthConfig.scopes); + } +}
diff --git a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java index 6e18170..7a11427 100644 --- a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java +++ b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java
@@ -22,20 +22,20 @@ import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.KeyConfig.SECRET_KEY_CONFIG_LABEL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import com.google.gerrit.extensions.client.AuthType; -import com.google.gerrit.httpd.CanonicalWebUrl; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.util.Providers; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.SortedMap; import org.eclipse.jgit.lib.Config; import org.junit.Before; import org.junit.Test; public class GitHubOAuthConfigTest { - CanonicalWebUrl canonicalWebUrl; Config config; private static final String testPasswordDevice = "/dev/zero"; @@ -46,18 +46,6 @@ config.setString(CONF_SECTION, null, "clientId", "theClientId"); config.setString("auth", null, "httpHeader", "GITHUB_USER"); config.setString("auth", null, "type", AuthType.HTTP.toString()); - - canonicalWebUrl = - Guice.createInjector( - new AbstractModule() { - @Override - protected void configure() { - bind(String.class) - .annotatedWith(com.google.gerrit.server.config.CanonicalWebUrl.class) - .toProvider(Providers.of(null)); - } - }) - .getInstance(CanonicalWebUrl.class); } @Test @@ -180,8 +168,46 @@ assertEquals(Optional.of(myDomain), githubOAuthConfig().getCookieDomain()); } + @Test + public void shouldReturnOverridesForSpecificHostName() { + setupEncryptionConfig(); + String vhost = "v.host.com"; + String scope1Name = "scopesRepo"; + String scope1Description = "repo scope description"; + String scope2Name = "scopesVHost"; + String scope2Description = "scope description"; + + // virtual host scopes + config.setString(CONF_SECTION, vhost, scope2Name, "USER_EMAIL"); + config.setInt(CONF_SECTION, vhost, scope2Name + "Sequence", 1); + config.setString(CONF_SECTION, vhost, scope2Name + "Description", scope2Description); + config.setString(CONF_SECTION, vhost, scope1Name, "REPO"); + config.setInt(CONF_SECTION, vhost, scope1Name + "Sequence", 0); + config.setString(CONF_SECTION, vhost, scope1Name + "Description", scope1Description); + + Map<String, SortedMap<ScopeKey, List<OAuthProtocol.Scope>>> virtualScopes = + githubOAuthConfig().getVirtualScopes(); + + assertTrue(virtualScopes.containsKey(vhost)); + + SortedMap<ScopeKey, List<OAuthProtocol.Scope>> vhostConfig = virtualScopes.get(vhost); + List<Map.Entry<ScopeKey, List<OAuthProtocol.Scope>>> entries = + new ArrayList<>(vhostConfig.entrySet()); + Map.Entry<ScopeKey, List<OAuthProtocol.Scope>> firstEntry = entries.get(0); + Map.Entry<ScopeKey, List<OAuthProtocol.Scope>> secondEntry = entries.get(1); + + assertEquals(firstEntry.getKey().name, scope1Name); + assertEquals(firstEntry.getKey().description, scope1Description); + assertEquals(firstEntry.getKey().sequence, 0); + assertEquals(List.of(OAuthProtocol.Scope.REPO), firstEntry.getValue()); + assertEquals(secondEntry.getKey().name, scope2Name); + assertEquals(secondEntry.getKey().description, scope2Description); + assertEquals(secondEntry.getKey().sequence, 1); + assertEquals(List.of(OAuthProtocol.Scope.USER_EMAIL), secondEntry.getValue()); + } + private GitHubOAuthConfig githubOAuthConfig() { - return new GitHubOAuthConfig(config, canonicalWebUrl); + return new GitHubOAuthConfig(config); } private void setupEncryptionConfig() {
diff --git a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java index 3e31d6b..f3dfb71 100644 --- a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java +++ b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java
@@ -26,10 +26,6 @@ import static org.junit.Assert.assertThrows; import com.google.gerrit.extensions.client.AuthType; -import com.google.gerrit.httpd.CanonicalWebUrl; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.util.Providers; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -43,7 +39,6 @@ public class OAuthTokenCipherTest { - CanonicalWebUrl canonicalWebUrl; Config config; @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -63,18 +58,6 @@ CONF_KEY_SECTION, VERSION1_KEY_ID, PASSWORD_DEVICE_CONFIG_LABEL, testPasswordDevice); config.setString( CONF_KEY_SECTION, VERSION2_KEY_ID, PASSWORD_DEVICE_CONFIG_LABEL, testPasswordDevice); - - canonicalWebUrl = - Guice.createInjector( - new AbstractModule() { - @Override - protected void configure() { - bind(String.class) - .annotatedWith(com.google.gerrit.server.config.CanonicalWebUrl.class) - .toProvider(Providers.of(null)); - } - }) - .getInstance(CanonicalWebUrl.class); } @Test @@ -193,7 +176,7 @@ } private OAuthTokenCipher objectUnderTest(Config testConfig) throws IOException { - return new OAuthTokenCipher(new GitHubOAuthConfig(testConfig, canonicalWebUrl)); + return new OAuthTokenCipher(new GitHubOAuthConfig(testConfig)); } private static Config createCommonConfig() {
diff --git a/github-plugin/.gitignore b/github-plugin/.gitignore index 80d6257..1080f67 100644 --- a/github-plugin/.gitignore +++ b/github-plugin/.gitignore
@@ -3,3 +3,6 @@ /.project /.settings/org.maven.ide.eclipse.prefs /.settings/org.eclipse.m2e.core.prefs +/node_modules +yarn-error.log +/.rollup.cache
diff --git a/github-plugin/package.json b/github-plugin/package.json new file mode 100644 index 0000000..209cc44 --- /dev/null +++ b/github-plugin/package.json
@@ -0,0 +1,20 @@ +{ + "name": "github-oauth-ui", + "description": "UI for the Gerrit GitHub OAuth plugin", + "browser": true, + "dependencies": { + "@gerritcodereview/typescript-api": "^3.8.0", + "@lit/ts-transformers": "^1.1.3", + "@polymer/polymer": "^3.5.1", + "@rollup/plugin-node-resolve": "^15.2.1", + "@rollup/plugin-terser": "^0.4.3", + "lit": "^2.8.0", + "rollup": "^3.29.4", + "typescript": "^4.9.5" + }, + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "tsc && rollup -c" + } +}
diff --git a/github-plugin/pom.xml b/github-plugin/pom.xml index b77cd4e..1eec4f3 100644 --- a/github-plugin/pom.xml +++ b/github-plugin/pom.xml
@@ -20,7 +20,7 @@ <parent> <artifactId>github-parent</artifactId> <groupId>com.googlesource.gerrit.plugins.github</groupId> - <version>3.8.0</version> + <version>3.9.0-rc5</version> </parent> <artifactId>github-plugin</artifactId> @@ -87,6 +87,57 @@ <goals> <goal>shade</goal> </goals> + <configuration> + <filters> + <filter> + <!-- is a signed jar hence the singature has to be removed during shading --> + <artifact>org.eclipse.mylyn.github:org.eclipse.egit.github.core</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.github.eirslett</groupId> + <artifactId>frontend-maven-plugin</artifactId> + <version>1.14.0</version> + <configuration> + <installDirectory>target</installDirectory> + </configuration> + <executions> + <execution> + <id>install node and yarn</id> + <goals> + <goal>install-node-and-yarn</goal> + </goals> + <configuration> + <nodeVersion>v17.9.1</nodeVersion> + <yarnVersion>v1.22.19</yarnVersion> + </configuration> + </execution> + <execution> + <id>yarn install</id> + <goals> + <goal>yarn</goal> + </goals> + <configuration> + <arguments>install</arguments> + </configuration> + </execution> + <execution> + <id>yarn build</id> + <goals> + <goal>yarn</goal> + </goals> + <configuration> + <arguments>build</arguments> + </configuration> </execution> </executions> </plugin> @@ -101,6 +152,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>com.google.gerrit</groupId> + <artifactId>gerrit-acceptance-framework</artifactId> + <version>${Gerrit-ApiVersion}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>${project.groupId}</groupId> <artifactId>github-oauth</artifactId> <version>${project.version}</version> @@ -108,7 +165,7 @@ <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> - <version>3.0.1</version> + <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> @@ -124,33 +181,33 @@ <dependency> <groupId>org.eclipse.mylyn.github</groupId> <artifactId>org.eclipse.egit.github.core</artifactId> - <version>1.3.1</version> + <version>6.1.0.202203080745-r</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>2.8.6</version> + <version>2.10.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> - <version>4.4</version> + <version>4.5.2</version> <scope>provided</scope> </dependency> <dependency> - <groupId>javax.mail</groupId> - <artifactId>mail</artifactId> - <version>1.4.5-rc1</version> + <groupId>com.sun.mail</groupId> + <artifactId>javax.mail</artifactId> + <version>1.6.2</version> </dependency> <dependency> - <groupId>org.apache.commons</groupId> + <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>1.3.2</version> + <version>2.14.0</version> </dependency> <dependency> <groupId>commons-discovery</groupId> <artifactId>commons-discovery</artifactId> - <version>20040218.194635</version> + <version>0.5</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId>
diff --git a/github-plugin/rollup.config.mjs b/github-plugin/rollup.config.mjs new file mode 100644 index 0000000..03d92be --- /dev/null +++ b/github-plugin/rollup.config.mjs
@@ -0,0 +1,17 @@ +import terser from '@rollup/plugin-terser'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +export default { + input: 'target/web/src/main/ts/main.js', + treeshake: false, + output: { + format: 'iife', + compact: true, + file: 'target/classes/static/github-plugin.js', + }, + context: 'window', + plugins: [ + terser(), + nodeResolve(), + ], +} \ No newline at end of file
diff --git a/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java b/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java index e826880..1c0fe3b 100644 --- a/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java +++ b/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java
@@ -15,10 +15,10 @@ import com.google.common.base.MoreObjects; import com.google.gerrit.entities.Account; +import com.google.gerrit.server.Sequences; import com.google.gerrit.server.ServerInitiated; import com.google.gerrit.server.account.externalids.ExternalId; import com.google.gerrit.server.account.externalids.ExternalIdFactory; -import com.google.gerrit.server.notedb.Sequences; import com.google.inject.Inject; import com.google.inject.Provider; import java.io.IOException;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java index a22126d..2bacafd 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
@@ -17,7 +17,6 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.google.gerrit.entities.Account; -import com.google.gerrit.httpd.CanonicalWebUrl; import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.SitePaths; @@ -75,10 +74,9 @@ public GitHubConfig( @GerritServerConfig Config config, final SitePaths site, - Provider<AllProjectsName> allProjectsNameProvider, - CanonicalWebUrl canonicalWebUrl) + Provider<AllProjectsName> allProjectsNameProvider) throws MalformedURLException { - super(config, canonicalWebUrl); + super(config); parseWizardFlow(config.getStringList(CONF_SECTION, null, CONF_WIZARD_FLOW), DEFAULT_SERVER); // Virtual host specific sections
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java index 5ff5363..d345556 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
@@ -47,7 +47,7 @@ new MenuEntry( "GitHub", Arrays.asList( - getItem("Scope", ghConfig.getScopeSelectionUrl(null)), + getItem("Scope", baseUrl + "/static/scope.html"), getItem("Profile", baseUrl + "/static/account.html"), getItem("Repositories", baseUrl + "/static/repositories.html"), getItem("Pull Requests", baseUrl + "/static/pullrequests.html"))));
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java index f75030d..ef24191 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
@@ -15,10 +15,16 @@ import com.google.gerrit.extensions.annotations.Exports; import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider; +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.gerrit.extensions.webui.JavaScriptPlugin; +import com.google.gerrit.extensions.webui.WebUiPlugin; +import com.google.gerrit.httpd.AllRequestFilter; +import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.name.Names; import com.google.inject.servlet.ServletModule; +import com.googlesource.gerrit.plugins.github.filters.GitHubGroupCacheRefreshFilter; import com.googlesource.gerrit.plugins.github.filters.GitHubOAuthFilter; import com.googlesource.gerrit.plugins.github.git.CreateProjectStep; import com.googlesource.gerrit.plugins.github.git.GitCloneStep; @@ -90,6 +96,9 @@ .annotatedWith(Exports.named("github")) .to(GitHubOAuthServiceProvider.class); + DynamicSet.bind(binder(), WebUiPlugin.class) + .toInstance(new JavaScriptPlugin("github-plugin.js")); + serve("*.css", "*.js", "*.png", "*.jpg", "*.woff", "*.gif", "*.ttf") .with(VelocityStaticServlet.class); serve("*.gh").with(VelocityControllerServlet.class); @@ -97,5 +106,9 @@ serve("/static/*").with(VelocityViewServlet.class); filterRegex("(?!/webhook).*").through(GitHubOAuthFilter.class); + + DynamicSet.bind(binder(), AllRequestFilter.class) + .to(GitHubGroupCacheRefreshFilter.class) + .in(Scopes.SINGLETON); } }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubGroupCacheRefreshFilter.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubGroupCacheRefreshFilter.java new file mode 100644 index 0000000..eb34962 --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubGroupCacheRefreshFilter.java
@@ -0,0 +1,78 @@ +// Copyright (C) 2023 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.filters; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.httpd.AllRequestFilter; +import com.googlesource.gerrit.plugins.github.group.GitHubGroupsCache; +import java.io.IOException; +import java.util.Optional; +import javax.inject.Inject; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class GitHubGroupCacheRefreshFilter extends AllRequestFilter { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final String LOGIN_URL = "/login"; + private static final String LOGIN_QUERY_FINAL = "final=true"; + private static final String ACCOUNT_COOKIE = "GerritAccount"; + private static final String INVALIDATE_CACHED_GROUPS = "RefreshGroups"; + + private final GitHubGroupsCache ghGroupsCache; + + @Inject + @VisibleForTesting + public GitHubGroupCacheRefreshFilter(GitHubGroupsCache ghGroupsCache) { + this.ghGroupsCache = ghGroupsCache; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException {} + + @Override + public void doFilter( + ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse); + + HttpServletRequest req = (HttpServletRequest) servletRequest; + if (req.getRequestURI().endsWith(LOGIN_URL) && req.getQueryString().equals(LOGIN_QUERY_FINAL)) { + HttpServletResponse resp = (HttpServletResponse) servletResponse; + String cookieResponse = resp.getHeader("Set-Cookie"); + if (cookieResponse != null && cookieResponse.contains(ACCOUNT_COOKIE)) { + req.getSession().setAttribute(INVALIDATE_CACHED_GROUPS, Boolean.TRUE); + } + } else if (hasSessionFlagForInvalidatingCachedUserGroups(req)) { + ghGroupsCache.invalidateCurrentUserGroups(); + req.getSession().removeAttribute(INVALIDATE_CACHED_GROUPS); + } + } + + private static boolean hasSessionFlagForInvalidatingCachedUserGroups(HttpServletRequest req) { + return Optional.ofNullable(req.getSession(false)) + .flatMap(session -> Optional.ofNullable(session.getAttribute(INVALIDATE_CACHED_GROUPS))) + .filter(refresh -> (Boolean) refresh) + .isPresent(); + } + + @Override + public void destroy() {} +}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/BatchImporter.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/BatchImporter.java index f26188d..d359526 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/BatchImporter.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/BatchImporter.java
@@ -44,7 +44,7 @@ } public synchronized void schedule(int idx, GitJob pullRequestImportJob) { - jobs.put(new Integer(idx), pullRequestImportJob); + jobs.put(Integer.valueOf(idx), pullRequestImportJob); executor.exec(pullRequestImportJob); } }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FanoutReplicationConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FanoutReplicationConfig.java index 412c2ff..4a3bc4b 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FanoutReplicationConfig.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FanoutReplicationConfig.java
@@ -54,12 +54,19 @@ FS.DETECTED); replicationConf.load(); - replicationConf.setString("remote", null, "url", url); + String currentUrl = replicationConf.getString("remote", null, "url"); + if (currentUrl == null) { + replicationConf.setString("remote", null, "url", url); + } List<String> projects = new ArrayList<>(Arrays.asList(replicationConf.getStringList("remote", null, "projects"))); projects.add(projectName); replicationConf.setStringList("remote", null, "projects", projects); - replicationConf.setString("remote", null, "push", "refs/*:refs/*"); + + String currentPushRefs = replicationConf.getString("remote", null, "push"); + if (currentPushRefs == null) { + replicationConf.setString("remote", null, "push", "refs/*:refs/*"); + } replicationConf.save(); } }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitCloneStep.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitCloneStep.java index 12e5101..9d09f51 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitCloneStep.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitCloneStep.java
@@ -13,6 +13,8 @@ // limitations under the License. package com.googlesource.gerrit.plugins.github.git; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.api.GerritApi; import com.google.gerrit.extensions.api.changes.NotifyHandling; @@ -34,6 +36,7 @@ import com.googlesource.gerrit.plugins.github.GitHubConfig; import java.io.File; import java.io.IOException; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.eclipse.jgit.api.FetchCommand; import org.eclipse.jgit.api.Git; @@ -50,11 +53,8 @@ private static final Logger LOG = LoggerFactory.getLogger(GitImporter.class); private final GitHubConfig config; - private final File gitDir; private final GerritApi gerritApi; private final OneOffRequestContext context; - private final String organisation; - private final String repository; private final File destinationDirectory; private final DynamicSet<ProjectDeletedListener> deletedListeners; private final ProjectCache projectCache; @@ -85,14 +85,11 @@ super(config.gitHubUrl, organisation, repository, gitHubRepoFactory); LOG.debug("GitHub Clone " + organisation + "/" + repository); this.config = config; - this.gitDir = config.gitDir.toFile(); this.gerritApi = gerritApi; this.context = context; - this.organisation = organisation; - this.repository = repository; this.projectName = organisation + "/" + repository; - this.destinationDirectory = prepareTargetGitDirectory(gitDir, this.projectName); + this.destinationDirectory = prepareTargetGitDirectory(config.gitDir.toFile(), this.projectName); this.deletedListeners = deletedListeners; this.projectCache = projectCache; this.repoManager = repoManager; @@ -113,7 +110,9 @@ try (ManualRequestContext requestContext = context.openAs(config.importAccountId)) { ProjectInput pi = new ProjectInput(); pi.name = projectName; - pi.parent = config.getBaseProject(getRepository().isPrivate()); + GitHubRepository ghRepository = getRepository(); + pi.parent = config.getBaseProject(ghRepository.isPrivate()); + pi.branches = Stream.ofNullable(ghRepository.getDefaultBranch()).collect(toList()); gerritApi.projects().create(pi).get(); } catch (ResourceConflictException e) { throw new GitDestinationAlreadyExistsException(projectName); @@ -128,7 +127,8 @@ Project.NameKey key = Project.nameKey(projectName); String sourceUri = getSourceUri(); try (Git git = Git.open(destinationDirectory)) { - FetchCommand fetch = git.fetch().setRefSpecs("refs/*:refs/*").setRemote(sourceUri); + FetchCommand fetch = + git.fetch().setRefSpecs("^refs/changes/*", "refs/*:refs/*").setRemote(sourceUri); fetch.setCredentialsProvider(getRepository().getCredentialsProvider()); if (progress != null) { fetch.setProgressMonitor(progress); @@ -146,10 +146,6 @@ } } - private boolean isNotEmpty(File destDirectory) { - return destDirectory.listFiles().length > 0; - } - @Override public boolean rollback() { File gitDirectory = destinationDirectory;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/MagicRefFoundException.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/MagicRefFoundException.java index 263ee9d..42b7e36 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/MagicRefFoundException.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/MagicRefFoundException.java
@@ -15,6 +15,8 @@ public class MagicRefFoundException extends GitException { + private static final long serialVersionUID = 1L; + public MagicRefFoundException(String message) { super(message); }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ProtectedBranchFoundException.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ProtectedBranchFoundException.java index 221152f..c156150 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ProtectedBranchFoundException.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ProtectedBranchFoundException.java
@@ -16,6 +16,8 @@ public class ProtectedBranchFoundException extends Exception { + private static final long serialVersionUID = 1L; + public ProtectedBranchFoundException(String msg) { super(msg); }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java index f2a7f06..810acd8 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java
@@ -27,10 +27,10 @@ import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser.GenericFactory; +import com.google.gerrit.server.Sequences; import com.google.gerrit.server.change.ChangeInserter; import com.google.gerrit.server.change.PatchSetInserter; import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.notedb.Sequences; import com.google.gerrit.server.project.InvalidChangeOperationException; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.query.change.ChangeData;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestImportJob.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestImportJob.java index 52d7b34..da16dc2 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestImportJob.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestImportJob.java
@@ -191,7 +191,7 @@ pullRequestOwner, revCommit, getChangeMessage(pr), - String.format(TOPIC_FORMAT, new Integer(pr.getNumber()))); + String.format(TOPIC_FORMAT, Integer.valueOf(pr.getNumber()))); if (changeId != null) { prChanges.add(changeId); }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/CurrentUsernameProvider.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/CurrentUsernameProvider.java new file mode 100644 index 0000000..028aa6b --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/CurrentUsernameProvider.java
@@ -0,0 +1,41 @@ +// Copyright (C) 2023 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.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.util.Optional; + +public class CurrentUsernameProvider implements Provider<String> { + public static final String CURRENT_USERNAME = "CurrentUsername"; + + private final Provider<CurrentUser> userProvider; + + @Inject + CurrentUsernameProvider(Provider<CurrentUser> userProvider) { + this.userProvider = userProvider; + } + + @Override + public String get() { + return Optional.ofNullable(userProvider.get()) + .filter(CurrentUser::isIdentifiedUser) + .map(CurrentUser::asIdentifiedUser) + .flatMap(IdentifiedUser::getUserName) + .orElse(null); + } +}
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 index 3504f2c..8f9c776 100644 --- 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
@@ -14,19 +14,21 @@ package com.googlesource.gerrit.plugins.github.group; +import static com.googlesource.gerrit.plugins.github.group.CurrentUsernameProvider.CURRENT_USERNAME; import static java.time.temporal.ChronoUnit.MINUTES; +import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.gerrit.entities.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.name.Named; +import com.google.inject.name.Names; import com.googlesource.gerrit.plugins.github.groups.OrganizationStructure; import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; import com.googlesource.gerrit.plugins.github.oauth.UserScopedProvider; @@ -95,7 +97,7 @@ private void loadOrganisations( String username, OrganizationStructure orgsTeams, GitHubLogin ghLogin) throws IOException { logger.debug("Getting list of public organisations for user '{}'", username); - Set<String> organisations = ghLogin.getMyOrganisationsLogins(); + Set<String> organisations = ghLogin.getMyOrganisationsLogins(username); for (String org : organisations) { orgsTeams.put(org, EVERYONE_TEAM_NAME); } @@ -106,6 +108,9 @@ return new CacheModule() { @Override protected void configure() { + bind(String.class) + .annotatedWith(Names.named(CurrentUsernameProvider.CURRENT_USERNAME)) + .toProvider(CurrentUsernameProvider.class); persist(ORGS_CACHE_NAME, String.class, OrganizationStructure.class) .expireAfterWrite(Duration.of(GROUPS_CACHE_TTL_MINS, MINUTES)) .loader(OrganisationLoader.class); @@ -115,14 +120,15 @@ } private final LoadingCache<String, OrganizationStructure> orgTeamsByUsername; - private final Provider<IdentifiedUser> userProvider; + private final Provider<String> usernameProvider; @Inject - GitHubGroupsCache( + @VisibleForTesting + public GitHubGroupsCache( @Named(ORGS_CACHE_NAME) LoadingCache<String, OrganizationStructure> byUsername, - Provider<IdentifiedUser> userProvider) { + @Named(CURRENT_USERNAME) Provider<String> usernameProvider) { this.orgTeamsByUsername = byUsername; - this.userProvider = userProvider; + this.usernameProvider = usernameProvider; } Set<String> getOrganizationsForUser(String username) { @@ -135,7 +141,7 @@ } Set<String> getOrganizationsForCurrentUser() throws ExecutionException { - return orgTeamsByUsername.get(userProvider.get().getUserName().get()).keySet(); + return orgTeamsByUsername.get(usernameProvider.get()).keySet(); } Set<String> getTeamsForUser(String organizationName, String username) { @@ -156,7 +162,7 @@ } Set<String> getTeamsForCurrentUser(String organizationName) { - return getTeamsForUser(organizationName, userProvider.get().getUserName().get()); + return getTeamsForUser(organizationName, usernameProvider.get()); } public Set<UUID> getGroupsForUser(String username) { @@ -170,4 +176,8 @@ } return groupsBuilder.build(); } + + public void invalidateCurrentUserGroups() { + orgTeamsByUsername.invalidate(usernameProvider.get()); + } }
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/PullRequestHandler.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/PullRequestHandler.java index 540b98a..361ccc2 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/PullRequestHandler.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/PullRequestHandler.java
@@ -46,7 +46,7 @@ String action = payload.getAction(); if (action.equals("opened") || action.equals("synchronize")) { GHRepository repository = payload.getRepository(); - Integer prNumber = new Integer(payload.getNumber()); + Integer prNumber = Integer.valueOf(payload.getNumber()); PullRequestImporter prImporter = prImportProvider.get(); String organization = repository.getOwnerName(); String name = repository.getName();
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java index af644fe..810632a 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java
@@ -20,7 +20,6 @@ import com.google.gerrit.server.account.GroupBackend; import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; -import com.google.inject.Injector; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; @@ -52,24 +51,20 @@ return null; } - private final Injector injector; private final List<Destination> configs; private final RemoteSiteUser.Factory replicationUserFactory; private final PluginUser pluginUser; private final GroupBackend groupBackend; - boolean replicateAllOnPluginStart; private final List<String> organisations; @Inject GitHubDestinations( - final Injector i, final SitePaths site, final RemoteSiteUser.Factory ruf, final GroupBackend gb, final PluginUser pu) throws ConfigInvalidException, IOException { - injector = i; pluginUser = pu; replicationUserFactory = ruf; groupBackend = gb;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityRuntimeProvider.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityRuntimeProvider.java index 144e77a..735ac4d 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityRuntimeProvider.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/PluginVelocityRuntimeProvider.java
@@ -30,12 +30,12 @@ @Singleton public class PluginVelocityRuntimeProvider implements Provider<RuntimeInstance> { - private static final String VELOCITY_FILE_RESOURCE_LOADER_PATH = "file.resource.loader.path"; - private static final String VELOCITY_FILE_RESOURCE_LOADER_CLASS = "file.resource.loader.class"; - private static final String VELOCITY_CLASS_RESOURCE_LOADER_CLASS = "class.resource.loader.class"; - private static final String VELOCITY_JAR_RESOURCE_LOADER_CLASS = "jar.resource.loader.class"; - private static final String VELOCITY_JAR_RESOURCE_LOADER_PATH = "jar.resource.loader.path"; - private static final String VELOCITY_RESOURCE_LOADER = "resource.loader"; + private static final String VELOCITY_FILE_RESOURCE_LOADER_PATH = "resource.loader.file.path"; + private static final String VELOCITY_FILE_RESOURCE_LOADER_CLASS = "resource.loader.jar.path"; + private static final String VELOCITY_CLASS_RESOURCE_LOADER_CLASS = "resource.loader.class.class"; + private static final String VELOCITY_JAR_RESOURCE_LOADER_CLASS = "resource.loader.jar.class"; + private static final String VELOCITY_JAR_RESOURCE_LOADER_PATH = "resource.loader.jar.path"; + private static final String VELOCITY_RESOURCE_LOADER = "resource.loaders"; private final SitePaths site; private String pluginName;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java index 85197dd..5e4498f 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
@@ -13,15 +13,20 @@ // limitations under the License. package com.googlesource.gerrit.plugins.github.velocity; +import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.GITHUB_PLUGIN_OAUTH_SCOPE; + import com.google.common.base.MoreObjects; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.AuthConfig; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.googlesource.gerrit.plugins.github.GitHubConfig; +import com.googlesource.gerrit.plugins.github.oauth.CanonicalWebUrls; import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider; +import com.googlesource.gerrit.plugins.github.oauth.VirtualDomainConfig; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Map.Entry; @@ -49,6 +54,9 @@ private final ScopedProvider<GitHubLogin> loginProvider; private final Provider<CurrentUser> userProvider; private final GitHubConfig config; + private final VirtualDomainConfig virtualDomainConfig; + private final CanonicalWebUrls canonicalWebUrls; + private final AuthConfig authConfig; @Inject public VelocityViewServlet( @@ -56,13 +64,19 @@ Provider<PluginVelocityModel> modelProvider, ScopedProvider<GitHubLogin> loginProvider, Provider<CurrentUser> userProvider, - GitHubConfig config) { + GitHubConfig config, + VirtualDomainConfig virutalDomainConfig, + CanonicalWebUrls canonicalWebUrls, + AuthConfig authConfig) { this.velocityRuntime = velocityRuntime; this.modelProvider = modelProvider; this.loginProvider = loginProvider; this.userProvider = userProvider; this.config = config; + this.virtualDomainConfig = virutalDomainConfig; + this.canonicalWebUrls = canonicalWebUrls; + this.authConfig = authConfig; } @Override @@ -71,6 +85,12 @@ HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; + if (!(req.getRequestURI().equals(GITHUB_PLUGIN_OAUTH_SCOPE) + || userProvider.get().isIdentifiedUser())) { + resp.sendRedirect(authConfig.getLoginUrl()); + return; + } + String pathInfo = STATIC_PREFIX + MoreObjects.firstNonNull((String) req.getAttribute("destUrl"), req.getPathInfo()); @@ -96,6 +116,8 @@ GitHubLogin gitHubLogin = loginProvider.get(request); model.put("myself", gitHubLogin.getMyself()); model.put("config", config); + model.put("scopeSelectionUrl", canonicalWebUrls.getScopeSelectionUrl()); + model.put("scopes", virtualDomainConfig.getScopes(request)); CurrentUser user = userProvider.get(); if (user.isIdentifiedUser()) {
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java index fc578af..0bb7e3e 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/AccountController.java
@@ -25,7 +25,6 @@ import com.google.gerrit.extensions.restapi.RawInput; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.ServerInitiated; -import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountManager; import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.AccountsUpdate; @@ -65,7 +64,6 @@ private final AddSshKey restAddSshKey; private final GetSshKeys restGetSshKeys; private final AccountManager accountManager; - private final AccountCache accountCache; private final PutPreferred putPreferred; private final PutName putName; private final Provider<AccountsUpdate> accountsUpdateProvider; @@ -79,7 +77,6 @@ final AddSshKey restAddSshKey, final GetSshKeys restGetSshKeys, final AccountManager accountManager, - final AccountCache accountCache, final PutPreferred putPreferred, final PutName putName, @ServerInitiated final Provider<AccountsUpdate> accountsUpdateProvider, @@ -90,7 +87,6 @@ this.restAddSshKey = restAddSshKey; this.restGetSshKeys = restGetSshKeys; this.accountManager = accountManager; - this.accountCache = accountCache; this.putPreferred = putPreferred; this.putName = putName; this.accountsUpdateProvider = accountsUpdateProvider;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java index e47f161..b7fc801 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/PullRequestListController.java
@@ -101,7 +101,7 @@ JsonArray prArray = new JsonArray(); for (GHPullRequest pr : repoEntry.getValue()) { JsonObject prObj = new JsonObject(); - prObj.add("id", new JsonPrimitive(new Integer(pr.getNumber()))); + prObj.add("id", new JsonPrimitive(Integer.valueOf(pr.getNumber()))); prObj.add("title", new JsonPrimitive(Strings.nullToEmpty(pr.getTitle()))); prObj.add("body", new JsonPrimitive(Strings.nullToEmpty(pr.getBody()))); prObj.add( @@ -137,7 +137,7 @@ if (githubRepo.isPresent()) { numPullRequests = collectPullRequestsFromGitHubRepository( - numPullRequests, allPullRequests, gitRepo, ghRepoName, githubRepo); + numPullRequests, allPullRequests, gitRepo, ghRepoName, githubRepo.get()); } } } @@ -149,13 +149,14 @@ Map<String, List<GHPullRequest>> allPullRequests, Repository gitRepo, String ghRepoName, - Optional<GHRepository> githubRepo) + GHRepository githubRepo) throws IncorrectObjectTypeException, IOException { List<GHPullRequest> repoPullRequests = Lists.newArrayList(); int count = numPullRequests; if (count < config.pullRequestListLimit) { - for (GHPullRequest ghPullRequest : githubRepo.get().listPullRequests(GHIssueState.OPEN)) { + for (GHPullRequest ghPullRequest : + githubRepo.queryPullRequests().state(GHIssueState.OPEN).list()) { if (isAnyCommitOfPullRequestToBeImported(gitRepo, ghPullRequest)) { repoPullRequests.add(ghPullRequest);
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesListController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesListController.java index 2cf6170..01a3d25 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesListController.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesListController.java
@@ -72,7 +72,7 @@ repository.add("organisation", new JsonPrimitive(organisation)); repository.add( "description", new JsonPrimitive(Strings.nullToEmpty(ghRepository.getDescription()))); - repository.add("private", new JsonPrimitive(new Boolean(ghRepository.isPrivate()))); + repository.add("private", new JsonPrimitive(Boolean.valueOf(ghRepository.isPrivate()))); jsonRepos.add(repository); numRepos++; }
diff --git a/github-plugin/src/main/resources/Documentation/config.md b/github-plugin/src/main/resources/Documentation/config.md index c9eebc5..4adcd41 100644 --- a/github-plugin/src/main/resources/Documentation/config.md +++ b/github-plugin/src/main/resources/Documentation/config.md
@@ -77,6 +77,10 @@ Default is empty read-only access to public information (includes public user profile info, public repository info, and gists). +github.<domain>.scopes +: Use only in conjunction with the `virtualhost` plugin to provide different GitHub scopes + selections for each virtual domain. It works the same way as `github.scopes`. + github.httpConnectionTimeout : Maximum time to wait for GitHub API to answer to a new HTTP connection attempt. Values should use common common unit unit suffixes to express their setting:
diff --git a/github-plugin/src/main/resources/static/repositories.html b/github-plugin/src/main/resources/static/repositories.html index 9db7e3f..5a3df08 100644 --- a/github-plugin/src/main/resources/static/repositories.html +++ b/github-plugin/src/main/resources/static/repositories.html
@@ -53,7 +53,7 @@ </select> <input type="text" id="filter" class="filter" name="filter" placeholder="Filter by name" /> </li> - <li class="info"><p>Not seeing your organizations or repositories? <a href="$config.getScopeSelectionUrl(null)">Login with a different GitHub Scope</a> and try again.</p></li> + <li class="info"><p>Not seeing your organizations or repositories? <a href="$scopeSelectionUrl">Login with a different GitHub Scope</a> and try again.</p></li> </ul> <div class="loading"> <p>Loading list of GitHub repositories ...</p>
diff --git a/github-plugin/src/main/resources/static/scope.html b/github-plugin/src/main/resources/static/scope.html index 113b5b5..ebc2580 100644 --- a/github-plugin/src/main/resources/static/scope.html +++ b/github-plugin/src/main/resources/static/scope.html
@@ -6,6 +6,14 @@ #include ("static/styles.html") #include ("static/scripts.html") </head> + <script type="text/javascript"> + function submitOAuth() { + const submitButton =$("button[type='submit']"); + submitButton.attr("disabled", true); + submitButton.find("span").text("Waiting for GitHub API response..."); + document.forms[0].submit(); + } + </script> <body> <!-- div.header start --> <div class="header"> @@ -19,7 +27,7 @@ <button type="button" onclick="window.location='/'" id="cancel"> <span class="button"><span>Cancel</span></span> </button> - <button type="submit" onclick="document.forms[0].submit()"> + <button type="submit" onclick="submitOAuth()"> <span class="button green"><span>Login ></span></span> </button> </div> @@ -36,7 +44,7 @@ <form class="signupform" method="get" action="/login"> <h5>Which level of GitHub access do you need?</h5> <ul class="scopes"> - #foreach ( $scope in $config.sortedScopesKeys ) + #foreach ( $scope in $scopes.keySet() ) <li> #set ( $scopeName = $scope.name.substring(6) ) #set ( $scopeDescription = $scope.description ) @@ -52,7 +60,7 @@ #end <p class="scopeDescription">$scopeDescription</p> <p class="scopePermissions">Allow to: - #set ( $scopeItems = $config.scopes.get($scope) ) + #set ( $scopeItems = $scopes.get($scope) ) #foreach ( $scopeItem in $scopeItems ) $scopeItem.description #if ( $foreach.count < $scopeItems.size())
diff --git a/github-plugin/src/main/ts/gr-github-oauth-progress.ts b/github-plugin/src/main/ts/gr-github-oauth-progress.ts new file mode 100644 index 0000000..3b53d47 --- /dev/null +++ b/github-plugin/src/main/ts/gr-github-oauth-progress.ts
@@ -0,0 +1,72 @@ +import { PluginApi } from '@gerritcodereview/typescript-api/plugin'; +import { AuthInfo } from '@gerritcodereview/typescript-api/rest-api'; +import { CSSResult, LitElement, css, html } from "lit"; +import { customElement, property, query, state } from 'lit/decorators.js'; + +@customElement('gr-github-oauth-progress') +export class GrGitHubOAuthProgress extends LitElement { + @query('#gitHubOAuthProgress') + gitHubOAuthProgress?: HTMLDialogElement; + + @property() plugin!: PluginApi; + + @state() authInfo?: AuthInfo + + @state() loggedIn?: boolean + + override connectedCallback() { + super.connectedCallback(); + const restApi = this.plugin.restApi(); + if (!this.authInfo) { + restApi.getConfig().then(config => this.authInfo = config?.auth); + } + restApi.getLoggedIn().then(loggedIn => this.loggedIn = loggedIn); + } + + static override get styles() { + return [ + window.Gerrit.styles.spinner as CSSResult, + window.Gerrit.styles.font as CSSResult, + window.Gerrit.styles.modal as CSSResult, + css` + .loginButton { + --gr-button-text-color: var(--header-text-color); + color: var(--header-text-color); + padding: var(--spacing-m) var(--spacing-l); + } + .loadingContainer { + display: flex; + gap: var(--spacing-s); + align-items: baseline; + padding: var(--spacing-xxl); + } + .loadingSpin { + vertical-align: top; + position: relative; + top: 3px; + } + `]; + } + + override render() { + if (!this.authInfo || this.loggedIn !== false) { + return + } + + return html` + <a class="loginButton" href=${this.authInfo.login_url} @click=${this.showModal}> + ${this.authInfo.login_text} + </a> + <dialog id="gitHubOAuthProgress"> + <div class="loadingContainer"> + <span class="loadingSpin"></span> + <span class="loadingText">Waiting for GitHub API response ...</span> + </div> + </dialog> + ` + } + + private showModal() { + setTimeout(() => this.gitHubOAuthProgress?.showModal(), 550); + } +}
diff --git a/github-plugin/src/main/ts/main.ts b/github-plugin/src/main/ts/main.ts new file mode 100644 index 0000000..2a465ca --- /dev/null +++ b/github-plugin/src/main/ts/main.ts
@@ -0,0 +1,9 @@ +import '@gerritcodereview/typescript-api/gerrit'; +import './gr-github-oauth-progress'; + +window.Gerrit.install(plugin => { + plugin.registerCustomComponent( + 'auth-link', + 'gr-github-oauth-progress', + { replace: true }); +});
diff --git a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FakeHttpSession.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FakeHttpSession.java new file mode 100644 index 0000000..6a820e7 --- /dev/null +++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FakeHttpSession.java
@@ -0,0 +1,112 @@ +// Copyright (C) 2023 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; + +import java.util.Enumeration; +import java.util.HashMap; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionContext; + +public class FakeHttpSession implements HttpSession { + private final HashMap<String, Object> attributes; + + public FakeHttpSession() { + this.attributes = new HashMap<>(); + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public String getId() { + return null; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public void setMaxInactiveInterval(int i) {} + + @Override + public int getMaxInactiveInterval() { + return 0; + } + + @Override + public HttpSessionContext getSessionContext() { + return null; + } + + @Override + public Object getAttribute(String s) { + return attributes.get(s); + } + + @Override + public Object getValue(String s) { + return getAttribute(s); + } + + @Override + public Enumeration<String> getAttributeNames() { + return java.util.Collections.enumeration(attributes.keySet()); + } + + @Override + public String[] getValueNames() { + return attributes.keySet().toArray(new String[0]); + } + + @Override + public void setAttribute(String s, Object o) { + attributes.put(s, o); + } + + @Override + public void putValue(String s, Object o) { + setAttribute(s, o); + } + + @Override + public void removeAttribute(String s) { + attributes.remove(s); + } + + @Override + public void removeValue(String s) { + removeAttribute(s); + } + + @Override + public void invalidate() { + attributes.clear(); + } + + @Override + public boolean isNew() { + return false; + } +}
diff --git a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FanoutReplicationConfigTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FanoutReplicationConfigTest.java new file mode 100644 index 0000000..3613c41 --- /dev/null +++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FanoutReplicationConfigTest.java
@@ -0,0 +1,99 @@ +// Copyright (C) 2023 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gerrit.server.config.SitePaths; +import com.googlesource.gerrit.plugins.github.git.FanoutReplicationConfig; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.commons.io.FileUtils; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FanoutReplicationConfigTest { + + private static final String CUSTOM_KEY = "mykey"; + private static final String CUSTOM_VALUE = "myvalue"; + private static final String REMOTE_ENDPOINT = "my-remote-endpoint"; + private static final String TEST_REMOTE_URL = "http://github.com/myurl"; + private static final String TEST_PROJECT_NAME = "myprojectname"; + private Path tempDir; + private SitePaths sitePaths; + + @Before + public void setup() throws Exception { + tempDir = Files.createTempDirectory(getClass().getSimpleName()); + sitePaths = new SitePaths(tempDir); + Files.createDirectories(sitePaths.etc_dir); + } + + @After + public void teardown() throws Exception { + FileUtils.deleteDirectory(tempDir.toFile()); + } + + @Test + public void shoudKeepAdHocSettingsInFanoutReplicationConfig() throws Exception { + FileBasedConfig currConfig = getReplicationConfig(); + currConfig.setString("remote", null, CUSTOM_KEY, CUSTOM_VALUE); + currConfig.save(); + + String url = "http://github.com/myurl"; + FanoutReplicationConfig fanoutReplicationConfig = new FanoutReplicationConfig(sitePaths); + fanoutReplicationConfig.addReplicationRemote(REMOTE_ENDPOINT, url, "myproject"); + + currConfig.load(); + assertThat(currConfig.getString("remote", null, CUSTOM_KEY)).isEqualTo(CUSTOM_VALUE); + } + + @Test + public void shoudKeepCustomUrlInFanoutReplicationConfig() throws Exception { + FileBasedConfig currConfig = getReplicationConfig(); + String customUrl = "http://my-custom-url"; + currConfig.setString("remote", null, "url", customUrl); + currConfig.save(); + + new FanoutReplicationConfig(sitePaths) + .addReplicationRemote(REMOTE_ENDPOINT, TEST_REMOTE_URL, TEST_PROJECT_NAME); + + currConfig.load(); + assertThat(currConfig.getString("remote", null, "url")).isEqualTo(customUrl); + } + + @Test + public void shoudKeepCustomPushRefSpecInFanoutReplicationConfig() throws Exception { + FileBasedConfig currConfig = getReplicationConfig(); + String customPushRefSpec = "+refs/heads/myheads/*:refs/heads/myheads/*"; + currConfig.setString("remote", null, "push", customPushRefSpec); + currConfig.save(); + + new FanoutReplicationConfig(sitePaths) + .addReplicationRemote(REMOTE_ENDPOINT, TEST_REMOTE_URL, TEST_PROJECT_NAME); + + currConfig.load(); + assertThat(currConfig.getString("remote", null, "push")).isEqualTo(customPushRefSpec); + } + + private FileBasedConfig getReplicationConfig() { + return new FileBasedConfig( + sitePaths.etc_dir.resolve("replication").resolve(REMOTE_ENDPOINT + ".config").toFile(), + FS.DETECTED); + } +}
diff --git a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java index 56f7b90..b7dfc60 100644 --- a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java +++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java
@@ -117,6 +117,6 @@ + "clientId = myclientid\n" + "clientSecret = mysecret\n" + configText); - return new GitHubConfig(gerritConfig, site, ALL_PROJECTS_NAME_PROVIDER, null); + return new GitHubConfig(gerritConfig, site, ALL_PROJECTS_NAME_PROVIDER); } }
diff --git a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubGroupCacheRefreshFilterTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubGroupCacheRefreshFilterTest.java new file mode 100644 index 0000000..5c1707f --- /dev/null +++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubGroupCacheRefreshFilterTest.java
@@ -0,0 +1,125 @@ +// Copyright (C) 2023 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.gerrit.util.http.testutil.FakeHttpServletRequest; +import com.google.gerrit.util.http.testutil.FakeHttpServletResponse; +import com.googlesource.gerrit.plugins.github.filters.GitHubGroupCacheRefreshFilter; +import com.googlesource.gerrit.plugins.github.group.GitHubGroupsCache; +import com.googlesource.gerrit.plugins.github.groups.OrganizationStructure; +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.junit.Before; +import org.junit.Test; + +public class GitHubGroupCacheRefreshFilterTest { + private static final FilterChain NOOP_FILTER_CHAIN_TEST = (req, res) -> {}; + private static final String GITHUB_USERNAME_TEST = "somegithubuser"; + private static final OrganizationStructure GITHUB_USER_ORGANIZATION = new OrganizationStructure(); + private static final String TEST_SERVER = "test-server"; + private static final int TEST_PORT = 80; + + private LoadingCache<String, OrganizationStructure> groupsByUsernameCache; + private GitHubGroupCacheRefreshFilter filter; + private FakeGroupCacheLoader groupsCacheLoader; + private int initialLoadCount; + + private static class FakeGroupCacheLoader extends CacheLoader<String, OrganizationStructure> { + private final String username; + private final OrganizationStructure organizationStructure; + private int loadCount; + + FakeGroupCacheLoader(String username, OrganizationStructure organizationStructure) { + this.username = username; + this.organizationStructure = organizationStructure; + } + + @Override + public OrganizationStructure load(String u) throws Exception { + if (u.equals(username)) { + loadCount++; + return organizationStructure; + } else { + return null; + } + } + + public int getLoadCount() { + return loadCount; + } + } + + @Before + public void setUp() throws Exception { + groupsCacheLoader = new FakeGroupCacheLoader(GITHUB_USERNAME_TEST, GITHUB_USER_ORGANIZATION); + groupsByUsernameCache = CacheBuilder.newBuilder().build(groupsCacheLoader); + filter = + new GitHubGroupCacheRefreshFilter( + new GitHubGroupsCache(groupsByUsernameCache, () -> GITHUB_USERNAME_TEST)); + // Trigger the initial load of the groups cache + assertThat(groupsByUsernameCache.get(GITHUB_USERNAME_TEST)).isEqualTo(GITHUB_USER_ORGANIZATION); + initialLoadCount = groupsCacheLoader.getLoadCount(); + } + + @Test + public void shouldReloadGroupsUponSuccessfulLogin() throws Exception { + FakeHttpServletRequest finalLoginRequest = newFinalLoginRequest(); + filter.doFilter(finalLoginRequest, newFinalLoginRedirectWithCookie(), NOOP_FILTER_CHAIN_TEST); + filter.doFilter( + newHomepageRequest(finalLoginRequest.getSession()), + new FakeHttpServletResponse(), + NOOP_FILTER_CHAIN_TEST); + + assertThat(groupsByUsernameCache.get(GITHUB_USERNAME_TEST)).isEqualTo(GITHUB_USER_ORGANIZATION); + assertThat(groupsCacheLoader.getLoadCount()).isEqualTo(initialLoadCount + 1); + } + + @Test + public void shouldNotReloadGroupsOnRegularRequests() throws Exception { + FakeHttpServletRequest regularRequest = new FakeHttpServletRequest(); + filter.doFilter(regularRequest, new FakeHttpServletResponse(), NOOP_FILTER_CHAIN_TEST); + filter.doFilter( + newHomepageRequest(null), new FakeHttpServletResponse(), NOOP_FILTER_CHAIN_TEST); + + assertThat(groupsByUsernameCache.get(GITHUB_USERNAME_TEST)).isEqualTo(GITHUB_USER_ORGANIZATION); + assertThat(groupsCacheLoader.getLoadCount()).isEqualTo(initialLoadCount); + } + + private ServletRequest newHomepageRequest(HttpSession session) { + return new FakeHttpServletRequest(TEST_SERVER, TEST_PORT, "", "/", null, session); + } + + private static HttpServletResponse newFinalLoginRedirectWithCookie() { + HttpServletResponse res = new FakeHttpServletResponse(); + res.setHeader("Set-Cookie", "GerritAccount=foo"); + return res; + } + + private static FakeHttpServletRequest newFinalLoginRequest() { + FakeHttpServletRequest req = + new FakeHttpServletRequest( + TEST_SERVER, TEST_PORT, "", "", () -> new FakeHttpSession(), null); + req.setQueryString("final=true"); + req.setPathInfo("/login"); + return req; + } +}
diff --git a/github-plugin/tsconfig-plugins-base.json b/github-plugin/tsconfig-plugins-base.json new file mode 100644 index 0000000..a19ebbf --- /dev/null +++ b/github-plugin/tsconfig-plugins-base.json
@@ -0,0 +1,50 @@ +/* TODO: this file should be included in @gerritcodereview/typescript-api */ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "inlineSourceMap": true, /* Generates corresponding '.map' file. */ + "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "removeComments": false, /* Emit comments to output */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true,/* Report errors for fallthrough cases in switch statement. */ + + "skipLibCheck": true, /* Do not check node_modules */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + + /* Advanced Options */ + "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "incremental": true, + "experimentalDecorators": true, + + "allowUmdGlobalAccess": true, + + "typeRoots": [ + /* typeRoots for Bazel */ + "../external/ui_dev_npm/node_modules/@types", + "../external/plugins_npm/node_modules/@types", + /* typeRoots for IDE */ + "../polygerrit-ui/node_modules/@types", + "../plugins/node_modules/@types" + ] + }, +}
diff --git a/github-plugin/tsconfig.json b/github-plugin/tsconfig.json new file mode 100644 index 0000000..f08de5a --- /dev/null +++ b/github-plugin/tsconfig.json
@@ -0,0 +1,10 @@ +{ + /* TODO: should be change to ./node_modules/@gerritcodereview/typescript-api/tsconfig-plugins-base.json' when NPM paclage is fixed */ + "extends": "./tsconfig-plugins-base.json", + "compilerOptions": { + "rootDir": ".", + "experimentalDecorators": true, + "skipLibCheck": true, + "outDir": "./target/web" + }, +} \ No newline at end of file
diff --git a/github-plugin/yarn.lock b/github-plugin/yarn.lock new file mode 100644 index 0000000..51435d5 --- /dev/null +++ b/github-plugin/yarn.lock
@@ -0,0 +1,316 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gerritcodereview/typescript-api@^3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@gerritcodereview/typescript-api/-/typescript-api-3.8.0.tgz#2e418b814d7451c40365b2dc4f88e9965ece0769" + integrity sha512-wUkIWUx99Rj1vxRYQISxyzN0nplqu7t5sRDyJ8R3yNNkvALQAMC6Whj63qzCsZsymVFzC5up3y+ZVxaeh7b+xA== + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9" + integrity sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ== + +"@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.6.0": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.6.3.tgz#25b4eece2592132845d303e091bad9b04cdcfe03" + integrity sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.0.0" + +"@lit/ts-transformers@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@lit/ts-transformers/-/ts-transformers-1.1.3.tgz#0d6c99c9a619dc762f896bd403546a7e396942be" + integrity sha512-I3Pp2J9SS09h3SiMxOQ87vVPZA74qZfYR1rD5by8F6VXYYwmN8DEe52tpi/u4Na2wE/XmkFgAg/vsVWz0fqvuw== + dependencies: + ts-clone-node "^1.0.0" + typescript "~4.7.4" + +"@polymer/polymer@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.5.1.tgz#4b5234e43b8876441022bcb91313ab3c4a29f0c8" + integrity sha512-JlAHuy+1qIC6hL1ojEUfIVD58fzTpJAoCxFwV5yr0mYTXV1H8bz5zy0+rC963Cgr9iNXQ4T9ncSjC2fkF9BQfw== + dependencies: + "@webcomponents/shadycss" "^1.9.1" + +"@rollup/plugin-node-resolve@^15.2.1": + version "15.2.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz#a15b14fb7969229e26a30feff2816d39eff503f0" + integrity sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-builtin-module "^3.2.1" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/plugin-terser@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz#c2bde2fe3a85e45fa68a454d48f4e73e57f98b30" + integrity sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA== + dependencies: + serialize-javascript "^6.0.1" + smob "^1.0.0" + terser "^5.17.4" + +"@rollup/pluginutils@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.4.tgz#74f808f9053d33bafec0cc98e7b835c9667d32ba" + integrity sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@types/estree@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453" + integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA== + +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + +"@types/trusted-types@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65" + integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ== + +"@webcomponents/shadycss@^1.9.1": + version "1.11.2" + resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.2.tgz#7539b0ad29598aa2eafee8b341059e20ac9e1006" + integrity sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw== + +acorn@^8.8.2: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +compatfactory@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/compatfactory/-/compatfactory-1.0.1.tgz#a5940f1d734b86c02bb818a67a412d4c306ccaf4" + integrity sha512-hR9u0HSZTKDNNchPtMHg6myeNx0XO+av7UZIJPsi4rPALJBHi/W5Mbwi19hC/xm6y3JkYpxVYjTqnSGsU5X/iw== + dependencies: + helpertypes "^0.0.18" + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + +helpertypes@^0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/helpertypes/-/helpertypes-0.0.18.tgz#fd2bf5d3351cc7d80f7876732361d3adba63e5b4" + integrity sha512-XRhfbSEmR+poXUC5/8AbmYNJb2riOT6qPzjGJZr0S9YedHiaY+/tzPYzWMUclYMEdCYo/1l8PDYrQFCj02v97w== + +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +lit-element@^3.3.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.3.3.tgz#10bc19702b96ef5416cf7a70177255bfb17b3209" + integrity sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.1.0" + "@lit/reactive-element" "^1.3.0" + lit-html "^2.8.0" + +lit-html@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.8.0.tgz#96456a4bb4ee717b9a7d2f94562a16509d39bffa" + integrity sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q== + dependencies: + "@types/trusted-types" "^2.0.2" + +lit@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/lit/-/lit-2.8.0.tgz#4d838ae03059bf9cafa06e5c61d8acc0081e974e" + integrity sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA== + dependencies: + "@lit/reactive-element" "^1.6.0" + lit-element "^3.3.0" + lit-html "^2.8.0" + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +resolve@^1.22.1: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rollup@^3.29.4: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + optionalDependencies: + fsevents "~2.3.2" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + +smob@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/smob/-/smob-1.4.1.tgz#66270e7df6a7527664816c5b577a23f17ba6f5b5" + integrity sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +terser@^5.17.4: + version "5.21.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.21.0.tgz#d2b27e92b5e56650bc83b6defa00a110f0b124b2" + integrity sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +ts-clone-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ts-clone-node/-/ts-clone-node-1.0.0.tgz#aaffa5478cf303471cec9c3c8169e117a0f87614" + integrity sha512-/cDYbr2HAXxFNeTT41c/xs/2bhLJjqnYheHsmA3AoHSt+n4JA4t0FL9Lk5O8kWnJ6jeB3kPcUoXIFtwERNzv6Q== + dependencies: + compatfactory "^1.0.1" + +typescript@^4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +typescript@~4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
diff --git a/pom.xml b/pom.xml index 5d6b07e..d0f54d8 100644 --- a/pom.xml +++ b/pom.xml
@@ -18,12 +18,12 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.googlesource.gerrit.plugins.github</groupId> <artifactId>github-parent</artifactId> - <version>3.8.0</version> + <version>3.9.0-rc5</version> <name>Gerrit Code Review - GitHub integration</name> <url>http://www.gerritforge.com</url> <packaging>pom</packaging> <properties> - <javaVersion>11</javaVersion> + <javaVersion>17</javaVersion> </properties> <licenses> <license> @@ -255,7 +255,7 @@ <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> - <version>3.0.0</version> + <version>3.0.5</version> </plugin> </plugins> </reporting> @@ -292,13 +292,13 @@ <dependency> <groupId>com.ryanharter.auto.value</groupId> <artifactId>auto-value-gson</artifactId> - <version>1.3.0</version> + <version>1.3.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> - <version>1.18.22</version> + <version>1.18.30</version> <scope>provided</scope> </dependency> </dependencies> @@ -313,7 +313,7 @@ <dependency> <groupId>com.google.truth</groupId> <artifactId>truth</artifactId> - <version>1.1.4</version> + <version>1.1.5</version> <scope>test</scope> </dependency> </dependencies>