/******************************************************************************* * 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 static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; import java.util.List; import org.junit.Before; import org.junit.Test; import com.google.common.collect.Lists; import co.mitro.core.exceptions.MitroServletException; import co.mitro.core.server.data.DBAcl; import co.mitro.core.server.data.DBAcl.AccessLevelType; 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.DBGroupSecret; import co.mitro.core.server.data.DBGroupSecret.CRITICAL; import co.mitro.core.server.data.RPC.EditSecretRequest; 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.servlets.MitroServlet.MitroRequestContext; public class GetSecretTest extends OrganizationsFixture { private int uniqueIds = 1000; private GetSecret servlet; private RPC.GetSecretRequest request; /** Secret that is part of testGroup (testIdentity has ADMIN access). */ private DBServerVisibleSecret testServerVisibleSecret; /** * Returns an id not shared in the DB. Catches bugs where we used ids from the wrong table. */ private int getUniqueId() { uniqueIds += 1; return uniqueIds; } private RPC.GetSecretResponse processCommand(DBIdentity requestor) throws IOException, SQLException, MitroServletException { RPC.GetSecretResponse rval = (RPC.GetSecretResponse) servlet.processCommand(new MitroRequestContext(requestor, gson.toJson(request), manager, null)); assertTrue(rval.encryptedGroupKey != null); return rval; } @Before public void setUp() throws SQLException { servlet = new GetSecret(); request = new RPC.GetSecretRequest(); testServerVisibleSecret = createSecret(testGroup, "group1", "critical1", null); } @Test public void testLimitedDisplay() throws IOException, SQLException, CyclicGroupError, MitroServletException { // TODO: test edit secret testServerVisibleSecret.setViewable(false); manager.svsDao.update(testServerVisibleSecret); // fetch secret via group 1 request.groupId = testGroup.getId(); request.secretId = testServerVisibleSecret.getId(); request.includeCriticalData = "display"; addToGroup(manager, this.testIdentity2, testGroup, AccessLevelType.ADMIN); try { servlet.processCommand(new MitroRequestContext(testIdentity, gson.toJson(request), manager, null)); fail("expected exception"); } catch (MitroServletException e) { assertTrue(e.getMessage().contains("you do not have permission to view the password")); } EditSecret editSecretServlet = new EditSecret(); EditSecretRequest esRequest = new EditSecretRequest(); esRequest.isViewable = true; esRequest.secretId = testServerVisibleSecret.getId(); // add the secret to the org. addOrgToGroup(manager, org, testGroup, AccessLevelType.ADMIN); addSecretToGroup(testServerVisibleSecret, org, "", ""); assertEquals(true, org.isTopLevelOrganization()); // TODO: // this line should be removed once the buggy DBAcl::addTransitiveGroupsAndUsers is fixed request.groupId = org.getId(); // // admin should now be able to view the secret. servlet.processCommand(new MitroRequestContext(admins.iterator().next(), gson.toJson(request), manager, null)); // ti2 cannot view the secret, or edit it. try { servlet.processCommand(new MitroRequestContext(testIdentity2, gson.toJson(request), manager, null)); fail("expected exception"); } catch (MitroServletException e) { assertTrue(e.getMessage().contains("you do not have permission to view the password")); } try { editSecretServlet.processCommand(new MitroRequestContext(testIdentity2, gson.toJson(esRequest), manager, null)); fail("expected exception"); } catch (MitroServletException e) { assertTrue(e.getMessage().contains("could not access secret for udpate")); } // testidentity1 should be able to edit the secret editSecretServlet.processCommand(new MitroRequestContext(testIdentity, gson.toJson(esRequest), manager, null)); request.groupId = testGroup.getId(); // now ti2 should be able to view the secret servlet.processCommand(new MitroRequestContext(testIdentity2, gson.toJson(request), manager, null)); } @Test public void testSingleSecretMultipleGroups() throws Exception { // create a secret in two groups, with totally unique ids // hack testGroup to change its id (we had a bug where we used the wrong ids) manager.groupDao.updateId(testGroup, getUniqueId()); addToGroup(testIdentity, testGroup, AccessLevelType.ADMIN); DBGroup g2 = new DBGroup(); g2.setName("g2"); g2.setId(getUniqueId()); g2.setPublicKeyString("public key"); manager.groupDao.create(g2); addToGroup(testIdentity, g2, AccessLevelType.ADMIN); DBServerVisibleSecret secret = createSecret(testGroup, "group1", "critical1", null); // hack ids to be unique (previously had a bug where we used wrong ids) DBGroupSecret g1Secret = manager.getGroupSecret(testGroup, secret); manager.svsDao.updateId(secret, getUniqueId()); manager.svsDao.refresh(secret); g1Secret.setServerVisibleSecret(secret); manager.groupSecretDao.update(g1Secret); manager.groupSecretDao.updateId(g1Secret, getUniqueId()); // also put the secret in group 2 DBGroupSecret g2Secret = addSecretToGroup(secret, g2, "group2", "critical2"); manager.groupSecretDao.updateId(g2Secret, getUniqueId()); // fetch secret via group 1 request.groupId = testGroup.getId(); request.secretId = secret.getId(); request.includeCriticalData = CRITICAL.INCLUDE_CRITICAL_DATA.getClientString(); manager.setRequestor(testIdentity, null); RPC.GetSecretResponse response = processCommand(testIdentity); assertEquals("group1", response.secret.encryptedClientData); assertEquals("critical1", response.secret.encryptedCriticalData); assertEquals(secret.getId(), response.secret.secretId); request.groupId = null; response = processCommand(testIdentity); // either order is possible assertTrue("group1".equals(response.secret.encryptedClientData) || "group2".equals(response.secret.encryptedClientData)); assertEquals(secret.getId(), response.secret.secretId); // fetch secret via group 2 request.groupId = g2.getId(); response = processCommand(testIdentity); assertEquals("critical2", response.secret.encryptedCriticalData); List<DBAudit> audits = DBAudit.getAllActionsByUser(manager, testIdentity); assertEquals(3, audits.size()); assertEquals(audits.get(0).getAction(), DBAudit.ACTION.GET_SECRET_WITH_CRITICAL); assertEquals(audits.get(0).getTargetSVS().getId(), secret.getId()); assertEquals(audits.get(1).getAction(), DBAudit.ACTION.GET_SECRET_WITH_CRITICAL); assertEquals(audits.get(1).getTargetSVS().getId(), secret.getId()); // fetch secret via non-existent group request.groupId = uniqueIds + 50; try { servlet.processCommand(new MitroRequestContext(testIdentity, gson.toJson(request), manager, null)); fail("expected exception"); } catch (MitroServletException e) { assertTrue(e.getMessage().contains("does not exist")); } } @SuppressWarnings("deprecation") @Test public void deprecatedUserId() throws Exception { // mismatched inner and outer identities request.userId = testIdentity2.getName(); request.groupId = testGroup.getId(); request.secretId = testServerVisibleSecret.getId(); try { processCommand(testIdentity); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("User ID does not match")); } // correct userId works request.userId = testIdentity.getName(); RPC.GetSecretResponse response = processCommand(testIdentity); assertEquals("group1", response.secret.encryptedClientData); } // Can only access secrets if you have permission. Verifies iSEC-MITRO-WAPT-2013-2 @Test public void secretAccessControl() throws Exception { // requesting secret as testIdentity2 doesn't work: no access! request.groupId = testGroup.getId(); request.secretId = testServerVisibleSecret.getId(); try { processCommand(testIdentity2); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("does not have permission")); } try { request.groupId = null; processCommand(testIdentity2); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("you do not have access")); } // secret is not in group2 DBGroup group2 = createGroupContainingIdentity(testIdentity2); request.groupId = group2.getId(); try { processCommand(testIdentity2); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("does not exist in group")); } // bad group request.groupId = getUniqueId(); try { processCommand(testIdentity); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("does not exist in group")); } // bad secret id request.groupId = testGroup.getId(); request.secretId = getUniqueId(); try { processCommand(testIdentity2); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("Invalid secret")); } } // Can access individual secrets but not shared secrets if account is not verified @Test public void unverifiedIdentity() throws Exception { testIdentity.setVerified(false); manager.identityDao.update(testIdentity); // works: only shared with testIdentity request.groupId = testGroup.getId(); request.secretId = testServerVisibleSecret.getId(); RPC.GetSecretResponse response = processCommand(testIdentity); assertEquals("group1", response.secret.encryptedClientData); // fails: shared secret addToGroup(testIdentity2, testGroup, AccessLevelType.ADMIN); try { processCommand(testIdentity); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("unverified")); } // Remove testIdentity2, add the secret to a group containing testIdentity2 boolean found = true; for (DBAcl acl : testGroup.getAcls()) { if (acl.getMemberIdentityId().getId() == testIdentity2.getId()) { found = true; manager.aclDao.delete(acl); break; } } assertTrue(found); DBGroup testId2Group = createGroupContainingIdentity(testIdentity2); addSecretToGroup(testServerVisibleSecret, testId2Group, "client", "critical"); // should not work: shared! try { processCommand(testIdentity); fail("expected exception"); } catch (MitroServletException e) { assertThat(e.getMessage(), containsString("unverified")); } } @Test public void testOrgGroupFirst() throws Exception { // get a secret via an org group, where the org ACL appears before the identity // at one point this caused a NullPointerException // the bug was order dependent, so the ACL on orgGroup is in order: // testIdentity, organization, user2 // we fetch both user2 and user1 so hopefully one will trigger it Iterator<DBIdentity> iterator = outsiders.iterator(); DBIdentity user1 = iterator.next(); DBIdentity user2 = iterator.next(); DBGroup org = createOrganization( user1, "org", Lists.newArrayList(user1), Lists.newArrayList(user2)); DBGroup orgGroup = createGroupContainingIdentity(user1); addOrgToGroup(manager, org, orgGroup, AccessLevelType.ADMIN); addToGroup(user2, orgGroup, AccessLevelType.ADMIN); addSecretToGroup(testServerVisibleSecret, orgGroup, "client", "critical"); request.secretId = testServerVisibleSecret.getId(); processCommand(user2); processCommand(user1); } }