Expose extension point for generic OAuth providers

Further development of OAuth authentication scheme support suggested in
I86fb8fab3 is to restrict the core to expose only the OAuth extension
point and use Gerrit plugin concept for OAuth provider implementations.

When multiple OAuth providers are deployed on Gerrit site (from one or
multiple plugins) selection page is shown to select OAuth provider per
user base (as it's known for OpenID authentication scheme). The only
difference is that the user can only select between deployed providers.

OAuth logo was borrowed from:

http://en.wikipedia.org/wiki/OAuth and
http://en.wikipedia.org/wiki/File:Oauth_logo.svg
The OAuth logo, designed by Chris Messina
Creative Commons Attribution-Share Alike 3.0 Unported license

Converted as base64 using: http://www.base64-image.de
Source for OAuth protocol description: the same link as above.

Bug: issue 2677
Bug: issue 2715
Contributed-by: Luca Milanesio <luca.milanesio@gmail.com>
Change-Id: I7da0a6b3f2a99b6188bd14cf2818f673a3ddd680
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a50c788..f121e1f 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -145,6 +145,16 @@
 The configured <<ldap.username,ldap.username>> identity is not used to obtain
 account information.
 +
+* OAUTH
++
+OAuth is a protocol that lets external apps request authorization to private
+details in a user's account without getting their password. This is
+preferred over Basic Authentication because tokens can be limited to specific
+types of data, and can be revoked by users at any time.
++
+Site owners have to register their application before getting started. Note
+that provider specific plugins must be used with this authentication scheme.
++
 * `DEVELOPMENT_BECOME_ANY_ACCOUNT`
 +
 *DO NOT USE*.  Only for use in a development environment.
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java
new file mode 100644
index 0000000..8375e31
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2015 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.extensions.auth.oauth;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+import java.io.IOException;
+
+/* Contract that OAuth provider must implement */
+@ExtensionPoint
+public interface OAuthServiceProvider {
+
+  /**
+   * Retrieve the request token.
+   *
+   * @return request token
+   */
+  OAuthToken getRequestToken();
+
+  /**
+   * Returns the URL where you should redirect your users to authenticate
+   * your application.
+   *
+   * @param requestToken the request token you need to authorize
+   * @return the URL where you should redirect your users
+   */
+  String getAuthorizationUrl(OAuthToken requestToken);
+
+  /**
+   * Retrieve the access token
+   *
+   * @param requestToken request token (obtained previously)
+   * @param verifier verifier code
+   * @return access token
+   */
+  OAuthToken getAccessToken(OAuthToken requestToken, OAuthVerifier verifier);
+
+  /**
+   * After establishing of secure communication channel, this method supossed to
+   * access the protected resoure and retrieve the username.
+   *
+   * @param token
+   * @return OAuth user information
+   * @throws IOException
+   */
+  OAuthUserInfo getUserInfo(OAuthToken token) throws IOException;
+
+  /**
+   * Returns the OAuth version of the service.
+   *
+   * @return oauth version as string
+   */
+  String getVersion();
+
+  /**
+   * Returns the name of this service. This name is resented the user to choose
+   * between multiple service providers
+   *
+   * @return name of the service
+   */
+  String getName();
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
new file mode 100644
index 0000000..901951e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthToken.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2015 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.extensions.auth.oauth;
+
+/* OAuth token */
+public class OAuthToken {
+
+  private final String token;
+  private final String secret;
+  private final String raw;
+
+  public OAuthToken(String token, String secret, String raw) {
+    this.token = token;
+    this.secret = secret;
+    this.raw = raw;
+  }
+
+  public String getToken() {
+    return token;
+  }
+
+  public String getSecret() {
+    return secret;
+  }
+
+  public String getRaw() {
+    return raw;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java
new file mode 100644
index 0000000..23a7bec
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthUserInfo.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2015 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.extensions.auth.oauth;
+
+public class OAuthUserInfo {
+
+  private final String externalId;
+  private final String userName;
+  private final String emailAddress;
+  private final String displayName;
+
+  public OAuthUserInfo(String externalId,
+      String userName,
+      String emailAddress,
+      String displayName) {
+    this.externalId = externalId;
+    this.userName = userName;
+    this.emailAddress = emailAddress;
+    this.displayName = displayName;
+  }
+
+  public String getExternalId() {
+    return externalId;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public String getEmailAddress() {
+    return emailAddress;
+  }
+
+  public String getDisplayName() {
+    return displayName;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthVerifier.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthVerifier.java
new file mode 100644
index 0000000..33c45c5
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/auth/oauth/OAuthVerifier.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2015 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.extensions.auth.oauth;
+
+/* OAuth verifier */
+public class OAuthVerifier {
+
+  private final String value;
+
+  public OAuthVerifier(String value) {
+    this.value = value;
+  }
+
+  public String getValue() {
+    return value;
+  }
+}
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 d81827c..27a4b53 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
@@ -718,6 +718,15 @@
           });
           break;
 
+        case OAUTH:
+          menuRight.addItem(C.menuSignIn(), new Command() {
+            @Override
+            public void execute() {
+              doSignIn(History.getToken());
+            }
+          });
+          break;
+
         case OPENID_SSO:
           menuRight.addItem(C.menuSignIn(), new Command() {
             public void execute() {
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 bbe6972..f41f67c 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
@@ -112,6 +112,7 @@
 
       case CLIENT_SSL_CERT_LDAP:
       case DEVELOPMENT_BECOME_ANY_ACCOUNT:
+      case OAUTH:
       case OPENID:
       case OPENID_SSO:
         break;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
index 7e1aa28..b2228a9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
@@ -35,7 +35,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class HttpLogoutServlet extends HttpServlet {
+public class HttpLogoutServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
 
   private final DynamicItem<WebSession> webSession;
@@ -44,7 +44,7 @@
   private final AuditService audit;
 
   @Inject
-  HttpLogoutServlet(final AuthConfig authConfig,
+  protected HttpLogoutServlet(final AuthConfig authConfig,
       final DynamicItem<WebSession> webSession,
       @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
       final AccountManager accountManager,
@@ -55,7 +55,7 @@
     this.audit = audit;
   }
 
-  private void doLogout(final HttpServletRequest req,
+  protected void doLogout(final HttpServletRequest req,
       final HttpServletResponse rsp) throws IOException {
     webSession.get().logout();
     if (logoutUrl != null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 3c4dfc5..4c36e4d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -33,8 +33,10 @@
 import com.google.gerrit.httpd.rpc.doc.QueryDocumentationFilter;
 import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
 import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
+import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gwtexpui.server.CacheControlFilter;
 import com.google.inject.Inject;
@@ -64,10 +66,12 @@
 
   private final UrlConfig cfg;
   private GerritUiOptions uiOptions;
+  private AuthConfig authConfig;
 
-  UrlModule(UrlConfig cfg, GerritUiOptions uiOptions) {
+  UrlModule(UrlConfig cfg, GerritUiOptions uiOptions, AuthConfig authConfig) {
     this.cfg = cfg;
     this.uiOptions = uiOptions;
+    this.authConfig = authConfig;
   }
 
   @Override
@@ -81,8 +85,11 @@
       serve("/Gerrit/*").with(legacyGerritScreen());
     }
     serve("/cat/*").with(CatServlet.class);
-    serve("/logout").with(HttpLogoutServlet.class);
-    serve("/signout").with(HttpLogoutServlet.class);
+
+    if (authConfig.getAuthType() != AuthType.OAUTH) {
+      serve("/logout").with(HttpLogoutServlet.class);
+      serve("/signout").with(HttpLogoutServlet.class);
+    }
     serve("/ssh_info").with(SshInfoServlet.class);
     serve("/static/*").with(StaticServlet.class);
 
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 3443968..76e1e41 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
@@ -101,6 +101,8 @@
         install(new BecomeAnyAccountModule());
         break;
 
+      case OAUTH:
+        // OAuth support is bound in WebAppInitializer and Daemon.
       case OPENID:
       case OPENID_SSO:
         // OpenID support is bound in WebAppInitializer and Daemon.
@@ -110,7 +112,7 @@
         throw new ProvisionException("Unsupported loginType: " + authConfig.getAuthType());
     }
 
-    install(new UrlModule(urlConfig, uiOptions));
+    install(new UrlModule(urlConfig, uiOptions, authConfig));
     install(new UiRpcModule());
     install(new GerritRequestModule());
     install(new GitOverHttpServlet.Module());
diff --git a/gerrit-oauth/BUCK b/gerrit-oauth/BUCK
new file mode 100644
index 0000000..4641e81
--- /dev/null
+++ b/gerrit-oauth/BUCK
@@ -0,0 +1,24 @@
+SRCS = glob(
+  ['src/main/java/**/*.java'],
+)
+RESOURCES = glob(['src/main/resources/**/*'])
+
+java_library(
+  name = 'oauth',
+  srcs = SRCS,
+  resources = RESOURCES,
+  deps = [
+    '//gerrit-common:annotations',
+    '//gerrit-extension-api:api',
+    '//gerrit-httpd:httpd',
+    '//gerrit-server:server',
+    '//lib:gson',
+    '//lib:guava',
+    '//lib/commons:codec',
+    '//lib/guice:guice',
+    '//lib/guice:guice-servlet',
+    '//lib/log:api',
+  ],
+  provided_deps = ['//lib:servlet-api-3_1'],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java
new file mode 100644
index 0000000..43b85bd
--- /dev/null
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthLogoutServlet.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2015 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.httpd.auth.oauth;
+
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.HttpLogoutServlet;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+class OAuthLogoutServlet extends HttpLogoutServlet {
+  private static final long serialVersionUID = 1L;
+
+  private final Provider<OAuthSession> oauthSession;
+
+  @Inject
+  OAuthLogoutServlet(AuthConfig authConfig,
+      DynamicItem<WebSession> webSession,
+      @CanonicalWebUrl @Nullable Provider<String> urlProvider,
+      AccountManager accountManager,
+      AuditService audit,
+      Provider<OAuthSession> oauthSession) {
+      super(authConfig, webSession, urlProvider, accountManager, audit);
+    this.oauthSession = oauthSession;
+  }
+
+  @Override
+  protected void doLogout(HttpServletRequest req, HttpServletResponse rsp)
+      throws IOException {
+    super.doLogout(req, rsp);
+    oauthSession.get().logout();
+  }
+}
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthModule.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthModule.java
new file mode 100644
index 0000000..f74e005
--- /dev/null
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthModule.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2015 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.httpd.auth.oauth;
+
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.inject.servlet.ServletModule;
+
+/** Servlets and support related to OAuth authentication. */
+public class OAuthModule extends ServletModule {
+
+  @Override
+  protected void configureServlets() {
+    filter("/login", "/login/*", "/oauth").through(OAuthWebFilter.class);
+    // This is needed to invalidate OAuth session during logout
+    serve("/logout").with(OAuthLogoutServlet.class);
+    DynamicMap.mapOf(binder(), OAuthServiceProvider.class);
+  }
+}
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
new file mode 100644
index 0000000..d625e02
--- /dev/null
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -0,0 +1,178 @@
+// Copyright (C) 2015 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.httpd.auth.oauth;
+
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.auth.oauth.OAuthToken;
+import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
+import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.inject.Inject;
+import com.google.inject.servlet.SessionScoped;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SessionScoped
+/* OAuth protocol implementation */
+class OAuthSession {
+  private static final Logger log = LoggerFactory.getLogger(OAuthSession.class);
+  private static final SecureRandom randomState = newRandomGenerator();
+  private final String state;
+  private final DynamicItem<WebSession> webSession;
+  private final AccountManager accountManager;
+  private OAuthServiceProvider serviceProvider;
+  private OAuthToken token;
+  private OAuthUserInfo user;
+  private String redirectUrl;
+
+  @Inject
+  OAuthSession(DynamicItem<WebSession> webSession,
+      AccountManager accountManager) {
+    this.state = generateRandomState();
+    this.webSession = webSession;
+    this.accountManager = accountManager;
+  }
+
+  boolean isLoggedIn() {
+    return token != null && user != null;
+  }
+
+  boolean isOAuthFinal(HttpServletRequest request) {
+    return Strings.emptyToNull(request.getParameter("code")) != null;
+  }
+
+  boolean login(HttpServletRequest request, HttpServletResponse response,
+      OAuthServiceProvider oauth) throws IOException {
+    if (isLoggedIn()) {
+      return true;
+    }
+
+    log.debug("Login " + this);
+
+    if (isOAuthFinal(request)) {
+      if (!checkState(request)) {
+        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        return false;
+      }
+
+      log.debug("Login-Retrieve-User " + this);
+      token = oauth.getAccessToken(null,
+          new OAuthVerifier(request.getParameter("code")));
+
+      user = oauth.getUserInfo(token);
+
+      if (isLoggedIn()) {
+        log.debug("Login-SUCCESS " + this);
+        authenticateAndRedirect(response);
+        return true;
+      } else {
+        response.sendError(SC_UNAUTHORIZED);
+        return false;
+      }
+    } else {
+      log.debug("Login-PHASE1 " + this);
+      redirectUrl = request.getRequestURI();
+      response.sendRedirect(oauth.getAuthorizationUrl(null) +
+          "&state=" + state);
+      return false;
+    }
+  }
+
+  private void authenticateAndRedirect(HttpServletResponse rsp)
+      throws IOException {
+    com.google.gerrit.server.account.AuthRequest areq =
+        new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
+    areq.setUserName(user.getUserName());
+    areq.setEmailAddress(user.getEmailAddress());
+    areq.setDisplayName(user.getDisplayName());
+    AuthResult arsp;
+    try {
+      arsp = accountManager.authenticate(areq);
+    } catch (AccountException e) {
+      log.error("Unable to authenticate user \"" + user + "\"", e);
+      rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+      return;
+    }
+
+    webSession.get().login(arsp, true);
+    String suffix = redirectUrl.substring(
+        OAuthWebFilter.GERRIT_LOGIN.length() + 1);
+    suffix = URLDecoder.decode(suffix, StandardCharsets.UTF_8.name());
+    rsp.sendRedirect(suffix);
+  }
+
+  void logout() {
+    token = null;
+    user = null;
+    redirectUrl = null;
+    serviceProvider = null;
+  }
+
+  private boolean checkState(ServletRequest request) {
+    String s = Strings.nullToEmpty(request.getParameter("state"));
+    if (!s.equals(state)) {
+      log.error("Illegal request state '" + s + "' on OAuthProtocol " + this);
+      return false;
+    }
+    return true;
+  }
+
+  private static SecureRandom newRandomGenerator() {
+    try {
+      return SecureRandom.getInstance("SHA1PRNG");
+    } catch (NoSuchAlgorithmException e) {
+      throw new IllegalArgumentException(
+          "No SecureRandom available for GitHub authentication", e);
+    }
+  }
+
+  private static String generateRandomState() {
+    byte[] state = new byte[32];
+    randomState.nextBytes(state);
+    return Base64.encodeBase64URLSafeString(state);
+  }
+
+  @Override
+  public String toString() {
+    return "OAuthSession [token=" + token + ", user=" + user + "]";
+  }
+
+  public void setServiceProvider(OAuthServiceProvider provider) {
+    this.serviceProvider = provider;
+  }
+
+  public OAuthServiceProvider getServiceProvider() {
+    return serviceProvider;
+  }
+}
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
new file mode 100644
index 0000000..7f93437
--- /dev/null
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
@@ -0,0 +1,223 @@
+// Copyright (C) 2015 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.httpd.auth.oauth;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.httpd.HtmlDomUtil;
+import com.google.gerrit.httpd.LoginUrlToken;
+import com.google.gerrit.httpd.template.SiteHeaderFooter;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@Singleton
+/* OAuth web filter uses active OAuth session to perform OAuth requests */
+class OAuthWebFilter implements Filter {
+  static final String GERRIT_LOGIN = "/login";
+
+  private final Provider<String> urlProvider;
+  private final Provider<CurrentUser> currentUserProvider;
+  private final Provider<OAuthSession> oauthSessionProvider;
+  private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
+  private final SiteHeaderFooter header;
+  private OAuthServiceProvider ssoProvider;
+
+  @Inject
+  OAuthWebFilter(@CanonicalWebUrl @Nullable Provider<String> urlProvider,
+      Provider<CurrentUser> currentUserProvider,
+      DynamicMap<OAuthServiceProvider> oauthServiceProviders,
+      Provider<OAuthSession> oauthSessionProvider,
+      SiteHeaderFooter header) {
+    this.urlProvider = urlProvider;
+    this.currentUserProvider = currentUserProvider;
+    this.oauthServiceProviders = oauthServiceProviders;
+    this.oauthSessionProvider = oauthSessionProvider;
+    this.header = header;
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    pickSSOServiceProvider();
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+    HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
+    if (currentUserProvider.get().isIdentifiedUser()) {
+      if (httpSession != null) {
+        httpSession.invalidate();
+      }
+      chain.doFilter(request, response);
+      return;
+    }
+
+    HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+    String provider = httpRequest.getParameter("provider");
+    OAuthSession oauthSession = oauthSessionProvider.get();
+    OAuthServiceProvider service = ssoProvider == null
+        ? oauthSession.getServiceProvider()
+        : ssoProvider;
+
+    if ((isGerritLogin(httpRequest)
+        || oauthSession.isOAuthFinal(httpRequest))
+        && !oauthSession.isLoggedIn()) {
+      if (service == null && Strings.isNullOrEmpty(provider)) {
+        selectProvider(httpRequest, httpResponse, null);
+        return;
+      } else {
+        if (service == null) {
+          service = findService(provider);
+        }
+        oauthSession.setServiceProvider(service);
+        oauthSession.login(httpRequest, httpResponse, service);
+      }
+    } else {
+      chain.doFilter(httpRequest, response);
+    }
+  }
+
+  private OAuthServiceProvider findService(String providerId)
+      throws ServletException {
+    Set<String> plugins = oauthServiceProviders.plugins();
+    for (String pluginName : plugins) {
+      Map<String, Provider<OAuthServiceProvider>> m =
+          oauthServiceProviders.byPlugin(pluginName);
+        for (Map.Entry<String, Provider<OAuthServiceProvider>> e
+            : m.entrySet()) {
+          if (providerId.equals(
+              String.format("%s_%s", pluginName, e.getKey()))) {
+            return e.getValue().get();
+          }
+        }
+    }
+    throw new ServletException("No provider found for: " + providerId);
+  }
+
+  private void selectProvider(HttpServletRequest req, HttpServletResponse res,
+      @Nullable String errorMessage)
+      throws IOException {
+    String self = req.getRequestURI();
+    String cancel = Objects.firstNonNull(
+        urlProvider != null ? urlProvider.get() : "/", "/");
+    cancel += LoginUrlToken.getToken(req);
+
+    Document doc = header.parse(OAuthWebFilter.class, "LoginForm.html");
+    HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName());
+    HtmlDomUtil.find(doc, "login_form").setAttribute("action", self);
+    HtmlDomUtil.find(doc, "cancel_link").setAttribute("href", cancel);
+
+    Element emsg = HtmlDomUtil.find(doc, "error_message");
+    if (Strings.isNullOrEmpty(errorMessage)) {
+      emsg.getParentNode().removeChild(emsg);
+    } else {
+      emsg.setTextContent(errorMessage);
+    }
+
+    Element providers = HtmlDomUtil.find(doc, "providers");
+
+    Set<String> plugins = oauthServiceProviders.plugins();
+    for (String pluginName : plugins) {
+      Map<String, Provider<OAuthServiceProvider>> m =
+          oauthServiceProviders.byPlugin(pluginName);
+        for (Map.Entry<String, Provider<OAuthServiceProvider>> e
+            : m.entrySet()) {
+          addProvider(providers, pluginName, e.getKey(),
+              e.getValue().get().getName());
+        }
+    }
+
+    sendHtml(res, doc);
+  }
+
+  private static void addProvider(Element form, String pluginName,
+      String id, String serviceName) {
+    Element div = form.getOwnerDocument().createElement("div");
+    div.setAttribute("id", id);
+    Element hyperlink = form.getOwnerDocument().createElement("a");
+    hyperlink.setAttribute("href", String.format("?provider=%s_%s",
+        pluginName, id));
+    hyperlink.setTextContent(serviceName +
+        " (" + pluginName + " plugin)");
+    div.appendChild(hyperlink);
+    form.appendChild(div);
+  }
+
+  private static void sendHtml(HttpServletResponse res, Document doc)
+      throws IOException {
+    byte[] bin = HtmlDomUtil.toUTF8(doc);
+    res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+    res.setContentType("text/html");
+    res.setCharacterEncoding(StandardCharsets.UTF_8.name());
+    res.setContentLength(bin.length);
+    try (ServletOutputStream out = res.getOutputStream()) {
+      out.write(bin);
+    }
+  }
+
+  private void pickSSOServiceProvider()
+      throws ServletException {
+    SortedSet<String> plugins = oauthServiceProviders.plugins();
+    if (plugins.isEmpty()) {
+      throw new ServletException(
+          "OAuth service provider wasn't installed");
+    }
+    if (plugins.size() == 1) {
+      SortedMap<String, Provider<OAuthServiceProvider>> services =
+          oauthServiceProviders.byPlugin(Iterables.getOnlyElement(plugins));
+      if (services.size() == 1) {
+        ssoProvider = Iterables.getOnlyElement(services.values()).get();
+      }
+    }
+  }
+
+  private static boolean isGerritLogin(HttpServletRequest request) {
+    return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
+  }
+}
diff --git a/gerrit-oauth/src/main/resources/com/google/gerrit/httpd/auth/oauth/LoginForm.html b/gerrit-oauth/src/main/resources/com/google/gerrit/httpd/auth/oauth/LoginForm.html
new file mode 100644
index 0000000..f7814c0
--- /dev/null
+++ b/gerrit-oauth/src/main/resources/com/google/gerrit/httpd/auth/oauth/LoginForm.html
@@ -0,0 +1,58 @@
+<html>
+  <head>
+    <title>Gerrit Code Review - Sign In</title>
+    <style>
+      #error_message {
+        padding: 5px;
+        margin-left: 5px;
+        margin-bottom: 5px;
+        width: 20em;
+        background-color: rgb(255, 255, 116);
+        font-weight: bold;
+      }
+      #cancel_link {
+        margin-left: 45px;
+      }
+      #logo_box {
+        padding-left: 160px;
+      }
+      #logo_img {
+        width: 239px;
+        height: 240px;
+        background: url('data:image/png;base64,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') no-repeat 0px 0px;
+      }
+    </style>
+    <style id="gerrit_sitecss" type="text/css"></style>
+  </head>
+  <body>
+    <div id="gerrit_topmenu" style="height:45px;" class="gerritTopMenu"></div>
+    <div id="gerrit_header"></div>
+    <div id="gerrit_body" class="gerritBody">
+      <h1>Sign In to Gerrit Code Review at <span id="hostName">example.com</span></h1>
+      <form method="POST" action="#" id="login_form">
+        <input type="hidden" name="link" id="f_link" value="1" />
+        <div id="logo_box"><div id="logo_img"></div></div>
+        <div id="error_message">Invalid OAuth provider.</div>
+
+        <div>Available OAuth providers:</div>
+
+        <div id="providers">
+        </div>
+
+        <div>
+          <a href="../" id="cancel_link">Cancel</a>
+        </div>
+
+        <div style="margin-top: 25px;">
+          <h2>What is OAuth protocol?</h2>
+          <p>OAuth is an open standard for authorization. OAuth provides client applications a 'secure delegated access'</p>
+          <p>to server resources on behalf of a resource owner. It specifies a process for resource owners to authorize</p>
+          <p>third-party access to their server resources without sharing their credentials.</p>
+        </div>
+      </form>
+    </div>
+    <div style="clear: both; margin-top: 15px; padding-top: 2px; margin-bottom: 15px;">
+      <div id="gerrit_footer"></div>
+    </div>
+  </body>
+</html>
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 3a89cd0..8b5fdc1 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -99,6 +99,7 @@
     '//gerrit-gwtexpui:server',
     '//gerrit-httpd:httpd',
     '//gerrit-lucene:lucene',
+    '//gerrit-oauth:oauth',
     '//gerrit-openid:openid',
     '//gerrit-reviewdb:server',
     '//gerrit-server:server',
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 88a16fb..e28b5ba 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
@@ -27,6 +27,7 @@
 import com.google.gerrit.httpd.RequestContextFilter;
 import com.google.gerrit.httpd.WebModule;
 import com.google.gerrit.httpd.WebSshGlueModule;
+import com.google.gerrit.httpd.auth.oauth.OAuthModule;
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -430,6 +431,8 @@
     if (authConfig.getAuthType() == AuthType.OPENID ||
         authConfig.getAuthType() == AuthType.OPENID_SSO) {
       modules.add(new OpenIdModule());
+    } else if (authConfig.getAuthType() == AuthType.OAUTH) {
+      modules.add(new OAuthModule());
     }
     modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index a2037b5..74884a4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -60,6 +60,7 @@
       case DEVELOPMENT_BECOME_ANY_ACCOUNT:
       case LDAP:
       case LDAP_BIND:
+      case OAUTH:
       case OPENID:
       case OPENID_SSO:
         break;
@@ -94,6 +95,7 @@
       case CUSTOM_EXTENSION:
       case DEVELOPMENT_BECOME_ANY_ACCOUNT:
       case HTTP:
+      case OAUTH:
       case OPENID:
       case OPENID_SSO:
         break;
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 6af9610..38a78ba 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
@@ -80,5 +80,8 @@
   CUSTOM_EXTENSION,
 
   /** Development mode to enable becoming anyone you want. */
-  DEVELOPMENT_BECOME_ANY_ACCOUNT
+  DEVELOPMENT_BECOME_ANY_ACCOUNT,
+
+  /** Generic OAuth provider over HTTP. */
+  OAUTH
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 938d940..4b8f0b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -40,7 +40,8 @@
 
   @Override
   public boolean allowsEdit(final Account.FieldName field) {
-    if (authConfig.getAuthType() == AuthType.HTTP) {
+    if (authConfig.getAuthType() == AuthType.HTTP
+        || authConfig.getAuthType() == AuthType.OAUTH) {
       switch (field) {
         case USER_NAME:
           return false;
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 b1ec6e4..c2cf95e 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
@@ -197,7 +197,7 @@
       case LDAP_BIND:
       case CLIENT_SSL_CERT_LDAP:
       case CUSTOM_EXTENSION:
-        // Its safe to assume yes for an HTTP authentication type, as the
+      case OAUTH:
         // only way in is through some external system that the admin trusts
         //
         return true;
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index fc73973..f557edc 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -8,6 +8,7 @@
     '//gerrit-extension-api:api',
     '//gerrit-httpd:httpd',
     '//gerrit-lucene:lucene',
+    '//gerrit-oauth:oauth',
     '//gerrit-openid:openid',
     '//gerrit-pgm:init-api',
     '//gerrit-pgm:init-base',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 56b092e..e9bd296 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -19,6 +19,7 @@
 
 import com.google.common.base.Splitter;
 import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.httpd.auth.oauth.OAuthModule;
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -343,6 +344,8 @@
     AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
     if (authConfig.getAuthType() == AuthType.OPENID) {
       modules.add(new OpenIdModule());
+    } else if (authConfig.getAuthType() == AuthType.OAUTH) {
+      modules.add(new OAuthModule());
     }
 
     return sysInjector.createChildInjector(modules);