Merge branch 'stable-2.11'
Change-Id: I5b4404e20a7159f519f705af57436a9c0682df50
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 32f751a..081d6c1 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
@@ -101,7 +101,7 @@
log.debug("Login " + this);
if (OAuthProtocol.isOAuthFinal(request)) {
log.debug("Login-FINAL " + this);
- login(oauth.loginPhase2(request, state));
+ login(oauth.loginPhase2(request, response, state));
this.state = ""; // Make sure state is used only once
if (isLoggedIn()) {
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 e7a830d..8cf64ab 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
@@ -37,9 +37,27 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.io.CharStreams;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.gerrit.extensions.auth.oauth.OAuthToken;
+import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
+
@Singleton
public class OAuthProtocol {
-
public static enum Scope {
DEFAULT(""),
USER("user"),
@@ -78,6 +96,7 @@
public String error;
public String errorDescription;
public String errorUri;
+ public String raw;
public AccessToken() {
}
@@ -86,7 +105,7 @@
this(token, "");
}
- public AccessToken(String token, String type, Scope... scopes) {
+ public AccessToken(String token, String type) {
this();
this.accessToken = token;
this.tokenType = type;
@@ -117,6 +136,18 @@
public boolean isError() {
return !Strings.isNullOrEmpty(error);
}
+
+ public String getRaw() {
+ return raw;
+ }
+
+ public void setRaw(String raw) {
+ this.raw = raw;
+ }
+
+ public OAuthToken toOAuthToken() {
+ return new OAuthToken(accessToken, null, getRaw());
+ }
}
@Inject
@@ -142,21 +173,26 @@
}
}
+ public String getAuthorizationUrl(String scopesString, String state) {
+ return config.gitHubOAuthUrl + "?client_id=" + config.gitHubClientId
+ + getURLEncodedParameter("&scope=", scopesString)
+ + getURLEncodedParameter("&redirect_uri=", config.oAuthFinalRedirectUrl)
+ + getURLEncodedParameter("&state=", state);
+ }
+
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);
String state = newRandomState(request.getRequestURI().toString());
- response.sendRedirect(String.format(
- "%s?client_id=%s%s&redirect_uri=%s&state=%s",
- config.gitHubOAuthUrl, config.gitHubClientId, scopesString,
- getURLEncoded(config.oAuthFinalRedirectUrl), getURLEncoded(state)));
+ log.debug("Initiating GitHub Login for ClientId=" + config.gitHubClientId + " Scopes=" + scopesString);
+ response.sendRedirect(getAuthorizationUrl(scopesString, state));
return state;
}
- private String getScope(Set<Scope> scopes) {
- if (scopes.size() <= 0) {
+ public String getScope(Set<Scope> scopes) {
+ if(scopes.size() <= 0) {
return "";
}
@@ -167,13 +203,13 @@
}
out.append(scope.getValue());
}
- return "&" + "scope=" + out.toString();
+ return out.toString();
}
public static boolean isOAuthFinal(HttpServletRequest request) {
return Strings.emptyToNull(request.getParameter("code")) != null;
}
-
+
public static boolean isOAuthFinalForOthers(HttpServletRequest request) {
String targetUrl = getTargetUrl(request);
if(targetUrl.equals(request.getRequestURI())) {
@@ -201,20 +237,23 @@
return OAuthProtocol.isOAuthLogin(httpRequest) || OAuthProtocol.isOAuthFinal(httpRequest);
}
- public AccessToken loginPhase2(HttpServletRequest request, String state)
- throws IOException {
-
+ public AccessToken loginPhase2(HttpServletRequest request,
+ HttpServletResponse response, String state) throws IOException {
String requestState = request.getParameter("state");
if (!Objects.equals(state, requestState)) {
throw new IOException("Invalid authentication state");
}
+ return getAccessToken(new OAuthVerifier(request.getParameter("code")));
+ }
+
+ public AccessToken getAccessToken(OAuthVerifier code) throws IOException {
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));
nvps.add(new BasicNameValuePair("client_secret", config.gitHubClientSecret));
- nvps.add(new BasicNameValuePair("code", request.getParameter("code")));
+ nvps.add(new BasicNameValuePair("code", code.getValue()));
post.setEntity(new UrlEncodedFormEntity(nvps, Charsets.UTF_8));
HttpResponse postResponse = httpProvider.get().execute(post);
@@ -236,12 +275,14 @@
+ " returned an error token: " + token);
throw new IOException("Invalid GitHub OAuth token");
}
+
return token;
}
- private static String getURLEncoded(String url) {
+ private static String getURLEncodedParameter(String prefix, String url) {
try {
- return URLEncoder.encode(url, "UTF-8");
+ return Strings.isNullOrEmpty(url) ?
+ "" : (prefix + URLEncoder.encode(url,"UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Cannot find UTF-8 encoding", e);
}
@@ -262,10 +303,9 @@
public static String getTargetOAuthFinal(HttpServletRequest httpRequest) {
String targetUrl = getTargetUrl(httpRequest);
- String code = getURLEncoded(httpRequest.getParameter("code"));
- String state = getURLEncoded(httpRequest.getParameter("state"));
- return targetUrl + (targetUrl.indexOf('?') < 0 ? '?' : '&') + "code="
- + code + "&state=" + state;
+ String code = getURLEncodedParameter("code=", httpRequest.getParameter("code"));
+ String state = getURLEncodedParameter("&state=", httpRequest.getParameter("state"));
+ return targetUrl + (targetUrl.indexOf('?') < 0 ? '?' : '&') + code + state;
}
@Override
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubOAuthServiceProvider.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubOAuthServiceProvider.java
new file mode 100644
index 0000000..575ea11
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubOAuthServiceProvider.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.github;
+
+import java.io.IOException;
+
+import org.kohsuke.github.GHMyself;
+import org.kohsuke.github.GitHub;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.auth.oauth.OAuthToken;
+import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
+import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.github.oauth.GitHubOAuthConfig;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol;
+
+public class GitHubOAuthServiceProvider implements OAuthServiceProvider {
+ public static final String VERSION = "2.10.3";
+ private static final Logger log = LoggerFactory
+ .getLogger(GitHubOAuthServiceProvider.class);
+
+ private final GitHubOAuthConfig config;
+ private final OAuthProtocol oauth;
+
+ @Inject
+ public GitHubOAuthServiceProvider(GitHubOAuthConfig config,
+ OAuthProtocol oauth) {
+ this.config = config;
+ this.oauth = oauth;
+ }
+
+ @Override
+ public String getAuthorizationUrl() {
+ return oauth.getAuthorizationUrl(
+ oauth.getScope(Sets.newHashSet(config.getDefaultScopes())), null);
+ }
+
+ @Override
+ public OAuthToken getAccessToken(OAuthVerifier verifier) {
+ try {
+ return oauth.getAccessToken(verifier).toOAuthToken();
+ } catch (IOException e) {
+ log.error("Invalid OAuth access verifier" + verifier.getValue(), e);
+ return null;
+ }
+ }
+
+ @Override
+ public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
+ String oauthToken = token.getToken();
+ GitHub hub = GitHub.connectUsingOAuth(oauthToken);
+ GHMyself myself = hub.getMyself();
+ String login = myself.getLogin();
+ return new OAuthUserInfo(AccountExternalId.SCHEME_GERRIT + login, login,
+ myself.getEmail(), myself.getName(), null);
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public String getName() {
+ return "GitHub";
+ }
+}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
index 0cd3820..20696ee 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/GitHubTopMenu.java
@@ -16,8 +16,10 @@
import com.google.gerrit.extensions.annotations.Listen;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -29,12 +31,14 @@
@Listen
@Singleton
public class GitHubTopMenu implements TopMenu {
- private List<MenuEntry> menuEntries;
- private Provider<CurrentUser> userProvider;
+ private final List<MenuEntry> menuEntries;
+ private final Provider<CurrentUser> userProvider;
+ private final AuthConfig authConfig;
@Inject
- public GitHubTopMenu(final @PluginName String pluginName,
- final Provider<CurrentUser> userProvider) {
+ public GitHubTopMenu(@PluginName String pluginName,
+ Provider<CurrentUser> userProvider,
+ AuthConfig authConfig) {
String baseUrl = "/plugins/" + pluginName;
this.menuEntries =
Arrays.asList(new MenuEntry("GitHub", Arrays.asList(
@@ -42,6 +46,7 @@
getItem("Repositories", baseUrl + "/static/repositories.html"),
getItem("Pull Requests", baseUrl + "/static/pullrequests.html"))));
this.userProvider = userProvider;
+ this.authConfig = authConfig;
}
private MenuItem getItem(String anchorName, String urlPath) {
@@ -50,7 +55,9 @@
@Override
public List<MenuEntry> getEntries() {
- if (userProvider.get() instanceof IdentifiedUser) {
+ if (userProvider.get() instanceof IdentifiedUser &&
+ // Only with HTTP authentication we can transparently trigger OAuth if needed
+ authConfig.getAuthType().equals(AuthType.HTTP)) {
return menuEntries;
} else {
return Collections.emptyList();
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 6bfbb16..625f664 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
@@ -13,6 +13,11 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.github;
+import org.apache.http.client.HttpClient;
+import org.apache.velocity.runtime.RuntimeInstance;
+
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Names;
@@ -65,6 +70,9 @@
bind(String.class).annotatedWith(GitHubURL.class).toProvider(
GitHubURLProvider.class);
+ bind(OAuthServiceProvider.class).annotatedWith(
+ Exports.named("github")).to(GitHubOAuthServiceProvider.class);
+
serve("*.css", "*.js", "*.png", "*.jpg", "*.woff", "*.gif", "*.ttf").with(
VelocityStaticServlet.class);
serve("*.gh").with(VelocityControllerServlet.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 fe48ccf..5a78252 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
@@ -33,6 +33,14 @@
private final Section httpd;
private final Section github;
private final Section gerrit;
+
+ public enum OAuthType {
+ /* Legacy Gerrit/HTTP authentication for GitHub through HTTP Header enrichment */
+ HTTP,
+
+ /* New native Gerrit/OAuth authentication provider */
+ OAUTH
+ }
@Inject
InitGitHub(final ConsoleUI ui, final Section.Factory sections) {
@@ -47,11 +55,6 @@
public void run() throws Exception {
ui.header("GitHub Integration");
- authSetDefault("httpExternalIdHeader", "GITHUB_OAUTH_TOKEN");
- authSetDefault("loginUrl","/login");
- authSetDefault("loginText", "Sign-in with GitHub");
- authSetDefault("registerPageUrl", "/#/register");
-
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"
@@ -71,9 +74,20 @@
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");
+
+ OAuthType authType = auth.select("Gerrit OAuth implementation", "type", OAuthType.HTTP);
+ if (authType.equals(OAuthType.HTTP)) {
+ auth.string("HTTP Authentication Header", "httpHeader", "GITHUB_USER");
+ httpd.set("filterClass",
+ "com.googlesource.gerrit.plugins.github.oauth.OAuthFilter");
+ authSetDefault("httpExternalIdHeader", "GITHUB_OAUTH_TOKEN");
+ authSetDefault("loginUrl","/login");
+ authSetDefault("loginText", "Sign-in with GitHub");
+ authSetDefault("registerPageUrl", "/#/register");
+ } else {
+ httpd.unset("filterClass");
+ httpd.unset("httpHeader");
+ }
}
private void authSetDefault(String key, String defValue) {
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java
index 2ddfef6..00181b8 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupBackend.java
@@ -18,6 +18,7 @@
import static com.googlesource.gerrit.plugins.github.group.GitHubGroup.NAME_PREFIX;
import static com.googlesource.gerrit.plugins.github.group.GitHubGroup.UUID_PREFIX;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.gerrit.common.data.GroupDescription.Basic;
@@ -127,6 +128,11 @@
@Override
public GroupMembership membershipsOf(IdentifiedUser user) {
- return ghMembershipProvider.get(user.getUserName());
+ String username = user.getUserName();
+ if (Strings.isNullOrEmpty(username)) {
+ return GroupMembership.EMPTY;
+ } else {
+ return ghMembershipProvider.get(username);
+ }
}
}