Configure virutal domain specific scopes

When using both `github` and `virutalhost` plugins together is may be
required to have different scope selections for each site. Currently
the `github` plugin does not allow host specific scope configuration.

This patch adds the ability to use config subsection to configure
different scopes per site. For example:

[github "second.site.co"]
  scopeRedonly = USER_EMAIL
  scopeReadonlySequence = 0
  scopeReadonlyDescription = Share only email address

will configure only a single `USER_EMAIL` scope for `second.site.co`.

In order to make this work, we also change the redirection flow, as
currently `/login` will redirect to the main canonical web url. Which
means that we cannot discover which site is used and set scopes on scope
selection page. Now we'll keep user on the same domain name.

Bug: Issue 307761450
Change-Id: I24973cb2914052f23e684854211e1a21011dfecf
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CannonicalWebUrls.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CannonicalWebUrls.java
new file mode 100644
index 0000000..321d0ea
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/CannonicalWebUrls.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 CannonicalWebUrls {
+  private final GitHubOAuthConfig oauthConf;
+  private final HttpCanonicalWebUrlProvider canonnicalWebUrlProvider;
+
+  static String trimTrailingSlash(String url) {
+    return CharMatcher.is('/').trimTrailingFrom(url);
+  }
+
+  @Inject
+  CannonicalWebUrls(
+      GitHubOAuthConfig oauthConf, HttpCanonicalWebUrlProvider canonicalWebUrlProvider) {
+    this.oauthConf = oauthConf;
+    this.canonnicalWebUrlProvider = 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(canonnicalWebUrlProvider.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..dafca3f 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 CannonicalWebUrls cannonicalWebUrls;
+  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,
+      CannonicalWebUrls cannonicalWebUrls,
+      VirtualDomainConfig virutalDomainConfig,
+      GitHubHttpConnector httpConnector) {
     this.config = config;
+    this.cannonicalWebUrls = cannonicalWebUrls;
+    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(cannonicalWebUrls.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..f396a80 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.CannonicalWebUrls.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..3b8d708 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 CannonicalWebUrls cannonicalWebUrls;
   private final Gson gson;
   private final Provider<HttpClient> httpProvider;
 
@@ -231,6 +232,7 @@
   @Inject
   public OAuthProtocol(
       GitHubOAuthConfig config,
+      CannonicalWebUrls cannonicalWebUrls,
       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.cannonicalWebUrls = cannonicalWebUrls;
     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=", cannonicalWebUrls.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..6833e71 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
@@ -24,18 +24,17 @@
 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.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 +45,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 +165,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();
+
+    assertEquals(virtualScopes.containsKey(vhost), true);
+
+    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/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/velocity/VelocityViewServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
index 85197dd..aab1ef0 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.CannonicalWebUrls;
 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 CannonicalWebUrls cannonicalWebUrls;
 
   @Inject
   public VelocityViewServlet(
@@ -56,13 +60,17 @@
       Provider<PluginVelocityModel> modelProvider,
       ScopedProvider<GitHubLogin> loginProvider,
       Provider<CurrentUser> userProvider,
-      GitHubConfig config) {
+      GitHubConfig config,
+      VirtualDomainConfig virutalDomainConfig,
+      CannonicalWebUrls cannonicalWebUrls) {
 
     this.velocityRuntime = velocityRuntime;
     this.modelProvider = modelProvider;
     this.loginProvider = loginProvider;
     this.userProvider = userProvider;
     this.config = config;
+    this.virtualDomainConfig = virutalDomainConfig;
+    this.cannonicalWebUrls = cannonicalWebUrls;
   }
 
   @Override
@@ -96,6 +104,8 @@
     GitHubLogin gitHubLogin = loginProvider.get(request);
     model.put("myself", gitHubLogin.getMyself());
     model.put("config", config);
+    model.put("scopeSelectionUrl", cannonicalWebUrls.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);
   }
 }