Author: al
Date: Wed Oct 24 15:48:38 2012
New Revision: 1401742

URL: http://svn.apache.org/viewvc?rev=1401742&view=rev
Log:
Implement SSL Client Certificate Authentication.

By Ali Lown.
https://reviews.apache.org/r/4994/

Added:
    
incubator/wave/trunk/src/org/waveprotocol/box/server/util/RegistrationUtil.java 
  (with props)
Modified:
    incubator/wave/trunk/README
    incubator/wave/trunk/server-config.xml
    incubator/wave/trunk/server.config.example
    incubator/wave/trunk/src/org/waveprotocol/box/server/CoreSettings.java
    
incubator/wave/trunk/src/org/waveprotocol/box/server/gxp/AuthenticationPage.gxp
    
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/AuthenticationServlet.java
    
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/ServerRpcProvider.java
    
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/UserRegistrationServlet.java
    
incubator/wave/trunk/test/org/waveprotocol/box/server/rpc/AuthenticationServletTest.java

Modified: incubator/wave/trunk/README
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/README?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- incubator/wave/trunk/README (original)
+++ incubator/wave/trunk/README Wed Oct 24 15:48:38 2012
@@ -67,8 +67,33 @@ Then, after updating a proto file, run
   ant -f build-proto.xml compile compile_json
 </code>
 
-Note: this generates files into proto_src. If files here exist without 
+Note: this generates files into proto_src. If files here exist without
 write permission, you will get permission denied errors from this step.
 
 Note also that you don't need protoc unless you want to change the proto
 files.
