/*******************************************************************************
* Copyright (c) 2013, 2014 Lectorius, Inc.
* Authors:
* Vijay Pandurangan (vijayp@mitro.co)
* Evan Jones (ej@mitro.co)
* Adam Hilss (ahilss@mitro.co)
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can contact the authors at inbound@mitro.co.
*******************************************************************************/
package co.mitro.core.servlets;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.annotation.WebServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.mitro.core.crypto.KeyInterfaces.CryptoError;
import co.mitro.core.crypto.KeyInterfaces.KeyFactory;
import co.mitro.core.crypto.KeyInterfaces.PrivateKeyInterface;
import co.mitro.core.crypto.KeyInterfaces.PublicKeyInterface;
import co.mitro.core.exceptions.MitroServletException;
import co.mitro.core.exceptions.UserVisibleException;
import co.mitro.core.server.Manager;
import co.mitro.core.server.ManagerFactory;
import co.mitro.core.server.data.DBAcl;
import co.mitro.core.server.data.DBAcl.CyclicGroupError;
import co.mitro.core.server.data.DBAudit;
import co.mitro.core.server.data.DBEmailQueue;
import co.mitro.core.server.data.DBGroup;
import co.mitro.core.server.data.DBIdentity;
import co.mitro.core.server.data.RPC.InviteNewUserRequest;
import co.mitro.core.server.data.RPC.InviteNewUserResponse;
import co.mitro.core.server.data.RPC.MitroRPC;
import co.mitro.core.util.Random;
import com.google.common.collect.Maps;
@WebServlet("/api/InviteNewUsers")
public class InviteNewUser extends MitroServlet {
private static final Logger logger = LoggerFactory.getLogger(InviteNewUser.class);
private static final long serialVersionUID = 1L;
// 12 chars = basically impossible to brute force
// http://blog.agilebits.com/2013/04/16/1password-hashcat-strong-master-passwords/
static final int GENERATED_PASSWORD_LENGTH = 12;
// Allow dependencies to be injected for testing
public InviteNewUser(ManagerFactory managerFactory, KeyFactory keyFactory) {
super(managerFactory, keyFactory);
}
@Override
protected MitroRPC processCommand(
MitroRequestContext context)
throws IOException, SQLException, MitroServletException {
InviteNewUserRequest request = gson.fromJson(context.jsonRequest, InviteNewUserRequest.class);
InviteNewUserResponse response = new InviteNewUserResponse();
response.publicKeys = Maps.newHashMap();
try {
for (String address : request.emailAddresses) {
DBIdentity invitee = inviteNewUser(context.manager, context.requestor, address);
response.publicKeys.put(address, invitee.getPublicKeyString());
}
} catch (CryptoError|CyclicGroupError e) {
throw new MitroServletException(e);
}
return response;
}
protected DBIdentity inviteNewUser(Manager manager, DBIdentity requestor, String emailAddress)
throws CryptoError, SQLException, MitroServletException, CyclicGroupError {
return InviteNewUser.inviteNewUser(keyFactory, manager, requestor, emailAddress);
}
/**
* Creates a new identity for a user that doesn't exist, generating a key and password.
* Queues an email to the user with their temporary password.
*/
public static DBIdentity inviteNewUser(
KeyFactory keyFactory, Manager manager, DBIdentity requestor, String emailAddress)
throws CryptoError, SQLException, MitroServletException, CyclicGroupError {
if (!Util.isEmailAddress(emailAddress)) {
throw new UserVisibleException("Invalid email address: " + emailAddress);
}
final String reqEmail = requestor.getName();
assert Util.isEmailAddress(reqEmail);
// generate a key
logger.debug("generating key for address {}", emailAddress);
PrivateKeyInterface privateKey = keyFactory.generate();
PublicKeyInterface publicKey = privateKey.exportPublicKey();
// generate a password
String generatedPassword = Random.makeRandomAlphanumericString(GENERATED_PASSWORD_LENGTH);
// Create the identity; we rely on the unique name constraint
DBIdentity identity = new DBIdentity();
// identites that are invited are by default verified by email
identity.setVerified(true);
identity.setName(emailAddress);
identity.setChangePasswordOnNextLogin(true);
identity.setPublicKeyString(publicKey.toString());
identity.setEncryptedPrivateKeyString(privateKey.exportEncrypted(generatedPassword));
DBIdentity.createUserInDb(manager, identity);
manager.identityDao.refresh(identity);
// Create a private group
PrivateKeyInterface groupKey = keyFactory.generate();
PublicKeyInterface publicGroupKey = groupKey.exportPublicKey();
DBGroup privateGroup = new DBGroup();
privateGroup.setName("");
privateGroup.setPublicKeyString(publicGroupKey.toString());
privateGroup.setAutoDelete(false);
manager.groupDao.create(privateGroup);
manager.groupDao.refresh(privateGroup);
// Encrypt the key for the user
DBAcl acl = new DBAcl();
acl.setGroup(privateGroup);
acl.setMemberIdentity(identity);
acl.setLevel(DBAcl.AccessLevelType.ADMIN);
acl.setGroupKeyEncryptedForMe(publicKey.encrypt(groupKey.toString()));
manager.aclDao.create(acl);
manager.addAuditLog(DBAudit.ACTION.INVITE_NEW_USER, requestor, identity, privateGroup, null, null);
// send an email
DBEmailQueue email =
DBEmailQueue.makeInvitation(reqEmail, emailAddress, generatedPassword);
manager.emailDao.create(email);
return identity;
}
}