/** * Copyright 2010 Google Inc. * * 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.authentication; import com.google.common.base.Preconditions; import org.waveprotocol.box.server.account.AccountData; import org.waveprotocol.box.server.persistence.AccountStore; import org.waveprotocol.box.server.persistence.PersistenceException; import org.waveprotocol.wave.model.wave.InvalidParticipantAddress; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.util.logging.Log; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; /** * A Simple login module which does username & password authentication against * users in a database. * * This code is based on the example here: * http://java.sun.com/developer/technicalArticles/Security/jaasv2/ * * @author josephg@gmail.com (Joseph Gentle) */ public class AccountStoreLoginModule implements LoginModule { private Subject subject; private ParticipantPrincipal principal; private CallbackHandler callbackHandler; private AccountStore accountStore; private final Log LOG = Log.get(AccountStoreLoginModule.class); enum Status { NOT, OK, COMMIT } private Status status; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { Preconditions.checkNotNull(callbackHandler, "Callback handler null"); accountStore = AccountStoreHolder.getAccountStore(); status = Status.NOT; this.subject = subject; this.callbackHandler = callbackHandler; } @Override public boolean login() throws LoginException { NameCallback nameCallback = new NameCallback("Username"); PasswordCallback passwordCallback = new PasswordCallback("Password", false); Callback callbacks[] = new Callback[] {nameCallback, passwordCallback}; try { callbackHandler.handle(callbacks); } catch (java.io.IOException e) { throw new LoginException(e.toString()); } catch (UnsupportedCallbackException e) { throw new LoginException("Error: " + e.getCallback().toString()); } boolean success; ParticipantId id = null; String address = nameCallback.getName(); if (!address.contains(ParticipantId.DOMAIN_PREFIX)) { address = address + ParticipantId.DOMAIN_PREFIX + AccountStoreHolder.getDefaultDomain(); } try { id = ParticipantId.of(address); AccountData account = accountStore.getAccount(id); char[] password = passwordCallback.getPassword(); if (account == null) { // The user doesn't exist. Auth failed. success = false; } else if (!account.isHuman()) { // The account is owned by a robot. Auth failed. success = false; } else if (password == null) { // Null password provided by callback. We require a password (even an empty one). success = false; } else if (!account.asHuman().getPasswordDigest().verify(password)) { // The supplied password doesn't match. Auth failed. success = false; } else { success = true; } } catch (InvalidParticipantAddress e) { // The supplied user address is invalid. Auth failed. success = false; } catch (PersistenceException e) { LOG.severe("Failed to retreive account data for " + id, e); throw new LoginException( "An unexpected error occured while trying to retrieve account information!"); } // The password is zeroed before it gets GC'ed for memory security. passwordCallback.clearPassword(); if (success) { principal = new ParticipantPrincipal(id); status = Status.OK; return true; } else { return false; } } @Override public boolean commit() { if (status == Status.NOT) { return false; } if (subject == null) { return false; } subject.getPrincipals().add(principal); status = Status.COMMIT; return true; } @Override public boolean abort() { if ((subject != null) && (principal != null)) { subject.getPrincipals().remove(principal); } principal = null; status = Status.NOT; return true; } @Override public boolean logout() { subject.getPrincipals().remove(principal); status = Status.NOT; return true; } }