Improvements of feedback from review I86fb8fab3
GitHub authentication has been submitted 9 months ago to Gerrit as
extra authentication backend: a lot of suggestions and after 57 (!)
patch-sets many improvements have been added to the code and are now
incorporated into GitHub master:
- Introduction of random state to prevent OAuth code injection and
replay attacks
- Upgrade to HTTPClient 4.4 and connection pooling
- Introduction of HTTP proxy for outbound connections when installed
within company firewalls
- Removal of heavy non serialisable objects from the HTTP Session
- Early release of HTTP connections to the pool to improve scalability
- Simplified set-up during init
- Improved documentation
Many thanks to all the people that participated in the Code-Review and
contributions: as Git doesn’t allow to have more than one author,
I will mention them below.
Thanks to: Edwin Kempin, David Pursehouse, Thomas Broyer,
David Ostrovsky, Sasa Zivkov, Andre Saddler, Dariusz Luksza,
Derek Hunter, Will DeBerry, Tom Hughes, Shawn Pearce
Change-Id: I19e761485692d9b4bc0ade8d0f3ad761b0fa5369
diff --git a/github-oauth/pom.xml b/github-oauth/pom.xml
index c0ab78f..e6d70e7 100644
--- a/github-oauth/pom.xml
+++ b/github-oauth/pom.xml
@@ -112,7 +112,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
- <version>4.2.5</version>
+ <version>4.4</version>
</dependency>
</dependencies>
</project>
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubHttpProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubHttpProvider.java
deleted file mode 100644
index 367a390..0000000
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubHttpProvider.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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 com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-import org.apache.http.HttpHost;
-import org.apache.http.client.HttpClient;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.PoolingClientConnectionManager;
-
-@Singleton
-public class GitHubHttpProvider implements Provider<HttpClient> {
- private static final int MAX_TOTAL_CONN = 1024;
- private static final int MAX_CONN_PER_ROUTE = 10;
- private static final int MAX_LOCALHOST_CONN = 512;
- private final PoolingClientConnectionManager connectionManager;
-
- public GitHubHttpProvider() {
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory
- .getSocketFactory()));
- schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory
- .getSocketFactory()));
-
- connectionManager = new PoolingClientConnectionManager(schemeRegistry);
- connectionManager.setMaxTotal(MAX_TOTAL_CONN);
- connectionManager.setDefaultMaxPerRoute(MAX_CONN_PER_ROUTE);
- HttpHost localhost = new HttpHost("locahost", 80);
- connectionManager.setMaxPerRoute(new HttpRoute(localhost),
- MAX_LOCALHOST_CONN);
- }
-
- @Override
- public HttpClient get() {
- return new DefaultHttpClient(connectionManager);
- }
-
-}
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 2950996..32f751a 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
@@ -16,20 +16,8 @@
import static java.util.concurrent.TimeUnit.DAYS;
-import com.google.common.base.MoreObjects;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken;
-import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
-
-import org.apache.http.HttpStatus;
-import org.kohsuke.github.GHMyself;
-import org.kohsuke.github.GitHub;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.io.IOException;
+import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -38,21 +26,30 @@
import java.util.SortedSet;
import java.util.TreeSet;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.Getter;
-public class GitHubLogin {
+import org.kohsuke.github.GHMyself;
+import org.kohsuke.github.GitHub;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.MoreObjects;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
+
+public class GitHubLogin implements Serializable {
+ private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(GitHubLogin.class);
private static final List<Scope> DEFAULT_SCOPES = Arrays.asList(
Scope.PUBLIC_REPO, Scope.USER_EMAIL);
- private static final int YEARS = 365;
private static final long SCOPE_COOKIE_NEVER_EXPIRES = DAYS
- .toSeconds(50 * YEARS);
+ .toSeconds(50 * 365);
@Singleton
public static class Provider extends HttpSessionProvider<GitHubLogin> {
@@ -63,21 +60,16 @@
}
@Getter
- protected AccessToken token;
+ private AccessToken token;
- @Getter
- protected GitHub hub;
-
- protected GHMyself myself;
-
- private transient OAuthProtocol oauth;
+ private String state;
private SortedSet<Scope> loginScopes;
private final GitHubOAuthConfig config;
- public GHMyself getMyself() {
+ public GHMyself getMyself() throws IOException {
if (isLoggedIn()) {
- return myself;
+ return getHub().getMyself();
} else {
return null;
}
@@ -85,92 +77,68 @@
public Set<String> getMyOrganisationsLogins() throws IOException {
if (isLoggedIn()) {
- return hub.getMyOrganizations().keySet();
+ return getHub().getMyOrganizations().keySet();
} else {
return Collections.emptySet();
}
}
@Inject
- public GitHubLogin(final OAuthProtocol oauth, final GitHubOAuthConfig config) {
- this.oauth = oauth;
+ public GitHubLogin(final GitHubOAuthConfig config) {
this.config = config;
}
public boolean isLoggedIn() {
- boolean loggedIn = token != null && hub != null;
- if (loggedIn && myself == null) {
- try {
- myself = hub.getMyself();
- } catch (Throwable e) {
- log.error("Connection to GitHub broken: logging out", e);
- logout();
- loggedIn = false;
- }
- }
- return loggedIn;
- }
-
- public boolean login(ServletRequest request, ServletResponse response,
- Scope... scopes) throws IOException {
- return login((HttpServletRequest) request, (HttpServletResponse) response,
- scopes);
+ return token != null;
}
public boolean login(HttpServletRequest request,
- HttpServletResponse response, Scope... scopes) throws IOException {
+ HttpServletResponse response, OAuthProtocol oauth, Scope... scopes) throws IOException {
if (isLoggedIn()) {
return true;
}
log.debug("Login " + this);
-
if (OAuthProtocol.isOAuthFinal(request)) {
log.debug("Login-FINAL " + this);
- AccessToken loginAccessToken = oauth.loginPhase2(request, response);
- if(loginAccessToken != null && !loginAccessToken.isError()) {
- login(loginAccessToken);
- }
+ login(oauth.loginPhase2(request, state));
+ this.state = ""; // Make sure state is used only once
if (isLoggedIn()) {
log.debug("Login-SUCCESS " + this);
response.sendRedirect(OAuthProtocol.getTargetUrl(request));
return true;
} else {
- response.sendError(HttpStatus.SC_UNAUTHORIZED);
return false;
}
} else {
this.loginScopes = getScopes(getScopesKey(request, response), scopes);
log.debug("Login-PHASE1 " + this);
- oauth.loginPhase1(request, response, loginScopes);
+ state = oauth.loginPhase1(request, response, loginScopes);
return false;
}
}
public void logout() {
- hub = null;
token = null;
}
- public OAuthProtocol getOAuthProtocol() {
- return oauth;
- }
-
public GitHub login(AccessToken authToken) throws IOException {
log.debug("Logging in using access token {}", authToken.accessToken);
this.token = authToken;
- this.hub = GitHub.connectUsingOAuth(authToken.accessToken);
- this.myself = hub.getMyself();
- return this.hub;
+ return getHub();
}
@Override
public String toString() {
- return "GitHubLogin [token=" + token + ", myself=" + myself + ", scopes="
+ return "GitHubLogin [token=" + token + ", scopes="
+ loginScopes + "]";
}
+ public GitHub getHub() throws IOException {
+ return GitHub.connectUsingOAuth(this.token.accessToken);
+ }
+
private String getScopesKey(HttpServletRequest request,
HttpServletResponse response) {
String scopeRequested = request.getParameter("scope");
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
index c41be26..7d30d19 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/GitHubOAuthConfig.java
@@ -14,36 +14,41 @@
package com.googlesource.gerrit.plugins.github.oauth;
import com.google.common.base.MoreObjects;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-
import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
import org.eclipse.jgit.lib.Config;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Singleton
-public class GitHubOAuthConfig {
- protected static final String CONF_SECTION = "github";
- private static final String LOGIN_OAUTH_AUTHORIZE = "/login/oauth/authorize";
- private static final String GITHUB_URL = "https://github.com";
- public static final String OAUTH_FINAL = "oauth";
- public static final String LOGIN_OAUTH_ACCESS_TOKEN =
+public
+class GitHubOAuthConfig {
+ public static final String CONF_SECTION = "github";
+ public static final String GITHUB_OAUTH_AUTHORIZE = "/login/oauth/authorize";
+ public static final String GITHUB_OAUTH_ACCESS_TOKEN =
"/login/oauth/access_token";
- public static final String OAUTH_LOGIN = "/login";
- public static final String OAUTH_LOGOUT = "/logout";
+ public static final String GITHUB_GET_USER = "/user";
+ public static final String GERRIT_OAUTH_FINAL = "/oauth";
+ public static final String GITHUB_URL_DEFAULT = "https://github.com";
+ public static final String GITHUB_API_URL_DEFAULT = "https://api.github.com";
+ public static final String GERRIT_LOGIN = "/login";
+ public static final String GERRIT_LOGOUT = "/logout";
public final String gitHubUrl;
+ public final String gitHubApiUrl;
public final String gitHubClientId;
public final String gitHubClientSecret;
public final String logoutRedirectUrl;
@@ -55,34 +60,49 @@
public final Map<String, List<OAuthProtocol.Scope>> scopes;
public final int fileUpdateMaxRetryCount;
public final int fileUpdateMaxRetryIntervalMsec;
- public final Config gerritConfig;
public final String oauthHttpHeader;
@Inject
- public GitHubOAuthConfig(@GerritServerConfig Config config)
- throws MalformedURLException {
- this.gerritConfig = config;
+ protected
+ GitHubOAuthConfig(@GerritServerConfig Config config,
+ @CanonicalWebUrl String canonicalWebUrl, AuthConfig authConfig) {
+ httpHeader =
+ Preconditions.checkNotNull(
+ config.getString("auth", null, "httpHeader"),
+ "HTTP Header for GitHub user must be provided");
+ gitHubUrl =
+ trimTrailingSlash(MoreObjects.firstNonNull(
+ config.getString(CONF_SECTION, null, "url"), GITHUB_URL_DEFAULT));
+ gitHubApiUrl =
+ trimTrailingSlash(MoreObjects.firstNonNull(
+ config.getString(CONF_SECTION, null, "apiUrl"),
+ GITHUB_API_URL_DEFAULT));
+ gitHubClientId =
+ Preconditions.checkNotNull(
+ config.getString(CONF_SECTION, null, "clientId"),
+ "GitHub `clientId` must be provided");
+ gitHubClientSecret =
+ Preconditions.checkNotNull(
+ config.getString(CONF_SECTION, null, "clientSecret"),
+ "GitHub `clientSecret` must be provided");
- httpHeader = config.getString("auth", null, "httpHeader");
oauthHttpHeader = config.getString("auth", null, "httpExternalIdHeader");
- gitHubUrl = dropTrailingSlash(
- MoreObjects.firstNonNull(config.getString(CONF_SECTION, null, "url"),
- GITHUB_URL));
- gitHubClientId = config.getString(CONF_SECTION, null, "clientId");
- gitHubClientSecret = config.getString(CONF_SECTION, null, "clientSecret");
- gitHubOAuthUrl = getUrl(gitHubUrl, LOGIN_OAUTH_AUTHORIZE);
- gitHubOAuthAccessTokenUrl = getUrl(gitHubUrl, LOGIN_OAUTH_ACCESS_TOKEN);
- logoutRedirectUrl = config.getString(CONF_SECTION, null, "logoutRedirectUrl");
+ gitHubOAuthUrl = gitHubUrl + GITHUB_OAUTH_AUTHORIZE;
+ gitHubOAuthAccessTokenUrl = gitHubUrl + GITHUB_OAUTH_ACCESS_TOKEN;
+ logoutRedirectUrl =
+ config.getString(CONF_SECTION, null, "logoutRedirectUrl");
oAuthFinalRedirectUrl =
- getUrl(config.getString("gerrit", null, "canonicalWebUrl"), OAUTH_FINAL);
+ trimTrailingSlash(canonicalWebUrl) + GERRIT_OAUTH_FINAL;
enabled =
config.getString("auth", null, "type").equalsIgnoreCase(
AuthType.HTTP.toString());
scopes = getScopes(config);
- fileUpdateMaxRetryCount = config.getInt(CONF_SECTION, "fileUpdateMaxRetryCount", 3);
- fileUpdateMaxRetryIntervalMsec = config.getInt(CONF_SECTION, "fileUpdateMaxRetryIntervalMsec", 3000);
+ fileUpdateMaxRetryCount =
+ config.getInt(CONF_SECTION, "fileUpdateMaxRetryCount", 3);
+ fileUpdateMaxRetryIntervalMsec =
+ config.getInt(CONF_SECTION, "fileUpdateMaxRetryIntervalMsec", 3000);
}
private Map<String, List<Scope>> getScopes(Config config) {
@@ -97,13 +117,13 @@
return scopes;
}
- private String dropTrailingSlash(String url) {
- return (url.endsWith("/") ? url.substring(0, url.length()-1):url);
+ private String trimTrailingSlash(String url) {
+ return CharMatcher.is('/').trimTrailingFrom(url);
}
private List<Scope> parseScopesString(String scopesString) {
ArrayList<Scope> scopes = new ArrayList<OAuthProtocol.Scope>();
- if(Strings.emptyToNull(scopesString) != null) {
+ if (Strings.emptyToNull(scopesString) != null) {
String[] scopesStrings = scopesString.split(",");
for (String scope : scopesStrings) {
scopes.add(Enum.valueOf(Scope.class, scope));
@@ -113,11 +133,6 @@
return scopes;
}
- private static String getUrl(String baseUrl, String path)
- throws MalformedURLException {
- return new URL(new URL(baseUrl), path).toExternalForm();
- }
-
public Scope[] getDefaultScopes() {
if (scopes == null || scopes.get("scopes") == null) {
return new Scope[0];
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
index 29d8840..646b602 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/IdentifiedUserGitHubLoginProvider.java
@@ -14,6 +14,12 @@
package com.googlesource.gerrit.plugins.github.oauth;
+import java.io.IOException;
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.server.IdentifiedUser;
@@ -22,34 +28,25 @@
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 org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Collection;
-
@Singleton
public class IdentifiedUserGitHubLoginProvider implements
UserScopedProvider<GitHubLogin> {
- private static final Logger log = LoggerFactory.getLogger(IdentifiedUserGitHubLoginProvider.class);
+ private static final Logger log = LoggerFactory
+ .getLogger(IdentifiedUserGitHubLoginProvider.class);
private static final String EXTERNAL_ID_PREFIX = "external:"
+ OAuthWebFilter.GITHUB_EXT_ID;
private final Provider<IdentifiedUser> userProvider;
- private OAuthProtocol oauth;
private GitHubOAuthConfig config;
private AccountCache accountCache;
@Inject
public IdentifiedUserGitHubLoginProvider(
final Provider<IdentifiedUser> identifiedUserProvider,
- final OAuthProtocol oauth, final GitHubOAuthConfig config,
- final AccountCache accountCache) {
+ final GitHubOAuthConfig config, final AccountCache accountCache) {
this.userProvider = identifiedUserProvider;
- this.oauth = oauth;
this.config = config;
this.accountCache = accountCache;
}
@@ -66,15 +63,14 @@
try {
AccessToken accessToken = newAccessTokenFromUser(username);
if (accessToken != null) {
- GitHubLogin login = new GitHubLogin(oauth, config);
+ GitHubLogin login = new GitHubLogin(config);
login.login(accessToken);
return login;
} else {
return null;
}
} catch (IOException e) {
- log.error("Cannot login to GitHub as '" + username
- + "'", e);
+ log.error("Cannot login to GitHub as '" + username + "'", e);
return null;
}
}
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
index e2a8f26..c0f3897 100644
--- 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
@@ -22,14 +22,15 @@
import com.google.gerrit.httpd.XGerritAuth;
import com.google.gerrit.server.account.AccountCache;
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 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.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@@ -68,7 +69,7 @@
private final OAuthCache oauthCache;
private final AccountCache accountCache;
- private final GitHubHttpProvider httpClientProvider;
+ private final Provider<HttpClient> httpClientProvider;
private final GitHubOAuthConfig config;
private final XGerritAuth xGerritAuth;
private ScopedProvider<GitHubLogin> ghLoginProvider;
@@ -115,7 +116,7 @@
@Inject
public OAuthGitFilter(OAuthCache oauthCache, AccountCache accountCache,
- GitHubHttpProvider httpClientProvider, GitHubOAuthConfig config,
+ PooledHttpClientProvider httpClientProvider, GitHubOAuthConfig config,
XGerritAuth xGerritAuth, GitHubLogin.Provider ghLoginProvider) {
this.oauthCache = oauthCache;
this.accountCache = accountCache;
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 563a71e..4358c0d 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
@@ -2,9 +2,11 @@
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
import com.google.common.io.CharStreams;
import com.google.gson.Gson;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.http.HttpResponse;
@@ -24,8 +26,11 @@
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import javax.servlet.ServletRequest;
@@ -58,12 +63,13 @@
}
}
private static final String ME_SEPARATOR = ",";
- private static final Logger LOG = LoggerFactory
+ private static final Logger log = LoggerFactory
.getLogger(OAuthProtocol.class);
+ private static SecureRandom randomState = newRandomGenerator();
private final GitHubOAuthConfig config;
- private final HttpClient http;
private final Gson gson;
+ private final Provider<HttpClient> httpProvider;
public static class AccessToken {
public String accessToken;
@@ -98,29 +104,13 @@
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result =
- prime * result
- + ((accessToken == null) ? 0 : accessToken.hashCode());
- result =
- prime * result + ((tokenType == null) ? 0 : tokenType.hashCode());
- return result;
+ return Objects.hash(accessToken, tokenType, error, errorDescription,
+ errorUri);
}
@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 (accessToken == null) {
- if (other.accessToken != null) return false;
- } else if (!accessToken.equals(other.accessToken)) return false;
- if (tokenType == null) {
- if (other.tokenType != null) return false;
- } else if (!tokenType.equals(other.tokenType)) return false;
- return true;
+ return Objects.deepEquals(this, obj);
}
public boolean isError() {
@@ -129,36 +119,49 @@
}
@Inject
- public OAuthProtocol(GitHubOAuthConfig config, GitHubHttpProvider httpClientProvider,
- /* We need to explicitly tell Guice which Provider<> we need as this class may be
- instantiated outside the standard Guice Module set-up (e.g. initial Servlet login
- filter) */
+ public OAuthProtocol(GitHubOAuthConfig config,
+ PooledHttpClientProvider httpClientProvider,
+ /*
+ * We need to explicitly tell Guice which Provider<> we need as this class
+ * may be instantiated outside the standard Guice Module set-up (e.g.
+ * initial Servlet login filter)
+ */
GsonProvider gsonProvider) {
this.config = config;
- this.http = httpClientProvider.get();
+ this.httpProvider = httpClientProvider;
this.gson = gsonProvider.get();
}
- public void loginPhase1(HttpServletRequest request,
- HttpServletResponse response, Set<Scope> scopes) throws IOException {
+ private static SecureRandom newRandomGenerator() {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(
+ "No SecureRandom available for GitHub authentication", e);
+ }
+ }
+ public String loginPhase1(HttpServletRequest request,
+ HttpServletResponse response, Set<Scope> scopes) throws IOException {
String scopesString = getScope(scopes);
- LOG.debug("Initiating GitHub Login for ClientId=" + config.gitHubClientId + " Scopes=" + scopesString);
+ log.debug("Initiating GitHub Login for ClientId=" + config.gitHubClientId
+ + " Scopes=" + scopesString);
+ String state = newRandomState(request.getRequestURI().toString());
response.sendRedirect(String.format(
- "%s?client_id=%s%s&redirect_uri=%s&state=%s%s", config.gitHubOAuthUrl,
- config.gitHubClientId, scopesString,
- getURLEncoded(config.oAuthFinalRedirectUrl),
- me(), getURLEncoded(request.getRequestURI().toString())));
+ "%s?client_id=%s%s&redirect_uri=%s&state=%s",
+ config.gitHubOAuthUrl, config.gitHubClientId, scopesString,
+ getURLEncoded(config.oAuthFinalRedirectUrl), getURLEncoded(state)));
+ return state;
}
private String getScope(Set<Scope> scopes) {
- if(scopes.size() <= 0) {
+ if (scopes.size() <= 0) {
return "";
}
-
+
StringBuilder out = new StringBuilder();
for (Scope scope : scopes) {
- if(out.length() > 0) {
+ if (out.length() > 0) {
out.append(",");
}
out.append(scope.getValue());
@@ -179,28 +182,33 @@
return Strings.emptyToNull(request.getParameter("code")) != null;
}
- public String me() {
- return "" + hashCode() + ME_SEPARATOR;
+ public String newRandomState(String redirectUrl) {
+ byte[] stateBin = new byte[20]; // SHA-1 size
+ randomState.nextBytes(stateBin);
+ return BaseEncoding.base64Url().encode(stateBin) + ME_SEPARATOR + redirectUrl;
}
public static boolean isOAuthLogin(HttpServletRequest request) {
- return request.getRequestURI().indexOf(GitHubOAuthConfig.OAUTH_LOGIN) >= 0;
+ return request.getRequestURI().indexOf(GitHubOAuthConfig.GERRIT_LOGIN) >= 0;
}
public static boolean isOAuthLogout(HttpServletRequest request) {
- return request.getRequestURI().indexOf(GitHubOAuthConfig.OAUTH_LOGOUT) >= 0;
+ return request.getRequestURI().indexOf(GitHubOAuthConfig.GERRIT_LOGOUT) >= 0;
}
public static boolean isOAuthRequest(HttpServletRequest httpRequest) {
return OAuthProtocol.isOAuthLogin(httpRequest) || OAuthProtocol.isOAuthFinal(httpRequest);
}
- public AccessToken loginPhase2(HttpServletRequest request,
- HttpServletResponse response) throws IOException {
+ public AccessToken loginPhase2(HttpServletRequest request, String state)
+ throws IOException {
- HttpPost post = null;
+ String requestState = request.getParameter("state");
+ if (!Objects.equals(state, requestState)) {
+ throw new IOException("Invalid authentication state");
+ }
- post = new HttpPost(config.gitHubOAuthAccessTokenUrl);
+ HttpPost post = new HttpPost(config.gitHubOAuthAccessTokenUrl);
post.setHeader("Accept", "application/json");
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("client_id", config.gitHubClientId));
@@ -208,46 +216,40 @@
nvps.add(new BasicNameValuePair("code", request.getParameter("code")));
post.setEntity(new UrlEncodedFormEntity(nvps, Charsets.UTF_8));
- try {
- HttpResponse postResponse = http.execute(post);
- if (postResponse.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_OK) {
- LOG.error("POST " + config.gitHubOAuthAccessTokenUrl
- + " request for access token failed with status "
- + postResponse.getStatusLine());
- EntityUtils.consume(postResponse.getEntity());
- return null;
- }
-
- InputStream content = postResponse.getEntity().getContent();
- String tokenJsonString =
- CharStreams.toString(new InputStreamReader(content,
- StandardCharsets.UTF_8));
- AccessToken token = gson.fromJson(tokenJsonString, AccessToken.class);
- if (token.isError()) {
- LOG.error("POST " + config.gitHubOAuthAccessTokenUrl
- + " returned an error token: " + token);
- }
- return token;
- } catch (IOException e) {
- LOG.error("POST " + config.gitHubOAuthAccessTokenUrl
- + " request for access token failed", e);
- return null;
+ HttpResponse postResponse = httpProvider.get().execute(post);
+ if (postResponse.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_OK) {
+ log.error("POST " + config.gitHubOAuthAccessTokenUrl
+ + " request for access token failed with status "
+ + postResponse.getStatusLine());
+ EntityUtils.consume(postResponse.getEntity());
+ throw new IOException("GitHub OAuth request failed");
}
+
+ InputStream content = postResponse.getEntity().getContent();
+ String tokenJsonString =
+ CharStreams.toString(new InputStreamReader(content,
+ StandardCharsets.UTF_8));
+ AccessToken token = gson.fromJson(tokenJsonString, AccessToken.class);
+ if (token.isError()) {
+ log.error("POST " + config.gitHubOAuthAccessTokenUrl
+ + " returned an error token: " + token);
+ throw new IOException("Invalid GitHub OAuth token");
+ }
+ return token;
}
private static String getURLEncoded(String url) {
try {
return URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
- // UTF-8 is hardcoded, cannot fail
- return null;
+ throw new IllegalStateException("Cannot find UTF-8 encoding", e);
}
}
public static String getTargetUrl(ServletRequest request) {
int meEnd = state(request).indexOf(ME_SEPARATOR);
if (meEnd > 0) {
- return state(request).substring(meEnd+1);
+ return state(request).substring(meEnd + 1);
} else {
return "";
}
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
index bf0a4b1..6929d16 100644
--- 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
@@ -51,16 +51,20 @@
private final GitHubOAuthConfig config;
private final Random retryRandom = new Random(System.currentTimeMillis());
- private SitePaths sites;
- private ScopedProvider<GitHubLogin> loginProvider;
+ private final SitePaths sites;
+ private final ScopedProvider<GitHubLogin> loginProvider;
+ private final OAuthProtocol oauth;
@Inject
- public OAuthWebFilter(GitHubOAuthConfig config, SitePaths sites,
+ public OAuthWebFilter(GitHubOAuthConfig config,
+ SitePaths sites,
+ OAuthProtocol oauth,
// 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.oauth = oauth;
this.loginProvider = loginProvider;
}
@@ -120,12 +124,12 @@
private void login(ServletRequest request, HttpServletRequest httpRequest,
HttpServletResponse httpResponse, GitHubLogin ghLogin) throws IOException {
- if (ghLogin.login(httpRequest, httpResponse)) {
+ if (ghLogin.login(httpRequest, httpResponse, oauth)) {
GHMyself myself = ghLogin.getMyself();
String user = myself.getLogin();
- updateSecureConfigWithRetry(ghLogin.hub.getMyOrganizations().keySet(),
- user, ghLogin.token.accessToken);
+ updateSecureConfigWithRetry(ghLogin.getHub().getMyOrganizations().keySet(),
+ user, ghLogin.getToken().accessToken);
}
}
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PooledHttpClientProvider.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PooledHttpClientProvider.java
new file mode 100644
index 0000000..0263877
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/PooledHttpClientProvider.java
@@ -0,0 +1,75 @@
+// 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 com.google.common.base.Strings;
+import com.google.gerrit.httpd.ProxyProperties;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.ProxyAuthenticationStrategy;
+import org.eclipse.jgit.lib.Config;
+
+import java.net.URL;
+
+public class PooledHttpClientProvider implements Provider<HttpClient> {
+ private final int maxTotalConnection;
+ private final int maxConnectionPerRoute;
+ private final ProxyProperties proxy;
+
+ @Inject
+ PooledHttpClientProvider(@GerritServerConfig Config config,
+ ProxyProperties proxyProperties) {
+ this.proxy = proxyProperties;
+ maxConnectionPerRoute = config.getInt("http", null,
+ "pooledMaxConnectionsPerRoute", 16);
+ maxTotalConnection = config.getInt("http", null,
+ "pooledMaxTotalConnections", 32);
+ }
+
+ @Override
+ public HttpClient get() {
+ HttpClientBuilder builder = HttpClientBuilder
+ .create()
+ .setMaxConnPerRoute(maxConnectionPerRoute)
+ .setMaxConnTotal(maxTotalConnection);
+
+ if (proxy.getProxyUrl() != null) {
+ URL url = proxy.getProxyUrl();
+ builder.setProxy(new HttpHost(url.getHost(), url.getPort()));
+ if (!Strings.isNullOrEmpty(proxy.getUsername())
+ && !Strings.isNullOrEmpty(proxy.getPassword())) {
+ UsernamePasswordCredentials creds = new UsernamePasswordCredentials(
+ proxy.getUsername(), proxy.getPassword());
+ CredentialsProvider credsProvider = new BasicCredentialsProvider();
+ credsProvider.setCredentials(new AuthScope(url.getHost(),
+ url.getPort()), creds);
+ builder.setDefaultCredentialsProvider(credsProvider);
+ builder.setProxyAuthenticationStrategy(
+ new ProxyAuthenticationStrategy());
+ }
+ }
+
+ return builder.build();
+ }
+}
diff --git a/github-plugin/pom.xml b/github-plugin/pom.xml
index fcf52a5..259f559 100644
--- a/github-plugin/pom.xml
+++ b/github-plugin/pom.xml
@@ -136,7 +136,7 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
- <version>4.0</version>
+ <version>4.4</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
index f4d070d..17c3b9e 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubConfig.java
@@ -16,11 +16,12 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.Maps;
import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-
import com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig;
import org.eclipse.jgit.lib.Config;
@@ -69,9 +70,13 @@
@Inject
- public GitHubConfig(@GerritServerConfig Config config, final SitePaths site, AllProjectsNameProvider allProjectsNameProvider)
+ public GitHubConfig(@GerritServerConfig Config config,
+ final SitePaths site,
+ AllProjectsNameProvider allProjectsNameProvider,
+ @CanonicalWebUrl String canonicalWebUrl,
+ AuthConfig authConfig)
throws MalformedURLException {
- super(config);
+ super(config, canonicalWebUrl, authConfig);
String[] wizardFlows =
config.getStringList(CONF_SECTION, null, CONF_WIZARD_FLOW);
for (String fromTo : wizardFlows) {
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
index 29727a4..6bfbb16 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GuiceHttpModule.java
@@ -17,7 +17,6 @@
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Names;
import com.google.inject.servlet.ServletModule;
-
import com.googlesource.gerrit.plugins.github.filters.GitHubOAuthFilter;
import com.googlesource.gerrit.plugins.github.git.CreateProjectStep;
import com.googlesource.gerrit.plugins.github.git.GitCloneStep;
@@ -25,8 +24,8 @@
import com.googlesource.gerrit.plugins.github.git.GitImporter;
import com.googlesource.gerrit.plugins.github.git.PullRequestImportJob;
import com.googlesource.gerrit.plugins.github.git.ReplicateProjectStep;
-import com.googlesource.gerrit.plugins.github.oauth.GitHubHttpProvider;
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+import com.googlesource.gerrit.plugins.github.oauth.PooledHttpClientProvider;
import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider;
import com.googlesource.gerrit.plugins.github.replication.RemoteSiteUser;
import com.googlesource.gerrit.plugins.github.velocity.PluginVelocityRuntimeProvider;
@@ -41,7 +40,7 @@
@Override
protected void configureServlets() {
- bind(HttpClient.class).toProvider(GitHubHttpProvider.class);
+ bind(HttpClient.class).toProvider(PooledHttpClientProvider.class);
bind(new TypeLiteral<ScopedProvider<GitHubLogin>>() {}).to(GitHubLogin.Provider.class);
bind(new TypeLiteral<ScopedProvider<GitImporter>>() {}).to(GitImporter.Provider.class);
@@ -68,9 +67,9 @@
serve("*.css", "*.js", "*.png", "*.jpg", "*.woff", "*.gif", "*.ttf").with(
VelocityStaticServlet.class);
- serve("*.html").with(VelocityViewServlet.class);
serve("*.gh").with(VelocityControllerServlet.class);
+ serve("/static/*").with(VelocityViewServlet.class);
filter("*").through(GitHubOAuthFilter.class);
}
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java
index 261587f..564fd3e 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/InitGitHub.java
@@ -13,16 +13,25 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.github;
+import java.net.URISyntaxException;
+
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.InitUtil;
import com.google.gerrit.pgm.init.api.Section;
import com.google.inject.Inject;
public class InitGitHub implements InitStep {
+ private static final String GITHUB_URL = "https://github.com";
+ private static final String GITHUB_API_URL = "https://api.github.com";
+ private static final String GITHUB_REGISTER_APPLICATION_PATH = "/settings/applications/new";
+ private static final String GERRIT_OAUTH_CALLBACK_PATH = "oauth";
+
private final ConsoleUI ui;
private final Section auth;
private final Section httpd;
- private Section github;
+ private final Section github;
+ private final Section gerrit;
@Inject
InitGitHub(final ConsoleUI ui, final Section.Factory sections) {
@@ -30,29 +39,60 @@
this.github = sections.get("github", null);
this.httpd = sections.get("httpd", null);
this.auth = sections.get("auth", null);
+ this.gerrit = sections.get("gerrit", null);
}
@Override
public void run() throws Exception {
ui.header("GitHub Integration");
- github.string("GitHub URL", "url", "https://github.com");
+ auth.set("httpHeader", "GITHUB_USER");
+ auth.set("httpExternalIdHeader", "GITHUB_OAUTH_TOKEN");
+ auth.set("loginUrl","/login");
+ auth.set("loginText", "Sign-in with GitHub");
+ auth.set("registerPageUrl", "/#/register");
- boolean gitHubAuth = ui.yesno(true, "Use GitHub for Gerrit login ?");
- if(gitHubAuth) {
- configureAuth();
- }
- }
+ github.string("GitHub URL", "url", GITHUB_URL);
+ github.string("GitHub API URL", "apiUrl", GITHUB_API_URL);
+ ui.message("\nNOTE: You might need to configure a proxy using http.proxy"
+ + " if you run Gerrit behind a firewall.\n");
- private void configureAuth() {
- github.string("ClientId", "clientId", null);
- github.string("ClientSecret", "clientSecret", null);
+ String gerritUrl = getAssumedCanonicalWebUrl();
+ ui.header("GitHub OAuth registration and credentials");
+ ui.message(
+ "Register Gerrit as GitHub application on:\n" +
+ "%s%s\n\n",
+ github.get("url"), GITHUB_REGISTER_APPLICATION_PATH);
+ ui.message("Settings (assumed Gerrit URL: %s)\n", gerritUrl);
+ ui.message("* Application name: Gerrit Code Review\n");
+ ui.message("* Homepage URL: %s\n", gerritUrl);
+ ui.message("* Authorization callback URL: %s%s\n\n", gerritUrl, GERRIT_OAUTH_CALLBACK_PATH);
+ ui.message("After registration is complete, enter the generated OAuth credentials:\n");
+ github.string("GitHub Client ID", "clientId", null);
+ github.passwordForKey("GitHub Client Secret", "clientSecret");
auth.string("HTTP Authentication Header", "httpHeader", "GITHUB_USER");
auth.set("type", "HTTP");
httpd.set("filterClass", "com.googlesource.gerrit.plugins.github.oauth.OAuthFilter");
}
+ private String getAssumedCanonicalWebUrl() {
+ String url = gerrit.get("canonicalWebUrl");
+ if (url != null) {
+ return url;
+ }
+
+ String httpListen = httpd.get("listenUrl");
+ if (httpListen != null) {
+ try {
+ return InitUtil.toURI(httpListen).toString();
+ } catch (URISyntaxException e) {
+ }
+ }
+
+ return String.format("http://%s:8080/", InitUtil.hostname());
+ }
+
@Override
public void postRun() throws Exception {
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java
index b844ecc..59ceb46 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/filters/GitHubOAuthFilter.java
@@ -18,6 +18,7 @@
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
import com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol;
import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider;
@@ -33,6 +34,7 @@
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
@Singleton
public class GitHubOAuthFilter implements Filter {
@@ -40,12 +42,14 @@
private final ScopedProvider<GitHubLogin> loginProvider;
private final Scope[] authScopes;
+ private final OAuthProtocol oauth;
@Inject
- public GitHubOAuthFilter(final ScopedProvider<GitHubLogin> loginProvider,
- final GitHubOAuthConfig githubOAuthConfig) {
+ public GitHubOAuthFilter(ScopedProvider<GitHubLogin> loginProvider,
+ GitHubOAuthConfig githubOAuthConfig, OAuthProtocol oauth) {
this.loginProvider = loginProvider;
this.authScopes = githubOAuthConfig.getDefaultScopes();
+ this.oauth = oauth;
}
@Override
@@ -58,7 +62,8 @@
GitHubLogin hubLogin = loginProvider.get((HttpServletRequest) request);
LOG.debug("GitHub login: " + hubLogin);
if (!hubLogin.isLoggedIn()) {
- hubLogin.login(request, response, authScopes);
+ hubLogin.login((HttpServletRequest) request,
+ (HttpServletResponse) response, oauth, authScopes);
return;
} else {
chain.doFilter(request, response);
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java
index 8e4f57e..c5cf07a 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/GitHubRepository.java
@@ -25,6 +25,7 @@
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.URIish;
import org.kohsuke.github.GHRepository;
+import org.kohsuke.github.GitHub;
import java.io.IOException;
@@ -39,14 +40,15 @@
private final String organisation;
private final String repository;
- private final GitHubLogin ghLogin;
private final String cloneUrl;
+ private final String username;
+ private final String password;
@Delegate
private GHRepository ghRepository;
public String getCloneUrl() {
- return cloneUrl.replace("://", "://" + ghLogin.getMyself().getLogin() + "@");
+ return cloneUrl.replace("://", "://" + username + "@");
}
public String getOrganisation() {
@@ -65,9 +67,12 @@
this.cloneUrl = gitHubUrl + "/" + organisation + "/" + repository + ".git";
this.organisation = organisation;
this.repository = repository;
- this.ghLogin = ghLoginProvider.get();
+ GitHubLogin ghLogin = ghLoginProvider.get();
+ GitHub gh = ghLogin.getHub();
+ this.username = ghLogin.getMyself().getLogin();
+ this.password = ghLogin.getToken().accessToken;
this.ghRepository =
- ghLogin.getHub().getRepository(organisation + "/" + repository);
+ gh.getRepository(organisation + "/" + repository);
}
public CredentialsProvider getCredentialsProvider() {
@@ -97,13 +102,13 @@
throws UnsupportedCredentialItem {
String username = uri.getUser();
if (username == null) {
- username = ghLogin.getMyself().getLogin();
+ username = GitHubRepository.this.username;
}
if (username == null) {
return false;
}
- String password = ghLogin.getToken().accessToken;
+ String password = GitHubRepository.this.password;
if (password == null) {
return false;
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java
index 44e5a23..5410c78 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/git/ReplicateProjectStep.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.github.git;
+import java.io.IOException;
+
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -43,7 +45,7 @@
final ScopedProvider<GitHubLogin> ghLoginProvider,
@GitHubURL String gitHubUrl,
@Assisted("organisation") String organisation,
- @Assisted("name") String repository) {
+ @Assisted("name") String repository) throws IOException {
super(gitHubUrl, organisation, repository, gitHubRepoFactory);
LOG.debug("Gerrit ReplicateProject " + organisation + "/" + repository);
this.replicationConfig = replicationConfig;
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
index b9f5d34..cd1a202 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/velocity/VelocityViewServlet.java
@@ -82,10 +82,7 @@
+ nextPage.uri;
}
- String pathInfo = MoreObjects.firstNonNull(destUrl, servletPath);
- if (!pathInfo.startsWith(STATIC_PREFIX)) {
- resp.sendError(HttpStatus.SC_NOT_FOUND);
- }
+ String pathInfo = STATIC_PREFIX + MoreObjects.firstNonNull(destUrl, servletPath);
try {
Template template = velocityRuntime.getTemplate(pathInfo, "UTF-8");
@@ -104,7 +101,7 @@
}
}
- private PluginVelocityModel initVelocityModel(HttpServletRequest request) {
+ private PluginVelocityModel initVelocityModel(HttpServletRequest request) throws IOException {
PluginVelocityModel model = modelProvider.get();
GitHubLogin gitHubLogin = loginProvider.get(request);
model.put("myself", gitHubLogin.getMyself());
diff --git a/github-plugin/src/main/resources/Documentation/about.md b/github-plugin/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..5ba04a6
--- /dev/null
+++ b/github-plugin/src/main/resources/Documentation/about.md
@@ -0,0 +1,26 @@
+
+This plugins allows to integrate Gerrit with external set of users configured
+on GitHub.
+It relies on the installation of the github-oauth Java library under the `$GERRIT_SITE/lib`
+in order filter all the HTTP requests through the GitHub OAuth 2.0 secure authentication.
+
+GitHub application registration
+-------------------------------
+
+GitHub uses OAuth2 as protocol to allow external apps request authorization to private
+details in a user's GitHub account without getting their password. This is
+preferred over Basic Authentication because tokens can be limited to specific
+types of data, and can be revoked by users at any time.
+
+Site owners have to register their application before getting started. For
+more information see [GitHub application registration page](https://github.com/settings/applications/new).
+A registered OAuth application is assigned a unique `Client ID` and `Client
+Secret`. The `Client Secret` should never be shared.
+
+The Gerrit OAuth callback `<gerrit canonical URL>/oauth`
+needs to be specified in the GitHub application registration to establish mutual
+trust between the two domains and exchange the authorization codes. The use of HTTPS
+for Gerrit is strongly recommended for keeping the secrets exchange confidential.
+
+`auth.httpHeader` is set to `GITHUB_USER` with this authentication method and `auth.type`
+must be set to HTTP.
\ No newline at end of file
diff --git a/github-plugin/src/main/resources/Documentation/config.md b/github-plugin/src/main/resources/Documentation/config.md
index 2d18944..9d7c87b 100644
--- a/github-plugin/src/main/resources/Documentation/config.md
+++ b/github-plugin/src/main/resources/Documentation/config.md
@@ -1,13 +1,6 @@
-Plugin @PLUGIN@
-===============
-This plugins allows to integrate Gerrit with external set of users configured
-on GitHub.
-It relies on the installation of the github-oauth Java library under the $GERRIT_SITE/lib
-in order filter all the HTTP requests through the GitHub OAuth 2.0 secure authentication.
-
-GitHub init step
-----------------
+GitHub configuration during Gerrit init
+---------------------------------------
This plugin provides a customized Gerrit init step for the self-configuration of
the main GitHub and Gerrit authentication settings for allowing the github-oauth
@@ -18,19 +11,58 @@
See below a sample session of relevant init steps for a default
configuration pointing to the Web GitHub instance:
- *** User Authentication
- ***
+```
+ *** GitHub Integration
+ ***
- Authentication method []: HTTP
- Get username from custom HTTP header [Y/n]? Y
- Username HTTP header []: GITHUB_USER
- SSO logout URL : /oauth/reset
+ GitHub URL [https://github.com]:
+ GitHub API URL [https://api.github.com]:
+ NOTE: You might need to configure a proxy using http.proxy if you run Gerrit behind a firewall.
- *** GitHub Integration
- ***
+ *** GitHub OAuth registration and credentials
+ ***
- GitHub URL [https://github.com]:
- Use GitHub for Gerrit login ? [Y/n]? Y
- ClientId []: 384cbe2e8d98192f9799
- ClientSecret []: f82c3f9b3802666f2adcc4c8cacfb164295b0a99
+ Register Gerrit as GitHub application on:
+ https://github.com/settings/applications/new
+
+ Settings (assumed Gerrit URL: http://localhost:8080/)
+ * Application name: Gerrit Code Review
+ * Homepage URL: http://localhost:8080/
+ * Authorization callback URL: http://localhost:8080/oauth
+
+ After registration is complete, enter the generated OAuth credentials:
+ GitHub Client ID [1ebea047915210179cf5]:
+ ClientSecret []: f82c3f9b3802666f2adcc4c8cacfb164295b0a99
+ confirm password :
+ HTTP Authentication Header [GITHUB_USER]:
+```
+
+Configuration
+-------------
+
+GitHub plugin read his configuration from gerrit.config under the `[github]` section.
+
+github.url
+: GitHub homepage URL. Default is `https://github.com`. Can be customized to a different
+ value for GitHub:Enterprise integration
+
+**NOTE** You might need to configure a proxy using the configuration `http.proxy` if you run
+Gerrit behind a firewall.
+
+github.apiUrl
+: GitHub API URL. Default is `https://api.github.com`. Can be customized to a different
+ value for GitHub:Enterprise integration
+
+github.clientId
+: The `Client ID`, that was received from GitHub when the application was registered. Required.
+
+github.clientSecret
+: The `Client Secret`, that was received from GitHub when the application was registered. Required.
+
+github.scopes
+: The GitHub scopes for allowing access to the user's public or private profile, organisations and
+ repositories. See [GitHub Scopes definition](https://developer.github.com/v3/oauth/#scopes)
+ for a detailed description of available scopes and their associated permissions.
+ Default is empty read-only access to public
+ information (includes public user profile info, public repository info, and gists).