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