+
+
+To enable SSL:
+
+Create a Java keystore for your server (e.g. using 
http://portecle.sourceforge.net/).
+You will need a key (e.g. called "server") whose subject Common Name (CN) is
+the hostname of your server.
+
+Set enable_ssl = true and set the ssl_keystore_path and ssl_keystore_password 
options.
+
+
+To enable X.509 client authentication:
+
+If your users have X.509 certificates which include their email address, you 
can have
+them logged in automatically (with their wave ID being the same as their email 
address):
+You can get X.509 certificates issued from any normal CA (e.g. StartSSL offer 
them for free).
+You can get your CA's certficate from their website, though note they might 
provide more than 1 certificate which you need to chain before your client 
certificates are considered trusted.
+
+1. Add the signing CA to your keystore file.
+2. Set enable_clientauth = true
+3. Set clientauth_cert_domain (to the part after the "@" in your email 
addresses).
+4. (optional) Set disable_loginpage = true to prevent password-based logins.
+
+Users will be automatically logged in when they access the site, with the
+username taken from the email address in their certificate.

Modified: incubator/wave/trunk/server-config.xml
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/server-config.xml?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- incubator/wave/trunk/server-config.xml (original)
+++ incubator/wave/trunk/server-config.xml Wed Oct 24 15:48:38 2012
@@ -31,6 +31,9 @@
   <property name="enable_ssl" value="false" />
   <property name="ssl_keystore_path" value="wiab.ks" />
   <property name="ssl_keystore_password" value="changeme" />
+  <property name="enable_clientauth" value="false" />
+  <property name="clientauth_cert_domain" value="" />
+  <property name="disable_loginpage" value="false" />
   <property name="search_type" value="lucene" />
   <property name="index_directory" value="_indexes" />
   <property name="analytics_account" value="" />
@@ -106,6 +109,9 @@
           <token key="ENABLE_SSL" value="${enable_ssl}" />
           <token key="SSL_KEYSTORE_PATH" value="${ssl_keystore_path}" />
           <token key="SSL_KEYSTORE_PASSWORD" value="${ssl_keystore_password}" 
/>
+          <token key="ENABLE_CLIENTAUTH" value="${enable_clientauth}" />"
+          <token key="CLIENTAUTH_CERT_DOMAIN" 
value="${clientauth_cert_domain}" />"
+          <token key="DISABLE_LOGINPAGE" value="${disable_loginpage}" />"
           <token key="SEARCH_TYPE" value="${search_type}" />
           <token key="INDEX_DIRECTORY" value="${index_directory}" />
           <token key="ANALYTICS_ACCOUNT" value="${analytics_account}" />

Modified: incubator/wave/trunk/server.config.example
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/server.config.example?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- incubator/wave/trunk/server.config.example (original)
+++ incubator/wave/trunk/server.config.example Wed Oct 24 15:48:38 2012
@@ -166,6 +166,15 @@ ssl_keystore_password = @SSL_KEYSTORE_PA
 # Default value: false
 enable_import = @ENABLE_IMPORT@
 
+# Enable client x509 cert. authentication?
+enable_clientauth = @ENABLE_CLIENTAUTH@
+
+# Domain of the email to look for as email field of x509 client auth 
certificates when using client authentication
+clientauth_cert_domain = @CLIENTAUTH_CERT_DOMAIN@
+
+# Disable login page to force x509-only authentication
+disable_loginpage = @DISABLE_LOGINPAGE@
+
 # Currently supported search types: memory, lucene.
 # Default value: lucene.
 search_type = @SEARCH_TYPE@

Modified: incubator/wave/trunk/src/org/waveprotocol/box/server/CoreSettings.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/CoreSettings.java?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- incubator/wave/trunk/src/org/waveprotocol/box/server/CoreSettings.java 
(original)
+++ incubator/wave/trunk/src/org/waveprotocol/box/server/CoreSettings.java Wed 
Oct 24 15:48:38 2012
@@ -63,6 +63,9 @@ public class CoreSettings {
   public static final String SSL_KEYSTORE_PATH = "ssl_keystore_path";
   public static final String SSL_KEYSTORE_PASSWORD = "ssl_keystore_password";
   public static final String ENABLE_IMPORT = "enable_import";
+  public static final String ENABLE_CLIENTAUTH = "enable_clientauth";
+  public static final String CLIENTAUTH_CERT_DOMAIN = "clientauth_cert_domain";
+  public static final String DISABLE_LOGINPAGE = "disable_loginpage";
   public static final String SEARCH_TYPE = "search_type";
   public static final String INDEX_DIRECTORY = "index_directory";
   public static final String ANALYTICS_ACCOUNT = "analytics_account";
@@ -225,6 +228,18 @@ public class CoreSettings {
       description = "Enable import servlet at <Server URL>/import", 
defaultValue = "false")
   private static boolean enableImport;
 
+  @Setting(name = ENABLE_CLIENTAUTH,
+      description = "Enable x509 certificated based authentication", 
defaultValue = "false")
+  private static boolean enableClientAuth;
+
+  @Setting(name = CLIENTAUTH_CERT_DOMAIN,
+      description = "Domain of email address in x509 cert", defaultValue = "")
+  private static String clientAuthCertDomain;
+
+  @Setting(name = DISABLE_LOGINPAGE,
+      description = "Disable login page to force x509 only", defaultValue = 
"false")
+  private static boolean disableLoginPage;
+
   @Setting(name = INDEX_DIRECTORY,
       description = "Location on disk where the index is persisted", 
defaultValue = "_indexes")
   private static String indexDirectory;

Modified: 
incubator/wave/trunk/src/org/waveprotocol/box/server/gxp/AuthenticationPage.gxp
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/gxp/AuthenticationPage.gxp?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/server/gxp/AuthenticationPage.gxp 
(original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/server/gxp/AuthenticationPage.gxp 
Wed Oct 24 15:48:38 2012
@@ -28,6 +28,7 @@
   <gxp:param name='domain' type='String' />
   <gxp:param name='message' type='String' />
   <gxp:param name='responseType' type='String' />
+  <gxp:param name='disableLoginPage' type='Boolean' />
   <gxp:param name='analyticsAccount' type='String'/>
 
   <html dir="ltr">
@@ -117,6 +118,10 @@
       </table>
       </td>
       <td valign="top" align="center" style="padding-right: 30px">
+      <gxp:if cond='disableLoginPage == true'>
+      HTTP Authentication disabled by Administrator. Install and use your 
certificate instead.
+      </gxp:if>
+      <gxp:if cond='disableLoginPage == false'>
       <form id="wiab_loginform" action="" method="post">
       <table class="form-noindent" style="padding: 1;" border="0"
         align="right">
@@ -189,6 +194,7 @@
         </tr>
       </table>
       </form>
+      </gxp:if>
       </td>
     </tr>
   </table>

Modified: 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/AuthenticationServlet.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/AuthenticationServlet.java?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/AuthenticationServlet.java
 (original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/AuthenticationServlet.java
 Wed Oct 24 15:48:38 2012
@@ -26,10 +26,16 @@ import com.google.inject.name.Named;
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.UrlEncoded;
 import org.waveprotocol.box.server.CoreSettings;
+import org.waveprotocol.box.server.account.HumanAccountDataImpl;
 import 
org.waveprotocol.box.server.authentication.HttpRequestBasedCallbackHandler;
 import org.waveprotocol.box.server.authentication.ParticipantPrincipal;
 import org.waveprotocol.box.server.authentication.SessionManager;
+import org.waveprotocol.box.server.persistence.AccountStore;
+import org.waveprotocol.box.server.persistence.PersistenceException;
 import org.waveprotocol.box.server.gxp.AuthenticationPage;
+import org.waveprotocol.box.server.robots.agent.welcome.WelcomeRobot;
+import org.waveprotocol.box.server.util.RegistrationUtil;
+import org.waveprotocol.wave.model.id.WaveIdentifiers;
 import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
 import org.waveprotocol.wave.model.wave.ParticipantId;
 import org.waveprotocol.wave.util.logging.Log;
@@ -46,14 +52,19 @@ import java.nio.charset.CharacterCodingE
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CodingErrorAction;
+import java.security.cert.X509Certificate;
 import java.security.Principal;
 
 import javax.inject.Singleton;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.Rdn;
+import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.login.Configuration;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
+import javax.security.auth.x500.X500Principal;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -72,23 +83,46 @@ public class AuthenticationServlet exten
   public static final String RESPONSE_STATUS_NONE = "NONE";
   public static final String RESPONSE_STATUS_FAILED = "FAILED";
   public static final String RESPONSE_STATUS_SUCCESS = "SUCCESS";
+  // The Object ID of the PKCS #9 email address stored in the client 
certificate.
+  // Source: 
http://www.rsa.com/products/bsafe/documentation/sslc251html/group__AD__COMMON__OIDS.html
+  private static final String OID_EMAIL = "1.2.840.113549.1.9.1";
 
   private static final Log LOG = Log.get(AuthenticationServlet.class);
 
+  private final AccountStore accountStore;
   private final Configuration configuration;
   private final SessionManager sessionManager;
   private final String domain;
+  private final boolean isClientAuthEnabled;
+  private final String clientAuthCertDomain;
+  private final boolean isRegistrationDisabled;
+  private final boolean isLoginPageDisabled;
+  private boolean failedClientAuth = false;
+private final WelcomeRobot welcomeBot;
   private final String analyticsAccount;
 
   @Inject
-  public AuthenticationServlet(Configuration configuration, SessionManager 
sessionManager,
+  public AuthenticationServlet(AccountStore accountStore,
+      Configuration configuration, SessionManager sessionManager,
       @Named(CoreSettings.WAVE_SERVER_DOMAIN) String domain,
+      @Named(CoreSettings.ENABLE_CLIENTAUTH) boolean isClientAuthEnabled,
+      @Named(CoreSettings.CLIENTAUTH_CERT_DOMAIN) String clientAuthCertDomain,
+      @Named(CoreSettings.DISABLE_REGISTRATION) boolean isRegistrationDisabled,
+      @Named(CoreSettings.DISABLE_LOGINPAGE) boolean isLoginPageDisabled,
+    WelcomeRobot welcomeBot,
       @Named(CoreSettings.ANALYTICS_ACCOUNT) String analyticsAccount) {
+    Preconditions.checkNotNull(accountStore, "AccountStore is null");
     Preconditions.checkNotNull(configuration, "Configuration is null");
     Preconditions.checkNotNull(sessionManager, "Session manager is null");
+    this.accountStore = accountStore;
     this.configuration = configuration;
     this.sessionManager = sessionManager;
     this.domain = domain.toLowerCase();
+    this.isClientAuthEnabled = isClientAuthEnabled;
+    this.clientAuthCertDomain = clientAuthCertDomain.toLowerCase();
+    this.isRegistrationDisabled = isRegistrationDisabled;
+    this.isLoginPageDisabled = isLoginPageDisabled;
+    this.welcomeBot = welcomeBot;
     this.analyticsAccount = analyticsAccount;
   }
 
@@ -132,39 +166,79 @@ public class AuthenticationServlet exten
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws IOException {
     req.setCharacterEncoding("UTF-8");
     LoginContext context;
-    try {
-      context = login(req.getReader());
-    } catch (LoginException e) {
-      String message = "The username or password you entered is incorrect.";
-      String responseType = RESPONSE_STATUS_FAILED;
-      LOG.info("User authentication failed: " + e.getLocalizedMessage());
-      resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
-      AuthenticationPage.write(resp.getWriter(), new 
GxpContext(req.getLocale()), domain, message,
-          analyticsAccount, responseType);
-      return;
-    }
+    Subject subject;
+    ParticipantId loggedInAddress = null;
 
-    Subject subject = context.getSubject();
+    if (isClientAuthEnabled) {
+      boolean skipClientAuth = false;
+      try {
+        X509Certificate[] certs = (X509Certificate[]) 
req.getAttribute("javax.servlet.request.X509Certificate");
 
-    ParticipantId loggedInAddress;
-    try {
-      loggedInAddress = getLoggedInUser(subject);
-    } catch (InvalidParticipantAddress e1) {
-      throw new IllegalStateException(
-          "The user provided valid authentication information, but the 
username"
-              + " isn't a valid user address.");
+        if (certs == null) {
+          if (isLoginPageDisabled) {
+            throw new IllegalStateException(
+                "No client X.509 certificate provided (you need to get a 
certificate"
+                    + "from your systems manager and import it into your 
browser).");
+          }
+          else {
+            failedClientAuth = true;
+            skipClientAuth = true;
+            doGet(req, resp);
+          }
+        }
+
+        if (!skipClientAuth) {
+          failedClientAuth = false;
+          subject = new Subject();
+          for (X509Certificate cert : certs) {
+            X500Principal principal = cert.getSubjectX500Principal();
+            subject.getPrincipals().add(principal);
+          }
+          loggedInAddress = getLoggedInUser(subject);
+        }
+      } catch (InvalidParticipantAddress e1) {
+        throw new IllegalStateException(
+            "The user provided valid authentication information, but the 
username"
+                + " isn't a valid user address.");
+      }
     }
 
-    if (loggedInAddress == null) {
+    if (!isLoginPageDisabled && loggedInAddress == null) {
       try {
-        context.logout();
+        context = login(req.getReader());
       } catch (LoginException e) {
-        // Logout failed. Absorb the error, since we're about to throw an
-        // illegal state exception anyway.
+        String message = "The username or password you entered is incorrect.";
+        String responseType = RESPONSE_STATUS_FAILED;
+        LOG.info("User authentication failed: " + e.getLocalizedMessage());
+        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        resp.setContentType("text/html;charset=utf-8");
+        AuthenticationPage.write(resp.getWriter(), new 
GxpContext(req.getLocale()), domain, message,
+            responseType, isLoginPageDisabled, analyticsAccount);
+        return;
+      }
+
+      subject = context.getSubject();
+
+      try {
+        loggedInAddress = getLoggedInUser(subject);
+      } catch (InvalidParticipantAddress e1) {
+        throw new IllegalStateException(
+            "The user provided valid authentication information, but the 
username"
+                + " isn't a valid user address.");
+      }
+
+      if (loggedInAddress == null) {
+        try {
+          context.logout();
+        } catch (LoginException e) {
+          // Logout failed. Absorb the error, since we're about to throw an
+          // illegal state exception anyway.
+        }
+
+        throw new IllegalStateException(
+            "The user provided valid authentication information, but we don't "
+                + "know how to map their identity to a wave user address.");
       }
-      throw new IllegalStateException(
-          "The user provided valid authentication information, but we don't "
-              + "know how to map their identity to a wave user address.");
     }
 
     HttpSession session = req.getSession(true);
@@ -192,6 +266,8 @@ public class AuthenticationServlet exten
       if (p instanceof ParticipantPrincipal) {
         address = ((ParticipantPrincipal) p).getName();
         break;
+      } else if (p instanceof X500Principal) {
+        return attemptClientCertificateLogin((X500Principal)p);
       }
     }
 
@@ -199,6 +275,68 @@ public class AuthenticationServlet exten
   }
 
   /**
+   * Attempts to authenticate the user using their client certificate.
+   *
+   * Retrieves the email from their certificate, using it as the wave username.
+   * If the user doesn't exist and registration is enabled, it will 
automatically create an account
+   * before continuing. Otherwise it will simply check if the account exists 
and authenticate based
+   * on that.
+   *
+   * @throws RuntimeException The encoding of the email is unsupported on this 
system
+   * @throws InvalidParticipantAddress The email address doesn't correspond to 
an account
+   */
+  private ParticipantId attemptClientCertificateLogin(X500Principal p)
+      throws RuntimeException, InvalidParticipantAddress {
+    String distinguishedName = p.getName();
+    try {
+      LdapName ldapName = new LdapName(distinguishedName);
+      for (Rdn rdn: ldapName.getRdns()) {
+        if (rdn.getType().equals(OID_EMAIL)) {
+          String email = decodeEmailFromCertificate((byte[])rdn.getValue());
+          if (email.endsWith("@" + clientAuthCertDomain)) {
+            // Check we decoded the string correctly.
+            Preconditions.checkState(WaveIdentifiers.isValidIdentifier(email),
+                "The decoded email is not a valid wave identifier");
+            ParticipantId id = ParticipantId.of(email);
+            if (!RegistrationUtil.doesAccountExist(accountStore, id)) {
+              if (!isRegistrationDisabled) {
+                if (!RegistrationUtil.createAccountIfMissing(accountStore, id, 
null, welcomeBot)) {
+                  return null;
+                }
+              } else {
+                throw new InvalidNameException(
+                    "User doesn't already exist, and registration disabled by 
administrator");
+              }
+            }
+            return id;
+          }
+        }
+      }
+    } catch (UnsupportedEncodingException ex) {
+      throw new RuntimeException(ex);
+    } catch (InvalidNameException ex) {
+      throw new InvalidParticipantAddress(distinguishedName,
+          "Certificate does not contain a valid distinguished name");
+    }
+    return null;
+  }
+
+  /**
+   * Decodes the user email from the X.509 certificate.
+   *
+   * Email address is assumed to be valid in ASCII, and less than 128 
characters long
+   *
+   * @param encoded Output from rdn.getValue(). 1st byte is the tag, second is 
the length.
+   * @return The decoded email in ASCII
+   * @throws UnsupportedEncodingException The email address wasn't in ASCII
+   */
+  private String decodeEmailFromCertificate(byte[] encoded) throws 
UnsupportedEncodingException {
+    // Check for < 130, since first 2 bytes are taken up as stated above.
+    Preconditions.checkState(encoded.length < 130,"The email address is longer 
than expected");
+    return new String(encoded, 2, encoded.length - 2, "ascii");
+  }
+
+  /**
    * On GET, present a login form if the user isn't authenticated.
    */
   @Override
@@ -212,10 +350,22 @@ public class AuthenticationServlet exten
     if (user != null) {
       redirectLoggedInUser(req, resp);
     } else {
-      resp.setStatus(HttpServletResponse.SC_OK);
+      if (isClientAuthEnabled && !failedClientAuth) {
+          X509Certificate[] certs = (X509Certificate[]) 
req.getAttribute("javax.servlet.request.X509Certificate");
+          if (certs != null) {
+            doPost(req, resp);
+          }
+      }
+
+      if (!isLoginPageDisabled) {
+        resp.setStatus(HttpServletResponse.SC_OK);
+      }
+      else {
+        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
+      }
       resp.setContentType("text/html;charset=utf-8");
       AuthenticationPage.write(resp.getWriter(), new 
GxpContext(req.getLocale()), domain, "",
-          RESPONSE_STATUS_NONE, analyticsAccount);
+          RESPONSE_STATUS_NONE, isLoginPageDisabled, analyticsAccount);
     }
   }
 

Modified: 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/ServerRpcProvider.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/ServerRpcProvider.java?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/ServerRpcProvider.java 
(original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/ServerRpcProvider.java 
Wed Oct 24 15:48:38 2012
@@ -540,6 +540,11 @@ public class ServerRpcProvider {
       sslContextFactory.setKeyStorePassword(sslKeystorePassword);
       sslContextFactory.setAllowRenegotiate(false);
       sslContextFactory.setExcludeCipherSuites(excludeCiphers);
+
+      // Note: we only actually needed client auth for AuthenticationServlet.
+      // Using Need instead of Want prevents web-sockets from working on
+      // Chrome.
+      sslContextFactory.setWantClientAuth(true);
     }
 
     for (InetSocketAddress address : httpAddresses) {

Modified: 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/UserRegistrationServlet.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/UserRegistrationServlet.java?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/UserRegistrationServlet.java
 (original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/server/rpc/UserRegistrationServlet.java
 Wed Oct 24 15:48:38 2012
@@ -29,6 +29,7 @@ import org.waveprotocol.box.server.gxp.U
 import org.waveprotocol.box.server.persistence.AccountStore;
 import org.waveprotocol.box.server.persistence.PersistenceException;
 import org.waveprotocol.box.server.robots.agent.welcome.WelcomeRobot;
+import org.waveprotocol.box.server.util.RegistrationUtil;
 import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
 import org.waveprotocol.wave.model.wave.ParticipantId;
 import org.waveprotocol.wave.util.logging.Log;
@@ -130,13 +131,8 @@ public final class UserRegistrationServl
       return "Invalid username";
     }
 
-    try {
-      if (accountStore.getAccount(id) != null) {
+    if(RegistrationUtil.doesAccountExist(accountStore, id)) {
         return "Account already exists";
-      }
-    } catch (PersistenceException e) {
-      LOG.severe("Failed to retreive account data for " + id, e);
-      return "An unexpected error occured while trying to retrieve account 
status";
     }
 
     if (password == null) {
@@ -144,19 +140,11 @@ public final class UserRegistrationServl
       password = "";
     }
 
-    HumanAccountDataImpl account =
-      new HumanAccountDataImpl(id, new PasswordDigest(password.toCharArray()));
-    try {
-      accountStore.putAccount(account);
-    } catch (PersistenceException e) {
-      LOG.severe("Failed to create new account for " + id, e);
+    if (!RegistrationUtil.createAccountIfMissing(accountStore, id,
+          new PasswordDigest(password.toCharArray()), welcomeBot)) {
       return "An unexpected error occured while trying to create the account";
     }
-    try {
-      welcomeBot.greet(account.getId());
-    } catch (IOException e) {
-      LOG.warning("Failed to create a welcome wavelet for " + id, e);
-    }
+
     return null;
   }
 

Added: 
incubator/wave/trunk/src/org/waveprotocol/box/server/util/RegistrationUtil.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/server/util/RegistrationUtil.java?rev=1401742&view=auto
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/server/util/RegistrationUtil.java 
(added)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/server/util/RegistrationUtil.java 
Wed Oct 24 15:48:38 2012
@@ -0,0 +1,71 @@
+/**
+ * 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 org.waveprotocol.box.server.util;
+
+import org.waveprotocol.box.server.account.HumanAccountDataImpl;
+import org.waveprotocol.box.server.authentication.PasswordDigest;
+import org.waveprotocol.box.server.persistence.AccountStore;
+import org.waveprotocol.box.server.persistence.PersistenceException;
+import org.waveprotocol.box.server.robots.agent.welcome.WelcomeRobot;
+import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.util.logging.Log;
+
+import java.io.IOException;
+
+/**
+ * User Registration utility methods.
+ *
+ * @author [email protected] (Ali Lown)
+ */
+public class RegistrationUtil {
+
+  private static final Log LOG = Log.get(RegistrationUtil.class);
+
+  private RegistrationUtil() {
+  }
+
+  public static boolean createAccountIfMissing(AccountStore accountStore, 
ParticipantId id,
+      PasswordDigest password, WelcomeRobot welcomeBot) {
+    HumanAccountDataImpl account = new HumanAccountDataImpl(id, password);
+    try {
+      LOG.info("Registering new account for" + id);
+      accountStore.putAccount(account);
+    } catch (PersistenceException e) {
+      LOG.severe("Failed to cretaea new account for " + id, e);
+      return false;
+    }
+    try {
+      welcomeBot.greet(account.getId());
+    } catch (IOException e) {
+      LOG.warning("Failed to create a welcome wavelet for " + id, e);;
+    }
+    return true;
+  }
+
+  public static boolean doesAccountExist(AccountStore accountStore, 
ParticipantId id) {
+    try {
+      if (accountStore.getAccount(id) != null) {
+        return true;
+      }
+    }
+    catch (PersistenceException e) {
+      LOG.severe("Failed to retrieve account data for " + id, e);
+      throw new RuntimeException("An unexpected error occurred trying to 
retrieve account status");
+    }
+    return false;
+  }
+}

Propchange: 
incubator/wave/trunk/src/org/waveprotocol/box/server/util/RegistrationUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
incubator/wave/trunk/test/org/waveprotocol/box/server/rpc/AuthenticationServletTest.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/test/org/waveprotocol/box/server/rpc/AuthenticationServletTest.java?rev=1401742&r1=1401741&r2=1401742&view=diff
==============================================================================
--- 
incubator/wave/trunk/test/org/waveprotocol/box/server/rpc/AuthenticationServletTest.java
 (original)
+++ 
incubator/wave/trunk/test/org/waveprotocol/box/server/rpc/AuthenticationServletTest.java
 Wed Oct 24 15:48:38 2012
@@ -37,6 +37,7 @@ import org.waveprotocol.box.server.authe
 import org.waveprotocol.box.server.authentication.SessionManager;
 import org.waveprotocol.box.server.persistence.AccountStore;
 import org.waveprotocol.box.server.persistence.memory.MemoryStore;
+import org.waveprotocol.box.server.robots.agent.welcome.WelcomeRobot;
 import org.waveprotocol.wave.model.wave.ParticipantId;
 import org.waveprotocol.wave.util.escapers.PercentEscaper;
 
@@ -63,6 +64,7 @@ public class AuthenticationServletTest e
   @Mock private HttpServletResponse resp;
   @Mock private HttpSession session;
   @Mock private SessionManager manager;
+  @Mock private WelcomeRobot welcomeBot;
 
   @Override
   protected void setUp() throws Exception {
@@ -73,9 +75,8 @@ public class AuthenticationServletTest e
         new HumanAccountDataImpl(USER, new 
PasswordDigest("password".toCharArray()));
     store.putAccount(account);
 
-    servlet =
-        new AuthenticationServlet(AuthTestUtil.makeConfiguration(), manager, 
"examPLe.com",
-            "UA-someid");
+    servlet = new AuthenticationServlet(store, 
AuthTestUtil.makeConfiguration(),
+        manager, "examPLe.com", false, "", false, false, welcomeBot, 
"UA-someid");
     AccountStoreHolder.init(store, "eXaMple.com");
   }
 


Reply via email to