/*******************************************************************************
* 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 java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.annotation.WebServlet;
import co.mitro.core.accesscontrol.AuthenticatedDB;
import co.mitro.core.crypto.KeyInterfaces.KeyFactory;
import co.mitro.core.exceptions.InvalidRequestException;
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.DBGroup;
import co.mitro.core.server.data.DBIdentity;
import co.mitro.core.server.data.RPC;
import co.mitro.core.server.data.RPC.CreateOrganizationRequest.PrivateGroupKeys;
import co.mitro.core.server.data.RPC.CreateOrganizationResponse;
import co.mitro.core.server.data.RPC.MitroRPC;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
@WebServlet("/api/CreateOrganization")
public class CreateOrganization extends MitroServlet {
private static final long serialVersionUID = 1L;
public CreateOrganization(ManagerFactory managerFactory, KeyFactory keyFactory) {
super(managerFactory, keyFactory);
}
/** Returns an error message if request is not valid. Checks basic request formatting. */
static String validateRequest(RPC.CreateOrganizationRequest request) {
if (Strings.isNullOrEmpty(request.name)) {
return "name cannot be empty";
}
if (Strings.isNullOrEmpty(request.publicKey)) {
return "publicKey cannot be empty";
}
if (request.adminEncryptedKeys == null || request.adminEncryptedKeys.isEmpty()) {
return "adminEncryptedKeys cannot be empty";
}
if (request.memberGroupKeys == null || request.memberGroupKeys.isEmpty()) {
return "memberGroupKeys cannot be empty";
}
// ensure that every admin is also a member
SetView<String> adminsNotInMembers = Sets.difference(
request.adminEncryptedKeys.keySet(), request.memberGroupKeys.keySet());
if (!adminsNotInMembers.isEmpty()) {
return "each admin must be a member";
}
return null;
}
@Override
protected MitroRPC processCommand(MitroRequestContext context)
throws IOException, SQLException, MitroServletException {
try {
RPC.CreateOrganizationRequest request = gson.fromJson(context.jsonRequest,
RPC.CreateOrganizationRequest.class);
String errorMessage = validateRequest(request);
if (errorMessage != null) {
// some errors shouldn't be shown, but "name cannot be empty" should
throw new UserVisibleException(errorMessage);
}
// this user must not already have an organization with this name.
@SuppressWarnings("deprecation")
AuthenticatedDB userDb = AuthenticatedDB.deprecatedNew(context.manager, context.requestor);
Set<DBGroup> organizations = userDb.getOrganizations();
for (DBGroup g : organizations) {
if (g.getName().equals(request.name)) {
throw new InvalidRequestException("duplicate organization name");
}
}
// Build the organization's group (contains admins)
DBGroup organizationGroup = new DBGroup();
organizationGroup.setType(DBGroup.Type.TOP_LEVEL_ORGANIZATION);
organizationGroup.setName(request.name);
organizationGroup.setAutoDelete(false);
organizationGroup.setPublicKeyString(request.publicKey);
organizationGroup.setScope(null);
organizationGroup.setSignatureString(null);
// Add all admins to the ACL (AuthenicatedDB.saveNewGroupWithAcls checks duplicates)
List<DBAcl> organizationAcls = makeAdminAclsForOrganization(userDb, organizationGroup, request.adminEncryptedKeys);
// actually create the organization
userDb.saveNewGroupWithAcls(organizationGroup, organizationAcls);
context.manager.groupDao.update(organizationGroup);
// create private groups for each member
addMembersToOrganization(userDb, organizationGroup, request.memberGroupKeys, context.manager);
String auditNote = request.memberGroupKeys.size() + " total members";
context.manager.addAuditLog(
DBAudit.ACTION.CREATE_ORGANIZATION, null, null, organizationGroup, null, auditNote);
CreateOrganizationResponse out = new RPC.CreateOrganizationResponse();
out.organizationGroupId = organizationGroup.getId();
return out;
} catch (CyclicGroupError e) {
throw new MitroServletException(e);
}
}
@SuppressWarnings("deprecation")
static void addMembersToOrganization(AuthenticatedDB userDb,
DBGroup organizationGroup, Map<String, PrivateGroupKeys> memberGroupKeys, Manager manager)
throws SQLException, CyclicGroupError, MitroServletException {
for (Map.Entry<String, PrivateGroupKeys> memberEntry : memberGroupKeys.entrySet()) {
PrivateGroupKeys privateGroupKeys = memberEntry.getValue();
DBGroup memberGroup = new DBGroup();
memberGroup.setPrivateGroup();
memberGroup.setPublicKeyString(privateGroupKeys.publicKey);
memberGroup.setScope(null);
memberGroup.setSignatureString(null);
assert (!Strings.isNullOrEmpty(memberEntry.getKey()));
DBIdentity memberIdentity = userDb.getIdentity(memberEntry.getKey());
// create the user's ACL
DBAcl userAcl = new DBAcl();
userAcl.setGroup(memberGroup);
userAcl.setLevel(DBAcl.AccessLevelType.MODIFY_SECRETS_BUT_NOT_MEMBERSHIP);
userAcl.setMemberIdentity(memberIdentity);
userAcl.setGroupKeyEncryptedForMe(privateGroupKeys.keyEncryptedForUser);
// create the ACL granting the organization access
DBAcl organizationAcl = new DBAcl();
organizationAcl.setGroup(memberGroup);
organizationAcl.setLevel(DBAcl.AccessLevelType.ADMIN);
organizationAcl.setMemberGroup(organizationGroup);
organizationAcl.setGroupKeyEncryptedForMe(privateGroupKeys.keyEncryptedForOrganization);
userDb.saveNewGroupWithAcls(memberGroup, ImmutableList.of(userAcl, organizationAcl));
}
}
static List<DBAcl> makeAdminAclsForOrganization(AuthenticatedDB userDb,
DBGroup organizationGroup, Map<String, String> adminEncryptedKeys) throws SQLException, CyclicGroupError {
List<DBAcl> organizationAcls = Lists.newArrayList();
for (Map.Entry<String, String> adminEntry : adminEncryptedKeys.entrySet()) {
DBIdentity identity = userDb.getIdentity(adminEntry.getKey());
String keyEncryptedForUser = adminEntry.getValue();
DBAcl acl = new DBAcl();
acl.setGroup(organizationGroup);
acl.setLevel(DBAcl.AccessLevelType.ADMIN);
acl.setMemberIdentity(identity);
acl.setGroupKeyEncryptedForMe(keyEncryptedForUser);
organizationAcls.add(acl);
}
return organizationAcls;
}
}