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");
+ }
+}