Implements OpenID domain filtering

Adds ability to only allow email addresses under specific domains to
be used for OpenID login.

The allowed domains can be configured in etc/gerrit.config under
section [auth] as "openIdDomain". The values are then stored across
GerritConfig and AuthConfig. If at least one openIdDomain is
configured, OpenIdServiceImpl checks for a match on the email
address used for login and returns error otherwise.

The need for this was described in Google Groups to limit OpenID to
Google Apps domain(s) of a company when using Gerrit for internal
development.

Change-Id: I549059e7ecea827009b632ef2e38d2ccdddf7cfb
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 0a5628a..1cad410 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -193,6 +193,16 @@
 By default, the list contains two values, `http://` and `https://`,
 allowing Gerrit to trust any OpenID it receives.
 
+[[auth.openIdDomain]]auth.openIdDomain::
++
+List of allowed OpenID email address domains. Only used if
+`auth.type` is set to "OPENID" or "OPENID_SSO".
++
+Domain is case insensitive and must be in the same form as it
+appears in the email address, for example, "example.com".
++
+By default, any domain is accepted.
+
 [[auth.maxOpenIdSessionAge]]auth.maxOpenIdSessionAge::
 +
 Time in seconds before an OpenID provider must force the user
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 0593bce..2946436 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -101,6 +101,7 @@
   private final AccountManager accountManager;
   private final ConsumerManager manager;
   private final List<OpenIdProviderPattern> allowedOpenIDs;
+  private final List<String> openIdDomains;
 
   /** Maximum age, in seconds, before forcing re-authentication of account. */
   private final int papeMaxAuthAge;
@@ -142,6 +143,7 @@
     accountManager = am;
     manager = new ConsumerManager();
     allowedOpenIDs = ac.getAllowedOpenIDs();
+    openIdDomains = ac.getOpenIdDomains();
     papeMaxAuthAge = (int) ConfigUtil.getTimeUnit(config, //
         "auth", null, "maxOpenIdSessionAge", -1, TimeUnit.SECONDS);
   }
@@ -355,6 +357,32 @@
       areq.setEmailAddress(fetchRsp.getAttributeValue("Email"));
     }
 
+    if (openIdDomains != null && openIdDomains.size() > 0) {
+      // Administrator limited email domains, which can be used for OpenID.
+      // Login process will only work if the passed email matches one
+      // of these domains.
+      //
+      final String email = areq.getEmailAddress();
+      int emailAtIndex = email.lastIndexOf("@");
+      if (emailAtIndex >= 0 && emailAtIndex < email.length() - 1) {
+        final String emailDomain = email.substring(emailAtIndex);
+
+        boolean match = false;
+        for (String domain : openIdDomains) {
+          if (emailDomain.equalsIgnoreCase(domain)) {
+            match = true;
+            break;
+          }
+        }
+
+        if (!match) {
+          log.error("Domain disallowed: " + emailDomain);
+          cancelWithError(req, rsp, "Domain disallowed");
+          return;
+        }
+      }
+    }
+
     if (claimedIdentifier != null) {
       // The user used a claimed identity which has delegated to the verified
       // identity we have in our AuthRequest above. We still should have a
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 9916257..a56d64e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -25,6 +25,7 @@
 import org.eclipse.jgit.lib.Config;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -40,6 +41,7 @@
   private final boolean gitBasicAuth;
   private final String logoutUrl;
   private final String openIdSsoUrl;
+  private final List<String> openIdDomains;
   private final List<OpenIdProviderPattern> trustedOpenIDs;
   private final List<OpenIdProviderPattern> allowedOpenIDs;
   private final String cookiePath;
@@ -56,6 +58,7 @@
     httpHeader = cfg.getString("auth", null, "httpheader");
     logoutUrl = cfg.getString("auth", null, "logouturl");
     openIdSsoUrl = cfg.getString("auth", null, "openidssourl");
+    openIdDomains = Arrays.asList(cfg.getStringList("auth", null, "openIdDomain"));
     trustedOpenIDs = toPatterns(cfg, "trustedOpenID");
     allowedOpenIDs = toPatterns(cfg, "allowedOpenID");
     cookiePath = cfg.getString("auth", null, "cookiepath");
@@ -127,6 +130,10 @@
     return openIdSsoUrl;
   }
 
+  public List<String> getOpenIdDomains() {
+    return openIdDomains;
+  }
+
   public String getCookiePath() {
     return cookiePath;
   }