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