Merge branch 'stable-3.9' into master

Change-Id: I0816eacb94e8f3301e1f11866ad014bde05a7bd9
diff --git a/github-oauth/pom.xml b/github-oauth/pom.xml
index d0594a3..256abc8 100644
--- a/github-oauth/pom.xml
+++ b/github-oauth/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>com.googlesource.gerrit.plugins.github</groupId>
     <artifactId>github-parent</artifactId>
-    <version>3.9.0-rc0</version>
+    <version>3.9.0-rc2</version>
   </parent>
   <artifactId>github-oauth</artifactId>
   <name>Gerrit Code Review - GitHub OAuth login</name>
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CanonicalWebUrls.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CanonicalWebUrls.java
new file mode 100644
index 0000000..faca0f9
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CanonicalWebUrls.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.GERRIT_OAUTH_FINAL;
+import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.GITHUB_PLUGIN_OAUTH_SCOPE;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.MoreObjects;
+import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class CanonicalWebUrls {
+  private final GitHubOAuthConfig oauthConf;
+  private final HttpCanonicalWebUrlProvider canonicalWebUrlProvider;
+
+  static String trimTrailingSlash(String url) {
+    return CharMatcher.is('/').trimTrailingFrom(url);
+  }
+
+  @Inject
+  CanonicalWebUrls(
+      GitHubOAuthConfig oauthConf, HttpCanonicalWebUrlProvider canonicalWebUrlProvider) {
+    this.oauthConf = oauthConf;
+    this.canonicalWebUrlProvider = canonicalWebUrlProvider;
+  }
+
+  public String getScopeSelectionUrl() {
+    return getCannonicalWebUrl()
+        + MoreObjects.firstNonNull(oauthConf.scopeSelectionUrl, GITHUB_PLUGIN_OAUTH_SCOPE);
+  }
+
+  String getOAuthFinalRedirectUrl() {
+    return getCannonicalWebUrl() + GERRIT_OAUTH_FINAL;
+  }
+
+  private String getCannonicalWebUrl() {
+    return trimTrailingSlash(canonicalWebUrlProvider.get());
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
index f65ad02..adfe5e3 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
@@ -64,6 +64,8 @@
 
   private SortedSet<Scope> loginScopes;
   private final GitHubOAuthConfig config;
+  private final CanonicalWebUrls canonicalWebUrls;
+  private final VirtualDomainConfig virtualDomainConfig;
   private final GitHubConnector gitHubConnector;
 
   public GHMyself getMyself() throws IOException {
@@ -81,8 +83,14 @@
   }
 
   @Inject
-  public GitHubLogin(GitHubOAuthConfig config, GitHubHttpConnector httpConnector) {
+  public GitHubLogin(
+      GitHubOAuthConfig config,
+      CanonicalWebUrls canonicalWebUrls,
+      VirtualDomainConfig virutalDomainConfig,
+      GitHubHttpConnector httpConnector) {
     this.config = config;
+    this.canonicalWebUrls = canonicalWebUrls;
+    this.virtualDomainConfig = virutalDomainConfig;
     this.gitHubConnector = GitHubConnectorHttpConnectorAdapter.adapt(httpConnector);
   }
 
@@ -108,12 +116,13 @@
         response.sendRedirect(OAuthProtocol.getTargetUrl(request));
       }
     } else {
-      Set<ScopeKey> configuredScopesProfiles = config.scopes.keySet();
+      Set<ScopeKey> configuredScopesProfiles = virtualDomainConfig.getScopes(request).keySet();
       String scopeRequested = getScopesKey(request, response);
       if (Strings.isNullOrEmpty(scopeRequested) && configuredScopesProfiles.size() > 1) {
-        response.sendRedirect(config.getScopeSelectionUrl(request));
+        response.sendRedirect(canonicalWebUrls.getScopeSelectionUrl());
       } else {
-        this.loginScopes = getScopes(MoreObjects.firstNonNull(scopeRequested, "scopes"), scopes);
+        this.loginScopes =
+            getScopes(request, MoreObjects.firstNonNull(scopeRequested, "scopes"), scopes);
         log.debug("Login-PHASE1 " + this);
         state = oauth.loginPhase1(request, response, loginScopes);
       }
@@ -179,15 +188,15 @@
     return null;
   }
 
-  private SortedSet<Scope> getScopes(String baseScopeKey, Scope... scopes) {
-    HashSet<Scope> fullScopes = new HashSet<>(scopesForKey(baseScopeKey));
+  private SortedSet<Scope> getScopes(HttpServletRequest req, String baseScopeKey, Scope... scopes) {
+    HashSet<Scope> fullScopes = new HashSet<>(scopesForKey(req, baseScopeKey));
     fullScopes.addAll(Arrays.asList(scopes));
 
     return new TreeSet<>(fullScopes);
   }
 
-  private List<Scope> scopesForKey(String baseScopeKey) {
-    return config.scopes.entrySet().stream()
+  private List<Scope> scopesForKey(HttpServletRequest req, String baseScopeKey) {
+    return virtualDomainConfig.getScopes(req).entrySet().stream()
         .filter(entry -> entry.getKey().name.equals(baseScopeKey))
         .map(entry -> entry.getValue())
         .findFirst()
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
index d86feda..16fcb28 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
@@ -13,14 +13,14 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.github.oauth;
 
+import static com.googlesource.gerrit.plugins.github.oauth.CanonicalWebUrls.trimTrailingSlash;
 import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.KeyConfig.PASSWORD_DEVICE_CONFIG_LABEL;
 
-import com.google.common.base.CharMatcher;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSortedMap;
 import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.httpd.CanonicalWebUrl;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
@@ -35,17 +35,16 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.SortedMap;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import javax.servlet.http.HttpServletRequest;
 import lombok.Getter;
 import org.eclipse.jgit.lib.Config;
 
 @Singleton
 public class GitHubOAuthConfig {
   private final Config config;
-  private final CanonicalWebUrl canonicalWebUrl;
 
   public static final String CONF_SECTION = "github";
   public static final String CONF_KEY_SECTION = "github-key";
@@ -66,10 +65,11 @@
   public final String httpHeader;
   public final String gitHubOAuthUrl;
   public final String gitHubOAuthAccessTokenUrl;
+  public final String scopeSelectionUrl;
   public final boolean enabled;
 
-  @Getter public final Map<ScopeKey, List<OAuthProtocol.Scope>> scopes;
-  @Getter public final List<ScopeKey> sortedScopesKeys;
+  @Getter public final SortedMap<ScopeKey, List<OAuthProtocol.Scope>> scopes;
+  @Getter public final Map<String, SortedMap<ScopeKey, List<OAuthProtocol.Scope>>> virtualScopes;
 
   public final int fileUpdateMaxRetryCount;
   public final int fileUpdateMaxRetryIntervalMsec;
@@ -82,9 +82,8 @@
   private final Optional<String> cookieDomain;
 
   @Inject
-  protected GitHubOAuthConfig(@GerritServerConfig Config config, CanonicalWebUrl canonicalWebUrl) {
+  protected GitHubOAuthConfig(@GerritServerConfig Config config) {
     this.config = config;
-    this.canonicalWebUrl = canonicalWebUrl;
 
     httpHeader =
         Preconditions.checkNotNull(
@@ -105,6 +104,7 @@
         Preconditions.checkNotNull(
             config.getString(CONF_SECTION, null, "clientSecret"),
             "GitHub `clientSecret` must be provided");
+    scopeSelectionUrl = config.getString(CONF_SECTION, null, "scopeSelectionUrl");
 
     oauthHttpHeader = config.getString("auth", null, "httpExternalIdHeader");
     gitHubOAuthUrl = gitHubUrl + GITHUB_OAUTH_AUTHORIZE;
@@ -114,10 +114,7 @@
     enabled = config.getString("auth", null, "type").equalsIgnoreCase(AuthType.HTTP.toString());
     cookieDomain = Optional.ofNullable(config.getString("auth", null, "cookieDomain"));
     scopes = getScopes(config);
-    sortedScopesKeys =
-        scopes.keySet().stream()
-            .sorted(Comparator.comparing(ScopeKey::getSequence))
-            .collect(Collectors.toList());
+    virtualScopes = getVirtualScopes(config);
 
     fileUpdateMaxRetryCount = config.getInt(CONF_SECTION, "fileUpdateMaxRetryCount", 3);
     fileUpdateMaxRetryIntervalMsec =
@@ -151,36 +148,29 @@
     currentKeyConfig = currentKeyConfigs.get(0);
   }
 
-  public String getOAuthFinalRedirectUrl(HttpServletRequest req) {
-    return req == null
-        ? GERRIT_OAUTH_FINAL
-        : trimTrailingSlash(canonicalWebUrl.get(req)) + GERRIT_OAUTH_FINAL;
+  private SortedMap<ScopeKey, List<Scope>> getScopes(Config config) {
+    return getScopesInSection(config, null);
   }
 
-  public String getScopeSelectionUrl(HttpServletRequest req) {
-    String canonicalUrl = req == null ? "" : trimTrailingSlash(canonicalWebUrl.get(req));
-    return canonicalUrl
-        + MoreObjects.firstNonNull(
-            config.getString(CONF_SECTION, null, "scopeSelectionUrl"), GITHUB_PLUGIN_OAUTH_SCOPE);
+  private Map<String, SortedMap<ScopeKey, List<Scope>>> getVirtualScopes(Config config) {
+    return config.getSubsections(CONF_SECTION).stream()
+        .collect(Collectors.toMap(k -> k, v -> getScopesInSection(config, v)));
   }
 
-  private Map<ScopeKey, List<Scope>> getScopes(Config config) {
-    return config.getNames(CONF_SECTION, true).stream()
+  private SortedMap<ScopeKey, List<Scope>> getScopesInSection(Config config, String subsection) {
+    return config.getNames(CONF_SECTION, subsection, true).stream()
         .filter(k -> k.startsWith("scopes"))
         .filter(k -> !k.endsWith("Description"))
         .filter(k -> !k.endsWith("Sequence"))
         .collect(
-            Collectors.toMap(
+            ImmutableSortedMap.toImmutableSortedMap(
+                Comparator.comparing(ScopeKey::getSequence),
                 k ->
                     new ScopeKey(
                         k,
-                        config.getString(CONF_SECTION, null, k + "Description"),
-                        config.getInt(CONF_SECTION, k + "Sequence", 0)),
-                v -> parseScopesString(config.getString(CONF_SECTION, null, v))));
-  }
-
-  private String trimTrailingSlash(String url) {
-    return CharMatcher.is('/').trimTrailingFrom(url);
+                        config.getString(CONF_SECTION, subsection, k + "Description"),
+                        config.getInt(CONF_SECTION, subsection, k + "Sequence", 0)),
+                v -> parseScopesString(config.getString(CONF_SECTION, subsection, v))));
   }
 
   private List<Scope> parseScopesString(String scopesString) {
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
index 5449347..63414b2 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
@@ -36,23 +36,20 @@
       ExternalId.SCHEME_EXTERNAL + ":" + OAuthWebFilter.GITHUB_EXT_ID;
 
   private final Provider<IdentifiedUser> userProvider;
-  private final GitHubOAuthConfig config;
   private final AccountCache accountCache;
-  private final GitHubHttpConnector httpConnector;
   private final OAuthTokenCipher oAuthTokenCipher;
+  private final Provider<GitHubLogin> gitHubLoginProvider;
 
   @Inject
   public IdentifiedUserGitHubLoginProvider(
+      Provider<GitHubLogin> gitHubLoginaprovider,
       Provider<IdentifiedUser> identifiedUserProvider,
-      GitHubOAuthConfig config,
-      GitHubHttpConnector httpConnector,
       AccountCache accountCache,
       OAuthTokenCipher oAuthTokenCipher) {
     this.userProvider = identifiedUserProvider;
-    this.config = config;
     this.accountCache = accountCache;
-    this.httpConnector = httpConnector;
     this.oAuthTokenCipher = oAuthTokenCipher;
+    this.gitHubLoginProvider = gitHubLoginaprovider;
   }
 
   @Override
@@ -67,7 +64,7 @@
     try {
       AccessToken accessToken = newAccessTokenFromUser(username);
       if (accessToken != null) {
-        GitHubLogin login = new GitHubLogin(config, httpConnector);
+        GitHubLogin login = gitHubLoginProvider.get();
         login.login(accessToken);
         return login;
       }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
index b93837b..035f1ce 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
@@ -155,6 +155,7 @@
   private static SecureRandom randomState = newRandomGenerator();
 
   private final GitHubOAuthConfig config;
+  private final CanonicalWebUrls canonicalWebUrls;
   private final Gson gson;
   private final Provider<HttpClient> httpProvider;
 
@@ -231,6 +232,7 @@
   @Inject
   public OAuthProtocol(
       GitHubOAuthConfig config,
+      CanonicalWebUrls canonicalWebUrls,
       PooledHttpClientProvider httpClientProvider,
       /*
        * We need to explicitly tell Guice which Provider<> we need as this class
@@ -239,6 +241,7 @@
        */
       GsonProvider gsonProvider) {
     this.config = config;
+    this.canonicalWebUrls = canonicalWebUrls;
     this.httpProvider = httpClientProvider;
     this.gson = gsonProvider.get();
   }
@@ -256,7 +259,7 @@
         + "?client_id="
         + config.gitHubClientId
         + getURLEncodedParameter("&scope=", scopesString)
-        + getURLEncodedParameter("&redirect_uri=", config.getOAuthFinalRedirectUrl(req))
+        + getURLEncodedParameter("&redirect_uri=", canonicalWebUrls.getOAuthFinalRedirectUrl())
         + getURLEncodedParameter("&state=", state);
   }
 
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/VirtualDomainConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/VirtualDomainConfig.java
new file mode 100644
index 0000000..a6b2b17
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/VirtualDomainConfig.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.List;
+import java.util.Optional;
+import java.util.SortedMap;
+import javax.servlet.http.HttpServletRequest;
+
+@Singleton
+public class VirtualDomainConfig {
+  private final GitHubOAuthConfig oauthConfig;
+
+  @Inject
+  VirtualDomainConfig(GitHubOAuthConfig oauthConfig) {
+    this.oauthConfig = oauthConfig;
+  }
+
+  public SortedMap<ScopeKey, List<OAuthProtocol.Scope>> getScopes(HttpServletRequest req) {
+    String serverName = req.getServerName();
+    return Optional.ofNullable(oauthConfig.virtualScopes.get(serverName))
+        .orElse(oauthConfig.scopes);
+  }
+}
diff --git a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java
index 9f90c56..7808ff4 100644
--- a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java
+++ b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfigTest.java
@@ -22,20 +22,20 @@
 import static com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig.KeyConfig.SECRET_KEY_CONFIG_LABEL;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.httpd.CanonicalWebUrl;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.util.Providers;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.SortedMap;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
 
 public class GitHubOAuthConfigTest {
 
-  CanonicalWebUrl canonicalWebUrl;
   Config config;
   private static final String testPasswordDevice = "/dev/zero";
 
@@ -46,18 +46,6 @@
     config.setString(CONF_SECTION, null, "clientId", "theClientId");
     config.setString("auth", null, "httpHeader", "GITHUB_USER");
     config.setString("auth", null, "type", AuthType.HTTP.toString());
-
-    canonicalWebUrl =
-        Guice.createInjector(
-                new AbstractModule() {
-                  @Override
-                  protected void configure() {
-                    bind(String.class)
-                        .annotatedWith(com.google.gerrit.server.config.CanonicalWebUrl.class)
-                        .toProvider(Providers.of(null));
-                  }
-                })
-            .getInstance(CanonicalWebUrl.class);
   }
 
   @Test
@@ -178,8 +166,46 @@
     assertEquals(Optional.of(myDomain), githubOAuthConfig().getCookieDomain());
   }
 
+  @Test
+  public void shouldReturnOverridesForSpecificHostName() {
+    setupEncryptionConfig();
+    String vhost = "v.host.com";
+    String scope1Name = "scopesRepo";
+    String scope1Description = "repo scope description";
+    String scope2Name = "scopesVHost";
+    String scope2Description = "scope description";
+
+    // virtual host scopes
+    config.setString(CONF_SECTION, vhost, scope2Name, "USER_EMAIL");
+    config.setInt(CONF_SECTION, vhost, scope2Name + "Sequence", 1);
+    config.setString(CONF_SECTION, vhost, scope2Name + "Description", scope2Description);
+    config.setString(CONF_SECTION, vhost, scope1Name, "REPO");
+    config.setInt(CONF_SECTION, vhost, scope1Name + "Sequence", 0);
+    config.setString(CONF_SECTION, vhost, scope1Name + "Description", scope1Description);
+
+    Map<String, SortedMap<ScopeKey, List<OAuthProtocol.Scope>>> virtualScopes =
+        githubOAuthConfig().getVirtualScopes();
+
+    assertTrue(virtualScopes.containsKey(vhost));
+
+    SortedMap<ScopeKey, List<OAuthProtocol.Scope>> vhostConfig = virtualScopes.get(vhost);
+    List<Map.Entry<ScopeKey, List<OAuthProtocol.Scope>>> entries =
+        new ArrayList<>(vhostConfig.entrySet());
+    Map.Entry<ScopeKey, List<OAuthProtocol.Scope>> firstEntry = entries.get(0);
+    Map.Entry<ScopeKey, List<OAuthProtocol.Scope>> secondEntry = entries.get(1);
+
+    assertEquals(firstEntry.getKey().name, scope1Name);
+    assertEquals(firstEntry.getKey().description, scope1Description);
+    assertEquals(firstEntry.getKey().sequence, 0);
+    assertEquals(List.of(OAuthProtocol.Scope.REPO), firstEntry.getValue());
+    assertEquals(secondEntry.getKey().name, scope2Name);
+    assertEquals(secondEntry.getKey().description, scope2Description);
+    assertEquals(secondEntry.getKey().sequence, 1);
+    assertEquals(List.of(OAuthProtocol.Scope.USER_EMAIL), secondEntry.getValue());
+  }
+
   private GitHubOAuthConfig githubOAuthConfig() {
-    return new GitHubOAuthConfig(config, canonicalWebUrl);
+    return new GitHubOAuthConfig(config);
   }
 
   private void setupEncryptionConfig() {
diff --git a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java
index 3e31d6b..f3dfb71 100644
--- a/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java
+++ b/github-oauth/src/test/java/com/googlesource/gerrit/plugins/github/oauth/OAuthTokenCipherTest.java
@@ -26,10 +26,6 @@
 import static org.junit.Assert.assertThrows;
 
 import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.httpd.CanonicalWebUrl;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.util.Providers;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
@@ -43,7 +39,6 @@
 
 public class OAuthTokenCipherTest {
 
-  CanonicalWebUrl canonicalWebUrl;
   Config config;
 
   @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();
@@ -63,18 +58,6 @@
         CONF_KEY_SECTION, VERSION1_KEY_ID, PASSWORD_DEVICE_CONFIG_LABEL, testPasswordDevice);
     config.setString(
         CONF_KEY_SECTION, VERSION2_KEY_ID, PASSWORD_DEVICE_CONFIG_LABEL, testPasswordDevice);
-
-    canonicalWebUrl =
-        Guice.createInjector(
-                new AbstractModule() {
-                  @Override
-                  protected void configure() {
-                    bind(String.class)
-                        .annotatedWith(com.google.gerrit.server.config.CanonicalWebUrl.class)
-                        .toProvider(Providers.of(null));
-                  }
-                })
-            .getInstance(CanonicalWebUrl.class);
   }
 
   @Test
@@ -193,7 +176,7 @@
   }
 
   private OAuthTokenCipher objectUnderTest(Config testConfig) throws IOException {
-    return new OAuthTokenCipher(new GitHubOAuthConfig(testConfig, canonicalWebUrl));
+    return new OAuthTokenCipher(new GitHubOAuthConfig(testConfig));
   }
 
   private static Config createCommonConfig() {
diff --git a/github-plugin/pom.xml b/github-plugin/pom.xml
index 15cfa79..96d3290 100644
--- a/github-plugin/pom.xml
+++ b/github-plugin/pom.xml
@@ -20,7 +20,7 @@
   <parent>
     <artifactId>github-parent</artifactId>
     <groupId>com.googlesource.gerrit.plugins.github</groupId>
-    <version>3.9.0-rc0</version>
+    <version>3.9.0-rc2</version>
   </parent>
 
   <artifactId>github-plugin</artifactId>
diff --git a/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java b/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java
index e826880..1c0fe3b 100644
--- a/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java
+++ b/github-plugin/src/main/java/com/google/gerrit/server/account/AccountImporter.java
@@ -15,10 +15,10 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.ServerInitiated;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIdFactory;
-import com.google.gerrit.server.notedb.Sequences;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.io.IOException;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
index a22126d..2bacafd 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Table;
 import com.google.gerrit.entities.Account;
-import com.google.gerrit.httpd.CanonicalWebUrl;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -75,10 +74,9 @@
   public GitHubConfig(
       @GerritServerConfig Config config,
       final SitePaths site,
-      Provider<AllProjectsName> allProjectsNameProvider,
-      CanonicalWebUrl canonicalWebUrl)
+      Provider<AllProjectsName> allProjectsNameProvider)
       throws MalformedURLException {
-    super(config, canonicalWebUrl);
+    super(config);
     parseWizardFlow(config.getStringList(CONF_SECTION, null, CONF_WIZARD_FLOW), DEFAULT_SERVER);
 
     // Virtual host specific sections
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
index 5ff5363..d345556 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
@@ -47,7 +47,7 @@
             new MenuEntry(
                 "GitHub",
                 Arrays.asList(
-                    getItem("Scope", ghConfig.getScopeSelectionUrl(null)),
+                    getItem("Scope", baseUrl + "/static/scope.html"),
                     getItem("Profile", baseUrl + "/static/account.html"),
                     getItem("Repositories", baseUrl + "/static/repositories.html"),
                     getItem("Pull Requests", baseUrl + "/static/pullrequests.html"))));
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java
index f2a7f06..810acd8 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/PullRequestCreateChange.java
@@ -27,10 +27,10 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.Sequences;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
index 85197dd..b7dc08f 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
@@ -20,8 +20,10 @@
 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 +51,8 @@
   private final ScopedProvider<GitHubLogin> loginProvider;
   private final Provider<CurrentUser> userProvider;
   private final GitHubConfig config;
+  private final VirtualDomainConfig virtualDomainConfig;
+  private final CanonicalWebUrls canonicalWebUrls;
 
   @Inject
   public VelocityViewServlet(
@@ -56,13 +60,17 @@
       Provider<PluginVelocityModel> modelProvider,
       ScopedProvider<GitHubLogin> loginProvider,
       Provider<CurrentUser> userProvider,
-      GitHubConfig config) {
+      GitHubConfig config,
+      VirtualDomainConfig virutalDomainConfig,
+      CanonicalWebUrls canonicalWebUrls) {
 
     this.velocityRuntime = velocityRuntime;
     this.modelProvider = modelProvider;
     this.loginProvider = loginProvider;
     this.userProvider = userProvider;
     this.config = config;
+    this.virtualDomainConfig = virutalDomainConfig;
+    this.canonicalWebUrls = canonicalWebUrls;
   }
 
   @Override
@@ -96,6 +104,8 @@
     GitHubLogin gitHubLogin = loginProvider.get(request);
     model.put("myself", gitHubLogin.getMyself());
     model.put("config", config);
+    model.put("scopeSelectionUrl", canonicalWebUrls.getScopeSelectionUrl());
+    model.put("scopes", virtualDomainConfig.getScopes(request));
 
     CurrentUser user = userProvider.get();
     if (user.isIdentifiedUser()) {
diff --git a/github-plugin/src/main/resources/Documentation/config.md b/github-plugin/src/main/resources/Documentation/config.md
index c9eebc5..4adcd41 100644
--- a/github-plugin/src/main/resources/Documentation/config.md
+++ b/github-plugin/src/main/resources/Documentation/config.md
@@ -77,6 +77,10 @@
     Default is empty read-only access to public 
     information (includes public user profile info, public repository info, and gists).
 
+github.<domain>.scopes
+:   Use only in conjunction with the `virtualhost` plugin to provide different GitHub scopes
+    selections for each virtual domain. It works the same way as `github.scopes`.
+
 github.httpConnectionTimeout
 :   Maximum time to wait for GitHub API to answer to a new HTTP connection attempt.
     Values should use common common unit unit suffixes to express their setting:
diff --git a/github-plugin/src/main/resources/static/repositories.html b/github-plugin/src/main/resources/static/repositories.html
index 9db7e3f..5a3df08 100644
--- a/github-plugin/src/main/resources/static/repositories.html
+++ b/github-plugin/src/main/resources/static/repositories.html
@@ -53,7 +53,7 @@
               </select>
               <input type="text" id="filter" class="filter" name="filter" placeholder="Filter by name" />
             </li>
-            <li class="info"><p>Not seeing your organizations or repositories? <a href="$config.getScopeSelectionUrl(null)">Login with a different GitHub Scope</a> and try again.</p></li>
+            <li class="info"><p>Not seeing your organizations or repositories? <a href="$scopeSelectionUrl">Login with a different GitHub Scope</a> and try again.</p></li>
           </ul>
           <div class="loading">
             <p>Loading list of GitHub repositories ...</p>
diff --git a/github-plugin/src/main/resources/static/scope.html b/github-plugin/src/main/resources/static/scope.html
index af86139..ebc2580 100644
--- a/github-plugin/src/main/resources/static/scope.html
+++ b/github-plugin/src/main/resources/static/scope.html
@@ -44,7 +44,7 @@
         <form class="signupform" method="get" action="/login">
           <h5>Which level of GitHub access do you need?</h5>
           <ul class="scopes">
-                #foreach ( $scope in $config.sortedScopesKeys )
+                #foreach ( $scope in $scopes.keySet() )
                     <li>
                         #set ( $scopeName = $scope.name.substring(6) )
                         #set ( $scopeDescription = $scope.description )
@@ -60,7 +60,7 @@
                         #end
                         <p class="scopeDescription">$scopeDescription</p>
                         <p class="scopePermissions">Allow to:
-                            #set ( $scopeItems = $config.scopes.get($scope) )
+                            #set ( $scopeItems = $scopes.get($scope) )
                             #foreach ( $scopeItem in $scopeItems )
                                 $scopeItem.description
                                 #if ( $foreach.count < $scopeItems.size())
diff --git a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java
index 56f7b90..b7dfc60 100644
--- a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java
+++ b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/GitHubConfigTest.java
@@ -117,6 +117,6 @@
             + "clientId = myclientid\n"
             + "clientSecret = mysecret\n"
             + configText);
-    return new GitHubConfig(gerritConfig, site, ALL_PROJECTS_NAME_PROVIDER, null);
+    return new GitHubConfig(gerritConfig, site, ALL_PROJECTS_NAME_PROVIDER);
   }
 }
diff --git a/pom.xml b/pom.xml
index 71dbe03..a0c4c21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.googlesource.gerrit.plugins.github</groupId>
   <artifactId>github-parent</artifactId>
-  <version>3.9.0-rc0</version>
+  <version>3.9.0-rc2</version>
   <name>Gerrit Code Review - GitHub integration</name>
   <url>http://www.gerritforge.com</url>
   <packaging>pom</packaging>