Add OpenID SSO support.

Configuring OPENID_SSO in gerrit.config will allow the admin
to specify an SSO entry point URL so that users clicking on
"Sign In" are sent directly to that URL.

Change-Id: I361b7b74a6f34a199578620540be31d1df0c5fef
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 0435f4e..8c291c7 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -88,6 +88,12 @@
 provider chosen by the end-user.  For more information see
 http://openid.net/[openid.net].
 +
+* `OpenID_SSO`
++
+Supports OpenID from a single provider.  There is no registration
+link, and the "Sign In" link sends the user directly to the provider's
+SSO entry point.
++
 * `HTTP`
 +
 Gerrit relies upon data presented in the HTTP request.  This includes
@@ -229,6 +235,13 @@
 +
 Default is 12 hours.
 
+[[auth.openIdSsoUrl]]auth.openIdSsoUrl::
++
+The SSO entry point URL.  Only used if `auth.type` was set to
+OpenID_SSO.
++
+The "Sign In" link will send users directly to this URL.
+
 [[auth.httpHeader]]auth.httpHeader::
 +
 HTTP header to trust the username from, or unset to select HTTP basic
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index 07a8534..89de3b4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -28,6 +28,7 @@
 public class GerritConfig implements Cloneable {
   protected String registerUrl;
   protected String httpPasswordUrl;
+  protected String openIdSsoUrl;
   protected List<OpenIdProviderPattern> allowedOpenIDs;
 
   protected GitwebConfig gitweb;
@@ -72,6 +73,14 @@
     httpPasswordUrl = url;
   }
 
+  public String getOpenIdSsoUrl() {
+      return openIdSsoUrl;
+  }
+
+  public void setOpenIdSsoUrl(final String u) {
+    openIdSsoUrl = u;
+  }
+
   public List<OpenIdProviderPattern> getAllowedOpenIDs() {
     return allowedOpenIDs;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 5c2a3190..091fbdc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.client;
 
 import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
+import com.google.gerrit.client.auth.openid.OpenIdSsoPanel;
 import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
 import com.google.gerrit.client.changes.ChangeConstants;
 import com.google.gerrit.client.changes.ChangeListScreen;
@@ -255,6 +256,13 @@
         Location.assign(selfRedirect("/become"));
         break;
 
+      case OPENID_SSO:
+        final RootPanel gBody = RootPanel.get("gerrit_body");
+        OpenIdSsoPanel singleSignOnPanel = new OpenIdSsoPanel();
+        gBody.add(singleSignOnPanel);
+        singleSignOnPanel.authenticate(SignInMode.SIGN_IN, token);
+        break;
+
       case OPENID:
         new OpenIdSignInDialog(SignInMode.SIGN_IN, token, null).center();
         break;
@@ -617,6 +625,14 @@
           });
           break;
 
+        case OPENID_SSO:
+          menuRight.addItem(C.menuSignIn(), new Command() {
+            public void execute() {
+              doSignIn(History.getToken());
+            }
+          });
+          break;
+
         case LDAP:
         case LDAP_BIND:
         case CUSTOM_EXTENSION:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
