Merge branch 'stable-2.10' into stable-2.11

Change-Id: I45d085503305d3f9d0bcc456c111caa5f70837ed
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 564fd3e..907f73d 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
@@ -32,6 +32,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) {
@@ -46,12 +54,6 @@
   public void run() throws Exception {
     ui.header("GitHub Integration");
 
-    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");
-
     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 +73,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");
+      auth.set("httpExternalIdHeader", "GITHUB_OAUTH_TOKEN");
+      auth.set("loginUrl","/login");
+      auth.set("loginText", "Sign-in with GitHub");
+      auth.set("registerPageUrl", "/#/register");
+    } else {
+      httpd.unset("filterClass");
+      httpd.unset("httpHeader");
+    }
   }
 
   private String getAssumedCanonicalWebUrl() {
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);
+    }
   }
 }