[GERRITHUB-5] SSH Key import with scope-specific OAuth login
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 69ceaec..489bd03 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
@@ -15,24 +15,39 @@
package com.googlesource.gerrit.plugins.github.oauth;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.SortedSet;
+import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.kohsuke.github.GHMyself;
import org.kohsuke.github.GitHub;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.servlet.SessionScoped;
import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.AccessToken;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
@SessionScoped
public class GitHubLogin {
+ private static final Logger log = LoggerFactory.getLogger(GitHubLogin.class);
+
public AccessToken token;
public GitHub hub;
- private String redirectUrl;
+ private SortedSet<Scope> scopesSet = new TreeSet<OAuthProtocol.Scope>();
private transient OAuthProtocol oauth;
+ private GHMyself myself;
+
+ public GHMyself getMyself() {
+ return myself;
+ }
+
@Inject
public GitHubLogin(OAuthProtocol oauth) {
this.oauth = oauth;
@@ -43,27 +58,54 @@
this.token = token;
}
- public boolean isLoggedIn() {
- return token != null && hub != null;
+ public boolean isLoggedIn(Scope... scopes) {
+ SortedSet<Scope> inputScopes =
+ new TreeSet<OAuthProtocol.Scope>(Arrays.asList(scopes));
+ boolean loggedIn =
+ scopesSet.equals(inputScopes) && token != null && hub != null;
+ if (loggedIn) {
+ try {
+ myself = hub.getMyself();
+ } catch (IOException e) {
+ log.error("Connection to GitHub broken: logging out", e);
+ logout();
+ loggedIn = false;
+ }
+ }
+ return loggedIn;
}
- public boolean login(HttpServletRequest request, HttpServletResponse response)
- throws IOException {
+ public boolean login(HttpServletRequest request,
+ HttpServletResponse response, Scope... scopes) throws IOException {
+ if (isLoggedIn(scopes)) {
+ return true;
+ }
+
+ setScopes(scopes);
+
if (oauth.isOAuthFinal(request)) {
init(oauth.loginPhase2(request, response));
- if(isLoggedIn()) {
- response.sendRedirect(redirectUrl);
+ if (isLoggedIn(scopes)) {
return true;
} else {
return false;
}
} else {
- redirectUrl = request.getRequestURL().toString();
- oauth.loginPhase1(request, response);
+ oauth.loginPhase1(request, response, scopes);
return false;
}
}
+ public void logout() {
+ scopesSet = new TreeSet<OAuthProtocol.Scope>();
+ hub = null;
+ token = null;
+ }
+
+ private void setScopes(Scope... scopes) {
+ this.scopesSet = new TreeSet<Scope>(Arrays.asList(scopes));
+ }
+
private void init(GitHubLogin initValues) {
this.hub = initValues.hub;
this.token = initValues.token;
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
index 7e43ba7..0aab679 100644
--- a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/oauth/OAuthFilter.java
@@ -72,7 +72,6 @@
Cookie gerritCookie = getGerritCookie(httpRequest);
OAuthCookie authCookie =
getOAuthCookie(httpRequest, (HttpServletResponse) response);
- String targetUrl = httpRequest.getParameter("state");
if (((oauth.isOAuthLogin(httpRequest) || oauth.isOAuthFinal(httpRequest)) && authCookie == null)
|| (authCookie == null && gerritCookie == null)) {
@@ -90,7 +89,7 @@
OAuthCookie userCookie =
cookieProvider.getFromUser(user, email, fullName);
httpResponse.addCookie(userCookie);
- httpResponse.sendRedirect(targetUrl);
+ httpResponse.sendRedirect(oauth.getTargetUrl(request));
return;
} else {
httpResponse.sendError(HttpURLConnection.HTTP_UNAUTHORIZED,
@@ -118,9 +117,8 @@
authCookie.fullName, config.httpEmailHeader, authCookie.email);
}
- if (targetUrl != null && oauth.isOAuthFinal(httpRequest)) {
- httpResponse.sendRedirect(config.getUrl(targetUrl,
- OAuthConfig.OAUTH_FINAL) + "?code=" + request.getParameter("code"));
+ if (oauth.isOAuthFinalForOthers(httpRequest)) {
+ httpResponse.sendRedirect(oauth.getTargetOAuthFinal(httpRequest));
return;
} else {
chain.doFilter(httpRequest, response);
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 790df59..ad89b9c 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
@@ -8,6 +8,7 @@
import java.util.ArrayList;
import java.util.List;
+import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -22,23 +23,50 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class OAuthProtocol {
- private static final Logger log = LoggerFactory.getLogger(OAuthProtocol.class);
+ public static enum Scope {
+ DEFAULT(""),
+ USER("user"),
+ USER_EMAIL("user:email"),
+ USER_FOLLOW("user:follow"),
+ PUBLIC_REPO("public_repo"),
+ REPO("repo"),
+ REPO_STATUS("repo_status"),
+ DELETE_REPO("delete_repo"),
+ NOTIFICATIONS("notifications"),
+ GIST("gist");
+
+ private final String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ private Scope(final String value) {
+ this.value = value;
+ }
+ }
+ private static final String ME_SEPARATOR = ",";
+
+ private static final Logger log = LoggerFactory
+ .getLogger(OAuthProtocol.class);
+
private final OAuthConfig config;
private final HttpClient http;
private final Gson gson;
-
+
public static class AccessToken {
public String access_token;
public String token_type;
}
-
+
@Inject
public OAuthProtocol(OAuthConfig config, HttpClient httpClient, Gson gson) {
this.config = config;
@@ -47,21 +75,57 @@
}
public void loginPhase1(HttpServletRequest request,
- HttpServletResponse response) throws IOException {
+ HttpServletResponse response, Scope... scopes) throws IOException {
response.sendRedirect(String.format(
- "%s?client_id=%s&redirect_uri=%s&state=%s", config.gitHubOAuthUrl,
- config.gitHubClientId, getURLEncoded(config.oAuthFinalRedirectUrl),
- getURLEncoded(request.getRequestURI().toString())));
+ "%s?client_id=%s%s&redirect_uri=%s&state=%s%s", config.gitHubOAuthUrl,
+ config.gitHubClientId, getScope(scopes),
+ getURLEncoded(config.oAuthFinalRedirectUrl),
+ me(), getURLEncoded(request.getRequestURI().toString())));
}
-
+
+ private String getScope(Scope[] scopes) {
+ if(scopes.length <= 0) {
+ return "";
+ }
+
+ StringBuilder out = new StringBuilder();
+ for (Scope scope : scopes) {
+ if(out.length() > 0) {
+ out.append(",");
+ }
+ out.append(scope.getValue());
+ }
+ return "&" +
+ "scope=" + out.toString();
+ }
+
public boolean isOAuthFinal(HttpServletRequest request) {
- return request.getRequestURI().endsWith(OAuthConfig.OAUTH_FINAL);
+ return Strings.emptyToNull(request.getParameter("code")) != null
+ && wasInitiatedByMe(request);
}
+ public boolean isOAuthFinalForOthers(HttpServletRequest request) {
+ String targetUrl = getTargetUrl(request);
+ if(targetUrl.equals(request.getRequestURI())) {
+ return false;
+ }
+
+ return Strings.emptyToNull(request.getParameter("code")) != null
+ && !wasInitiatedByMe(request);
+ }
+
+ private String me() {
+ return "" + hashCode() + ME_SEPARATOR;
+ }
+
+ public boolean wasInitiatedByMe(HttpServletRequest request) {
+ return state(request).startsWith(me());
+ }
+
public boolean isOAuthLogin(HttpServletRequest request) {
return request.getRequestURI().indexOf(OAuthConfig.OAUTH_LOGIN) >= 0;
}
-
+
public GitHubLogin loginPhase2(HttpServletRequest request,
HttpServletResponse response) throws IOException {
@@ -104,7 +168,7 @@
return null;
}
}
-
+
private String getURLEncoded(String url) {
try {
return URLEncoder.encode(url, "UTF-8");
@@ -113,4 +177,25 @@
return null;
}
}
+
+ public String getTargetUrl(ServletRequest request) {
+ int meEnd = state(request).indexOf(ME_SEPARATOR);
+ if (meEnd > 0) {
+ return state(request).substring(meEnd+1);
+ } else {
+ return "";
+ }
+ }
+
+ private String state(ServletRequest request) {
+ return Strings.nullToEmpty(request.getParameter("state"));
+ }
+
+ public 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;
+ }
}
diff --git a/github-plugin/pom.xml b/github-plugin/pom.xml
index 83e7e83..6935a38 100644
--- a/github-plugin/pom.xml
+++ b/github-plugin/pom.xml
@@ -102,7 +102,7 @@
<dependencies>
<dependency>
<groupId>com.google.gerrit</groupId>
- <artifactId>gerrit-${Gerrit-ApiType}-api</artifactId>
+ <artifactId>gerrit-plugin-api</artifactId>
<version>${Gerrit-ApiVersion}</version>
<scope>provided</scope>
</dependency>
diff --git a/github-plugin/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/github-plugin/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
deleted file mode 100644
index adbb03a..0000000
--- a/github-plugin/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.pgm.init;
-
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-
-import java.io.IOException;
-import java.util.List;
-
-/** Global variables used by the 'init' command. */
-@Singleton
-public class InitFlags {
- /** Recursively delete the site path if initialization fails. */
- public boolean deleteOnFailure;
-
- /** Run the daemon (and open the web UI in a browser) after initialization. */
- public boolean autoStart;
-
- public final FileBasedConfig cfg;
- public final FileBasedConfig sec;
- public final List<String> installPlugins;
-
- @Inject
- InitFlags(final SitePaths site,
- final @InstallPlugins List<String> installPlugins) throws IOException,
- ConfigInvalidException {
- this.installPlugins = installPlugins;
- cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
- sec = new FileBasedConfig(site.secure_config, FS.DETECTED);
-
- cfg.load();
- sec.load();
- }
-}
diff --git a/github-plugin/src/main/java/com/google/gerrit/pgm/init/InitStep.java b/github-plugin/src/main/java/com/google/gerrit/pgm/init/InitStep.java
deleted file mode 100644
index 4fa3f90..0000000
--- a/github-plugin/src/main/java/com/google/gerrit/pgm/init/InitStep.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.pgm.init;
-
-/** A single step in the site initialization process. */
-public interface InitStep {
- public void run() throws Exception;
-}
diff --git a/github-plugin/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java b/github-plugin/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
deleted file mode 100644
index 73db6f5..0000000
--- a/github-plugin/src/main/java/com/google/gerrit/pgm/init/InstallPlugins.java
+++ /dev/null
@@ -1,25 +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.google.gerrit.pgm.init;
-
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@BindingAnnotation
-@Retention(RetentionPolicy.RUNTIME)
-public @interface InstallPlugins {
-}
diff --git a/github-plugin/src/main/java/com/google/gerrit/pgm/init/Section.java b/github-plugin/src/main/java/com/google/gerrit/pgm/init/Section.java
deleted file mode 100644
index 387b93a..0000000
--- a/github-plugin/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.pgm.init;
-
-import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-/** Helper to edit a section of the configuration files. */
-public class Section {
- public interface Factory {
- Section get(@Assisted("section") String section,
- @Assisted("subsection") String subsection);
- }
-
- private final InitFlags flags;
- private final SitePaths site;
- private final ConsoleUI ui;
- private final String section;
- private final String subsection;
-
- @Inject
- public Section(final InitFlags flags, final SitePaths site,
- final ConsoleUI ui, @Assisted("section") final String section,
- @Assisted("subsection") @Nullable final String subsection) {
- this.flags = flags;
- this.site = site;
- this.ui = ui;
- this.section = section;
- this.subsection = subsection;
- }
-
- String get(String name) {
- return flags.cfg.getString(section, null, name);
- }
-
- public void set(final String name, final String value) {
- final ArrayList<String> all = new ArrayList<String>();
- all.addAll(Arrays.asList(flags.cfg.getStringList(section, subsection, name)));
-
- if (value != null) {
- if (all.size() == 0 || all.size() == 1) {
- flags.cfg.setString(section, subsection, name, value);
- } else {
- all.set(0, value);
- flags.cfg.setStringList(section, subsection, name, all);
- }
-
- } else if (all.size() == 0) {
- } else if (all.size() == 1) {
- flags.cfg.unset(section, subsection, name);
- } else {
- all.remove(0);
- flags.cfg.setStringList(section, subsection, name, all);
- }
- }
-
- public <T extends Enum<?>> void set(final String name, final T value) {
- if (value != null) {
- set(name, value.name());
- } else {
- unset(name);
- }
- }
-
- public void unset(String name) {
- set(name, (String) null);
- }
-
- public String string(final String title, final String name, final String dv) {
- return string(title, name, dv, false);
- }
-
- public String string(final String title, final String name, final String dv,
- final boolean nullIfDefault) {
- final String ov = get(name);
- String nv = ui.readString(ov != null ? ov : dv, "%s", title);
- if (nullIfDefault && nv == dv) {
- nv = null;
- }
- if (!eq(ov, nv)) {
- set(name, nv);
- }
- return nv;
- }
-
- public File path(final String title, final String name, final String defValue) {
- return site.resolve(string(title, name, defValue));
- }
-
- public <T extends Enum<?>> T select(final String title, final String name,
- final T defValue) {
- return select(title, name, defValue, false);
- }
-
- public <T extends Enum<?>> T select(final String title, final String name,
- final T defValue, final boolean nullIfDefault) {
- final boolean set = get(name) != null;
- T oldValue = ConfigUtil.getEnum(flags.cfg, section, subsection, name, defValue);
- T newValue = ui.readEnum(oldValue, "%s", title);
- if (nullIfDefault && newValue == defValue) {
- newValue = null;
- }
- if (!set || oldValue != newValue) {
- if (newValue != null) {
- set(name, newValue);
- } else {
- unset(name);
- }
- }
- return newValue;
- }
-
- public String select(final String title, final String name, final String dv,
- Set<String> allowedValues) {
- final String ov = get(name);
- String nv = ui.readString(ov != null ? ov : dv, allowedValues, "%s", title);
- if (!eq(ov, nv)) {
- set(name, nv);
- }
- return nv;
- }
-
- public String password(final String username, final String password) {
- final String ov = getSecure(password);
-
- String user = flags.sec.getString(section, subsection, username);
- if (user == null) {
- user = get(username);
- }
-
- if (user == null) {
- flags.sec.unset(section, subsection, password);
- return null;
- }
-
- if (ov != null) {
- // If the user already has a password stored, try to reuse it
- // rather than prompting for a whole new one.
- //
- if (ui.isBatch() || !ui.yesno(false, "Change %s's password", user)) {
- return ov;
- }
- }
-
- final String nv = ui.password("%s's password", user);
- if (!eq(ov, nv)) {
- setSecure(password, nv);
- }
- return nv;
- }
-
- public String getSecure(String name) {
- return flags.sec.getString(section, subsection, name);
- }
-
- public void setSecure(String name, String value) {
- if (value != null) {
- flags.sec.setString(section, subsection, name, value);
- } else {
- flags.sec.unset(section, subsection, name);
- }
- }
-
- String getName() {
- return section;
- }
-
- private static boolean eq(final String a, final String b) {
- if (a == null && b == null) {
- return true;
- }
- return a != null ? a.equals(b) : false;
- }
-}
diff --git a/github-plugin/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/github-plugin/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
deleted file mode 100644
index e8cf0ab..0000000
--- a/github-plugin/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.pgm.util;
-
-import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
-
-import java.io.Console;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Set;
-
-/** Console based interaction with the invoking user. */
-public abstract class ConsoleUI {
- /** Get a UI instance, assuming interactive mode. */
- public static ConsoleUI getInstance() {
- return getInstance(false);
- }
-
- /** Get a UI instance, possibly forcing batch mode. */
- public static ConsoleUI getInstance(final boolean batchMode) {
- Console console = batchMode ? null : System.console();
- return console != null ? new Interactive(console) : new Batch();
- }
-
- /** Constructs an exception indicating the user aborted the operation. */
- protected static Die abort() {
- return new Die("aborted by user");
- }
-
- /** Obtain all values from an enumeration. */
- @SuppressWarnings("unchecked")
- protected static <T extends Enum<?>> T[] all(final T value) {
- try {
- return (T[]) value.getClass().getMethod("values").invoke(null);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Cannot obtain enumeration values", e);
- } catch (SecurityException e) {
- throw new IllegalArgumentException("Cannot obtain enumeration values", e);
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Cannot obtain enumeration values", e);
- } catch (InvocationTargetException e) {
- throw new IllegalArgumentException("Cannot obtain enumeration values", e);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException("Cannot obtain enumeration values", e);
- }
- }
-
- /** @return true if this is a batch UI that has no user interaction. */
- public abstract boolean isBatch();
-
- /** Display a header message before a series of prompts. */
- public abstract void header(String fmt, Object... args);
-
- /** Display a message. */
- public abstract void message(String fmt, Object... args);
-
- /** Request the user to answer a yes/no question. */
- public abstract boolean yesno(Boolean def, String fmt, Object... args);
-
- /** Prints a message asking the user to let us know when its safe to continue. */
- public abstract void waitForUser();
-
- /** Prompt the user for a string, suggesting a default, and returning choice. */
- public abstract String readString(String def, String fmt, Object... args);
-
- /** Prompt the user to make a choice from an allowed list of values. */
- public abstract String readString(String def, Set<String> allowedValues,
- String fmt, Object... args);
-
- /** Prompt the user for an integer value, suggesting a default. */
- public int readInt(int def, String fmt, Object... args) {
- for (;;) {
- String p = readString(String.valueOf(def), fmt, args);
- try {
- return Integer.parseInt(p.trim(), 10);
- } catch (NumberFormatException e) {
- System.err.println("error: Invalid integer format: " + p.trim());
- }
- }
- }
-
- /** Prompt the user for a password, returning the string; null if blank. */
- public abstract String password(String fmt, Object... args);
-
- /** Prompt the user to make a choice from an enumeration's values. */
- public abstract <T extends Enum<?>> T readEnum(T def, String fmt,
- Object... args);
-
- private static class Interactive extends ConsoleUI {
- private final Console console;
-
- Interactive(final Console console) {
- this.console = console;
- }
-
- @Override
- public boolean isBatch() {
- return false;
- }
-
- @Override
- public boolean yesno(Boolean def, String fmt, Object... args) {
- final String prompt = String.format(fmt, args);
- for (;;) {
- String y = "y";
- String n = "n";
- if (def != null) {
- if (def) {
- y = "Y";
- } else {
- n = "N";
- }
- }
-
- String yn = console.readLine("%-30s [%s/%s]? ", prompt, y, n);
- if (yn == null) {
- throw abort();
- }
- yn = yn.trim();
- if (def != null && yn.isEmpty()) {
- return def;
- }
- if (yn.equalsIgnoreCase("y") || yn.equalsIgnoreCase("yes")) {
- return true;
- }
- if (yn.equalsIgnoreCase("n") || yn.equalsIgnoreCase("no")) {
- return false;
- }
- }
- }
-
- @Override
- public void waitForUser() {
- if (console.readLine("Press enter to continue ") == null) {
- throw abort();
- }
- }
-
- @Override
- public String readString(String def, String fmt, Object... args) {
- final String prompt = String.format(fmt, args);
- String r;
- if (def != null) {
- r = console.readLine("%-30s [%s]: ", prompt, def);
- } else {
- r = console.readLine("%-30s : ", prompt);
- }
- if (r == null) {
- throw abort();
- }
- r = r.trim();
- if (r.isEmpty()) {
- return def;
- }
- return r;
- }
-
- @Override
- public String readString(String def, Set<String> allowedValues, String fmt,
- Object... args) {
- for (;;) {
- String r = readString(def, fmt, args);
- if (allowedValues.contains(r.toLowerCase())) {
- return r.toLowerCase();
- }
- if (!"?".equals(r)) {
- console.printf("error: '%s' is not a valid choice\n", r);
- }
- console.printf(" Supported options are:\n");
- for (final String v : allowedValues) {
- console.printf(" %s\n", v.toString().toLowerCase());
- }
- }
- }
-
- @Override
- public String password(String fmt, Object... args) {
- final String prompt = String.format(fmt, args);
- for (;;) {
- final char[] a1 = console.readPassword("%-30s : ", prompt);
- if (a1 == null) {
- throw abort();
- }
-
- final char[] a2 = console.readPassword("%30s : ", "confirm password");
- if (a2 == null) {
- throw abort();
- }
-
- final String s1 = new String(a1);
- final String s2 = new String(a2);
- if (!s1.equals(s2)) {
- console.printf("error: Passwords did not match; try again\n");
- continue;
- }
- return !s1.isEmpty() ? s1 : null;
- }
- }
-
- @Override
- public <T extends Enum<?>> T readEnum(T def, String fmt, Object... args) {
- final String prompt = String.format(fmt, args);
- final T[] options = all(def);
- for (;;) {
- String r = console.readLine("%-30s [%s/?]: ", prompt, def.toString());
- if (r == null) {
- throw abort();
- }
- r = r.trim();
- if (r.isEmpty()) {
- return def;
- }
- for (final T e : options) {
- if (equalsIgnoreCase(e.toString(), r)) {
- return e;
- }
- }
- if (!"?".equals(r)) {
- console.printf("error: '%s' is not a valid choice\n", r);
- }
- console.printf(" Supported options are:\n");
- for (final T e : options) {
- console.printf(" %s\n", e.toString().toLowerCase());
- }
- }
- }
-
- @Override
- public void header(String fmt, Object... args) {
- fmt = fmt.replaceAll("\n", "\n*** ");
- console.printf("\n*** " + fmt + "\n*** \n\n", args);
- }
-
- @Override
- public void message(String fmt, Object... args) {
- console.printf(fmt, args);
- }
- }
-
- private static class Batch extends ConsoleUI {
- @Override
- public boolean isBatch() {
- return true;
- }
-
- @Override
- public boolean yesno(Boolean def, String fmt, Object... args) {
- return def != null ? def : true;
- }
-
- @Override
- public String readString(String def, String fmt, Object... args) {
- return def;
- }
-
- @Override
- public String readString(String def, Set<String> allowedValues, String fmt,
- Object... args) {
- return def;
- }
-
- @Override
- public void waitForUser() {
- }
-
- @Override
- public String password(String fmt, Object... args) {
- return null;
- }
-
- @Override
- public <T extends Enum<?>> T readEnum(T def, String fmt, Object... args) {
- return def;
- }
-
- @Override
- public void header(String fmt, Object... args) {
- }
-
- @Override
- public void message(String fmt, Object... args) {
- }
- }
-}
diff --git a/github-plugin/src/main/java/com/google/gerrit/pgm/util/Die.java b/github-plugin/src/main/java/com/google/gerrit/pgm/util/Die.java
deleted file mode 100644
index 96e2ec8..0000000
--- a/github-plugin/src/main/java/com/google/gerrit/pgm/util/Die.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.pgm.util;
-
-public class Die extends RuntimeException {
- private static final long serialVersionUID = 1L;
-
- public Die(final String why) {
- super(why);
- }
-
- public Die(final String why, final Throwable cause) {
- super(why, cause);
- }
-}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/HttpModule.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/HttpModule.java
index 63b0bc3..1199569 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/HttpModule.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/HttpModule.java
@@ -7,14 +7,17 @@
import com.googlesource.gerrit.plugins.github.oauth.GitHubHttpProvider;
import com.googlesource.gerrit.plugins.github.pullsync.PullRequestsServlet;
import com.googlesource.gerrit.plugins.github.replication.RemoteSiteUser;
+import com.googlesource.gerrit.plugins.github.wizard.SshKeysImportServlet;
public class HttpModule extends ServletModule {
@Override
protected void configureServlets() {
bind(HttpClient.class).toProvider(GitHubHttpProvider.class);
+
install(new FactoryModuleBuilder().build(RemoteSiteUser.Factory.class));
- serve("/*").with(PullRequestsServlet.class);
+ serve("/").with(PullRequestsServlet.class);
+ serve("/sshimport").with(SshKeysImportServlet.class);
}
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/pullsync/PullRequestsServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/pullsync/PullRequestsServlet.java
index 3bb401d..7f2477f 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/pullsync/PullRequestsServlet.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/pullsync/PullRequestsServlet.java
@@ -2,12 +2,29 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.kohsuke.github.GHCommitPointer;
+import org.kohsuke.github.GHIssueState;
+import org.kohsuke.github.GHOrganization;
+import org.kohsuke.github.GHPullRequest;
+import org.kohsuke.github.GHRepository;
+import org.kohsuke.github.GitHub;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -18,14 +35,18 @@
@Singleton
public class PullRequestsServlet extends HttpServlet {
private static final long serialVersionUID = 3635343057427548273L;
+ private static final Logger log = LoggerFactory
+ .getLogger(PullRequestsServlet.class);
private Provider<GitHubLogin> loginProvider;
private GitHubDestinations destinations;
+ private ProjectCache projects;
@Inject
public PullRequestsServlet(Provider<GitHubLogin> loginProvider,
- GitHubDestinations destinations) {
+ GitHubDestinations destinations, ProjectCache projects) {
this.loginProvider = loginProvider;
this.destinations = destinations;
+ this.projects = projects;
}
@Override
@@ -35,18 +56,56 @@
PrintWriter out = null;
try {
GitHubLogin hubLogin = loginProvider.get();
- if (!hubLogin.isLoggedIn()) {
- if (!hubLogin.login(req, resp)) {
- return;
- }
+ if (!hubLogin.isLoggedIn() && !hubLogin.login(req, resp)) {
+ return;
}
+
+ GitHub hub = hubLogin.hub;
out = resp.getWriter();
- out.println("<html><body><pre>");
- for (Destination dest : destinations.getDestinations()) {
- out.println(dest.getRemote().getURIs());
+ out.println("<html><body>");
+
+ for (String orgName : destinations.getOrganisations()) {
+ GHOrganization org = hub.getOrganization(orgName);
+ for (GHRepository repo : org.getRepositories().values()) {
+ String repoName = repo.getName();
+ ProjectState project = projects.get(new NameKey(repoName));
+ if (project == null) {
+ log.debug("GitHub repo " + orgName + "/" + repoName
+ + " does not have a correspondant Gerrit Project: skipped");
+ continue;
+ }
+
+ out.println("<h1>Project: " + project.getProject().getName()
+ + "</h1>");
+
+ List<GHPullRequest> pullRequests =
+ repo.getPullRequests(GHIssueState.OPEN);
+ for (GHPullRequest pullRequest : pullRequests) {
+
+ out.println("<form id=\"%s\">");
+ out.println("<H2>Pull Request #" + pullRequest.getNumber()
+ + "</H2>");
+ out.println("> Title: " + pullRequest.getTitle());
+ out.println("> Body: " + pullRequest.getBody());
+
+ GHCommitPointer pullHead = pullRequest.getHead();
+ out.println("> Pull Repository: "
+ + pullHead.getRepository().getUrl());
+ out.println("> Pull SHA-1: " + pullHead.getSha());
+ out.println("> Pull ref-spec: " + pullHead.getRef());
+
+ GHCommitPointer pullBase = pullRequest.getBase();
+ out.println("> Base Repository: "
+ + pullBase.getRepository().getUrl());
+ out.println("> Base SHA-1: " + pullBase.getSha());
+ out.println("> Base ref-spec: " + pullBase.getRef());
+ }
+
+ }
+ org.getRepositories();
};
- out.println("</pre></body></html>");
+ out.println("</body></html>");
} finally {
if (out != null) {
out.close();
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java
index 45d277c..2fb60fc 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/replication/GitHubDestinations.java
@@ -17,6 +17,7 @@
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -65,6 +66,7 @@
private final GitRepositoryManager gitRepositoryManager;
private final GroupBackend groupBackend;
boolean replicateAllOnPluginStart;
+ private final List<String> organisations;
@Inject
GitHubDestinations(final Injector i, final SitePaths site,
@@ -78,6 +80,18 @@
gitRepositoryManager = grm;
groupBackend = gb;
configs = getDestinations(new File(site.etc_dir, "replication.config"));
+ organisations = getOrganisations(configs);
+ }
+
+ private List<String> getOrganisations(List<Destination> destinations) {
+ ArrayList<String> organisations = new ArrayList<String>();
+ for (Destination destination : destinations) {
+ for (URIish urish : destination.getRemote().getURIs()) {
+ String[] uriPathParts = urish.getPath().split("/");
+ organisations.add(uriPathParts[0]);
+ }
+ }
+ return organisations;
}
private List<Destination> getDestinations(File cfgPath)
@@ -150,4 +164,8 @@
public List<Destination> getDestinations() {
return configs;
}
+
+ public List<String> getOrganisations() {
+ return organisations;
+ }
}
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/SshKeysImportServlet.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/SshKeysImportServlet.java
new file mode 100644
index 0000000..b7e4ac9
--- /dev/null
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/SshKeysImportServlet.java
@@ -0,0 +1,92 @@
+package com.googlesource.gerrit.plugins.github.wizard;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.kohsuke.github.GHKey;
+import org.kohsuke.github.GHMyself;
+
+import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.account.AddSshKey;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
+import com.googlesource.gerrit.plugins.github.oauth.OAuthProtocol.Scope;
+
+@Singleton
+public class SshKeysImportServlet extends HttpServlet {
+ private static final long serialVersionUID = 5565594120346641704L;
+ private AddSshKey addSshKey;
+ private Provider<GitHubLogin> loginProvider;
+ private Provider<IdentifiedUser> userProvider;
+
+ @Inject
+ public SshKeysImportServlet(final AddSshKey addSshKey,
+ Provider<GitHubLogin> loginProvider, Provider<IdentifiedUser> userProvider) {
+ this.addSshKey = addSshKey;
+ this.loginProvider = loginProvider;
+ this.userProvider = userProvider;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ GitHubLogin hubLogin = loginProvider.get();
+ if (!hubLogin.login(req, resp, Scope.USER)) {
+ return;
+ }
+
+ PrintWriter out = resp.getWriter();
+ out.println("<html><body><pre>");
+ try {
+
+ GHMyself myself = hubLogin.getMyself();
+ List<GHKey> pubKeys = myself.getPublicKeys();
+ for (GHKey ghKey : pubKeys) {
+ AccountResource res = new AccountResource(userProvider.get());
+ AddSshKey.Input key = new AddSshKey.Input();
+ final String sshKey = ghKey.getKey();
+ out.println("Importing key " + sshKey);
+ final ByteArrayInputStream keyIs =
+ new ByteArrayInputStream(sshKey.getBytes());
+ key.raw = new RawInput() {
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return keyIs;
+ }
+
+ @Override
+ public String getContentType() {
+ return "text/plain";
+ }
+
+ @Override
+ public long getContentLength() {
+ return sshKey.length();
+ }
+ };
+ try {
+ addSshKey.apply(res, key);
+ } catch (Exception e) {
+ throw new IOException("Cannot store SSH Key '" + sshKey + "'", e);
+ }
+ }
+ } finally {
+ out.println("</pre></body></html>");
+ out.close();
+ }
+ }
+
+}