new file mode 100644
index 0000000..3dd54a7
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2012 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.google.gerrit.client.auth.openid;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gerrit.common.auth.SignInMode;
+import com.google.gerrit.common.auth.openid.DiscoveryResult;
+import com.google.gerrit.common.auth.openid.OpenIdUrls;
+import com.google.gwt.dom.client.FormElement;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Hidden;
+
+import java.util.Map;
+
+public class OpenIdSsoPanel extends FlowPanel {
+  private final FormPanel redirectForm;
+  private final FlowPanel redirectBody;
+  private final String ssoUrl;
+
+  public OpenIdSsoPanel() {
+    super();
+    redirectBody = new FlowPanel();
+    redirectBody.setVisible(false);
+    redirectForm = new FormPanel();
+    redirectForm.add(redirectBody);
+
+    add(redirectForm);
+
+    ssoUrl = Gerrit.getConfig().getOpenIdSsoUrl();
+  }
+
+  public void authenticate(SignInMode requestedMode, final String token) {
+    OpenIdUtil.SVC.discover(ssoUrl, requestedMode, /* remember */ false, token,
+        new GerritCallback<DiscoveryResult>() {
+          public void onSuccess(final DiscoveryResult result) {
+            onDiscovery(result);
+          }
+        });
+  }
+
+  private void onDiscovery(final DiscoveryResult result) {
+    switch (result.status) {
+      case VALID:
+        redirectForm.setMethod(FormPanel.METHOD_POST);
+        redirectForm.setAction(result.providerUrl);
+        redirectBody.clear();
+        for (final Map.Entry<String, String> e : result.providerArgs.entrySet()) {
+          redirectBody.add(new Hidden(e.getKey(), e.getValue()));
+        }
+        FormElement.as(redirectForm.getElement()).setTarget("_top");
+        redirectForm.submit();
+        break;
+    }
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index f92f13d..72bb0c2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -90,6 +90,10 @@
         config.setAllowedOpenIDs(authConfig.getAllowedOpenIDs());
         break;
 
+      case OPENID_SSO:
+        config.setOpenIdSsoUrl(authConfig.getOpenIdSsoUrl());
+        break;
+
       case LDAP:
       case LDAP_BIND:
         config.setRegisterUrl(cfg.getString("auth", null, "registerurl"));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 0d14b79..1a48bb5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -108,6 +108,7 @@
         break;
 
       case OPENID:
+      case OPENID_SSO:
         // OpenID support is bound in WebAppInitializer and Daemon.
       case CUSTOM_EXTENSION:
         break;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 282bbc9..02b0a1d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -367,7 +367,8 @@
     }
 
     AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
-    if (authConfig.getAuthType() == AuthType.OPENID) {
+    if (authConfig.getAuthType() == AuthType.OPENID ||
+        authConfig.getAuthType() == AuthType.OPENID_SSO) {
       modules.add(new OpenIdModule());
     }
     modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
index 962426b..b615fc5 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
@@ -18,6 +18,9 @@
   /** Login relies upon the OpenID standard: {@link "http://openid.net/"} */
   OPENID,
 
+  /** Login relies upon the OpenID standard: {@link "http://openid.net/"} in Single Sign On mode */
+  OPENID_SSO,
+
   /**
    * Login relies upon the container/web server security.
    * <p>
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 a0f0d36..a37fde0 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
@@ -37,6 +37,7 @@
   private final String httpHeader;
   private final boolean trustContainerAuth;
   private final String logoutUrl;
+  private final String openIdSsoUrl;
   private final List<OpenIdProviderPattern> trustedOpenIDs;
   private final List<OpenIdProviderPattern> allowedOpenIDs;
   private final String cookiePath;
@@ -51,6 +52,7 @@
     authType = toType(cfg);
     httpHeader = cfg.getString("auth", null, "httpheader");
     logoutUrl = cfg.getString("auth", null, "logouturl");
+    openIdSsoUrl = cfg.getString("auth", null, "openidssourl");
     trustedOpenIDs = toPatterns(cfg, "trustedOpenID");
     allowedOpenIDs = toPatterns(cfg, "allowedOpenID");
     cookiePath = cfg.getString("auth", null, "cookiepath");
@@ -106,6 +108,10 @@
     return logoutUrl;
   }
 
+  public String getOpenIdSsoUrl() {
+    return openIdSsoUrl;
+  }
+
   public String getCookiePath() {
     return cookiePath;
   }
@@ -146,6 +152,10 @@
         //
         return true;
 
+      case OPENID_SSO:
+        // There's only one provider in SSO mode, so it must be okay.
+        return true;
+
       case OPENID:
         // All identities must be trusted in order to trust the account.
         //