diff --git a/BUILD b/BUILD new file mode 100644 index 00000000..919c4531 --- /dev/null +++ b/BUILD @@ -0,0 +1,25 @@ +load("//tools/bzl:genrule2.bzl", "genrule2") + +genrule( + name = "github", + srcs = [ + ":github-plugin", + ":github-oauth", + ], + outs = ["github.zip"], + cmd = "zip -o $@ $(SRCS)", +) + +genrule( + name = "github-oauth", + srcs = ["//plugins/github/github-oauth:github-oauth_deploy.jar"], + outs = ["github-oauth.jar"], + cmd = "cp $< $@", +) + +genrule( + name = "github-plugin", + srcs = ["//plugins/github/github-plugin"], + outs = ["github-plugin.jar"], + cmd = "cp $< $@", +) diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..7410d3c5 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,3 @@ +pluginPipeline(formatCheckId: 'gerritforge:github-format-3852e64366bb37d13b8baf8af9b15cfd38eb9227', + buildCheckId: 'gerritforge:github-3852e64366bb37d13b8baf8af9b15cfd38eb9227', + gjfVersion: '1.22.0') diff --git a/README.md b/README.md index 5a950193..f4e98c3d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -GitHub plugin +# DEPRECATION NOTICE + +GerritForge has decided to [change the license to BSL](https://gitenterprise.me/2025/09/30/re-licensing-gerritforge-plugins-welcome-to-gerrit-enterprise/) +therefore the Apache 2.0 version of this plugin is deprecated. +The recommended version of the github plugin is on [GitHub](https://github.com/GerritForge/github) +and the development continues on [GerritHub.io](https://review.gerrithub.io/admin/repos/GerritForge/github,general). + +GitHub plugin (DEPRECATED) ============= This plugin allows existing GitHub repositories to be integrated as Gerrit projects. @@ -78,13 +85,13 @@ reference on how to build Gerrit using Bazel. 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 +143,10 @@ Note: Client ID & Client Secret are generated that used in the next step. * GitHub Integration * GitHub URL: [https://github.com]: -* Use GitHub for Gerrit login? [Y/n] Y +* GitHub API URL: [https://api.github.com]: * ClientId []: * ClientSecret []: +* Gerrit OAuth implementation [http/?]: ### Receiving Pull Request events to automatically import @@ -195,4 +203,22 @@ Found 2 ref(s): Please remove or rename the following refs and try again: 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) + +#### Enforcing Size Quota + +When a repository is imported from GitHub, the plugin verifies that its size does not exceed any +configured quota constraints. + +A common case is a quota defined by the +[quota plugin](https://gerrit.googlesource.com/plugins/quota/+/refs/heads/master/src/main/resources/Documentation/config.md#quota), +which can restrict how much storage space repositories may consume or how many repositories can be +created within a given namespace. + +If an imported repository exceeds the allowed quota, the operation fails with an error message. +For example: + +```text +Unable to create repository foo/bar: project cannot be created because a quota for the namespace +'foo/*' allows at most 3 projects and 3 projects already exist. +``` diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl new file mode 100644 index 00000000..5282ce4c --- /dev/null +++ b/external_plugin_deps.bzl @@ -0,0 +1,115 @@ +load("@bazel_tools//tools/build_defs/repo:java.bzl", "java_import_external") +load("//tools/bzl:maven_jar.bzl", "maven_jar") + +JENKINS = "JENKINS:" +ECLIPSE_EGIT = "ECLIPSE_EGIT:" + +def external_plugin_deps(): + maven_jar( + name = "github-api", + artifact = "org.kohsuke:github-api:1.316", + sha1 = "90ea530f3aeceb46be27b924ae25b4b7b2388c9d", + ) + + maven_jar( + name = "axis", + artifact = "org.apache.axis:axis:1.4", + sha1 = "94a9ce681a42d0352b3ad22659f67835e560d107", + attach_source = False, + ) + + maven_jar( + name = "axis-jaxrpc", + artifact = "org.apache.axis:axis-jaxrpc:1.4", + sha1 = "b393f1f0c0d95b68c86d0b1ab2e687bb71f3c075", + attach_source = False, + ) + + maven_jar( + name = "eclipse-mylyn-github", + artifact = "org.eclipse.mylyn.github:org.eclipse.egit.github.core:6.1.0.202203080745-r", + repository = ECLIPSE_EGIT, + sha1 = "a0bc7ce9f17e2d41bbfbf08e4bc63c3ae0ec15b7", + attach_source = False, + ) + + maven_jar( + name = "commons-discovery", + artifact = "commons-discovery:commons-discovery:0.5", + sha1 = "3a8ac816bbe02d2f88523ef22cbf2c4abd71d6a8", + attach_source = False, + ) + + maven_jar( + name = "velocity", + artifact = "org.apache.velocity:velocity-engine-core:2.3", + sha1 = "e2133b723d0e42be74880d34de6bf6538ea7f915", + ) + + maven_jar( + name = "lombok", + artifact = "org.projectlombok:lombok:1.18.32", + sha1 = "17d46b3e205515e1e8efd3ee4d57ce8018914163", + ) + + maven_jar( + name = "com-sun-mail", + artifact = "com.sun.mail:javax.mail:1.6.2", + sha1 = "935151eb71beff17a2ffac15dd80184a99a0514f", + ) + + maven_jar( + name = "javax-activation", + artifact = "javax.activation:activation:1.1", + sha1 = "e6cb541461c2834bdea3eb920f1884d1eb508b50", + ) + + maven_jar( + name = "jackson-core", + artifact = "com.fasterxml.jackson.core:jackson-core:2.15.2", + sha1 = "a6fe1836469a69b3ff66037c324d75fc66ef137c", + ) + + maven_jar( + name = "jackson-databind", + artifact = "com.fasterxml.jackson.core:jackson-databind:2.15.2", + sha1 = "9353b021f10c307c00328f52090de2bdb4b6ff9c", + ) + + maven_jar( + name = "jackson-annotations", + artifact = "com.fasterxml.jackson.core:jackson-annotations:2.15.2", + sha1 = "4724a65ac8e8d156a24898d50fd5dbd3642870b8", + ) + + maven_jar( + name = "org-ow2-asm", + artifact = "org.ow2.asm:asm:9.6", + sha1 = "aa205cf0a06dbd8e04ece91c0b37c3f5d567546a", + ) + + maven_jar( + name = "org-ow2-asm-tree", + artifact = "org.ow2.asm:asm-tree:9.6", + sha1 = "c0cdda9d211e965d2a4448aa3fd86110f2f8c2de", + ) + + maven_jar( + name = "org-ow2-asm-commons", + artifact = "org.ow2.asm:asm-commons:9.6", + sha1 = "f1a9e5508eff490744144565c47326c8648be309", + ) + + maven_jar( + name = "bridge-method-injector", + artifact = "com.infradna.tool:bridge-method-injector:1.29", + repository = JENKINS, + sha1 = "5b6c616c7a6e04beb4178327d616af4f1bbe88da", + ) + + maven_jar( + name = "bridge-method-annotation", + artifact = "com.infradna.tool:bridge-method-annotation:1.29", + repository = JENKINS, + sha1 = "55dd67d0578d107697803a95cb9c235a9bd83ec1", + ) diff --git a/github-oauth/BUILD b/github-oauth/BUILD new file mode 100644 index 00000000..4f80450f --- /dev/null +++ b/github-oauth/BUILD @@ -0,0 +1,37 @@ +load("//tools/bzl:junit.bzl", "junit_tests") +load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_DEPS_NEVERLINK") + +java_binary( + name = "github-oauth", + main_class = "Dummy", + visibility = ["//visibility:public"], + runtime_deps = [":github-oauth-lib"], +) + +java_library( + name = "github-oauth-lib", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = PLUGIN_DEPS_NEVERLINK + [ + "//lib:servlet-api", + "@bridge-method-annotation//jar", + "@bridge-method-injector//jar", + "@commons-io//jar", + "@github-api//jar", + "@jackson-annotations//jar", + "@jackson-core//jar", + "@jackson-databind//jar", + "@org-ow2-asm-commons//jar", + "@org-ow2-asm-tree//jar", + "@org-ow2-asm//jar", + ], +) + +junit_tests( + name = "github-oauth_tests", + srcs = glob(["src/test/java/**/*.java"]), + tags = ["github"], + deps = PLUGIN_DEPS + [ + ":github-oauth-lib", + ], +) diff --git a/github-oauth/pom.xml b/github-oauth/pom.xml deleted file mode 100644 index 75b9c09e..00000000 --- a/github-oauth/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - 4.0.0 - - com.googlesource.gerrit.plugins.github - github-parent - 3.8.0 - - github-oauth - Gerrit Code Review - GitHub OAuth login - http://maven.apache.org - - UTF-8 - - - - - org.apache.maven.plugins - maven-shade-plugin - 1.6 - - true - - - com.google.*:* - javax.inject:*:* - aopalliance:aopalliance:* - org.slf4j:* - log4j:log4j:* - commons-lang:*:* - commons-codec:*:* - com.google.guava:* - - - - - - package - - shade - - - - - - - - - com.google.gerrit - gerrit-plugin-api - ${project.version} - provided - - - javax.servlet - javax.servlet-api - 3.0.1 - provided - - - com.google.inject - guice - 4.1.0 - provided - - - com.google.guava - guava - 20.0 - provided - - - org.kohsuke - github-api - 1.116 - - - com.infradna.tool - bridge-method-injector - 1.18 - - - org.apache.httpcomponents - httpclient - 4.4 - provided - - - junit - junit - test - - - 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 00000000..faca0f98 --- /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 ece944db..3aca14a9 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 @@ -20,7 +20,6 @@ import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Singleton; -import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken; import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope; import java.io.IOException; import java.io.Serializable; @@ -34,11 +33,12 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import lombok.Getter; 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; @@ -57,13 +57,19 @@ public GitHubLogin get(HttpServletRequest request) { } } - @Getter private AccessToken token; + private String accessToken; private String state; private SortedSet loginScopes; private final GitHubOAuthConfig config; - private final HttpConnector httpConnector; + private final CanonicalWebUrls canonicalWebUrls; + private final VirtualDomainConfig virtualDomainConfig; + private final GitHubConnector gitHubConnector; + + public String getAccessToken() { + return accessToken; + } public GHMyself getMyself() throws IOException { if (isLoggedIn()) { @@ -72,21 +78,39 @@ public GHMyself getMyself() throws IOException { return null; } - public Set getMyOrganisationsLogins() throws IOException { + public Set 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() { - return token != null; + return accessToken != null; } public void login( @@ -99,7 +123,7 @@ public void login( log.debug("Login " + this); if (OAuthProtocol.isOAuthFinal(request)) { log.debug("Login-FINAL " + this); - login(oauth.loginPhase2(request, response, state)); + login(oauth.loginPhase2(request, response, state).accessToken); this.state = ""; // Make sure state is used only once if (isLoggedIn()) { @@ -107,12 +131,13 @@ public void login( response.sendRedirect(OAuthProtocol.getTargetUrl(request)); } } else { - Set configuredScopesProfiles = config.scopes.keySet(); + Set 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); } @@ -120,28 +145,28 @@ public void login( } public void logout() { - token = null; + accessToken = null; } - public GitHub login(AccessToken authToken) throws IOException { - log.debug("Logging in using access token {}", authToken.accessToken); - this.token = authToken; + public GitHub login(String authAccessToken) throws IOException { + log.debug("Logging in using access token {}", authAccessToken); + this.accessToken = authAccessToken; return getHub(); } @Override public String toString() { - return "GitHubLogin [token=" + token + ", scopes=" + loginScopes + "]"; + return "GitHubLogin [token=" + accessToken + ", scopes=" + loginScopes + "]"; } public GitHub getHub() throws IOException { - if (token == null) { + if (accessToken == null) { return null; } return new GitHubBuilder() .withEndpoint(config.gitHubApiUrl) - .withOAuthToken(token.accessToken) - .withConnector(httpConnector) + .withOAuthToken(accessToken) + .withConnector(gitHubConnector) .build(); } @@ -178,16 +203,16 @@ public String getScopesKeyFromCookie(HttpServletRequest request) { return null; } - private SortedSet getScopes(String baseScopeKey, Scope... scopes) { - HashSet fullScopes = new HashSet<>(scopesForKey(baseScopeKey)); + private SortedSet getScopes(HttpServletRequest req, String baseScopeKey, Scope... scopes) { + HashSet fullScopes = new HashSet<>(scopesForKey(req, baseScopeKey)); fullScopes.addAll(Arrays.asList(scopes)); return new TreeSet<>(fullScopes); } - private List scopesForKey(String baseScopeKey) { - return config.scopes.entrySet().stream() - .filter(entry -> entry.getKey().name.equals(baseScopeKey)) + private List scopesForKey(HttpServletRequest req, String baseScopeKey) { + return virtualDomainConfig.getScopes(req).entrySet().stream() + .filter(entry -> entry.getKey().name().equals(baseScopeKey)) .map(entry -> entry.getValue()) .findFirst() .orElse(DEFAULT_SCOPES); 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 19eebf21..8993d3fd 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,15 @@ 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 +65,11 @@ public class GitHubOAuthConfig { public final String httpHeader; public final String gitHubOAuthUrl; public final String gitHubOAuthAccessTokenUrl; + public final String scopeSelectionUrl; public final boolean enabled; - @Getter public final Map> scopes; - @Getter public final List sortedScopesKeys; + public final SortedMap> scopes; + public final Map>> virtualScopes; public final int fileUpdateMaxRetryCount; public final int fileUpdateMaxRetryIntervalMsec; @@ -83,9 +82,8 @@ public class GitHubOAuthConfig { private final Optional 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 +104,7 @@ protected GitHubOAuthConfig(@GerritServerConfig Config config, CanonicalWebUrl c 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 +114,7 @@ protected GitHubOAuthConfig(@GerritServerConfig Config config, CanonicalWebUrl c 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,39 +148,33 @@ protected GitHubOAuthConfig(@GerritServerConfig Config config, CanonicalWebUrl c currentKeyConfig = currentKeyConfigs.get(0); } - public String getOAuthFinalRedirectUrl(HttpServletRequest req) { - return req == null - ? GERRIT_OAUTH_FINAL - : trimTrailingSlash(canonicalWebUrl.get(req)) + GERRIT_OAUTH_FINAL; + private SortedMap> 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); + static Map>> getVirtualScopes(Config config) { + return config.getSubsections(CONF_SECTION).stream() + .collect(Collectors.toMap(k -> k, v -> getScopesInSection(config, v))); } - private Map> getScopes(Config config) { - return config.getNames(CONF_SECTION, true).stream() + private static SortedMap> 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::sequence), 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)))); + config.getString(CONF_SECTION, subsection, k + "Description"), + config.getInt(CONF_SECTION, subsection, k + "Sequence", 0)), + v -> parseScopesString(config.getString(CONF_SECTION, subsection, v)))); } - private String trimTrailingSlash(String url) { - return CharMatcher.is('/').trimTrailingFrom(url); - } - - private List parseScopesString(String scopesString) { + private static List parseScopesString(String scopesString) { ArrayList result = new ArrayList<>(); if (Strings.emptyToNull(scopesString) != null) { String[] scopesStrings = scopesString.split(","); 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 54493471..91505dfe 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 @@ public class IdentifiedUserGitHubLoginProvider implements UserScopedProvider userProvider; - private final GitHubOAuthConfig config; private final AccountCache accountCache; - private final GitHubHttpConnector httpConnector; private final OAuthTokenCipher oAuthTokenCipher; + private final Provider gitHubLoginProvider; @Inject public IdentifiedUserGitHubLoginProvider( + Provider gitHubLoginaprovider, Provider 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,8 +64,8 @@ public GitHubLogin get(String username) { try { AccessToken accessToken = newAccessTokenFromUser(username); if (accessToken != null) { - GitHubLogin login = new GitHubLogin(config, httpConnector); - login.login(accessToken); + GitHubLogin login = gitHubLoginProvider.get(); + login.login(accessToken.accessToken); return login; } return null; diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java index 955c86db..ce1fd5c5 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java @@ -37,7 +37,7 @@ public Loader(GitHubLogin ghLogin) { @Override public String load(AccessToken accessToken) throws Exception { - ghLogin.login(accessToken); + ghLogin.login(accessToken.accessToken); return ghLogin.getMyself().getLogin(); } } 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 b93837bd..d3678c19 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 @@ -26,7 +26,6 @@ import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import lombok.Getter; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; @@ -139,14 +138,18 @@ public static enum Scope { /** Grants the ability to add and update GitHub Actions workflow files. */ WORKFLOW("workflow", "Manage actions workflow files."); - @Getter private final String value; + public final String value; - @Getter private final String description; + public final String description; - private Scope(final String value, final String description) { + Scope(final String value, final String description) { this.value = value; this.description = description; } + + public String getDescription() { + return description; + } } private static final String ME_SEPARATOR = ","; @@ -155,6 +158,7 @@ private Scope(final String value, final String description) { private static SecureRandom randomState = newRandomGenerator(); private final GitHubOAuthConfig config; + private final CanonicalWebUrls canonicalWebUrls; private final Gson gson; private final Provider httpProvider; @@ -231,6 +235,7 @@ public OAuthToken toOAuthToken() { @Inject public OAuthProtocol( GitHubOAuthConfig config, + CanonicalWebUrls canonicalWebUrls, PooledHttpClientProvider httpClientProvider, /* * We need to explicitly tell Guice which Provider<> we need as this class @@ -239,6 +244,7 @@ public OAuthProtocol( */ GsonProvider gsonProvider) { this.config = config; + this.canonicalWebUrls = canonicalWebUrls; this.httpProvider = httpClientProvider; this.gson = gsonProvider.get(); } @@ -256,7 +262,7 @@ public String getAuthorizationUrl(String scopesString, String state, HttpServlet + "?client_id=" + config.gitHubClientId + getURLEncodedParameter("&scope=", scopesString) - + getURLEncodedParameter("&redirect_uri=", config.getOAuthFinalRedirectUrl(req)) + + getURLEncodedParameter("&redirect_uri=", canonicalWebUrls.getOAuthFinalRedirectUrl()) + getURLEncodedParameter("&state=", state); } @@ -289,7 +295,7 @@ public String getScope(Set scopes) { if (out.length() > 0) { out.append(","); } - out.append(scope.getValue()); + out.append(scope.value); } return out.toString(); } diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipher.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipher.java index a8133dca..799617c4 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipher.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipher.java @@ -148,7 +148,8 @@ static List splitKeyIdFromMaterial(String base64EncryptedString) { if (nOfTokens != 2) { throw new IllegalStateException( String.format( - "The encrypted key is expected to contain 2 tokens (keyId:key), whereas it contains %d tokens", + "The encrypted key is expected to contain 2 tokens (keyId:key), whereas it contains" + + " %d tokens", nOfTokens)); } return tokens; 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 0bca5702..60cef6a7 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 @@ -91,7 +91,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } if (ghLogin != null && ghLogin.isLoggedIn()) { - String hashedToken = oAuthTokenCipher.encrypt(ghLogin.getToken().accessToken); + String hashedToken = oAuthTokenCipher.encrypt(ghLogin.getAccessToken()); httpRequest = new AuthenticatedHttpRequest( httpRequest, @@ -131,7 +131,7 @@ private void login( String user = myself.getLogin(); updateSecureConfigWithRetry( - ghLogin.getHub().getMyOrganizations().keySet(), user, ghLogin.getToken().accessToken); + ghLogin.getMyOrganisationsLogins(user), user, ghLogin.getAccessToken()); } } diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PasswordGenerator.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PasswordGenerator.java index 2f36425b..78360868 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PasswordGenerator.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PasswordGenerator.java @@ -57,7 +57,8 @@ public boolean generate(Path passwordFilePath) { long length = passwordFile.length(); if (length != PASSWORD_LENGTH_DEFAULT) { throw logErrorAndCreateRuntimeException( - "'%s' password file exists but has an invalid length of %d bytes. The expected length is %d bytes.", + "'%s' password file exists but has an invalid length of %d bytes. The expected length" + + " is %d bytes.", passwordFilePath, length, PASSWORD_LENGTH_DEFAULT); } return false; diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/ScopeKey.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/ScopeKey.java index a4751c7e..6b7ecfb7 100644 --- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/ScopeKey.java +++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/ScopeKey.java @@ -14,16 +14,4 @@ package com.googlesource.gerrit.plugins.github.oauth; -import lombok.Getter; - -public class ScopeKey { - @Getter public final String name; - @Getter public final String description; - @Getter public final int sequence; - - public ScopeKey(String name, String description, int sequence) { - this.name = name; - this.description = description; - this.sequence = sequence; - } -} +public record ScopeKey(String name, String description, int sequence) {} 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 00000000..a6b2b175 --- /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> 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 6e181701..d79b3b7a 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 @@ public void setUp() { 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,49 @@ public void shouldReturnTheCookieDomainFromAuth() { 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>> virtualScopes = getVirtualScopes(); + + assertTrue(virtualScopes.containsKey(vhost)); + + SortedMap> vhostConfig = virtualScopes.get(vhost); + List>> entries = + new ArrayList<>(vhostConfig.entrySet()); + Map.Entry> firstEntry = entries.get(0); + Map.Entry> 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 Map>> getVirtualScopes() { + return GitHubOAuthConfig.getVirtualScopes(config); + } + 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 3e31d6b7..f3dfb715 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 @@ public void setUp() { 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() throws IOException { } 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 80d62575..1080f671 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/BUILD b/github-plugin/BUILD new file mode 100644 index 00000000..3ff3d067 --- /dev/null +++ b/github-plugin/BUILD @@ -0,0 +1,104 @@ +load( + "//plugins/github:java_library_without_header_compilation.bzl", + "java_library_without_header_compilation", +) +load("//tools/bzl:junit.bzl", "junit_tests") +load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_DEPS_NEVERLINK", "PLUGIN_TEST_DEPS", "gerrit_plugin") + +SOURCES_WITH_LOMBOK_USAGE = [ + "src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java", + "src/main/java/com/googlesource/gerrit/plugins/github/GitHubURL.java", +] + +gerrit_plugin( + name = "github-plugin", + srcs = glob( + ["src/main/java/**/*.java"], + exclude = SOURCES_WITH_LOMBOK_USAGE, + ), + dir_name = "github", + manifest_entries = [ + "Gerrit-PluginName: github-plugin", + "Gerrit-Module: com.googlesource.gerrit.plugins.github.GuiceModule", + "Gerrit-HttpModule: com.googlesource.gerrit.plugins.github.GuiceHttpModule", + "Gerrit-InitStep: com.googlesource.gerrit.plugins.github.InitGitHub", + "Implementation-Title: GitHub plugin", + "Implementation-Vendor: GerritForge", + "Implementation-URL: http://www.gerritforge.com", + ], + resource_jars = ["//plugins/github/github-plugin/web:github-plugin"], + resources = glob(["src/main/resources/**/*"]), + deps = [ + ":github-plugin-lib", + ":replication-api", + "//plugins/github/github-oauth:github-oauth-lib", + "@axis-jaxrpc//jar", + "@axis//jar", + "@com-sun-mail//jar", + "@commons-codec//jar", + "@commons-discovery//jar", + "@commons-io//jar", + "@eclipse-mylyn-github//jar", + "@github-api//jar", + "@javax-activation//jar", + "@velocity//jar", + ], +) + +java_library_without_header_compilation( + name = "github-plugin-lib", + dep = ":github-plugin-lib_impl", + visibility = ["//visibility:public"], +) + +java_library( + name = "github-plugin-lib_impl", + srcs = SOURCES_WITH_LOMBOK_USAGE, + deps = PLUGIN_DEPS_NEVERLINK + [ + ":lombok", + "//plugins/github/github-oauth:github-oauth-lib", + "@axis-jaxrpc//jar", + "@axis//jar", + "@commons-codec//jar", + "@commons-discovery//jar", + "@commons-io//jar", + "@eclipse-mylyn-github//jar", + "@github-api//jar", + "@velocity//jar", + ], +) + +junit_tests( + name = "github-plugin_tests", + srcs = glob(["src/test/java/**/*.java"]), + tags = ["github"], + deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ + ":github-plugin-lib", + ":github-plugin__plugin", + "//javatests/com/google/gerrit/util/http/testutil", + "//plugins/github/github-oauth:github-oauth-lib", + "//plugins/replication:replication-api", + "@commons-io//jar", + "@github-api//jar", + ], +) + +java_plugin( + name = "lombok_plugin", + generates_api = True, + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + deps = ["@lombok//jar"], +) + +java_library( + name = "lombok", + exported_plugins = [":lombok_plugin"], + neverlink = True, + exports = ["@lombok//jar"], +) + +java_library( + name = "replication-api", + neverlink = True, + exports = ["//plugins/replication:replication-api"], +) diff --git a/github-plugin/package.json b/github-plugin/package.json new file mode 100644 index 00000000..209cc448 --- /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 deleted file mode 100644 index b77cd4e6..00000000 --- a/github-plugin/pom.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - 4.0.0 - - github-parent - com.googlesource.gerrit.plugins.github - 3.8.0 - - - github-plugin - jar - Gerrit Code Review - GitHub plugin - - - plugin - ${project.version} - /Users/lucamilanesio/Documents/workspace-gerrit/gerrithub/gerrit - - - - - - org.apache.maven.plugins - maven-shade-plugin - 1.6 - - true - - - com.google.*:* - javax.inject:*:* - aopalliance:aopalliance:* - org.slf4j:* - log4j:log4j:* - commons-lang:*:* - commons-codec:*:* - commons-io:*:* - com.google.guava:* - com.fasterxml.jackson.core:* - org.kohsuke:github-api:* - org.jenkins-ci:annotation-indexer:* - com.infradna.tool:* - - - - - - com.googlesource.gerrit.plugins.github.GuiceModule - com.googlesource.gerrit.plugins.github.GuiceHttpModule - com.googlesource.gerrit.plugins.github.InitGitHub - - GerritForge - http://www.gerritforge.com - - ${Gerrit-ApiType} - ${project.artifactId} - ${project.version} - - ${Gerrit-ApiType} - ${Gerrit-ApiVersion} - github-plugin - - - - - - - - package - - shade - - - - - - - - - - com.google.gerrit - gerrit-plugin-api - ${Gerrit-ApiVersion} - provided - - - ${project.groupId} - github-oauth - ${project.version} - - - javax.servlet - javax.servlet-api - 3.0.1 - provided - - - org.apache.axis - axis - 1.4 - - - org.apache.axis - axis-jaxrpc - 1.4 - - - org.eclipse.mylyn.github - org.eclipse.egit.github.core - 1.3.1 - - - com.google.code.gson - gson - 2.8.6 - - - org.apache.httpcomponents - httpclient - 4.4 - provided - - - javax.mail - mail - 1.4.5-rc1 - - - org.apache.commons - commons-io - 1.3.2 - - - commons-discovery - commons-discovery - 20040218.194635 - - - org.apache.velocity - velocity-engine-core - 2.3 - - - junit - junit - - - com.google.truth - truth - - - diff --git a/github-plugin/rollup.config.mjs b/github-plugin/rollup.config.mjs new file mode 100644 index 00000000..03d92bed --- /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 e826880f..1c0fe3b7 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 a22126dd..2bacafdc 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 NextPage(final String pageUri, final boolean redirect) { public GitHubConfig( @GerritServerConfig Config config, final SitePaths site, - Provider allProjectsNameProvider, - CanonicalWebUrl canonicalWebUrl) + Provider 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 5ff53637..d345556a 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 @@ public GitHubTopMenu( 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 f75030d5..add26570 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; @@ -27,6 +33,7 @@ import com.googlesource.gerrit.plugins.github.git.MagicRefCheckStep; import com.googlesource.gerrit.plugins.github.git.ProtectedBranchesCheckStep; import com.googlesource.gerrit.plugins.github.git.PullRequestImportJob; +import com.googlesource.gerrit.plugins.github.git.QuotaCheckStep; import com.googlesource.gerrit.plugins.github.git.ReplicateProjectStep; import com.googlesource.gerrit.plugins.github.notification.WebhookServlet; import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; @@ -71,6 +78,10 @@ protected void configureServlets() { new FactoryModuleBuilder() .implement(MagicRefCheckStep.class, MagicRefCheckStep.class) .build(MagicRefCheckStep.Factory.class)); + install( + new FactoryModuleBuilder() + .implement(QuotaCheckStep.class, QuotaCheckStep.class) + .build(QuotaCheckStep.Factory.class)); install( new FactoryModuleBuilder() .implement(PullRequestImportJob.class, PullRequestImportJob.class) @@ -90,6 +101,9 @@ protected void configureServlets() { .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 +111,9 @@ protected void configureServlets() { 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/GuiceModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java index 9bff585d..991ba5ae 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceModule.java @@ -18,18 +18,13 @@ import com.google.gerrit.extensions.restapi.RestApiModule; import com.google.gerrit.extensions.webui.TopMenu; import com.google.gerrit.server.account.GroupBackend; -import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.events.EventListener; import com.google.gerrit.server.project.ProjectResource; import com.google.gson.Gson; import com.google.inject.AbstractModule; -import com.google.inject.Inject; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.assistedinject.FactoryModuleBuilder; -import com.googlesource.gerrit.plugins.github.git.FanoutReplicationConfig; -import com.googlesource.gerrit.plugins.github.git.FileBasedReplicationConfig; -import com.googlesource.gerrit.plugins.github.git.ReplicationConfig; import com.googlesource.gerrit.plugins.github.group.GitHubGroupBackend; import com.googlesource.gerrit.plugins.github.group.GitHubGroupMembership; import com.googlesource.gerrit.plugins.github.group.GitHubGroupsCache; @@ -42,17 +37,9 @@ import com.googlesource.gerrit.plugins.github.replication.ReplicationStatusFlatFile; import com.googlesource.gerrit.plugins.github.replication.ReplicationStatusListener; import com.googlesource.gerrit.plugins.github.replication.ReplicationStatusStore; -import java.nio.file.Files; public class GuiceModule extends AbstractModule { - private final SitePaths site; - - @Inject - public GuiceModule(SitePaths site) { - this.site = site; - } - @Override protected void configure() { bind(new TypeLiteral>() {}) @@ -75,12 +62,6 @@ protected void configure() { } }); - if (Files.exists(site.etc_dir.resolve("replication"))) { - bind(ReplicationConfig.class).to(FanoutReplicationConfig.class).in(Scopes.SINGLETON); - } else { - bind(ReplicationConfig.class).to(FileBasedReplicationConfig.class).in(Scopes.SINGLETON); - } - bind(ReplicationStatusStore.class).to(ReplicationStatusFlatFile.class).in(Scopes.SINGLETON); bind(Gson.class).toProvider(GerritGsonProvider.class); } diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java index 894e1a5b..00ec0bd2 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java @@ -128,7 +128,8 @@ private void setupGitHubOAuthTokenCipher() { if (maybeCurrentKeyId.isPresent()) { if (!ui.yesno( false, - "Current GitHub OAuth token cipher is configured under the %s key id. Do you want to configure a new one?", + "Current GitHub OAuth token cipher is configured under the %s key id. Do you want to" + + " configure a new one?", maybeCurrentKeyId.get())) { return; } 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 00000000..eb34962b --- /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/filters/GitHubOAuthFilter.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java index b025737e..17496fa7 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java @@ -25,7 +25,6 @@ import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; import com.googlesource.gerrit.plugins.github.oauth.IdentifiedUserGitHubLoginProvider; import com.googlesource.gerrit.plugins.github.oauth.OAuthFilter; -import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken; import com.googlesource.gerrit.plugins.github.oauth.OAuthTokenCipher; import com.googlesource.gerrit.plugins.github.oauth.OAuthWebFilter; import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider; @@ -83,7 +82,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha .substring( ExternalId.SCHEME_EXTERNAL.length() + OAuthWebFilter.GITHUB_EXT_ID.length() + 1); String decryptedToken = oAuthTokenCipher.decrypt(oauthToken); - hubLogin.login(new AccessToken(decryptedToken)); + hubLogin.login(decryptedToken); } chain.doFilter(request, response); 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 f26188d3..d359526f 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 void cancel() { } 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 deleted file mode 100644 index 412c2ffc..00000000 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FanoutReplicationConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2020 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.git; - -import com.google.gerrit.server.config.SitePaths; -import com.google.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; - -public class FanoutReplicationConfig implements ReplicationConfig { - private final SitePaths site; - private final FileBasedConfig secureConf; - - @Inject - public FanoutReplicationConfig(final SitePaths site) { - this.site = site; - this.secureConf = new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED); - } - - @Override - public void addSecureCredentials(String authUsername, String authToken) - throws IOException, ConfigInvalidException { - secureConf.load(); - secureConf.setString("remote", authUsername, "username", authUsername); - secureConf.setString("remote", authUsername, "password", authToken); - secureConf.save(); - } - - @Override - public void addReplicationRemote(String githubUsername, String url, String projectName) - throws IOException, ConfigInvalidException { - - FileBasedConfig replicationConf = - new FileBasedConfig( - new File(site.etc_dir.toFile(), String.format("replication/%s.config", githubUsername)), - FS.DETECTED); - - replicationConf.load(); - replicationConf.setString("remote", null, "url", url); - List 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/*"); - replicationConf.save(); - } -} diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FileBasedReplicationConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FileBasedReplicationConfig.java deleted file mode 100644 index 842bde39..00000000 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/FileBasedReplicationConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2013 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.git; - -import com.google.gerrit.server.config.SitePaths; -import com.google.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; - -public class FileBasedReplicationConfig implements ReplicationConfig { - private final FileBasedConfig secureConf; - private final FileBasedConfig replicationConf; - - @Inject - public FileBasedReplicationConfig(final SitePaths site) { - replicationConf = - new FileBasedConfig(new File(site.etc_dir.toFile(), "replication.config"), FS.DETECTED); - secureConf = new FileBasedConfig(site.secure_config.toFile(), FS.DETECTED); - } - - @Override - public synchronized void addSecureCredentials(String authUsername, String authToken) - throws IOException, ConfigInvalidException { - secureConf.load(); - secureConf.setString("remote", authUsername, "username", authUsername); - secureConf.setString("remote", authUsername, "password", authToken); - secureConf.save(); - } - - @Override - public synchronized void addReplicationRemote(String username, String url, String projectName) - throws IOException, ConfigInvalidException { - replicationConf.load(); - replicationConf.setString("remote", username, "url", url); - List projects = - new ArrayList<>( - Arrays.asList(replicationConf.getStringList("remote", username, "projects"))); - projects.add(projectName); - replicationConf.setStringList("remote", username, "projects", projects); - replicationConf.setString("remote", username, "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 12e5101c..d2b589f8 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,13 +13,14 @@ // 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; import com.google.gerrit.extensions.api.projects.ProjectInput; import com.google.gerrit.extensions.events.ProjectDeletedListener; import com.google.gerrit.extensions.registration.DynamicSet; -import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountState; @@ -34,6 +35,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 +52,8 @@ public class GitCloneStep extends ImportStep { 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 deletedListeners; private final ProjectCache projectCache; @@ -85,14 +84,11 @@ public GitCloneStep( 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,12 +109,13 @@ private void createNewProject() throws GitException { 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); } catch (RestApiException e) { - throw new GitException("Unable to create repository " + projectName, e); + throw new GitException( + "Unable to create repository " + projectName + ":" + e.getMessage(), e); } } @@ -128,7 +125,8 @@ public void doImport(ProgressMonitor progress) throws GitException { 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 +144,6 @@ public void doImport(ProgressMonitor progress) throws GitException { } } - 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/GitHubRepository.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java index 09e3fb73..f4321bd4 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java @@ -66,7 +66,7 @@ public GitHubRepository( GitHubLogin ghLogin = ghLoginProvider.get(); GitHub gh = ghLogin.getHub(); this.username = ghLogin.getMyself().getLogin(); - this.password = ghLogin.getToken().accessToken; + this.password = ghLogin.getAccessToken(); this.ghRepository = gh.getRepository(organisation + "/" + repository); } diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubUser.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubUser.java index 2109ea93..606cd96a 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubUser.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubUser.java @@ -17,14 +17,13 @@ import com.google.common.base.Optional; import com.google.common.base.Strings; import java.io.IOException; -import lombok.Getter; import org.kohsuke.github.GHUser; import org.kohsuke.github.GitUser; public class GitHubUser { - @Getter private final String login; - @Getter private final String name; - @Getter private final String email; + public final String login; + public final String name; + public final String email; private GitHubUser(GHUser gitHubUser, GitUser author) throws IOException { this.login = initLogin(gitHubUser).or(generateLogin(author.getName())); diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitImporter.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitImporter.java index ff814115..d901d8a6 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitImporter.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitImporter.java @@ -28,6 +28,7 @@ public static class Provider extends HttpSessionProvider {} private static final Logger log = LoggerFactory.getLogger(GitImporter.class); private final ProtectedBranchesCheckStep.Factory protectedBranchesCheckFactory; private final MagicRefCheckStep.Factory magicRefCheckFactory; + private final QuotaCheckStep.Factory quotaCheckFactory; private final GitCloneStep.Factory cloneFactory; private final CreateProjectStep.Factory projectFactory; private final ReplicateProjectStep.Factory replicateFactory; @@ -39,6 +40,7 @@ public GitImporter( CreateProjectStep.Factory projectFactory, ReplicateProjectStep.Factory replicateFactory, MagicRefCheckStep.Factory magicRefCheckFactory, + QuotaCheckStep.Factory quotaCheckFactory, JobExecutor executor, IdentifiedUser user) { super(executor, user); @@ -47,6 +49,7 @@ public GitImporter( this.projectFactory = projectFactory; this.replicateFactory = replicateFactory; this.magicRefCheckFactory = magicRefCheckFactory; + this.quotaCheckFactory = quotaCheckFactory; } public void clone(int idx, String organisation, String repository, String description) { @@ -57,12 +60,14 @@ public void clone(int idx, String organisation, String repository, String descri MagicRefCheckStep magicRefCheckStep = magicRefCheckFactory.create(organisation, repository); CreateProjectStep projectStep = projectFactory.create(organisation, repository, description, user.getUserName().get()); + QuotaCheckStep quotaCheckStep = quotaCheckFactory.create(organisation, repository); ReplicateProjectStep replicateStep = replicateFactory.create(organisation, repository); GitImportJob gitCloneJob = new GitImportJob( idx, organisation, repository, + quotaCheckStep, protectedBranchesCheckStep, magicRefCheckStep, cloneStep, 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 263ee9df..42b7e364 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 221152f1..c1561502 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 f2a7f06e..e69b1183 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; @@ -96,7 +96,10 @@ public Change.Id addCommitToChange( final RevCommit pullRequestCommit, final String pullRequestMessage, final String topic) - throws NoSuchChangeException, IOException, InvalidChangeOperationException, UpdateException, + throws NoSuchChangeException, + IOException, + InvalidChangeOperationException, + UpdateException, RestApiException { try (BatchUpdate bu = updateFactory.create( @@ -230,7 +233,7 @@ private void insertPatchSet( PatchSetInserter patchSetInserter = patchSetInserterFactory.create(changeNotes, psId, cherryPickCommit); patchSetInserter.setMessage(pullRequestMessage); - patchSetInserter.setValidate(false); + patchSetInserter.disableValidation(); bu.addOp(change.getId(), patchSetInserter); bu.execute(); 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 52d7b349..be8b7c08 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 @@ private List addPullRequestToChange(GHPullRequest pr, Repository gitRepo) th 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); } @@ -203,15 +203,21 @@ private List addPullRequestToChange(GHPullRequest pr, Repository gitRepo) th } private com.google.gerrit.entities.Account.Id getOrRegisterAccount(GitHubUser author) - throws BadRequestException, ResourceConflictException, UnprocessableEntityException, - IOException, ConfigInvalidException { - return getOrRegisterAccount(author.getLogin(), author.getName(), author.getEmail()); + throws BadRequestException, + ResourceConflictException, + UnprocessableEntityException, + IOException, + ConfigInvalidException { + return getOrRegisterAccount(author.login, author.name, author.email); } private com.google.gerrit.entities.Account.Id getOrRegisterAccount( String login, String name, String email) - throws BadRequestException, ResourceConflictException, UnprocessableEntityException, - IOException, ConfigInvalidException { + throws BadRequestException, + ResourceConflictException, + UnprocessableEntityException, + IOException, + ConfigInvalidException { Optional gerritId = externalIdByScheme(ExternalId.SCHEME_GERRIT, login); if (gerritId.isPresent()) { return gerritId.get().accountId(); diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/QuotaCheckStep.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/QuotaCheckStep.java new file mode 100644 index 00000000..057e565e --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/QuotaCheckStep.java @@ -0,0 +1,80 @@ +// Copyright (C) 2025 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.git; + +import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.server.quota.QuotaBackend; +import com.google.gerrit.server.quota.QuotaResponse; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.googlesource.gerrit.plugins.github.GitHubConfig; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QuotaCheckStep extends ImportStep { + private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckStep.class); + private final QuotaBackend quotaBackend; + + private static final long BYTES_PER_KB = 1024L; + + public interface Factory { + QuotaCheckStep create( + @Assisted("organisation") String organisation, @Assisted("repository") String repository); + } + + @Inject + public QuotaCheckStep( + QuotaBackend quotaBackend, + GitHubConfig config, + GitHubRepository.Factory gitHubRepoFactory, + @Assisted("organisation") String organisation, + @Assisted("repository") String repository) { + super(config.gitHubUrl, organisation, repository, gitHubRepoFactory); + this.quotaBackend = quotaBackend; + } + + @Override + public void doImport(ProgressMonitor progress) throws GitException { + Project.NameKey fullProjectName = + Project.nameKey(getOrganisation() + "/" + getRepositoryName()); + try { + progress.beginTask("Getting repository size", 1); + LOG.debug("{}|Getting repository size", fullProjectName); + long size = getRepository().getSize() * BYTES_PER_KB; + LOG.debug("{}|Repository size: {} Kb", fullProjectName, size); + + LOG.debug("{}|Checking repository size is allowed by quota", fullProjectName); + if (size > 0) { + QuotaResponse.Aggregated aggregated = + quotaBackend.currentUser().project(fullProjectName).dryRun(REPOSITORY_SIZE_GROUP, size); + aggregated.throwOnError(); + } + progress.update(1); + } catch (Exception e) { + LOG.error("{}|Quota does not allow importing repo", fullProjectName, e); + throw new QuotaEnforcedException(e.getMessage(), e); + } finally { + progress.endTask(); + } + LOG.debug("{}|SUCCESS repository size is allowed", fullProjectName); + } + + @Override + public boolean rollback() { + return true; + } +} diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicationConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/QuotaEnforcedException.java similarity index 59% rename from github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicationConfig.java rename to github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/QuotaEnforcedException.java index 8b7712a3..8ee4aea8 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicationConfig.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/QuotaEnforcedException.java @@ -1,4 +1,4 @@ -// Copyright (C) 2013 The Android Open Source Project +// Copyright (C) 2025 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. @@ -11,17 +11,18 @@ // 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.git; -import java.io.IOException; -import org.eclipse.jgit.errors.ConfigInvalidException; +public class QuotaEnforcedException extends GitException { -public interface ReplicationConfig { + private static final long serialVersionUID = 1L; - void addSecureCredentials(String authUsername, String authToken) - throws IOException, ConfigInvalidException; + public QuotaEnforcedException(String message, Exception e) { + super(message, e); + } - void addReplicationRemote(String username, String url, String projectName) - throws IOException, ConfigInvalidException; + @Override + public String getErrorDescription() { + return String.format("Quota enforcing error. %s", getMessage()); + } } diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java index b321127f..4e75d31e 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java @@ -13,22 +13,21 @@ // limitations under the License. package com.googlesource.gerrit.plugins.github.git; +import com.google.gerrit.extensions.registration.DynamicItem; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.googlesource.gerrit.plugins.github.GitHubURL; -import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; -import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider; +import com.googlesource.gerrit.plugins.replication.api.ReplicationRemotesApi; import java.io.IOException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ReplicateProjectStep extends ImportStep { private static final Logger LOG = LoggerFactory.getLogger(ReplicateProjectStep.class); - private final ReplicationConfig replicationConfig; - private final String authUsername; - private final String authToken; - private final String gitHubUrl; + private final DynamicItem replicationRemotesUpdaterItem; + private final ReplicationRemoteConfigBuilder remoteConfigBuilder; public interface Factory { ReplicateProjectStep create( @@ -37,20 +36,17 @@ ReplicateProjectStep create( @Inject public ReplicateProjectStep( - final ReplicationConfig replicationConfig, + final DynamicItem replicationRemotesUpdaterItem, final GitHubRepository.Factory gitHubRepoFactory, - final ScopedProvider ghLoginProvider, + ReplicationRemoteConfigBuilder remoteConfigBuilder, @GitHubURL String gitHubUrl, @Assisted("organisation") String organisation, @Assisted("name") String repository) throws IOException { super(gitHubUrl, organisation, repository, gitHubRepoFactory); + this.remoteConfigBuilder = remoteConfigBuilder; LOG.debug("Gerrit ReplicateProject " + organisation + "/" + repository); - this.replicationConfig = replicationConfig; - GitHubLogin ghLogin = ghLoginProvider.get(); - this.authUsername = ghLogin.getMyself().getLogin(); - this.authToken = ghLogin.getToken().accessToken; - this.gitHubUrl = gitHubUrl; + this.replicationRemotesUpdaterItem = replicationRemotesUpdaterItem; } @Override @@ -58,11 +54,15 @@ public void doImport(ProgressMonitor progress) throws Exception { progress.beginTask("Setting up Gerrit replication", 2); String repositoryName = getOrganisation() + "/" + getRepositoryName(); + Config remoteConfig = remoteConfigBuilder.build(repositoryName); progress.update(1); - replicationConfig.addSecureCredentials(authUsername, authToken); + + ReplicationRemotesApi updater = replicationRemotesUpdaterItem.get(); + if (updater != null) { + updater.update(remoteConfig); + } progress.update(1); - replicationConfig.addReplicationRemote( - authUsername, gitHubUrl + "/${name}.git", repositoryName); + progress.endTask(); } diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicationRemoteConfigBuilder.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicationRemoteConfigBuilder.java new file mode 100644 index 00000000..fc808d83 --- /dev/null +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicationRemoteConfigBuilder.java @@ -0,0 +1,80 @@ +// Copyright (C) 2024 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.git; + +import com.google.common.base.Strings; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.github.GitHubURL; +import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; +import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider; +import com.googlesource.gerrit.plugins.replication.api.ReplicationRemotesApi; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.jgit.lib.Config; + +class ReplicationRemoteConfigBuilder { + private final String gitHubUrl; + private final String username; + private final String authToken; + private final DynamicItem replicationConfigItem; + + @Inject + ReplicationRemoteConfigBuilder( + DynamicItem replicationRemotesItem, + ScopedProvider ghLoginProvider, + @GitHubURL String gitHubUrl) + throws IOException { + this.gitHubUrl = gitHubUrl; + this.replicationConfigItem = replicationRemotesItem; + GitHubLogin ghLogin = ghLoginProvider.get(); + this.username = ghLogin.getMyself().getLogin(); + this.authToken = ghLogin.getAccessToken(); + } + + Config build(String repositoryName) { + Config remoteConfig = replicationConfigItem.get().get(username); + + remoteConfig.setString("remote", username, "username", username); + remoteConfig.setString("remote", username, "password", authToken); + + setRemoteConfigIfNotSet(remoteConfig, "url", gitHubUrl + "/${name}.git"); + + String[] existingProjects = getProjects(); + List projects = new ArrayList<>(List.of(existingProjects)); + projects.add(repositoryName); + + remoteConfig.setStringList("remote", username, "projects", projects); + setRemoteConfigIfNotSet(remoteConfig, "push", "refs/*:refs/*"); + + return remoteConfig; + } + + private void setRemoteConfigIfNotSet(Config remoteConfig, String key, String value) { + String existingValue = remoteConfig.getString("remote", username, key); + if (Strings.isNullOrEmpty(existingValue)) { + remoteConfig.setString("remote", username, key, value); + } + } + + private String[] getProjects() { + ReplicationRemotesApi config = replicationConfigItem.get(); + if (config != null) { + return config.get(username).getStringList("remote", username, "projects"); + } + + return new String[0]; + } +} 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 00000000..028aa6b0 --- /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 { + public static final String CURRENT_USERNAME = "CurrentUsername"; + + private final Provider userProvider; + + @Inject + CurrentUsernameProvider(Provider 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/GitHubGroup.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroup.java index ba9581c2..68b1a7a6 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroup.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroup.java @@ -14,17 +14,17 @@ package com.googlesource.gerrit.plugins.github.group; +import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.AccountGroup.UUID; import com.google.gerrit.entities.GroupDescription.Basic; -import lombok.Getter; public abstract class GitHubGroup implements Basic { public static final String UUID_PREFIX = "github:"; public static final String NAME_PREFIX = "github/"; - @Getter protected final UUID groupUUID; + protected final UUID groupUUID; - @Getter protected final String url; + protected final String url; GitHubGroup(UUID groupUUID, String url) { this.groupUUID = groupUUID; @@ -35,4 +35,14 @@ public abstract class GitHubGroup implements Basic { public String getEmailAddress() { return ""; } + + @Override + public AccountGroup.UUID getGroupUUID() { + return groupUUID; + } + + @Override + public String getUrl() { + return url; + } } 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 3504f2c4..8f9c7760 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 loadOrganisationsAndTeams( private void loadOrganisations( String username, OrganizationStructure orgsTeams, GitHubLogin ghLogin) throws IOException { logger.debug("Getting list of public organisations for user '{}'", username); - Set organisations = ghLogin.getMyOrganisationsLogins(); + Set organisations = ghLogin.getMyOrganisationsLogins(username); for (String org : organisations) { orgsTeams.put(org, EVERYONE_TEAM_NAME); } @@ -106,6 +108,9 @@ public static Module module() { 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 @@ protected void configure() { } private final LoadingCache orgTeamsByUsername; - private final Provider userProvider; + private final Provider usernameProvider; @Inject - GitHubGroupsCache( + @VisibleForTesting + public GitHubGroupsCache( @Named(ORGS_CACHE_NAME) LoadingCache byUsername, - Provider userProvider) { + @Named(CURRENT_USERNAME) Provider usernameProvider) { this.orgTeamsByUsername = byUsername; - this.userProvider = userProvider; + this.usernameProvider = usernameProvider; } Set getOrganizationsForUser(String username) { @@ -135,7 +141,7 @@ Set getOrganizationsForUser(String username) { } Set getOrganizationsForCurrentUser() throws ExecutionException { - return orgTeamsByUsername.get(userProvider.get().getUserName().get()).keySet(); + return orgTeamsByUsername.get(usernameProvider.get()).keySet(); } Set getTeamsForUser(String organizationName, String username) { @@ -156,7 +162,7 @@ Set getTeamsForUser(String organizationName, String username) { } Set getTeamsForCurrentUser(String organizationName) { - return getTeamsForUser(organizationName, userProvider.get().getUserName().get()); + return getTeamsForUser(organizationName, usernameProvider.get()); } public Set getGroupsForUser(String username) { @@ -170,4 +176,8 @@ public Set getGroupsForUser(String username) { } 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 540b98ae..361ccc2d 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 @@ public boolean doAction(PullRequest payload) throws IOException { 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/notification/WebhookServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/WebhookServlet.java index 1bdce3c0..23bad3b2 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/WebhookServlet.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/notification/WebhookServlet.java @@ -167,7 +167,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) resp.setStatus(SC_INTERNAL_SERVER_ERROR); return; } - requestScopedLoginProvider.get(req).login(login.getToken()); + requestScopedLoginProvider.get(req).login(login.getAccessToken()); if (callHander(handler, body)) { resp.setStatus(SC_NO_CONTENT); 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 af644fee..6d24b3e7 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 @@ -18,23 +18,17 @@ import com.google.common.collect.Lists; import com.google.gerrit.server.PluginUser; 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 com.googlesource.gerrit.plugins.replication.api.ReplicationConfig; import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; -import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,28 +46,24 @@ static String replaceName(String in, String name) { return null; } - private final Injector injector; private final List configs; private final RemoteSiteUser.Factory replicationUserFactory; private final PluginUser pluginUser; private final GroupBackend groupBackend; - boolean replicateAllOnPluginStart; private final List organisations; @Inject GitHubDestinations( - final Injector i, - final SitePaths site, + final ReplicationConfig replicationConfig, final RemoteSiteUser.Factory ruf, final GroupBackend gb, final PluginUser pu) - throws ConfigInvalidException, IOException { - injector = i; + throws ConfigInvalidException { pluginUser = pu; replicationUserFactory = ruf; groupBackend = gb; - configs = getDestinations(site.etc_dir.resolve("replication.config")); + configs = getDestinations(replicationConfig.getConfig()); organisations = getOrganisations(configs); } @@ -88,22 +78,7 @@ private List getOrganisations(List destinations) { return result; } - private List getDestinations(Path cfgPath) - throws ConfigInvalidException, IOException { - if (!Files.exists(cfgPath) || Files.size(cfgPath) == 0) { - return Collections.emptyList(); - } - - FileBasedConfig cfg = new FileBasedConfig(cfgPath.toFile(), FS.DETECTED); - try { - cfg.load(); - } catch (ConfigInvalidException e) { - throw new ConfigInvalidException( - String.format("Config file %s is invalid: %s", cfg.getFile(), e.getMessage()), e); - } catch (IOException e) { - throw new IOException(String.format("Cannot read %s: %s", cfg.getFile(), e.getMessage()), e); - } - + private List getDestinations(Config cfg) throws ConfigInvalidException { ImmutableList.Builder dest = ImmutableList.builder(); for (RemoteConfig c : allRemotes(cfg)) { if (c.getURIs().isEmpty()) { @@ -113,9 +88,7 @@ private List getDestinations(Path cfgPath) for (URIish u : c.getURIs()) { if (u.getPath() == null || !u.getPath().contains("${name}")) { throw new ConfigInvalidException( - String.format( - "remote.%s.url \"%s\" lacks ${name} placeholder in %s", - c.getName(), u, cfg.getFile())); + String.format("remote.%s.url \"%s\" lacks ${name} placeholder", c.getName(), u)); } } @@ -136,7 +109,7 @@ private List getDestinations(Path cfgPath) return dest.build(); } - private static List allRemotes(FileBasedConfig cfg) throws ConfigInvalidException { + private static List allRemotes(Config cfg) throws ConfigInvalidException { Set names = cfg.getSubsections("remote"); List result = Lists.newArrayListWithCapacity(names.size()); for (String name : names) { @@ -145,8 +118,7 @@ private static List allRemotes(FileBasedConfig cfg) throws ConfigI result.add(new RemoteConfig(cfg, name)); } } catch (URISyntaxException e) { - throw new ConfigInvalidException( - String.format("remote %s has invalid URL in %s", name, cfg.getFile())); + throw new ConfigInvalidException(String.format("remote %s has invalid URL", name)); } } return result; 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 144e77a9..735ac4d8 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 { - 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 85197dda..5e4498f9 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 @@ public class VelocityViewServlet extends HttpServlet { private final ScopedProvider loginProvider; private final Provider userProvider; private final GitHubConfig config; + private final VirtualDomainConfig virtualDomainConfig; + private final CanonicalWebUrls canonicalWebUrls; + private final AuthConfig authConfig; @Inject public VelocityViewServlet( @@ -56,13 +64,19 @@ public VelocityViewServlet( Provider modelProvider, ScopedProvider loginProvider, Provider 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 @@ public void service(ServletRequest request, ServletResponse response) 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 @@ private PluginVelocityModel initVelocityModel(HttpServletRequest request) throws 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 fc578af0..1b7327f3 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 @@ public class AccountController implements VelocityController { 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 accountsUpdateProvider; @@ -79,7 +77,6 @@ public AccountController( final AddSshKey restAddSshKey, final GetSshKeys restGetSshKeys, final AccountManager accountManager, - final AccountCache accountCache, final PutPreferred putPreferred, final PutName putName, @ServerInitiated final Provider accountsUpdateProvider, @@ -90,7 +87,6 @@ public AccountController( this.restAddSshKey = restAddSshKey; this.restGetSshKeys = restGetSshKeys; this.accountManager = accountManager; - this.accountCache = accountCache; this.putPreferred = putPreferred; this.putName = putName; this.accountsUpdateProvider = accountsUpdateProvider; @@ -160,7 +156,7 @@ private void setAccountIdentity(IdentifiedUser user, HttpServletRequest req) .update( "Set Username from GitHub", accountId, - u -> u.addExternalId(externalIdFactory.create(key, accountId, null, null))); + u -> u.addExternalId(externalIdFactory.create(key, accountId))); } catch (Exception e) { throw new IllegalArgumentException( "Internal error while trying to set username='" + username + "'"); 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 e47f1617..b7fc8015 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 @@ public void doAction( 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 @@ private Map> getPullRequests( if (githubRepo.isPresent()) { numPullRequests = collectPullRequestsFromGitHubRepository( - numPullRequests, allPullRequests, gitRepo, ghRepoName, githubRepo); + numPullRequests, allPullRequests, gitRepo, ghRepoName, githubRepo.get()); } } } @@ -149,13 +149,14 @@ private int collectPullRequestsFromGitHubRepository( Map> allPullRequests, Repository gitRepo, String ghRepoName, - Optional githubRepo) + GHRepository githubRepo) throws IncorrectObjectTypeException, IOException { List 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 2cf6170f..01a3d25e 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 @@ public void doAction( 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 c9eebc51..4adcd417 100644 --- a/github-plugin/src/main/resources/Documentation/config.md +++ b/github-plugin/src/main/resources/Documentation/config.md @@ -77,6 +77,10 @@ github.scopes Default is empty read-only access to public information (includes public user profile info, public repository info, and gists). +github..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 9db7e3fc..5a3df085 100644 --- a/github-plugin/src/main/resources/static/repositories.html +++ b/github-plugin/src/main/resources/static/repositories.html @@ -53,7 +53,7 @@
Select GitHub repositories to clone and replicate
-
  • Not seeing your organizations or repositories? Login with a different GitHub Scope and try again.

  • +
  • Not seeing your organizations or repositories? Login with a different GitHub Scope and try again.

  • Loading list of GitHub repositories ...

    diff --git a/github-plugin/src/main/resources/static/scope.html b/github-plugin/src/main/resources/static/scope.html index 113b5b5b..f8d05466 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") +
    @@ -19,7 +27,7 @@

    Login Scope Selection

    -
    @@ -36,12 +44,12 @@

    Login Scope Selection

    Which level of GitHub access do you need?
      - #foreach ( $scope in $config.sortedScopesKeys ) + #foreach ( $scope in $scopes.keySet() )
    • - #set ( $scopeName = $scope.name.substring(6) ) - #set ( $scopeDescription = $scope.description ) + #set ( $scopeName = $scope.name().substring(6) ) + #set ( $scopeDescription = $scope.description() ) #set ( $checked = "" ) - #if ( ( $scopeCookie && $scopeCookie == $scope.name ) || $scopeName == "" ) + #if ( ( $scopeCookie && $scopeCookie == $scope.name() ) || $scopeName == "" ) #set ( $checked = "checked" ) #end @@ -52,7 +60,7 @@
      Which level of GitHub access do you need?
      #end

      $scopeDescription

      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/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 00000000..b24efba9 --- /dev/null +++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/FakeHttpSession.java @@ -0,0 +1,114 @@ +// 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; +import org.junit.Ignore; + +@Ignore +public class FakeHttpSession implements HttpSession { + private final HashMap 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 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/GitHubConfigTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java index 56f7b90c..b7dfc60d 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 @@ private GitHubConfig newGitHubConfig(String configText) throws Exception { + "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 00000000..5c1707f9 --- /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 groupsByUsernameCache; + private GitHubGroupCacheRefreshFilter filter; + private FakeGroupCacheLoader groupsCacheLoader; + private int initialLoadCount; + + private static class FakeGroupCacheLoader extends CacheLoader { + 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/src/test/java/com/googlesource/gerrit/plugins/github/git/ReplicationRemoteConfigBuilderTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/git/ReplicationRemoteConfigBuilderTest.java new file mode 100644 index 00000000..015b52e7 --- /dev/null +++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/git/ReplicationRemoteConfigBuilderTest.java @@ -0,0 +1,101 @@ +// Copyright (C) 2024 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.git; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.gerrit.extensions.registration.DynamicItem; +import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin; +import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider; +import com.googlesource.gerrit.plugins.replication.api.ReplicationRemotesApi; +import org.eclipse.jgit.lib.Config; +import org.junit.Test; +import org.kohsuke.github.GHMyself; + +public class ReplicationRemoteConfigBuilderTest { + private final String repoName = "test-repo"; + private final String username = "test-user"; + private final String password = "myHighlySecretPassword"; + private final String gitHubUrl = "htpps://github.com"; + + @Test + public void shouldBuildConfig() throws Exception { + ReplicationRemoteConfigBuilder builder = newReplicationRemoteConfigBuilder(); + Config actual = builder.build(repoName); + + assertThat(actual.getString("remote", username, "username")).isEqualTo(username); + assertThat(actual.getString("remote", username, "password")).isEqualTo(password); + assertThat(actual.getString("remote", username, "url")).isEqualTo(gitHubUrl + "/${name}.git"); + assertThat(actual.getStringList("remote", username, "projects")) + .isEqualTo(new String[] {repoName}); + assertThat(actual.getString("remote", username, "push")).isEqualTo("refs/*:refs/*"); + } + + @Test + public void shouldAppendProjectToConfig() throws Exception { + String prevProject = "imported-project"; + Config currentConfig = new Config(); + currentConfig.setString("remote", username, "projects", prevProject); + + ReplicationRemoteConfigBuilder builder = newReplicationRemoteConfigBuilder(currentConfig); + Config actual = builder.build(repoName); + + assertThat(actual.getString("remote", username, "username")).isEqualTo(username); + assertThat(actual.getString("remote", username, "password")).isEqualTo(password); + assertThat(actual.getString("remote", username, "url")).isEqualTo(gitHubUrl + "/${name}.git"); + assertThat(actual.getStringList("remote", username, "projects")) + .isEqualTo(new String[] {prevProject, repoName}); + assertThat(actual.getString("remote", username, "push")).isEqualTo("refs/*:refs/*"); + } + + @Test + public void shoudKeepCustomUrlAndPushRefSpecInRemoteConfig() throws Exception { + Config currentConfig = new Config(); + String customPushRefSpec = "+refs/heads/myheads/*:refs/heads/myheads/*"; + String customUrl = "ssh://github@github.com/myuser/${name}.git"; + currentConfig.setString("remote", username, "push", customPushRefSpec); + currentConfig.setString("remote", username, "url", customUrl); + + ReplicationRemoteConfigBuilder builder = newReplicationRemoteConfigBuilder(currentConfig); + Config actual = builder.build(repoName); + + assertThat(actual.getString("remote", username, "push")).isEqualTo(customPushRefSpec); + assertThat(actual.getString("remote", username, "url")).isEqualTo(customUrl); + } + + private ReplicationRemoteConfigBuilder newReplicationRemoteConfigBuilder() throws Exception { + return newReplicationRemoteConfigBuilder(new Config()); + } + + private ReplicationRemoteConfigBuilder newReplicationRemoteConfigBuilder(Config currentConfig) + throws Exception { + GitHubLogin gitHubLoginMock = mock(GitHubLogin.class); + GHMyself ghMyselfMock = mock(GHMyself.class); + ScopedProvider scopedProviderMock = mock(ScopedProvider.class); + ReplicationRemotesApi replicationRemotesApi = mock(ReplicationRemotesApi.class); + DynamicItem replicationRemotesItem = mock(DynamicItem.class); + + when(ghMyselfMock.getLogin()).thenReturn(username); + when(gitHubLoginMock.getMyself()).thenReturn(ghMyselfMock); + when(gitHubLoginMock.getAccessToken()).thenReturn(password); + when(scopedProviderMock.get()).thenReturn(gitHubLoginMock); + when(replicationRemotesApi.get(username)).thenReturn(currentConfig); + when(replicationRemotesItem.get()).thenReturn(replicationRemotesApi); + + return new ReplicationRemoteConfigBuilder( + replicationRemotesItem, scopedProviderMock, gitHubUrl); + } +} diff --git a/github-plugin/tsconfig-plugins-base.json b/github-plugin/tsconfig-plugins-base.json new file mode 100644 index 00000000..a19ebbf3 --- /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 00000000..f08de5ac --- /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/web/BUILD b/github-plugin/web/BUILD new file mode 100644 index 00000000..035d9999 --- /dev/null +++ b/github-plugin/web/BUILD @@ -0,0 +1,42 @@ +load("//tools/js:eslint.bzl", "plugin_eslint") +load("//tools/bzl:js.bzl", "gerrit_js_bundle") +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") + +package_group( + name = "visibility", + packages = ["//plugins/github/github-plugin/..."], +) + +package(default_visibility = [":visibility"]) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//plugins:tsconfig-plugins-base.json", + ], +) + +ts_project( + name = "github-plugin-ts", + srcs = glob( + ["**/*.ts"], + ), + incremental = True, + out_dir = "_bazel_ts_out", + tsc = "//tools/node_tools:tsc-bin", + tsconfig = ":tsconfig", + deps = [ + "@plugins_npm//@gerritcodereview/typescript-api", + "@plugins_npm//lit", + "@plugins_npm//rxjs", + ], +) + +gerrit_js_bundle( + name = "github-plugin", + srcs = [":github-plugin-ts"], + entry_point = "_bazel_ts_out/main.js", +) + +plugin_eslint() diff --git a/github-plugin/web/eslint.config.js b/github-plugin/web/eslint.config.js new file mode 100644 index 00000000..86e21afd --- /dev/null +++ b/github-plugin/web/eslint.config.js @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +const {defineConfig} = require('eslint/config'); + +// eslint-disable-next-line no-undef +__plugindir = 'github/github-plugin/web'; + +const gerritEslint = require('../../eslint.config.js'); + +module.exports = defineConfig([ + { + extends: [gerritEslint], + }, +]); \ No newline at end of file diff --git a/github-plugin/web/gr-github-oauth-progress.ts b/github-plugin/web/gr-github-oauth-progress.ts new file mode 100644 index 00000000..292e294f --- /dev/null +++ b/github-plugin/web/gr-github-oauth-progress.ts @@ -0,0 +1,89 @@ +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; + + @state() currentNavigationPath?: string; + + 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); + document.addEventListener( + 'nav-report', + // the `nav-report` event is emitted before `window.location` is updated, in order + // to get the current location, we need to delay `this.updateLocationPath` execution + // by putting it on the end of processing queue + () => setTimeout(() => this.updateLocationPath(), 0)); + this.updateLocationPath(); + } + + 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; + } + const loginWithRedirect = new URL(this.authInfo.login_url ?? "/", window.location.origin); + if (this.currentNavigationPath) { + loginWithRedirect.pathname = loginWithRedirect.pathname + this.currentNavigationPath; + } + + return html` + + ${this.authInfo.login_text} + +

      +
      + + Waiting for GitHub API response ... +
      +
      + ` + } + + private updateLocationPath() { + this.currentNavigationPath = window.location.pathname; + } + + private showModal() { + setTimeout(() => this.gitHubOAuthProgress?.showModal(), 550); + } +} diff --git a/github-plugin/web/main.ts b/github-plugin/web/main.ts new file mode 100644 index 00000000..2a465cab --- /dev/null +++ b/github-plugin/web/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/web/tsconfig.json b/github-plugin/web/tsconfig.json new file mode 100644 index 00000000..c2dd829f --- /dev/null +++ b/github-plugin/web/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig-plugins-base.json", + "compilerOptions": { + "experimentalDecorators": true, + /* outDir for IDE (overridden by Bazel rule arg) */ + "outDir": "../../../../.ts-out/plugins/github-plugin/ui", + }, + "include": [ + "**/*", + ], +} \ No newline at end of file diff --git a/github-plugin/yarn.lock b/github-plugin/yarn.lock new file mode 100644 index 00000000..51435d5b --- /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/java_library_without_header_compilation.bzl b/java_library_without_header_compilation.bzl new file mode 100644 index 00000000..a1250d08 --- /dev/null +++ b/java_library_without_header_compilation.bzl @@ -0,0 +1,30 @@ +# See https://github.com/bazelbuild/bazel/issues/12837 +# for workaround suggestion for incompatibility between +# Bazel Turbine processor and Lombok library. + +def _java_header_compilation_transition(settings, attr): + _ignore = (settings, attr) + return {"//command_line_option:java_header_compilation": "False"} + +java_header_compilation_transition = transition( + implementation = _java_header_compilation_transition, + inputs = [], + outputs = ["//command_line_option:java_header_compilation"], +) + +def _java_library_without_header_compilation(ctx): + return [java_common.merge([d[JavaInfo] for d in ctx.attr.dep])] + +java_library_without_header_compilation = rule( + implementation = _java_library_without_header_compilation, + attrs = { + "dep": attr.label( + providers = [JavaInfo], + mandatory = True, + cfg = java_header_compilation_transition, + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, +) diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 5d6b07ed..00000000 --- a/pom.xml +++ /dev/null @@ -1,321 +0,0 @@ - - - - 4.0.0 - com.googlesource.gerrit.plugins.github - github-parent - 3.8.0 - Gerrit Code Review - GitHub integration - http://www.gerritforge.com - pom - - 11 - - - - Apache License, 2.0 - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. - - - - - github-oauth - github-plugin - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - ${javaVersion} - ${javaVersion} - - - - - - - - org.codehaus.mojo - findbugs-maven-plugin - 3.0.0 - - - - - - - true - - - false - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/artifactory/public/ - - - - true - - sonatype - https://oss.sonatype.org/content/repositories/snapshots/ - - - - true - - - false - - gerrit-api-repository - https://gerrit-api.commondatastorage.googleapis.com/release/ - - - - - com.ryanharter.auto.value - auto-value-gson - 1.3.0 - provided - - - org.projectlombok - lombok - 1.18.22 - provided - - - - - - junit - junit - 4.13.2 - test - - - com.google.truth - truth - 1.1.4 - test - - - -