GitHub OAuth authentication with Gerrit Git/HTTP protocol

It is possible now to use the standard GitHub OAuth
token on Git / HTTP with Gerrit Code Review.

The details on how GitHub OAuth Token for Git/HTTP
are described at:
https://help.github.com/articles/git-over-https-using-oauth-token

This allows to completely "shadow" your existing
GitHub repository behind a Gerrit Code Review front-end.
Using OAuth token you can automate the batch builds
over HTTP(/S) without having to manage Gerrit HTTP passwords.

NOTE: this requires the [auth.gitBasicAuth] setting
on your gerrit.config as currently there is no way to
seamlessly integrate SSO with Gerrit on Git/HTTP.
The GitHub plugin "automate" a random HTTP password
generate and use it internally for a transparent
BasicAuth, after having validated the GitHub OAuth Token.

Change-Id: I23552e1b559f50056fdc48248f44696726aa1fef
diff --git a/github-oauth/src/main/java/com/google/gerrit/httpd/XGerritAuth.java b/github-oauth/src/main/java/com/google/gerrit/httpd/XGerritAuth.java
new file mode 100644
index 0000000..25a308e
--- /dev/null
+++ b/github-oauth/src/main/java/com/google/gerrit/httpd/XGerritAuth.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 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;
+
+import javax.servlet.http.Cookie;
+
+import com.google.gerrit.httpd.WebSessionManager.Val;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class XGerritAuth {
+  public static final String X_GERRIT_AUTH = "X-Gerrit-Auth";
+  private WebSessionManager manager;
+
+  @Inject
+  public XGerritAuth(WebSessionManager manager) {
+    this.manager = manager;
+  }
+  
+  public String getAuthValue(Cookie gerritCookie) {
+    Val session = manager.get(new WebSessionManager.Key(gerritCookie.getValue()));
+    return session.getAuth();
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedLoginHttpRequest.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedLoginHttpRequest.java
new file mode 100644
index 0000000..2c5469b
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedLoginHttpRequest.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class AuthenticatedLoginHttpRequest extends AuthenticatedPathHttpRequest {
+
+  public AuthenticatedLoginHttpRequest(HttpServletRequest request,
+      String userHeader, String username) {
+    super(request, "/login", userHeader, username);
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedLoginHttpResponse.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedLoginHttpResponse.java
new file mode 100644
index 0000000..5e4b586
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedLoginHttpResponse.java
@@ -0,0 +1,232 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class AuthenticatedLoginHttpResponse extends HttpServletResponseWrapper {
+  private Cookie gerritCookie;
+  private int status;
+  private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+  private String characterEncoding = "UTF-8";
+  private String contentType = "text/plain";
+
+  public AuthenticatedLoginHttpResponse(HttpServletResponse httpResponse) {
+    super(httpResponse);
+  }
+
+  @Override
+  public void addCookie(Cookie cookie) {
+    if(cookie.getName().equals(OAuthWebFilter.GERRIT_COOKIE_NAME)) {
+      this.gerritCookie = cookie;
+    }
+  }
+
+  public Cookie getGerritCookie() {
+    return gerritCookie;
+  }
+
+  @Override
+  public void addDateHeader(String name, long date) {
+  }
+  
+  @Override
+  public void addHeader(String name, String value) {
+  }
+
+  @Override
+  public void addIntHeader(String name, int value) {
+  }
+
+  @Override
+  public boolean containsHeader(String name) {
+    return false;
+  }
+
+  @Override
+  public String getHeader(String name) {
+    return null;
+  }
+
+  @Override
+  public Collection<String> getHeaderNames() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public Collection<String> getHeaders(String name) {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public int getStatus() {
+    return status;
+  }
+
+  @Override
+  public void sendError(int sc, String msg) throws IOException {
+    this.status = sc;
+  }
+
+  @Override
+  public void sendError(int sc) throws IOException {
+    this.status = sc;
+  }
+
+  @Override
+  public void sendRedirect(String location) throws IOException {
+    this.status = SC_MOVED_TEMPORARILY;
+  }
+
+  @Override
+  public void setDateHeader(String name, long date) {
+  }
+
+  @Override
+  public void setHeader(String name, String value) {
+  }
+
+  @Override
+  public void setIntHeader(String name, int value) {
+  }
+
+  @Override
+  public void setStatus(int sc, String sm) {
+    this.status = sc;
+  }
+
+  @Override
+  public void setStatus(int sc) {
+    this.status = sc;
+  }
+
+  @Override
+  public void flushBuffer() throws IOException {
+  }
+
+  @Override
+  public int getBufferSize() {
+    return 256;
+  }
+
+  @Override
+  public String getCharacterEncoding() {
+    return characterEncoding;
+  }
+
+  @Override
+  public String getContentType() {
+    return contentType;
+  }
+
+  @Override
+  public Locale getLocale() {
+    return super.getLocale();
+  }
+
+  @Override
+  public ServletOutputStream getOutputStream() throws IOException {
+    return new ServletOutputStream() {
+      
+      @Override
+      public void write(int b) throws IOException {
+        outputStream.write(b);
+      }
+      
+      @Override
+      public void setWriteListener(WriteListener arg0) {
+      }
+      
+      @Override
+      public boolean isReady() {
+        return true;
+      }
+    };
+  }
+
+  @Override
+  public ServletResponse getResponse() {
+    return super.getResponse();
+  }
+
+  @Override
+  public PrintWriter getWriter() throws IOException {
+    return new PrintWriter(outputStream);
+  }
+
+  @Override
+  public boolean isCommitted() {
+    return false;
+  }
+
+  @Override
+  public boolean isWrapperFor(Class<?> wrappedType) {
+    return false;
+  }
+
+  @Override
+  public boolean isWrapperFor(ServletResponse wrapped) {
+    return false;
+  }
+
+  @Override
+  public void reset() {
+  }
+
+  @Override
+  public void resetBuffer() {
+  }
+
+  @Override
+  public void setBufferSize(int size) {
+  }
+
+  @Override
+  public void setCharacterEncoding(String charset) {
+    this.characterEncoding = charset;
+  }
+
+  @Override
+  public void setContentLength(int len) {
+  }
+
+  @Override
+  public void setContentLengthLong(long length) {
+  }
+
+  @Override
+  public void setContentType(String type) {
+    this.contentType = type;
+  }
+
+  @Override
+  public void setLocale(Locale loc) {
+  }
+
+  @Override
+  public void setResponse(ServletResponse response) {
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedPathHttpRequest.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedPathHttpRequest.java
new file mode 100644
index 0000000..53f7075
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/AuthenticatedPathHttpRequest.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+
+
+import javax.servlet.http.HttpServletRequest;
+
+public class AuthenticatedPathHttpRequest extends AuthenticatedHttpRequest {
+
+  private final String contextPath;
+  private final StringBuffer requestURL;
+  private String requestURI;
+  private String requestPath;
+
+  public AuthenticatedPathHttpRequest(HttpServletRequest request, String requestPath,
+      String userHeader, String username) {
+    super(request, userHeader, username);
+
+    this.requestPath = requestPath;
+    this.contextPath = super.getContextPath();
+    this.requestURL = super.getRequestURL();
+    this.requestURI = super.getRequestURI();
+  }
+
+  @Override
+  public String getRequestURI() {
+    return contextPath + requestPath;
+  }
+
+  @Override
+  public StringBuffer getRequestURL() {
+    return new StringBuffer(requestURL.substring(0,
+        requestURL.indexOf(requestURI))
+        + getRequestURI());
+  }
+
+  @Override
+  public String getServletPath() {
+    return requestPath;
+  }
+
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
index b10a606..bb2554c 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubLogin.java
@@ -170,6 +170,7 @@
   public GitHub login(AccessToken authToken) throws IOException {
     this.token = authToken;
     this.hub = GitHub.connectUsingOAuth(authToken.access_token);
+    this.myself = hub.getMyself();
     return this.hub;
   }
 
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java
new file mode 100644
index 0000000..54751d1
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCache.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import java.util.concurrent.ExecutionException;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken;
+
+@Singleton
+public class OAuthCache {
+  private static final String CACHE_NAME = "github_oauth";
+  
+  public static class Loader extends CacheLoader<AccessToken, String> {
+    private GitHubLogin ghLogin;
+
+    @Inject
+    public Loader(GitHubLogin ghLogin) {
+      this.ghLogin = ghLogin;
+    }
+
+    @Override
+    public String load(AccessToken accessToken) throws Exception {
+      ghLogin.login(accessToken);
+      return ghLogin.getMyself().getLogin();
+    }
+  }
+
+  public static Module module() {
+    return new CacheModule() {
+      @Override
+      protected void configure() {
+        cache(CACHE_NAME, AccessToken.class, String.class)
+          .loader(Loader.class);
+        bind(OAuthCache.class);
+      }
+    };
+  }
+
+  private LoadingCache<AccessToken, String> byAccesToken;
+  
+  @Inject
+  public OAuthCache(@Named(CACHE_NAME) LoadingCache<AccessToken, String> byAccessToken) {
+    this.byAccesToken = byAccessToken;
+  }
+  
+  public String getLoginByAccessToken(AccessToken accessToken)
+      throws ExecutionException {
+    return byAccesToken.get(accessToken);
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java
index 54fee4f..5550bfa 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthCookie.java
@@ -23,6 +23,7 @@
 public class OAuthCookie extends Cookie {
   private static final long serialVersionUID = 2771690299147135167L;
   public static final String OAUTH_COOKIE_NAME = "GerritOAuth";
+  public static final OAuthCookie ANONYMOUS = new OAuthCookie();
 
   public final String user;
   public final String email;
@@ -57,6 +58,14 @@
     return clearTextCookie.toString();
   }
 
+  private OAuthCookie() {
+    super(OAUTH_COOKIE_NAME, "");
+    this.user = "";
+    this.scopes = null;
+    this.fullName = "";
+    this.email = "";
+  }
+
   public OAuthCookie(TokenCipher cipher, Cookie cookie)
       throws OAuthTokenException {
     super(OAUTH_COOKIE_NAME, cookie.getValue());
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
index 7f6e3e6..94c2bba 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
@@ -14,10 +14,7 @@
 package com.googlesource.gerrit.plugins.github.oauth;
 
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Random;
-import java.util.Set;
+import java.util.regex.Pattern;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -25,47 +22,40 @@
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
 
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.kohsuke.github.GHMyself;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Strings;
-import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.httpd.GitOverHttpServlet;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
 import com.google.inject.Inject;
+import com.google.inject.Injector;
 import com.google.inject.Singleton;
 
 @Singleton
 public class OAuthFilter implements Filter {
   private static final org.slf4j.Logger log = LoggerFactory
       .getLogger(OAuthFilter.class);
-  private static final String GERRIT_COOKIE_NAME = "GerritAccount";
+  private static Pattern GIT_HTTP_REQUEST_PATTERN = Pattern
+      .compile(GitOverHttpServlet.URL_REGEX);
 
   private final GitHubOAuthConfig config;
-  private final OAuthCookieProvider cookieProvider;
-  private final Random retryRandom = new Random(System.currentTimeMillis());
-  private SitePaths sites;
-  private ScopedProvider<GitHubLogin> loginProvider;
+  private final OAuthGitFilter gitFilter;
+  private final OAuthWebFilter webFilter;
 
   @Inject
-  public OAuthFilter(GitHubOAuthConfig config, SitePaths sites,
-      // We need to explicitly tell Guice the correct implementation
-      // as this filter is instantiated with a standard Gerrit WebModule
-      GitHubLogin.Provider loginProvider) {
+  public OAuthFilter(GitHubOAuthConfig config, 
+      OAuthWebFilter webFilter, Injector injector) {
     this.config = config;
-    this.sites = sites;
-    this.loginProvider = loginProvider;
-    this.cookieProvider = new OAuthCookieProvider(TokenCipher.get());
+    this.webFilter = webFilter;
+    Injector childInjector = injector.createChildInjector(OAuthCache.module());
+    this.gitFilter = childInjector.getInstance(OAuthGitFilter.class);
   }
 
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
+    gitFilter.init(filterConfig);
+    webFilter.init(filterConfig);
   }
 
   @Override
@@ -77,194 +67,18 @@
       return;
     }
 
-    HttpServletRequest httpRequest = (HttpServletRequest) request;
-    HttpServletResponse httpResponse = (HttpServletResponse) response;
-    log.debug("doFilter(" + httpRequest.getRequestURI() + ") code="
-        + request.getParameter("code"));
-
-    Cookie gerritCookie = getGerritCookie(httpRequest);
-    try {
-      GitHubLogin ghLogin = loginProvider.get(httpRequest);
-
-      OAuthCookie authCookie =
-          getOAuthCookie(httpRequest, (HttpServletResponse) response);
-
-      if (OAuthProtocol.isOAuthLogout((HttpServletRequest) request)) {
-        logout(request, response, chain, httpRequest);
-      } else if (OAuthProtocol.isOAuthRequest(httpRequest)) {
-        login(request, httpRequest, httpResponse, ghLogin);
-      } else {
-        httpRequest = enrichAuthenticatedRequest(httpRequest, authCookie);
-
-        if (OAuthProtocol.isOAuthFinalForOthers(httpRequest)) {
-          httpResponse.sendRedirect(OAuthProtocol
-              .getTargetOAuthFinal(httpRequest));
-        } else {
-          chain.doFilter(httpRequest, response);
-        }
-      }
-    } finally {
-      HttpSession httpSession = httpRequest.getSession();
-      if (gerritCookie != null && httpSession != null) {
-        String gerritCookieValue = gerritCookie.getValue();
-        String gerritSessionValue =
-            (String) httpSession.getAttribute("GerritAccount");
-
-        if (gerritSessionValue == null) {
-          httpSession.setAttribute("GerritAccount", gerritCookieValue);
-        } else if (!gerritSessionValue.equals(gerritCookieValue)) {
-          httpSession.invalidate();
-        }
-      }
+    String requestUrl = ((HttpServletRequest) request).getRequestURI();
+    if (GIT_HTTP_REQUEST_PATTERN.matcher(requestUrl).matches()) {
+      gitFilter.doFilter(request, response, chain);
+    } else {
+      webFilter.doFilter(request, response, chain);
     }
   }
 
-  private HttpServletRequest enrichAuthenticatedRequest(
-      HttpServletRequest httpRequest, OAuthCookie authCookie) {
-    httpRequest =
-        authCookie == null ? httpRequest : new AuthenticatedHttpRequest(
-            httpRequest, config.httpHeader, authCookie.user,
-            config.httpDisplaynameHeader, authCookie.fullName,
-            config.httpEmailHeader, authCookie.email);
-    return httpRequest;
-  }
-
-  private void login(ServletRequest request, HttpServletRequest httpRequest,
-      HttpServletResponse httpResponse, GitHubLogin ghLogin) throws IOException {
-    if (ghLogin.login(httpRequest, httpResponse)) {
-      GHMyself myself = ghLogin.getMyself();
-      String user = myself.getLogin();
-
-      updateSecureConfigWithRetry(ghLogin.hub.getMyOrganizations().keySet(),
-          user, ghLogin.token.access_token);
-    }
-  }
-
-  private void logout(ServletRequest request, ServletResponse response,
-      FilterChain chain, HttpServletRequest httpRequest) throws IOException,
-      ServletException {
-    getGitHubLogin(request).logout();
-    GitHubLogoutServletResponse bufferedResponse = new GitHubLogoutServletResponse((HttpServletResponse) response,
-        config.logoutRedirectUrl);
-    chain.doFilter(httpRequest, bufferedResponse);
-  }
-
-  private GitHubLogin getGitHubLogin(ServletRequest request) {
-    return loginProvider.get((HttpServletRequest) request);
-  }
-
-  private void updateSecureConfigWithRetry(Set<String> organisations,
-      String user, String access_token) {
-    int retryCount = 0;
-
-    while (retryCount < config.fileUpdateMaxRetryCount) {
-      try {
-        updateSecureConfig(organisations, user, access_token);
-        return;
-      } catch (IOException e) {
-        retryCount++;
-        int retryInterval =
-            retryRandom.nextInt(config.fileUpdateMaxRetryIntervalMsec);
-        log.warn("Error whilst trying to update " + sites.secure_config
-            + (retryCount < config.fileUpdateMaxRetryCount ? ": attempt #" + retryCount + " will be retried after " + retryInterval + " msecs":""), e);
-        try {
-          Thread.sleep(retryInterval);
-        } catch (InterruptedException e1) {
-          log.error("Thread has been cancelled before retrying to save "
-              + sites.secure_config);
-          return;
-        }
-      } catch (ConfigInvalidException e) {
-        log.error("Cannot update " + sites.secure_config
-            + " as the file is corrupted", e);
-        return;
-      }
-    }
-  }
-
-  private synchronized void updateSecureConfig(Set<String> organisations,
-      String user, String access_token) throws IOException,
-      ConfigInvalidException {
-    FileBasedConfig currentSecureConfig =
-        new FileBasedConfig(sites.secure_config, FS.DETECTED);
-    long currentSecureConfigUpdateTs = sites.secure_config.lastModified();
-    currentSecureConfig.load();
-
-    boolean configUpdate = updateConfigSection(currentSecureConfig, user, user, access_token);
-    for (String organisation : organisations) {
-      configUpdate |= updateConfigSection(currentSecureConfig, organisation, user, access_token);
-    }
-
-    if(!configUpdate) {
-      return;
-    }
-
-    log.info("Updating " + sites.secure_config + " credentials for user " + user);
-
-    if (sites.secure_config.lastModified() != currentSecureConfigUpdateTs) {
-      throw new ConcurrentFileBasedConfigWriteException("File "
-          + sites.secure_config + " was written at "
-          + formatTS(sites.secure_config.lastModified())
-          + " while was trying to update security for user " + user);
-    }
-    currentSecureConfig.save();
-  }
-
-  private String formatTS(long ts) {
-    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ts));
-  }
-
-  private boolean updateConfigSection(FileBasedConfig config,
-      String section, String user, String password) {
-    String configUser = config.getString("remote", section, "username");
-    String configPassword = config.getString("remote", section, "password");
-    if(configUser == null || !configUser.equals(user) || configPassword.equals(password)) {
-      return false;
-    }
-
-    config.setString("remote", section, "username", user);
-    config.setString("remote", section, "password", password);
-    return true;
-  }
-
-  private Cookie getGerritCookie(HttpServletRequest httpRequest) {
-    for (Cookie cookie : getCookies(httpRequest)) {
-      if (cookie.getName().equalsIgnoreCase(GERRIT_COOKIE_NAME)) {
-        return cookie;
-      }
-    }
-    return null;
-  }
-
-  private Cookie[] getCookies(HttpServletRequest httpRequest) {
-    Cookie[] cookies = httpRequest.getCookies();
-    return cookies == null ? new Cookie[0]:cookies;
-  }
-
-  private OAuthCookie getOAuthCookie(HttpServletRequest request,
-      HttpServletResponse response) {
-    for (Cookie cookie : getCookies(request)) {
-      if (cookie.getName().equalsIgnoreCase(OAuthCookie.OAUTH_COOKIE_NAME)
-          && !Strings.isNullOrEmpty(cookie.getValue())) {
-        try {
-          return cookieProvider.getFromCookie(cookie);
-        } catch (OAuthTokenException e) {
-          log.warn(
-              "Invalid cookie detected: cleaning up and sending a reset back to the browser",
-              e);
-          cookie.setValue("");
-          cookie.setPath("/");
-          cookie.setMaxAge(0);
-          response.addCookie(cookie);
-          return null;
-        }
-      }
-    }
-    return null;
-  }
-
   @Override
   public void destroy() {
-    log.info("Init");
+    log.info("Destroy");
+    gitFilter.destroy();
+    webFilter.destroy();
   }
 }
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthGitFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthGitFilter.java
new file mode 100644
index 0000000..d14068d
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthGitFilter.java
@@ -0,0 +1,322 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.httpd.XGerritAuth;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.account.PutHttpPassword;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
+
+@Singleton
+public class OAuthGitFilter implements Filter {
+  private static final String GITHUB_X_OAUTH_BASIC = "x-oauth-basic";
+  private static final org.slf4j.Logger log = LoggerFactory
+      .getLogger(OAuthGitFilter.class);
+  public static final String GIT_REALM_NAME =
+      "GitHub authentication for Gerrit Code Review";
+  private static final String GIT_AUTHORIZATION_HEADER = "Authorization";
+  private static final String GIT_AUTHENTICATION_BASIC = "Basic ";
+
+  private final OAuthCache oauthCache;
+  private final AccountCache accountCache;
+  private final GitHubHttpProvider httpClientProvider;
+  private final GitHubOAuthConfig config;
+  private final OAuthCookieProvider cookieProvider;
+  private final XGerritAuth xGerritAuth;
+
+  public static class BasicAuthHttpRequest extends HttpServletRequestWrapper {
+    private HashMap<String, String> headers = new HashMap<String, String>();
+
+    public BasicAuthHttpRequest(HttpServletRequest request, String username,
+        String password) {
+      super(request);
+
+      try {
+        headers.put(
+            GIT_AUTHORIZATION_HEADER,
+            GIT_AUTHENTICATION_BASIC
+                + Base64.encodeBase64String((username + ":" + password)
+                    .getBytes(OAuthGitFilter.encoding(request))));
+      } catch (UnsupportedEncodingException e) {
+        // This cannot really happen as we have already used the encoding for
+        // decoding the request
+      }
+    }
+
+    @Override
+    public Enumeration<String> getHeaderNames() {
+      final Enumeration<String> wrappedHeaderNames = super.getHeaderNames();
+      HashSet<String> headerNames = new HashSet<String>(headers.keySet());
+      while (wrappedHeaderNames.hasMoreElements()) {
+        headerNames.add(wrappedHeaderNames.nextElement());
+      }
+      return Iterators.asEnumeration(headerNames.iterator());
+    }
+
+    @Override
+    public String getHeader(String name) {
+      String headerValue = headers.get(name);
+      if (headerValue != null) {
+        return headerValue;
+      } else {
+        return super.getHeader(name);
+      }
+    }
+  }
+
+  @Inject
+  public OAuthGitFilter(OAuthCache oauthCache, AccountCache accountCache,
+      GitHubHttpProvider httpClientProvider, GitHubOAuthConfig config,
+      XGerritAuth xGerritAuth) {
+    this.oauthCache = oauthCache;
+    this.accountCache = accountCache;
+    this.httpClientProvider = httpClientProvider;
+    this.config = config;
+    this.cookieProvider = new OAuthCookieProvider(TokenCipher.get());
+    this.xGerritAuth = xGerritAuth;
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+    HttpServletResponse httpResponse =
+        new OAuthGitWrappedResponse((HttpServletResponse) response);
+    log.debug("OAuthGitFilter(" + httpRequest.getRequestURL() + ") code="
+        + request.getParameter("code"));
+
+    OAuthCookie oAuthCookie =
+        getAuthenticationCookieFromGitRequestUsingOAuthToken(httpRequest,
+            httpResponse);
+    if (oAuthCookie == null) {
+      return;
+    }
+    String gerritPassword =
+        oAuthCookie == OAuthCookie.ANONYMOUS ? null : accountCache
+            .getByUsername(oAuthCookie.user).getPassword(oAuthCookie.user);
+
+    if (gerritPassword == null && oAuthCookie != OAuthCookie.ANONYMOUS) {
+      gerritPassword =
+          generateRandomGerritPassword(oAuthCookie, httpRequest, httpResponse,
+              chain);
+      httpResponse.sendRedirect(getRequestPathWithQueryString(httpRequest));
+      return;
+    }
+
+    if (oAuthCookie != OAuthCookie.ANONYMOUS) {
+      httpRequest =
+          new BasicAuthHttpRequest(httpRequest, oAuthCookie.user,
+              gerritPassword);
+    }
+
+    chain.doFilter(httpRequest, httpResponse);
+  }
+
+  private String getRequestPathWithQueryString(HttpServletRequest httpRequest) {
+    String requestPathWithQueryString =
+        httpRequest.getContextPath() + httpRequest.getServletPath()
+            + Strings.nullToEmpty(httpRequest.getPathInfo()) + "?"
+            + httpRequest.getQueryString();
+    return requestPathWithQueryString;
+  }
+
+  private String generateRandomGerritPassword(OAuthCookie oAuthCookie,
+      HttpServletRequest httpRequest, HttpServletResponse httpResponse,
+      FilterChain chain) throws IOException, ServletException {
+    log.warn("User " + oAuthCookie.user + " has not a Gerrit HTTP password: "
+        + "generating a random one in order to be able to use Git over HTTP");
+    Cookie gerritCookie =
+        getGerritLoginCookie(oAuthCookie.user, httpRequest, httpResponse, chain);
+    String xGerritAuthValue = xGerritAuth.getAuthValue(gerritCookie);
+
+    HttpPut putRequest =
+        new HttpPut(getRequestUrlWithAlternatePath(httpRequest,
+            "/accounts/self/password.http"));
+    putRequest.setHeader("Cookie",
+        gerritCookie.getName() + "=" + gerritCookie.getValue() + "; "
+            + oAuthCookie.getName() + "=" + oAuthCookie.getValue());
+    putRequest.setHeader(XGerritAuth.X_GERRIT_AUTH, xGerritAuthValue);
+
+    putRequest.setEntity(new StringEntity("{\"generate\":true}",
+        ContentType.APPLICATION_JSON));
+    HttpResponse putResponse = httpClientProvider.get().execute(putRequest);
+    if (putResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+      throw new ServletException(
+          "Cannot generate HTTP password for authenticating user "
+              + oAuthCookie.user);
+    }
+
+    return accountCache.getByUsername(oAuthCookie.user).getPassword(
+        oAuthCookie.user);
+  }
+
+  private URI getRequestUrlWithAlternatePath(HttpServletRequest httpRequest,
+      String alternatePath) throws MalformedURLException {
+    URL originalUrl = new URL(httpRequest.getRequestURL().toString());
+    String contextPath = httpRequest.getContextPath();
+    return URI.create(originalUrl.getProtocol() + "://" + originalUrl.getHost()
+        + ":" + getPort(originalUrl) + contextPath + alternatePath);
+  }
+
+  private int getPort(URL originalUrl) {
+    String protocol = originalUrl.getProtocol().toLowerCase();
+    int port = originalUrl.getPort();
+    if (port == -1) {
+      return protocol.equals("https") ? 443 : 80;
+    } else {
+      return port;
+    }
+  }
+
+  private Cookie getGerritLoginCookie(String username,
+      HttpServletRequest httpRequest, HttpServletResponse httpResponse,
+      FilterChain chain) throws IOException, ServletException {
+    AuthenticatedPathHttpRequest loginRequest =
+        new AuthenticatedLoginHttpRequest(httpRequest, config.httpHeader,
+            username);
+    AuthenticatedLoginHttpResponse loginResponse =
+        new AuthenticatedLoginHttpResponse(httpResponse);
+    chain.doFilter(loginRequest, loginResponse);
+    return loginResponse.getGerritCookie();
+  }
+
+  private OAuthCookie getAuthenticationCookieFromGitRequestUsingOAuthToken(
+      HttpServletRequest req, HttpServletResponse rsp) throws IOException {
+    final String httpBasicAuth = getHttpBasicAuthenticationHeader(req);
+    if (httpBasicAuth == null) {
+      return OAuthCookie.ANONYMOUS;
+    }
+
+    if (isInvalidHttpAuthenticationHeader(httpBasicAuth)) {
+      rsp.sendError(SC_UNAUTHORIZED);
+      return null;
+    }
+
+    String oauthToken = StringUtils.substringBefore(httpBasicAuth, ":");
+    String oauthKeyword = StringUtils.substringAfter(httpBasicAuth, ":");
+    if (Strings.isNullOrEmpty(oauthToken)
+        || Strings.isNullOrEmpty(oauthKeyword)) {
+      rsp.sendError(SC_UNAUTHORIZED);
+      return null;
+    }
+
+    if (!oauthKeyword.equalsIgnoreCase(GITHUB_X_OAUTH_BASIC)) {
+      return OAuthCookie.ANONYMOUS;
+    }
+
+    boolean loginSuccessful = false;
+    String oauthLogin = null;
+    try {
+      oauthLogin =
+          oauthCache.getLoginByAccessToken(new AccessToken(oauthToken));
+      loginSuccessful = !Strings.isNullOrEmpty(oauthLogin);
+    } catch (ExecutionException e) {
+      log.warn("Login failed for OAuth token " + oauthToken, e);
+      loginSuccessful = false;
+    }
+
+    if (!loginSuccessful) {
+      rsp.sendError(SC_FORBIDDEN);
+      return null;
+    }
+
+    return cookieProvider.getFromUser(oauthLogin, "", "", new TreeSet<Scope>());
+  }
+
+
+  private boolean isInvalidHttpAuthenticationHeader(String usernamePassword) {
+    return usernamePassword.indexOf(':') < 1;
+  }
+
+  static String encoding(HttpServletRequest req) {
+    return Objects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
+  }
+
+  private String getHttpBasicAuthenticationHeader(final HttpServletRequest req)
+      throws UnsupportedEncodingException {
+    String hdr = req.getHeader(GIT_AUTHORIZATION_HEADER);
+    if (hdr == null || !hdr.startsWith(GIT_AUTHENTICATION_BASIC)) {
+      return null;
+    } else {
+      return new String(Base64.decodeBase64(hdr
+          .substring(GIT_AUTHENTICATION_BASIC.length())), encoding(req));
+    }
+  }
+
+  @Override
+  public void destroy() {
+    log.info("Destroy");
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthGitWrappedResponse.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthGitWrappedResponse.java
new file mode 100644
index 0000000..9b046cf
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthGitWrappedResponse.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class OAuthGitWrappedResponse extends HttpServletResponseWrapper {
+  public static final String GIT_REALM_NAME =
+      "GitHub authentication for Gerrit Code Review";
+  private static final String GIT_AUTHENTICATION_BASIC = "Basic ";
+  private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
+
+  public OAuthGitWrappedResponse(HttpServletResponse response) {
+    super(response);
+  }
+
+  @Override
+  public void sendError(int sc) throws IOException {
+    if (sc == SC_UNAUTHORIZED) {
+      requestBasicAuthenticationHandshake();
+    }
+    super.sendError(sc);
+  }
+
+  @Override
+  public void sendError(int sc, String msg) throws IOException {
+    if (sc == SC_UNAUTHORIZED) {
+      requestBasicAuthenticationHandshake();
+    }
+    super.sendError(sc, msg);
+  }
+
+  private void requestBasicAuthenticationHandshake() throws IOException {
+    setStatus(SC_UNAUTHORIZED);
+    StringBuilder v = new StringBuilder();
+    v.append(GIT_AUTHENTICATION_BASIC);
+    v.append("realm=\"").append(GIT_REALM_NAME).append("\"");
+    setHeader(WWW_AUTHENTICATE, v.toString());
+  }
+}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
index d0d705f..222288b 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthProtocol.java
@@ -71,7 +71,12 @@
     public AccessToken() {
     }
 
+    public AccessToken(String token) {
+      this(token, "");
+    }
+
     public AccessToken(String token, String type, Scope... scopes) {
+      this();
       this.access_token = token;
       this.token_type = type;
     }
@@ -81,6 +86,33 @@
       return "AccessToken [access_token=" + access_token + ", token_type="
           + token_type + "]";
     }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result =
+          prime * result
+              + ((access_token == null) ? 0 : access_token.hashCode());
+      result =
+          prime * result + ((token_type == null) ? 0 : token_type.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      AccessToken other = (AccessToken) obj;
+      if (access_token == null) {
+        if (other.access_token != null) return false;
+      } else if (!access_token.equals(other.access_token)) return false;
+      if (token_type == null) {
+        if (other.token_type != null) return false;
+      } else if (!token_type.equals(other.token_type)) return false;
+      return true;
+    }
   }
 
   @Inject
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java
new file mode 100644
index 0000000..10985fd
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthWebFilter.java
@@ -0,0 +1,275 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.github.oauth;
+
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.kohsuke.github.GHMyself;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class OAuthWebFilter implements Filter {
+  private static final org.slf4j.Logger log = LoggerFactory
+      .getLogger(OAuthWebFilter.class);
+  public static final String GERRIT_COOKIE_NAME = "GerritAccount";
+
+  private final GitHubOAuthConfig config;
+  private final OAuthCookieProvider cookieProvider;
+  private final Random retryRandom = new Random(System.currentTimeMillis());
+  private SitePaths sites;
+  private ScopedProvider<GitHubLogin> loginProvider;
+
+  @Inject
+  public OAuthWebFilter(GitHubOAuthConfig config, SitePaths sites,
+  // We need to explicitly tell Guice the correct implementation
+  // as this filter is instantiated with a standard Gerrit WebModule
+      GitHubLogin.Provider loginProvider) {
+    this.config = config;
+    this.sites = sites;
+    this.loginProvider = loginProvider;
+    this.cookieProvider = new OAuthCookieProvider(TokenCipher.get());
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+    HttpServletResponse httpResponse = (HttpServletResponse) response;
+    log.debug("OAuthWebFilter(" + httpRequest.getRequestURL() + ") code="
+        + request.getParameter("code"));
+
+    Cookie gerritCookie = getGerritCookie(httpRequest);
+    try {
+      GitHubLogin ghLogin = loginProvider.get(httpRequest);
+
+      OAuthCookie authCookie =
+          getOAuthCookie(httpRequest, (HttpServletResponse) response);
+
+      if (OAuthProtocol.isOAuthLogout(httpRequest)) {
+        logout(request, response, chain, httpRequest);
+      } else if (OAuthProtocol.isOAuthRequest(httpRequest)) {
+        login(request, httpRequest, httpResponse, ghLogin);
+      } else {
+        httpRequest = enrichAuthenticatedRequest(httpRequest, authCookie);
+
+        if (OAuthProtocol.isOAuthFinalForOthers(httpRequest)) {
+          httpResponse.sendRedirect(OAuthProtocol
+              .getTargetOAuthFinal(httpRequest));
+        } else {
+          chain.doFilter(httpRequest, response);
+        }
+      }
+    } finally {
+      HttpSession httpSession = httpRequest.getSession();
+      if (gerritCookie != null && httpSession != null) {
+        String gerritCookieValue = gerritCookie.getValue();
+        String gerritSessionValue =
+            (String) httpSession.getAttribute("GerritAccount");
+
+        if (gerritSessionValue == null) {
+          httpSession.setAttribute("GerritAccount", gerritCookieValue);
+        } else if (!gerritSessionValue.equals(gerritCookieValue)) {
+          httpSession.invalidate();
+        }
+      }
+    }
+  }
+
+  private HttpServletRequest enrichAuthenticatedRequest(
+      HttpServletRequest httpRequest, OAuthCookie authCookie) {
+    httpRequest =
+        authCookie == null ? httpRequest : new AuthenticatedHttpRequest(
+            httpRequest, config.httpHeader, authCookie.user,
+            config.httpDisplaynameHeader, authCookie.fullName,
+            config.httpEmailHeader, authCookie.email);
+    return httpRequest;
+  }
+
+  private void login(ServletRequest request, HttpServletRequest httpRequest,
+      HttpServletResponse httpResponse, GitHubLogin ghLogin) throws IOException {
+    if (ghLogin.login(httpRequest, httpResponse)) {
+      GHMyself myself = ghLogin.getMyself();
+      String user = myself.getLogin();
+
+      updateSecureConfigWithRetry(ghLogin.hub.getMyOrganizations().keySet(),
+          user, ghLogin.token.access_token);
+    }
+  }
+
+  private void logout(ServletRequest request, ServletResponse response,
+      FilterChain chain, HttpServletRequest httpRequest) throws IOException,
+      ServletException {
+    getGitHubLogin(request).logout();
+    GitHubLogoutServletResponse bufferedResponse =
+        new GitHubLogoutServletResponse((HttpServletResponse) response,
+            config.logoutRedirectUrl);
+    chain.doFilter(httpRequest, bufferedResponse);
+  }
+
+  private GitHubLogin getGitHubLogin(ServletRequest request) {
+    return loginProvider.get((HttpServletRequest) request);
+  }
+
+  private void updateSecureConfigWithRetry(Set<String> organisations,
+      String user, String access_token) {
+    int retryCount = 0;
+
+    while (retryCount < config.fileUpdateMaxRetryCount) {
+      try {
+        updateSecureConfig(organisations, user, access_token);
+        return;
+      } catch (IOException e) {
+        retryCount++;
+        int retryInterval =
+            retryRandom.nextInt(config.fileUpdateMaxRetryIntervalMsec);
+        log.warn("Error whilst trying to update "
+            + sites.secure_config
+            + (retryCount < config.fileUpdateMaxRetryCount ? ": attempt #"
+                + retryCount + " will be retried after " + retryInterval
+                + " msecs" : ""), e);
+        try {
+          Thread.sleep(retryInterval);
+        } catch (InterruptedException e1) {
+          log.error("Thread has been cancelled before retrying to save "
+              + sites.secure_config);
+          return;
+        }
+      } catch (ConfigInvalidException e) {
+        log.error("Cannot update " + sites.secure_config
+            + " as the file is corrupted", e);
+        return;
+      }
+    }
+  }
+
+  private synchronized void updateSecureConfig(Set<String> organisations,
+      String user, String access_token) throws IOException,
+      ConfigInvalidException {
+    FileBasedConfig currentSecureConfig =
+        new FileBasedConfig(sites.secure_config, FS.DETECTED);
+    long currentSecureConfigUpdateTs = sites.secure_config.lastModified();
+    currentSecureConfig.load();
+
+    boolean configUpdate =
+        updateConfigSection(currentSecureConfig, user, user, access_token);
+    for (String organisation : organisations) {
+      configUpdate |=
+          updateConfigSection(currentSecureConfig, organisation, user,
+              access_token);
+    }
+
+    if (!configUpdate) {
+      return;
+    }
+
+    log.info("Updating " + sites.secure_config + " credentials for user "
+        + user);
+
+    if (sites.secure_config.lastModified() != currentSecureConfigUpdateTs) {
+      throw new ConcurrentFileBasedConfigWriteException("File "
+          + sites.secure_config + " was written at "
+          + formatTS(sites.secure_config.lastModified())
+          + " while was trying to update security for user " + user);
+    }
+    currentSecureConfig.save();
+  }
+
+  private String formatTS(long ts) {
+    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ts));
+  }
+
+  private boolean updateConfigSection(FileBasedConfig config, String section,
+      String user, String password) {
+    String configUser = config.getString("remote", section, "username");
+    String configPassword = config.getString("remote", section, "password");
+    if (configUser == null || !configUser.equals(user)
+        || configPassword.equals(password)) {
+      return false;
+    }
+
+    config.setString("remote", section, "username", user);
+    config.setString("remote", section, "password", password);
+    return true;
+  }
+
+  private Cookie getGerritCookie(HttpServletRequest httpRequest) {
+    for (Cookie cookie : getCookies(httpRequest)) {
+      if (cookie.getName().equalsIgnoreCase(GERRIT_COOKIE_NAME)) {
+        return cookie;
+      }
+    }
+    return null;
+  }
+
+  private Cookie[] getCookies(HttpServletRequest httpRequest) {
+    Cookie[] cookies = httpRequest.getCookies();
+    return cookies == null ? new Cookie[0] : cookies;
+  }
+
+  private OAuthCookie getOAuthCookie(HttpServletRequest request,
+      HttpServletResponse response) {
+    for (Cookie cookie : getCookies(request)) {
+      if (cookie.getName().equalsIgnoreCase(OAuthCookie.OAUTH_COOKIE_NAME)
+          && !Strings.isNullOrEmpty(cookie.getValue())) {
+        try {
+          return cookieProvider.getFromCookie(cookie);
+        } catch (OAuthTokenException e) {
+          log.warn(
+              "Invalid cookie detected: cleaning up and sending a reset back to the browser",
+              e);
+          cookie.setValue("");
+          cookie.setPath("/");
+          cookie.setMaxAge(0);
+          response.addCookie(cookie);
+          return null;
+        }
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void destroy() {
+    log.info("Init");
+  }
+}