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