Merge "Expose extension point for generic OAuth providers" into stable-2.10
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index c2221df..4ad7d21 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);