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