/*******************************************************************************
* 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.Set;
import javax.servlet.annotation.WebServlet;
import co.mitro.core.accesscontrol.AuthenticatedDB;
import co.mitro.core.exceptions.MitroServletException;
import co.mitro.core.exceptions.PermissionException;
import co.mitro.core.exceptions.UnverifiedUserException;
import co.mitro.core.server.data.DBAcl;
import co.mitro.core.server.data.DBGroup;
import co.mitro.core.server.data.DBGroupSecret;
import co.mitro.core.server.data.DBGroupSecret.CRITICAL;
import co.mitro.core.server.data.DBIdentity;
import co.mitro.core.server.data.DBServerVisibleSecret;
import co.mitro.core.server.data.RPC;
import co.mitro.core.server.data.RPC.GetSecretResponse;
import co.mitro.core.server.data.RPC.MitroRPC;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@WebServlet("/api/GetSecret")
public class GetSecret extends MitroServlet {
private static final long serialVersionUID = 1L;
private static Set<String> viewOnlyPlatforms = ImmutableSet.of("ANDROID", "IOS");
private boolean criticalRequestOK(AuthenticatedDB udb, DBServerVisibleSecret svs, CRITICAL requestedAccess, MitroRequestContext context) throws SQLException, MitroServletException {
if (svs.isViewable() || (CRITICAL.NO_CRITICAL_DATA == requestedAccess)) {
return true;
}
if (CRITICAL.INCLUDE_CRITICAL_DATA_FOR_DISPLAY == requestedAccess) {
return (null != udb.getServerSecretForViewOrEdit(svs.getId()));
}
if (CRITICAL.INCLUDE_CRITICAL_DATA == requestedAccess) {
if (viewOnlyPlatforms.contains(context.platform)) {
// these platforms use the include_critical even for display, so we need to reject their requests.
return (null != udb.getServerSecretForViewOrEdit(svs.getId()));
}
}
return true;
}
@Override
protected MitroRPC processCommand(MitroRequestContext context)
throws IOException, SQLException, MitroServletException {
RPC.GetSecretRequest in = gson.fromJson(
context.jsonRequest,
RPC.GetSecretRequest.class);
// TODO: Remove this once the client code no longer sends this id
@SuppressWarnings("deprecation")
String deprecatedId = in.userId;
throwIfRequestorDoesNotEqualUserId(context.requestor, deprecatedId);
@SuppressWarnings("deprecation")
AuthenticatedDB udb = AuthenticatedDB.deprecatedNew(context.manager, context.requestor);
DBServerVisibleSecret svs = null;
svs = context.manager.svsDao.queryForId(in.secretId);
if (svs == null) {
throw new MitroServletException("Invalid secret: " + in.secretId);
}
CRITICAL includeCritical = CRITICAL.fromClientString(in.includeCriticalData);
if (!criticalRequestOK(udb, svs, includeCritical, context)) {
throw new PermissionException("you do not have permission to view the password");
}
DBGroupSecret secret = null;
if (null == in.groupId) {
// Find any group we share with the secret
String getGroupSecretIdQuery = "SELECT group_secret.id FROM group_secret, acl WHERE "
+ " acl.group_id = group_secret.group_id and acl.member_identity = " + context.requestor.getId()
+ " AND \"serverVisibleSecret_id\" = "+ in.secretId + " LIMIT 1";
List<String[]> groupSecretResults =
Lists.newArrayList(context.manager.groupSecretDao.queryRaw(getGroupSecretIdQuery));
for (String[] row : groupSecretResults) {
secret = context.manager.groupSecretDao.queryForId(Integer.parseInt(row[0], 10));
}
if (null == secret) {
throw new MitroServletException("you do not have access to secret " + in.secretId);
}
} else {
for (DBGroupSecret gs : svs.getGroupSecrets()) {
if (gs.getGroup().getId() == in.groupId) {
secret = gs;
break;
}
}
if (null == secret) {
throw new MitroServletException("secret id " + in.secretId + " does not exist in group " +
in.groupId + " (perhaps you have the wrong group?)");
}
}
// can the user actually access this group?
// TODO: Create a data access API that forces ACL checks; similar code in AddSecret.
Set<DBIdentity> users = Sets.newHashSet();
Set<DBGroup> groups = Sets.newHashSet();
DBGroup g = secret.getGroup();
context.manager.groupDao.refresh(g);
// List users and groups in *this specific* group; not all with access to the secret
g.addTransitiveGroupsAndUsers(context.manager, DBAcl.allAccessTypes(), users, groups);
if (!users.contains(context.requestor)) {
throw new MitroServletException("user does not have permission to access group");
}
if (!context.requestor.isVerified()) {
// unverified users cannot access secret if shared with users or groups
boolean forbidAccess = users.size() > 1;
if (!forbidAccess) {
// If the secret is shared with multiple groups, forbid access. The secret could be shared
// with me, and a group containing only me, but whatever.
// TODO: Make this a database COUNT query
int secretGroupCount = context.manager.groupSecretDao.queryForEq(
DBGroupSecret.SVS_ID_NAME, svs.getId()).size();
forbidAccess = secretGroupCount > 1;
}
if (forbidAccess) {
throw new UnverifiedUserException("cannot list this secret because your account " + context.requestor.getName() + " is unverified and it is shared with others");
}
}
GetSecretResponse out = new RPC.GetSecretResponse();
out.groupId = g.getId();
for (DBAcl acl : g.getAcls()) {
if (acl.getMemberIdentityIdAsInteger() != null && acl.getMemberIdentityIdAsInteger() == context.requestor.getId()) {
out.encryptedGroupKey = acl.getGroupKeyEncryptedForMe();
break;
}
}
assert (out.encryptedGroupKey != null);
secret.addToRpcSecret(
context.manager,
out.secret,
includeCritical,
context.requestor.getName());
out.secret.canEditServerSecret = (null != udb.getServerSecretForViewOrEdit(svs.getId()));
return out;
}
}