From 2f0c27184f610c0de9b913f3b03ad4c7fc4a005d Mon Sep 17 00:00:00 2001 From: remillet Date: Sat, 13 Jan 2018 03:51:11 -0800 Subject: [PATCH] DRYD-221: Roles and perms refactoring of transaction handling. --- .../xmlreplay/ServiceResult.java | 1 + .../IntegrationTests/xmlreplay/XmlReplay.java | 54 ++-- .../batch/accountroles-post-bigbird.xml | 2 +- .../batch/permissionroles-post-bird.xml | 2 +- .../xmlreplay/batch/role-create-bird.xml | 2 +- .../test-data/xmlreplay/security.xml | 61 +++- .../security/1-bigbird-permission.xml | 29 +- .../test-data/xmlreplay/security/1-vocab.xml | 14 + .../security/10-permissionroles-elmo.xml | 2 +- .../security/11-bigbird-permission-CRU.xml | 26 +- .../11-permissionroles-bigbird-CRU.xml | 2 +- .../security/12-bigbird-permission-R.xml | 14 +- .../security/12-permissionroles-bigbird-R.xml | 2 +- .../security/13-permissionroles-bigbird.xml | 2 +- .../security/14-permissionroles-bogus.xml | 13 + .../xmlreplay/security/3-role-test-cm.xml | 9 +- .../security/3a-update-role-test-cm.xml | 2 +- .../xmlreplay/security/4-role-intern.xml | 2 +- .../security/7-accountroles-bigbird.xml | 2 +- .../security/8-account-roles-elmo.xml | 2 +- .../security/9-permissionroles-bigbird.xml | 2 +- .../security/create-account-grover.xml | 16 + .../security/res/get1-loansin-CRUDL.res.xml | 37 +++ .../xmlreplay/security/slipOutPerm-update.xml | 8 + .../xmlreplay/security/slipOutPerm.xml | 11 + .../xmlreplay/security/slipOutRole.xml | 7 + .../xmlreplay/security/slipOutVocab-1.xml | 14 + .../xmlreplay/security/slipOutVocab-2.xml | 14 + .../src/main/resources/accounts_common.xsd | 4 +- .../main/resources/db/postgresql/account.sql | 8 +- .../services/account/AccountResource.java | 166 +++++++---- .../account/AccountRoleSubResource.java | 20 +- .../storage/AccountDocumentHandler.java | 13 +- .../storage/AccountRoleDocumentHandler.java | 81 +++-- .../account/storage/AccountStorageClient.java | 15 +- .../storage/csidp/TokenStorageClient.java | 91 +++--- .../collectionspace/authentication/AuthN.java | 12 + .../services/client/PermissionClient.java | 2 + .../services/client/RoleClient.java | 7 +- .../test/PermissionRoleServiceTest.java | 4 +- .../test/RolePermissionServiceTest.java | 2 +- .../client/test/RoleServiceTest.java | 18 +- .../driver/AuthorizationSeedDriver.java | 13 +- .../importer/AuthorizationGen.java | 81 +++-- .../importer/AuthorizationSeed.java | 2 +- .../services/authorization/RoleResource.java | 42 ++- .../PermissionDocumentHandler.java.txt | 263 +--------------- .../storage/PermissionValidatorHandler.java | 37 ++- .../storage/RoleDocumentHandler.java | 8 + .../storage/RoleValidatorHandler.java | 16 +- .../resources/db/postgresql/authorization.sql | 24 +- .../services/authorization/AuthZ.java | 18 +- .../spi/CSpacePermissionManager.java | 2 +- .../spring/SpringPermissionManager.java | 20 +- .../services/common/api/Tools.java | 10 + .../authorization/PermissionResource.java | 280 ++++++++++++++---- .../PermissionRoleSubResource.java | 28 +- .../storage/AuthorizationDelegate.java | 71 +++-- .../storage/PermissionDocumentHandler.java | 92 +++--- .../PermissionRoleDocumentHandler.java | 85 ++++-- .../storage/PermissionStorageConstants.java | 3 + .../storage/RoleStorageConstants.java | 4 +- .../services/common/SecurityResourceBase.java | 80 ++++- .../AuthorizationCommon.java | 280 +++++++++--------- .../AuthorizationRoleRel.java | 19 ++ .../authorization_mgt/PermissionRoleUtil.java | 139 +++++++-- .../context/RemoteServiceContextImpl.java | 25 +- .../common/context/ServiceContext.java | 2 +- .../document/AbstractDocumentHandlerImpl.java | 5 +- .../document/InconsistentStateException.java | 23 ++ .../services/common/document/JaxbUtils.java | 41 ++- .../common/document/TransactionException.java | 4 - .../common/storage/StorageClient.java | 3 + .../common/storage/TransactionContext.java | 3 + .../storage/jpa/JPATransactionContext.java | 73 ++++- .../jpa/JpaRelationshipStorageClient.java | 64 ++-- .../storage/jpa/JpaStorageClientImpl.java | 49 ++- .../common/storage/jpa/JpaStorageUtils.java | 1 + .../client/java/RepositoryClientImpl.java | 9 + .../src/main/resources/permissions.xsd | 36 ++- 80 files changed, 1712 insertions(+), 1038 deletions(-) create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-vocab.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/14-permissionroles-bogus.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/create-account-grover.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/res/get1-loansin-CRUDL.res.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm-update.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutRole.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-1.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-2.xml create mode 100644 services/common/src/main/java/org/collectionspace/services/common/document/InconsistentStateException.java diff --git a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/ServiceResult.java b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/ServiceResult.java index e4e602e02..4736f5da4 100644 --- a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/ServiceResult.java +++ b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/ServiceResult.java @@ -54,6 +54,7 @@ public class ServiceResult { public String error = ""; public String fromTestID = ""; public String auth = ""; + public String adminAuth = ""; public String boundary = ""; public String payloadStrictness = ""; public long contentLength = 0; diff --git a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java index 865ffd49a..b9ae62c5c 100644 --- a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java +++ b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java @@ -16,6 +16,7 @@ import java.util.*; * See example usage in calling class XmlReplayTest in services/IntegrationTests, and also in main() in this class. * @author Laramie Crocker */ +@SuppressWarnings("unchecked") public class XmlReplay { public XmlReplay(String basedir, String reportsDir){ @@ -93,7 +94,8 @@ public class XmlReplay { return reportsList; } - public String toString(){ + @Override + public String toString(){ return "XmlReplay{"+this.basedir+", "+this.defaultAuthsMap+", "+this.dump+", "+this.reportsDir+'}'; } @@ -151,7 +153,7 @@ public class XmlReplay { /** Creates new instances of XmlReplay, one for each controlFile specified in the master, * and setting defaults from this instance, but not sharing ServiceResult objects or maps. */ - public List> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception { + public List> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception { List> list = new ArrayList>(); org.dom4j.Document document; if (readOptionsFromMaster){ @@ -229,8 +231,8 @@ public class XmlReplay { return runTests(testGroupID, ""); } - public List autoDelete(String logName){ - return autoDelete(this.serviceResultsMap, logName, 0); + public List autoDelete(String logName){ + return autoDelete(this.defaultAuthsMap, this.serviceResultsMap, logName, 0); } /** Use this method to clean up resources created on the server that returned CSIDs, if you have @@ -238,14 +240,14 @@ public class XmlReplay { * @param serviceResultsMap a Map of ServiceResult objects, which will contain ServiceResult.deleteURL. * @return a List of debug info about which URLs could not be deleted. */ - private static List autoDelete(Map serviceResultsMap, String logName, int reattempt) { + private static List autoDelete(AuthsMap defaultAuths, Map serviceResultsMap, String logName, int reattempt) { List results = new ArrayList(); HashMap reattemptList = new HashMap(); int deleteFailures = 0; for (ServiceResult pr : serviceResultsMap.values()) { try { if (pr.autoDelete == true && Tools.notEmpty(pr.deleteURL)){ - ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.auth, pr.testID, "[autodelete:"+logName+"]"); + ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, defaultAuths.getDefaultAuth(), pr.testID, "[autodelete:"+logName+"]"); if (deleteResult.gotExpectedResult() == false || deleteResult.responseCode != 200) { reattemptList.put(REATTEMPT_KEY + deleteFailures++, pr); // We need to try again after our dependents have been deleted. cow() } @@ -276,7 +278,7 @@ public class XmlReplay { // our MAX_REATTEMPTS limit. // if (reattemptList.size() > 0 && reattempt < MAX_REATTEMPTS) { - return autoDelete(reattemptList, logName, ++reattempt); // recursive call + return autoDelete(defaultAuths, reattemptList, logName, ++reattempt); // recursive call } return results; @@ -288,7 +290,8 @@ public class XmlReplay { public String getDefaultAuth(){ return map.get(defaultID); } - public String toString(){ + @Override + public String toString(){ return "AuthsMap: {default='"+defaultID+"'; "+map.keySet()+'}'; } } @@ -314,7 +317,8 @@ public class XmlReplay { public boolean payloads = false; //public static final ServiceResult.DUMP_OPTIONS dumpServiceResultOptions = ServiceResult.DUMP_OPTIONS; public ServiceResult.DUMP_OPTIONS dumpServiceResult = ServiceResult.DUMP_OPTIONS.minimal; - public String toString(){ + @Override + public String toString(){ return "payloads: "+payloads+" dumpServiceResult: "+dumpServiceResult; } } @@ -426,7 +430,7 @@ public class XmlReplay { byte[] b = FileUtils.readFileToByteArray(new File(expectedResponseParts.responseFilename)); String expectedPartContent = new String(b); Map vars = expectedResponseParts.varsList.get(0); //just one part, so just one varsList. Must be there, even if empty. - expectedPartContent = evalStruct.eval(expectedPartContent, serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc); + expectedPartContent = XmlReplayEval.eval(expectedPartContent, serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc); serviceResult.expectedContentExpanded = expectedPartContent; String label = "NOLABEL"; String leftID = "{from expected part, label:"+label+" filename: "+expectedResponseParts.responseFilename+"}"; @@ -556,7 +560,7 @@ public class XmlReplay { } else { tests = testgroup.selectNodes("test"); } - String authForTest = ""; + String authForTest = ""; // reset auth for each test group int testElementIndex = -1; for (Node testNode : tests) { @@ -587,12 +591,10 @@ public class XmlReplay { if (Tools.notEmpty(authIDForTest)){ currentAuthForTest = authsMap.map.get(authIDForTest); - } - else { + } else { String tokenAuthExpression = testNode.valueOf("@tokenauth"); - if (Tools.notEmpty(tokenAuthExpression)){ - currentAuthForTest = "Bearer " + evalStruct.eval(tokenAuthExpression, serviceResultsMap, null, jexl, jc); + currentAuthForTest = "Bearer " + XmlReplayEval.eval(tokenAuthExpression, serviceResultsMap, null, jexl, jc); } } @@ -604,7 +606,7 @@ public class XmlReplay { } if (uri.indexOf("$")>-1){ - uri = evalStruct.eval(uri, serviceResultsMap, null, jexl, jc); + uri = XmlReplayEval.eval(uri, serviceResultsMap, null, jexl, jc); } fullURL = fixupFullURL(fullURL, protoHostPort, uri); @@ -723,17 +725,19 @@ public class XmlReplay { hasError = ! serviceResult.gotExpectedResult(); } - boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto); - String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError)+"; time:"+(System.currentTimeMillis()-startTime); - String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": ""; - report.addTestResult(serviceResult); - if ( (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) - || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full) ){ + if ((dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full)) { System.out.println("\r\n#---------------------#"); } - System.out.println(timeString()+" "+leader+serviceResultRow+"\r\n"); + + String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:" + testIDLabel +": ": ""; + String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError) + "; time:" + (System.currentTimeMillis()-startTime); + //System.out.println(timeString() + "\n" + leader + serviceResultRow + "\r\n"); + System.out.println(String.format("Time: %s\nLeader: %s\nAuth: %s\nResult: %s\r\n", + timeString(), testIDLabel, Tools.notEmpty(authIDForTest) ? authIDForTest : "", serviceResultRow)); + + boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto); if (dump.payloads || (doingAuto&&hasError) ) { if (Tools.notBlank(serviceResult.requestPayload)){ System.out.println("\r\n========== request payload ==============="); @@ -759,7 +763,7 @@ public class XmlReplay { } } if (Tools.isTrue(autoDeletePOSTS) && param_autoDeletePOSTS){ - autoDelete(serviceResultsMap, "default", 0); + autoDelete(defaultAuths, serviceResultsMap, "default", 0); } } @@ -823,7 +827,7 @@ public class XmlReplay { return result; } - public static void main(String[]args) throws Exception { + public static void main(String[]args) throws Exception { Options options = createOptions(); //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null)); CommandLineParser parser = new GnuParser(); diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/accountroles-post-bigbird.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/accountroles-post-bigbird.xml index 834f75752..2fd065a4d 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/accountroles-post-bigbird.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/accountroles-post-bigbird.xml @@ -8,6 +8,6 @@ ${createRole.CSID} - ROLE_1_BIRD + xROLE_1_BIRD diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/permissionroles-post-bird.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/permissionroles-post-bird.xml index c5e3482bf..82223f2df 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/permissionroles-post-bird.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/permissionroles-post-bird.xml @@ -10,6 +10,6 @@ ${createRole.CSID} - ROLE_1_BIRD + xROLE_1_BIRD diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/role-create-bird.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/role-create-bird.xml index bd12dd5c4..c7adf9a3c 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/role-create-bird.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/role-create-bird.xml @@ -1,5 +1,5 @@ - ROLE_1_BIRD + xROLE_1_BIRD Role for testing batch invoke permissions diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security.xml index 8c9316c25..5074eeaa4 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security.xml @@ -6,19 +6,72 @@ dXNlcjFAbXVzZXVtMS5vcmc6dXNlcjFAbXVzZXVtMS5vcmc= YmlnYmlyZDIwMTA6YmlnYmlyZDIwMTA= ZWxtbzIwMTA6ZWxtbzIwMTA= + Z3JvdmVyMjAxODpncm92ZXIyMDE4 + + + + POST + /cspace-services/authorization/permissions + security/slipOutPerm.xml + + + POST + /cspace-services/authorization/roles + security/slipOutRole.xml + + + POST + /cspace-services/accounts + security/create-account-grover.xml + + + POST + /cspace-services/vocabularies + security/slipOutVocab-1.xml + + + PUT + /cspace-services/authorization/permissions/${slipOutPerm.CSID} + security/slipOutPerm-update.xml + + + 403 + POST + /cspace-services/vocabularies + security/slipOutVocab-2.xml + + + + + + GET + /cspace-services/authorization/permissions/1-loansin-CRUDL + + + security/res/get1-loansin-CRUDL.res.xml + + + + + GET + /cspace-services/authorization/permissions/1-loansin-CRUDL/permroles + + + POST /cspace-services/authorization/permissions security/2-elmo-permission.xml + 400 POST /cspace-services/authorization/permissions/${permElmo.CSID}/permroles - security/10-permissionroles-elmo.xml + security/14-permissionroles-bogus.xml POST @@ -41,6 +94,7 @@ dimension1 + 404 DELETE /cspace-services/authorization/permissions/${permElmo.CSID}/permroles @@ -284,8 +338,6 @@ dimension/2-put.xml - - GET /cspace-services/dimensions/ @@ -417,7 +469,8 @@ - Deleting permroles from bigbird2010 + Deleting permroles already deleted in test ID="deletePermrolesBigbird" + 404 DELETE /cspace-services/authorization/permissions/${permBigbird.CSID}/permroles diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-bigbird-permission.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-bigbird-permission.xml index 3f7afdb7c..dc7e16c00 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-bigbird-permission.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-bigbird-permission.xml @@ -1,18 +1,17 @@ - dimensions - - CREATE - - - READ - - - UPDATE - - - DELETE - - PERMIT + dimensions + + CREATE + + + READ + + + UPDATE + + + DELETE + + PERMIT - diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-vocab.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-vocab.xml new file mode 100644 index 000000000..01d878d63 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/1-vocab.xml @@ -0,0 +1,14 @@ + + + + TestOrder Vocabulary + TestOrderVocab + + urn:cspace:org.collectionspace.demo:vocabulary:name(TestOrderVocab)'TestOrder Vocabulary' + enum + This is a test vocabulary + Some mythical book + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/10-permissionroles-elmo.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/10-permissionroles-elmo.xml index af52b93e4..bf6b9a426 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/10-permissionroles-elmo.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/10-permissionroles-elmo.xml @@ -7,7 +7,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleIntern.CSID} - ROLE_TEST_INTERN + xROLE_TEST_INTERN diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-bigbird-permission-CRU.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-bigbird-permission-CRU.xml index bb7ae5607..65a686aea 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-bigbird-permission-CRU.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-bigbird-permission-CRU.xml @@ -1,16 +1,14 @@ - - dimensions - - CREATE - - - READ - - - UPDATE - - PERMIT + + dimensions + + CREATE + + + READ + + + UPDATE + + PERMIT - diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-permissionroles-bigbird-CRU.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-permissionroles-bigbird-CRU.xml index df8889fb6..93b097eb8 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-permissionroles-bigbird-CRU.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/11-permissionroles-bigbird-CRU.xml @@ -7,7 +7,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleTestCM.CSID} - ROLE_TEST_CM + xROLE_TEST_CM diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-bigbird-permission-R.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-bigbird-permission-R.xml index 6d4da79f5..895abc518 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-bigbird-permission-R.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-bigbird-permission-R.xml @@ -1,10 +1,8 @@ - - dimensions - - READ - - PERMIT + + dimensions + + READ + + PERMIT - diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-permissionroles-bigbird-R.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-permissionroles-bigbird-R.xml index f4517f2d9..7c262ebff 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-permissionroles-bigbird-R.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/12-permissionroles-bigbird-R.xml @@ -7,7 +7,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleTestCM.CSID} - ROLE_TEST_CM + xROLE_TEST_CM diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/13-permissionroles-bigbird.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/13-permissionroles-bigbird.xml index f4517f2d9..7c262ebff 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/13-permissionroles-bigbird.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/13-permissionroles-bigbird.xml @@ -7,7 +7,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleTestCM.CSID} - ROLE_TEST_CM + xROLE_TEST_CM diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/14-permissionroles-bogus.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/14-permissionroles-bogus.xml new file mode 100644 index 000000000..740a305ba --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/14-permissionroles-bogus.xml @@ -0,0 +1,13 @@ + + + + bogus-perm + dimensions + + + bogus-role + xROLE_TEST_INTERN + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3-role-test-cm.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3-role-test-cm.xml index ccd7034d6..38356e748 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3-role-test-cm.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3-role-test-cm.xml @@ -1,28 +1,23 @@ - ROLE_TEST_CM + xROLE_TEST_CM role for ROLE_TEST_CM - 4381 1-vocabularies-RL vocabularies RL - 4382 1-groups-RL groups RL - 4381 - 1-vocabularies-CRUL + 1-vocabularies-CRUDL vocabularies CRUL - 4382 - 1-groups-CRUL groups CRUL diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3a-update-role-test-cm.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3a-update-role-test-cm.xml index 2178ce6d9..4b7ce62fa 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3a-update-role-test-cm.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/3a-update-role-test-cm.xml @@ -1,6 +1,6 @@ - ROLE_TEST_CM + xROLE_TEST_CM role for ROLE_TEST_CM 1-vocabularies-RL diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/4-role-intern.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/4-role-intern.xml index b16fc0503..529dda484 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/4-role-intern.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/4-role-intern.xml @@ -1,6 +1,6 @@ - ROLE_TEST_INTERN + xROLE_TEST_INTERN role for ROLE_TEST_INTERN 4381 diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/7-accountroles-bigbird.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/7-accountroles-bigbird.xml index a1d518c04..4f6690596 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/7-accountroles-bigbird.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/7-accountroles-bigbird.xml @@ -9,7 +9,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleTestCM.CSID} - ROLE_TEST_CM + xROLE_TEST_CM diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/8-account-roles-elmo.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/8-account-roles-elmo.xml index 5c321065e..aa6165d54 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/8-account-roles-elmo.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/8-account-roles-elmo.xml @@ -9,7 +9,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleIntern.CSID} - ROLE_TEST_INTERN + xROLE_TEST_INTERN diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/9-permissionroles-bigbird.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/9-permissionroles-bigbird.xml index 08df47170..555819a7c 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/9-permissionroles-bigbird.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/9-permissionroles-bigbird.xml @@ -7,7 +7,7 @@ xmlns:ns2="http://collectionspace.org/services/authorization"> ${roleTestCM.CSID} - ROLE_TEST_CM + xROLE_TEST_CM diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/create-account-grover.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/create-account-grover.xml new file mode 100644 index 000000000..6ca2535e9 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/create-account-grover.xml @@ -0,0 +1,16 @@ + + + grover2018 + grover2018 + grover@cspace.org + 1234567890 + grover2018 + + Z3JvdmVyMjAxOA== + + 1 + + + ${slipOutRole.CSID} + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/res/get1-loansin-CRUDL.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/res/get1-loansin-CRUDL.res.xml new file mode 100644 index 000000000..ef5478efb --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/res/get1-loansin-CRUDL.res.xml @@ -0,0 +1,37 @@ + + + Generated admin permission. + loansin + CRUDL + + CREATE + 1011301984 + 1:loansin#create + + + READ + 1927741434 + 1:loansin#read + + + UPDATE + 1524749869 + 1:loansin#update + + + DELETE + 1028137743 + 1:loansin#delete + + + SEARCH + 1457259276 + 1:loansin#search + + PERMIT + immutable + immutable + 1 + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm-update.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm-update.xml new file mode 100644 index 000000000..2d6d2905f --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm-update.xml @@ -0,0 +1,8 @@ + + + vocabularies + + READ + + PERMIT + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm.xml new file mode 100644 index 000000000..58830e585 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutPerm.xml @@ -0,0 +1,11 @@ + + + vocabularies + + CREATE + + + READ + + PERMIT + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutRole.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutRole.xml new file mode 100644 index 000000000..3a58e2795 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutRole.xml @@ -0,0 +1,7 @@ + + + SLIP_OUT_ROLE1 + + ${slipOutPerm.CSID} + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-1.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-1.xml new file mode 100644 index 000000000..563e05298 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-1.xml @@ -0,0 +1,14 @@ + + + + SlipRole Vocabulary + sliprole1 + + urn:cspace:org.collectionspace.demo:vocabulary:name(sliprole1)'SlipRole Vocabulary' + enum + This is a test vocabulary + Some rug book + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-2.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-2.xml new file mode 100644 index 000000000..2dc75cbb0 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/security/slipOutVocab-2.xml @@ -0,0 +1,14 @@ + + + + SlipRole Vocabulary + sliprole2 + + urn:cspace:org.collectionspace.demo:vocabulary:name(sliprole2)'SlipRole Vocabulary' + enum + This is a test vocabulary + Some rug book + + + diff --git a/services/account/jaxb/src/main/resources/accounts_common.xsd b/services/account/jaxb/src/main/resources/accounts_common.xsd index 51a1e9083..0fab3f390 100644 --- a/services/account/jaxb/src/main/resources/accounts_common.xsd +++ b/services/account/jaxb/src/main/resources/accounts_common.xsd @@ -167,8 +167,8 @@ - - + + diff --git a/services/account/pstore/src/main/resources/db/postgresql/account.sql b/services/account/pstore/src/main/resources/db/postgresql/account.sql index 307e9b0e0..7055839bf 100644 --- a/services/account/pstore/src/main/resources/db/postgresql/account.sql +++ b/services/account/pstore/src/main/resources/db/postgresql/account.sql @@ -3,8 +3,12 @@ DROP TABLE IF EXISTS accounts_common CASCADE; DROP TABLE IF EXISTS accounts_tenants CASCADE; DROP TABLE IF EXISTS tenants CASCADE; DROP SEQUENCE IF EXISTS hibernate_sequence; -create table accounts_common (csid varchar(128) not null, created_at timestamp not null, email varchar(255) not null, mobile varchar(255), person_ref_name varchar(255), phone varchar(255), screen_name varchar(128) not null, status varchar(15) not null, updated_at timestamp, userid varchar(128) not null, metadata_protection varchar(255), roles_protection varchar(255), primary key (csid), unique (userid)); -create table accounts_tenants (HJID int8 not null, tenant_id varchar(128) not null, TENANTS_ACCOUNTS_COMMON_CSID varchar(128), primary key (HJID)); +create table accounts_common (csid varchar(128) not null, created_at timestamp not null, email varchar(255) not null, mobile varchar(255), person_ref_name varchar(255), phone varchar(255), screen_name varchar(128) not null, + status varchar(15) not null, updated_at timestamp, userid varchar(128) not null, + metadata_protection varchar(255), roles_protection varchar(255), + primary key (csid), unique (userid)); + + create table accounts_tenants (HJID int8 not null, tenant_id varchar(128) not null, TENANTS_ACCOUNTS_COMMON_CSID varchar(128), primary key (HJID)); create table tenants (id varchar(128) not null, created_at timestamp not null, name varchar(255) not null, config_md5hash varchar(255), authorities_initialized boolean not null, disabled boolean not null, updated_at timestamp, primary key (id)); alter table accounts_tenants add constraint FKFDA649B05A9CEEB5 foreign key (TENANTS_ACCOUNTS_COMMON_CSID) references accounts_common; create sequence hibernate_sequence; diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java b/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java index d7a86fb48..d664d8c88 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/AccountResource.java @@ -48,6 +48,7 @@ import org.collectionspace.services.common.context.ServiceContextFactory; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.query.UriInfoImpl; import org.collectionspace.services.common.storage.StorageClient; +import org.collectionspace.services.common.storage.TransactionContext; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; import org.collectionspace.services.config.tenant.EmailConfig; import org.collectionspace.services.config.tenant.TenantBindingType; @@ -56,12 +57,9 @@ import org.jboss.resteasy.util.HttpResponseCodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -88,7 +86,7 @@ import javax.xml.bind.DatatypeConverter; @Path(AccountClient.SERVICE_PATH) @Consumes("application/xml") @Produces("application/xml") -public class AccountResource extends SecurityResourceBase { +public class AccountResource extends SecurityResourceBase { final Logger logger = LoggerFactory.getLogger(AccountResource.class); final StorageClient storageClient = new AccountStorageClient(); @@ -216,9 +214,17 @@ public class AccountResource extends SecurityResourceBase { @PUT @Path("{csid}") - public AccountsCommon updateAccount(@Context UriInfo ui, @PathParam("csid") String csid,AccountsCommon theUpdate) { + public AccountsCommon updateAccount(@Context UriInfo ui, @PathParam("csid") String csid, AccountsCommon theUpdate) { return (AccountsCommon)update(ui, csid, theUpdate, AccountsCommon.class); } + + /* + * Use this when you have an existing and active ServiceContext. + */ + public AccountsCommon updateAccount(ServiceContext parentContext, UriInfo ui, String csid, AccountsCommon theUpdate) { + return (AccountsCommon)update(parentContext, ui, csid, theUpdate, AccountsCommon.class); + } + /** * Resets an accounts password. @@ -230,13 +236,12 @@ public class AccountResource extends SecurityResourceBase { * * @param ui * @return - * @throws UnsupportedEncodingException - * @throws DocumentNotFoundException + * @throws * @throws IOException */ @POST @Path(PROCESS_PASSWORD_RESET_PATH) - synchronized public Response processPasswordReset(Passwordreset passwordreset, @Context UriInfo ui) throws UnsupportedEncodingException, DocumentNotFoundException { + synchronized public Response processPasswordReset(Passwordreset passwordreset, @Context UriInfo ui) { Response response = null; // @@ -300,31 +305,45 @@ public class AccountResource extends SecurityResourceBase { response = Response.status(Response.Status.BAD_REQUEST).entity(errMsg).type("text/plain").build(); return response; } - - // // + // Finally, try to update the account with the new password. // String tenantId = token.getTenantId(); TenantBindingType tenantBindingType = ServiceMain.getInstance().getTenantBindingConfigReader().getTenantBinding(tenantId); EmailConfig emailConfig = tenantBindingType.getEmailConfig(); if (emailConfig != null) { try { - if (AuthorizationCommon.hasTokenExpired(emailConfig, token) == false) { - AccountsCommon accountUpdate = new AccountsCommon(); - accountUpdate.setUserId(targetAccount.getUserId()); - accountUpdate.setPassword(password.getBytes()); - updateAccount(ui, targetAccount.getCsid(), accountUpdate); - TokenStorageClient.update(tokenId, false); // disable the token so it can't be used again. - String msg = String.format("Successfully reset password using token ID='%s'.", - token.getId()); - response = Response.status(Response.Status.OK).entity(msg).type("text/plain").build(); - } else { - String errMsg = String.format("Could not reset password using token with ID='%s'. Password reset token has expired.", - token.getId()); - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errMsg).type("text/plain").build(); - } - } catch (NoSuchAlgorithmException e) { - String errMsg = String.format("Could not reset password for using token ID='%s'. Error: '%s'", + ServiceContext ctx = createServiceContext((AccountsCommon) null, AccountsCommon.class, ui); + TransactionContext transactionCtx = ctx.openConnection(); + try { + if (AuthorizationCommon.hasTokenExpired(emailConfig, token) == false) { + transactionCtx.beginTransaction(); + AccountsCommon accountUpdate = new AccountsCommon(); + accountUpdate.setUserId(targetAccount.getUserId()); + accountUpdate.setPassword(password.getBytes()); + updateAccount(ctx, ui, targetAccount.getCsid(), accountUpdate); + TokenStorageClient.update(transactionCtx, tokenId, false); // disable the token so it can't be used again. + transactionCtx.commitTransaction(); + // + // Success! + // + String msg = String.format("Successfully reset password using token ID='%s'.", token.getId()); + response = Response.status(Response.Status.OK).entity(msg).type("text/plain").build(); + } else { + String errMsg = String.format("Could not reset password using token with ID='%s'. Password reset token has expired.", + token.getId()); + response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errMsg).type("text/plain").build(); + } + } catch (Throwable t) { + transactionCtx.markForRollback(); + String errMsg = String.format("Could not reset password using token ID='%s'. Error: '%s'", + t.getMessage(), token.getId()); + response = Response.status(Response.Status.BAD_REQUEST).entity(errMsg).type("text/plain").build(); + } finally { + ctx.closeConnection(); + } + } catch (Exception e) { + String errMsg = String.format("Could not reset password using token ID='%s'. Error: '%s'", e.getMessage(), token.getId()); response = Response.status(Response.Status.BAD_REQUEST).entity(errMsg).type("text/plain").build(); } @@ -440,7 +459,7 @@ public class AccountResource extends SecurityResourceBase { return result; } - + @DELETE @Path("{csid}") public Response deleteAccount(@Context UriInfo uriInfo, @PathParam("csid") String csid) { @@ -449,8 +468,10 @@ public class AccountResource extends SecurityResourceBase { try { AccountsCommon account = (AccountsCommon)get(csid, AccountsCommon.class); + // // If marked as metadata immutable, do not delete - if (AccountClient.IMMUTABLE.equals(account.getMetadataProtection())) { + // + if (AccountClient.IMMUTABLE.equals(account.getMetadataProtection())) { Response response = Response.status(Response.Status.FORBIDDEN).entity("Account: "+csid+" is immutable.").type("text/plain").build(); return response; @@ -461,11 +482,22 @@ public class AccountResource extends SecurityResourceBase { // ServiceContext ctx = createServiceContext((AccountsCommon) null, AccountsCommon.class, uriInfo); - ctx.openConnection(); + TransactionContext transactionContext = ctx.openConnection(); try { + transactionContext.beginTransaction(); + // + // Delete all the account-role relationships + // AccountRoleSubResource subResource = new AccountRoleSubResource("accounts/accountroles"); subResource.deleteAccountRole(ctx, csid, SubjectType.ROLE); + // + // Now delete the account. + // getStorageClient(ctx).delete(ctx, csid); + transactionContext.commitTransaction(); + } catch (Throwable t) { + transactionContext.markForRollback(); + throw t; } finally { ctx.closeConnection(); } @@ -476,9 +508,11 @@ public class AccountResource extends SecurityResourceBase { return Response.status(HttpResponseCodes.SC_OK).build(); } - @POST + @POST @Path("{csid}/accountroles") - public Response createAccountRole(@QueryParam("_method") String method, + public Response createAccountRole( + @Context UriInfo uriInfo, + @QueryParam("_method") String method, @PathParam("csid") String accCsid, AccountRole input) { if (method != null) { @@ -488,21 +522,29 @@ public class AccountResource extends SecurityResourceBase { } logger.debug("createAccountRole with accCsid=" + accCsid); ensureCSID(accCsid, ServiceMessages.POST_FAILED+ "accountroles account "); + try { AccountsCommon account = (AccountsCommon)get(accCsid, AccountsCommon.class); - // If marked as roles immutable, do not create + // If marked as immutable, fail. if (AccountClient.IMMUTABLE.equals(account.getRolesProtection())) { Response response = Response.status(Response.Status.FORBIDDEN).entity("Roles for Account: "+accCsid+" are immutable.").type("text/plain").build(); return response; } - AccountRoleSubResource subResource = - new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); - String accrolecsid = subResource.createAccountRole((ServiceContext)null, input, SubjectType.ROLE); - UriBuilder path = UriBuilder.fromResource(AccountResource.class); - path.path(accCsid + "/accountroles/" + accrolecsid); - Response response = Response.created(path.build()).build(); - return response; + + ServiceContext ctx = createServiceContext((AccountsCommon) null, AccountsCommon.class, uriInfo); + ctx.openConnection(); + try { + AccountRoleSubResource subResource = + new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); + String accrolecsid = subResource.createAccountRole(ctx, input, SubjectType.ROLE); + UriBuilder path = UriBuilder.fromResource(AccountResource.class); + path.path(accCsid + "/accountroles/" + accrolecsid); + Response response = Response.created(path.build()).build(); + return response; + } finally { + ctx.closeConnection(); + } } catch (Exception e) { throw bigReThrow(e, ServiceMessages.POST_FAILED, accCsid); } @@ -516,15 +558,18 @@ public class AccountResource extends SecurityResourceBase { logger.debug("getAccountRole with accCsid=" + accCsid); ensureCSID(accCsid, ServiceMessages.GET_FAILED+ "accountroles account "); AccountRoleRel result = null; + ServiceContext ctx = null; + try { AccountRoleSubResource subResource = new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); //get relationships for an account - result = subResource.getAccountRoleRel((ServiceContext)null, accCsid, SubjectType.ROLE, accrolecsid); + result = subResource.getAccountRoleRel(ctx, accCsid, SubjectType.ROLE, accrolecsid); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED, accCsid); } checkResult(result, accCsid, ServiceMessages.GET_FAILED); + return result; } @@ -535,12 +580,13 @@ public class AccountResource extends SecurityResourceBase { logger.debug("getAccountRole with accCsid=" + accCsid); ensureCSID(accCsid, ServiceMessages.GET_FAILED+ "accountroles account "); AccountRole result = null; + ServiceContext ctx = null; try { AccountRoleSubResource subResource = new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); //get relationships for an account - result = subResource.getAccountRole((ServiceContext)null, accCsid, SubjectType.ROLE); + result = subResource.getAccountRole(ctx, accCsid, SubjectType.ROLE); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED, accCsid); } @@ -566,22 +612,30 @@ public class AccountResource extends SecurityResourceBase { return result; } - public Response deleteAccountRole(String accCsid, AccountRole input) { + public Response deleteAccountRole(String accCsid, AccountRole input) { logger.debug("deleteAccountRole with accCsid=" + accCsid); ensureCSID(accCsid, ServiceMessages.DELETE_FAILED+ "accountroles account "); try { AccountsCommon account = (AccountsCommon)get(accCsid, AccountsCommon.class); // If marked as roles immutable, do not delete - if(AccountClient.IMMUTABLE.equals(account.getRolesProtection())) { + if (AccountClient.IMMUTABLE.equals(account.getRolesProtection())) { Response response = Response.status(Response.Status.FORBIDDEN).entity("Roles for Account: "+accCsid+" are immutable.").type("text/plain").build(); return response; } - AccountRoleSubResource subResource = - new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); - //delete all relationships for an account - subResource.deleteAccountRole((ServiceContext)null, accCsid, SubjectType.ROLE, input); + + ServiceContext ctx = createServiceContext((AccountsCommon) null, + AccountsCommon.class, (UriInfo) null); + ctx.openConnection(); + try { + AccountRoleSubResource subResource = + new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); + //delete all relationships for an account + subResource.deleteAccountRole(ctx, accCsid, SubjectType.ROLE, input); + } finally { + ctx.closeConnection(); + } } catch (Exception e) { throw bigReThrow(e, ServiceMessages.DELETE_FAILED, accCsid); } @@ -591,7 +645,8 @@ public class AccountResource extends SecurityResourceBase { @DELETE @Path("{csid}/accountroles") - public Response deleteAccountRole(@PathParam("csid") String accCsid) { + public Response deleteAccountRole(@Context UriInfo uriInfo, @PathParam("csid") String accCsid) { + logger.debug("deleteAccountRole: All roles related to account with accCsid=" + accCsid); ensureCSID(accCsid, ServiceMessages.DELETE_FAILED+ "accountroles account "); @@ -603,10 +658,17 @@ public class AccountResource extends SecurityResourceBase { Response.status(Response.Status.FORBIDDEN).entity("Roles for Account: "+accCsid+" are immutable.").type("text/plain").build(); return response; } - AccountRoleSubResource subResource = - new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); - //delete all relationships for an account - subResource.deleteAccountRole((ServiceContext)null, accCsid, SubjectType.ROLE); + + ServiceContext ctx = createServiceContext((AccountsCommon) null, AccountsCommon.class, uriInfo); + ctx.openConnection(); + try { + AccountRoleSubResource subResource = + new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); + //delete all relationships for an account + subResource.deleteAccountRole(ctx, accCsid, SubjectType.ROLE); + } finally { + ctx.closeConnection(); + } } catch (Exception e) { throw bigReThrow(e, ServiceMessages.DELETE_FAILED, accCsid); } diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/AccountRoleSubResource.java b/services/account/service/src/main/java/org/collectionspace/services/account/AccountRoleSubResource.java index 9d3dce195..3ad10062b 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/AccountRoleSubResource.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/AccountRoleSubResource.java @@ -175,23 +175,23 @@ public class AccountRoleSubResource */ public String createAccountRole(ServiceContext parentCtx, AccountRole input, SubjectType subject) throws Exception { - // // We need to associate every new account with the Spring Security Admin role so we can make // changes to the Spring Security ACL tables. The Spring Security Admin role has NO CollectionSpace // specific permissions. It is an internal/private role that service consumers and end-users NEVER see. // - //Preserve the original incoming list of roles + // Preserve the original incoming list of roles List inputRoleValues = input.getRole(); - //Change the role list to be just the Spring role - List springRoles = new ArrayList(); - input.setRole(springRoles); - RoleValue springAdminRole = new RoleValue(); - springRoles.add(springAdminRole); - springAdminRole.setRoleId(AuthN.ROLE_SPRING_ADMIN_ID); - springAdminRole.setRoleName(AuthN.ROLE_SPRING_ADMIN_NAME); + // Temporarily change the role list to be just the Spring role + List springRoleValueList = new ArrayList(); + input.setRole(springRoleValueList); + + RoleValue springAdminRoleValue = new RoleValue(); + springRoleValueList.add(springAdminRoleValue); + springAdminRoleValue.setRoleId(AuthN.ROLE_SPRING_ADMIN_ID); + springAdminRoleValue.setRoleName(AuthN.ROLE_SPRING_ADMIN_NAME); // The Spring role relationship may already exist, if it does then we'll get a PersistenceException that // we'll just ignore. @@ -210,7 +210,7 @@ public class AccountRoleSubResource } // - // Now we'll add the account relationships for the original incoming roles. + // Now we'll add the actual account-role relationships for the original incoming payload. // input.setRole(inputRoleValues); ServiceContext ctx = createServiceContext(parentCtx, input, subject); diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountDocumentHandler.java b/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountDocumentHandler.java index daa2245cc..eddd79470 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountDocumentHandler.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountDocumentHandler.java @@ -41,6 +41,7 @@ import org.collectionspace.services.account.RoleValue; import org.collectionspace.services.client.AccountClient; import org.collectionspace.services.client.AccountFactory; import org.collectionspace.services.client.AccountRoleFactory; +import org.collectionspace.services.common.storage.TransactionContext; import org.collectionspace.services.common.storage.jpa.JpaDocumentHandler; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentFilter; @@ -146,13 +147,23 @@ public class AccountDocumentHandler } @Override + /** + * If the create payload included a list of role, relate them to the account. + */ public void completeCreate(DocumentWrapper wrapDoc) throws Exception { AccountsCommon accountsCommon = wrapDoc.getWrappedObject(); List roleValueList = account.getRole(); if (roleValueList != null && roleValueList.size() > 0) { + // + // To prevent new Accounts being created (especially low-level Spring Security accounts/SIDs), we'll first flush the current + // JPA context to ensure our Account can be successfully persisted. + // + TransactionContext jpaTransactionContext = this.getServiceContext().getCurrentTransactionContext(); + jpaTransactionContext.flush(); + AccountRoleSubResource subResource = new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); AccountRole accountRole = AccountRoleFactory.createAccountRoleInstance(accountsCommon, roleValueList, true, true); - String accountRoleCsid = subResource.createAccountRole(this.getServiceContext(), accountRole, SubjectType.ROLE); + subResource.createAccountRole(this.getServiceContext(), accountRole, SubjectType.ROLE); } } diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountRoleDocumentHandler.java b/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountRoleDocumentHandler.java index 9ea090609..442fd1dd0 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountRoleDocumentHandler.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountRoleDocumentHandler.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; import org.collectionspace.services.common.authorization_mgt.AuthorizationRoleRel; +import org.collectionspace.services.common.authorization_mgt.PermissionRoleUtil; import org.collectionspace.services.authorization.AccountRole; import org.collectionspace.services.authorization.AccountRoleRel; import org.collectionspace.services.authorization.AccountValue; @@ -35,9 +36,11 @@ import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.storage.jpa.JpaDocumentFilter; import org.collectionspace.services.common.storage.jpa.JpaDocumentHandler; +import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentFilter; import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.common.context.ServiceContextProperties; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,7 +107,8 @@ public class AccountRoleDocumentHandler /* (non-Javadoc) * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#handleGet(org.collectionspace.services.common.document.DocumentWrapper) */ - @Override + @SuppressWarnings({ "unchecked" }) + @Override public void handleGet(DocumentWrapper> wrapDoc) throws Exception { AccountRole output = extractCommonPart(wrapDoc); setCommonPart(output); @@ -207,33 +211,54 @@ public class AccountRoleDocumentHandler // Ensure the roles exist } - public void fillCommonPart(AccountRole ar, + /** + * Populate the incoming wrapDoc (AccountRoleRel) object with account-role data. + * + * @param accountRole + * @param wrapDoc + * @param handleDelete + * @throws Exception + */ + public void fillCommonPart(AccountRole accountRole, DocumentWrapper> wrapDoc, boolean handleDelete) throws Exception { - List arrl = wrapDoc.getWrappedObject(); - SubjectType subject = ar.getSubject(); + List accountRoleRelList = wrapDoc.getWrappedObject(); + SubjectType subject = accountRole.getSubject(); if (subject == null) { //it is not required to give subject as URI determines the subject subject = getSubject(getServiceContext()); - } else { - //subject mismatch should have been checked during validation } + if (subject.equals(SubjectType.ROLE)) { - //FIXME: potential index out of bounds exception...negative test needed - AccountValue av = ar.getAccount().get(0); - - for (RoleValue rv : ar.getRole()) { - AccountRoleRel arr = buildAccountRoleRel(av, rv, handleDelete); - arrl.add(arr); - } + if (accountRole.getAccount() != null && accountRole.getAccount().size() == 1) { + AccountValue av = accountRole.getAccount().get(0); // Since ROLE is the subject, they'll be just one account + for (RoleValue rv : accountRole.getRole()) { + if (rv.getRoleName() == null) { // We need the role name, so get it if the payload just includes the role CSID + rv = PermissionRoleUtil.fetchRoleValue(getServiceContext(), rv.getRoleId()); + } + AccountRoleRel arr = buildAccountRoleRel(av, rv, handleDelete); + accountRoleRelList.add(arr); + } + } else { + String msg = "There must be one (and only one) account supplied in an account-role relationship where the subject is the role."; + throw new DocumentException(msg); + } } else if (SubjectType.ACCOUNT.equals(subject)) { - //FIXME: potential index out of bounds exception...negative test needed - RoleValue rv = ar.getRole().get(0); - for (AccountValue av : ar.getAccount()) { - AccountRoleRel arr = buildAccountRoleRel(av, rv, handleDelete); - arrl.add(arr); - } + if (accountRole.getRole() != null && accountRole.getRole().size() == 1) { + RoleValue rv = accountRole.getRole().get(0); + for (AccountValue av : accountRole.getAccount()) { + AccountRoleRel arr = buildAccountRoleRel(av, rv, handleDelete); + accountRoleRelList.add(arr); + } + } else { + String msg = "There must be one (and only one) role supplied in an account-role relationship where the subject is the account."; + throw new DocumentException(msg); + } + } else { + String msg = String.format("Unknown subject '%s' encounted when working with an AccountRole object", + subject.value()); + throw new DocumentException(msg); } } @@ -355,18 +380,18 @@ public class AccountRoleDocumentHandler * @return the account role rel */ private AccountRoleRel buildAccountRoleRel(AccountValue av, RoleValue rv, boolean handleDelete) { - AccountRoleRel arr = new AccountRoleRel(); - arr.setAccountId(av.getAccountId()); - arr.setUserId(av.getUserId()); - arr.setScreenName(av.getScreenName()); - arr.setRoleId(rv.getRoleId()); - arr.setRoleName(rv.getRoleName()); + AccountRoleRel accountRoleRel = new AccountRoleRel(); + accountRoleRel.setAccountId(av.getAccountId()); + accountRoleRel.setUserId(av.getUserId()); + accountRoleRel.setScreenName(av.getScreenName()); + accountRoleRel.setRoleId(rv.getRoleId()); + accountRoleRel.setRoleName(rv.getRoleName()); String relationshipId = rv.getRoleRelationshipId(); if (relationshipId != null && handleDelete == true) { - arr.setHjid(Long.parseLong(relationshipId)); // set this so we can convince JPA to del the relation + accountRoleRel.setHjid(Long.parseLong(relationshipId)); // set this so we can convince JPA to delete the relationship } - return arr; + return accountRoleRel; } /** @@ -375,7 +400,7 @@ public class AccountRoleDocumentHandler * @param ctx the ctx * @return the subject */ - static SubjectType getSubject(ServiceContext ctx) { + static SubjectType getSubject(ServiceContext ctx) { Object o = ctx.getProperty(ServiceContextProperties.SUBJECT); if (o == null) { throw new IllegalArgumentException(ServiceContextProperties.SUBJECT diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountStorageClient.java b/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountStorageClient.java index ffdf5398d..0977ce61c 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountStorageClient.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/storage/AccountStorageClient.java @@ -73,21 +73,24 @@ public class AccountStorageClient extends JpaStorageClientImpl { try { account = (AccountsCommon) handler.getCommonPart(); handler.prepare(Action.CREATE); - DocumentWrapper wrapDoc = - new DocumentWrapperImpl(account); + DocumentWrapper wrapDoc = new DocumentWrapperImpl(account); handler.handle(Action.CREATE, wrapDoc); jpaConnectionContext.beginTransaction(); // - // If userid and password are given, add to default id provider + // If userid and password are given, add to default ID provider -i.e., add it to the Spring Security account list // if (account.getUserId() != null && isForCSpaceIdentityProvider(account.getPassword())) { User user = userStorageClient.create(account.getUserId(), account.getPassword()); - jpaConnectionContext.persist(user); + jpaConnectionContext.persist(user); } - + // + // Now add the account to the CSpace list of accounts + // account.setCreatedAtItem(new Date()); jpaConnectionContext.persist(account); - + // + // Finish creating related resources -e.g., account-role relationships + // handler.complete(Action.CREATE, wrapDoc); jpaConnectionContext.commitTransaction(); diff --git a/services/account/service/src/main/java/org/collectionspace/services/account/storage/csidp/TokenStorageClient.java b/services/account/service/src/main/java/org/collectionspace/services/account/storage/csidp/TokenStorageClient.java index 2836fda5a..747ee40ef 100644 --- a/services/account/service/src/main/java/org/collectionspace/services/account/storage/csidp/TokenStorageClient.java +++ b/services/account/service/src/main/java/org/collectionspace/services/account/storage/csidp/TokenStorageClient.java @@ -40,7 +40,10 @@ import org.collectionspace.services.common.document.BadRequestException; import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.document.JaxbUtils; +import org.collectionspace.services.common.document.TransactionException; import org.collectionspace.services.common.security.SecurityUtils; +import org.collectionspace.services.common.storage.TransactionContext; +import org.collectionspace.services.common.storage.jpa.JPATransactionContext; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; import org.slf4j.Logger; @@ -84,75 +87,65 @@ public class TokenStorageClient { } /** - * Get token for given ID - * @param em EntityManager + * Update a token for given an id * @param id + * @param enabledFlag + * @throws TransactionException */ - static public Token get(String id) throws DocumentNotFoundException { - EntityManagerFactory emf = JpaStorageUtils.getEntityManagerFactory(); + static public void update(TransactionContext transactionContext, String id, boolean enabledFlag) throws DocumentNotFoundException, TransactionException { Token tokenFound = null; - try { - EntityManager em = emf.createEntityManager(); - tokenFound = get(em, id); - } finally { - if (emf != null) { - JpaStorageUtils.releaseEntityManagerFactory(emf); + tokenFound = get((JPATransactionContext)transactionContext, id); + if (tokenFound != null) { + tokenFound.setEnabled(enabledFlag); + tokenFound.setUpdatedAtItem(new Date()); + if (logger.isDebugEnabled()) { + logger.debug("Updated token=" + JaxbUtils.toString(tokenFound, Token.class)); } + } else { + String msg = String.format("Could not find token with id='%s'", id); + throw new DocumentNotFoundException(msg); } - - return tokenFound; } /** - * Update a token for given an id + * Get token for given ID + * @param em EntityManager * @param id - * @param enabledFlag - */ - static public void update(String id, boolean enabledFlag) throws DocumentNotFoundException { - EntityManagerFactory emf = JpaStorageUtils.getEntityManagerFactory(); - EntityManager em = null; + */ + public static Token get(JPATransactionContext jpaTransactionContext, String id) throws DocumentNotFoundException, TransactionException { + Token tokenFound = null; + tokenFound = (Token) jpaTransactionContext.find(Token.class, id); + if (tokenFound == null) { + String msg = "Could not find token with ID=" + id; + logger.error(msg); + throw new DocumentNotFoundException(msg); + } + + return tokenFound; + } + + static public Token get(String id) throws DocumentNotFoundException { Token tokenFound = null; + EntityManagerFactory emf = JpaStorageUtils.getEntityManagerFactory(); + try { - em = emf.createEntityManager(); - tokenFound = get(em, id); - if (tokenFound != null) { - em.getTransaction().begin(); - tokenFound.setEnabled(enabledFlag); - tokenFound.setUpdatedAtItem(new Date()); - if (logger.isDebugEnabled()) { - logger.debug("Updated token=" + JaxbUtils.toString(tokenFound, Token.class)); - } - em.getTransaction().commit(); - } else { - String msg = String.format("Could not find token with id='%s'", id); - throw new DocumentNotFoundException(msg); - } + EntityManager em = emf.createEntityManager(); + tokenFound = (Token) em.find(Token.class, id); + if (tokenFound == null) { + String msg = "Could not find token with ID=" + id; + logger.error(msg); + throw new DocumentNotFoundException(msg); + } } finally { - if (em != null && em.isOpen()) { - em.close(); - } if (emf != null) { JpaStorageUtils.releaseEntityManagerFactory(emf); } - } - } - - public static Token get(EntityManager em, String id) throws DocumentNotFoundException { - Token tokenFound = null; - - em.getTransaction().begin(); - tokenFound = em.find(Token.class, id); - em.getTransaction().commit(); - if (tokenFound == null) { - String msg = "Could not find token with ID=" + id; - logger.error(msg); - throw new DocumentNotFoundException(msg); } return tokenFound; - } + } /** * Deletes the token with given id diff --git a/services/authentication/service/src/main/java/org/collectionspace/authentication/AuthN.java b/services/authentication/service/src/main/java/org/collectionspace/authentication/AuthN.java index 5d8f71ca1..81f8a5e6b 100644 --- a/services/authentication/service/src/main/java/org/collectionspace/authentication/AuthN.java +++ b/services/authentication/service/src/main/java/org/collectionspace/authentication/AuthN.java @@ -90,6 +90,7 @@ public class AuthN { public static final String ROLE_SPRING_ADMIN_ID = "-1"; public static final String ROLE_SPRING_ADMIN_NAME = "ROLE_SPRING_ADMIN"; + public static final String ROLE_SPRING_GROUP_NAME = "Spring Security Administrator"; // Define a special account value for the tenantManager. Yes, this is a hack, but // less troublesome than the alternatives. @@ -100,6 +101,17 @@ public class AuthN { //FIXME initialize with the help of configuration meta data authnContext = new SpringAuthNContext(); } + + public boolean isSystemAdmin() { + boolean result = false; + + String currentUserId = this.getUserId(); + if (currentUserId.equals(AuthN.SPRING_ADMIN_USER) || currentUserId.equals(AuthN.ADMIN_TENANT_NAME)) { + result = true; + } + + return result; + } public final static AuthN get() { return self; diff --git a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java index 49d4f627b..5bb5c4bc2 100644 --- a/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java +++ b/services/authorization-mgt/client/src/main/java/org/collectionspace/services/client/PermissionClient.java @@ -49,6 +49,8 @@ public class PermissionClient extends AbstractServiceClientImpl permValues = new Hashtable(); diff --git a/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RoleServiceTest.java b/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RoleServiceTest.java index 9ac0801ea..5a70fb614 100644 --- a/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RoleServiceTest.java +++ b/services/authorization-mgt/client/src/test/java/org/collectionspace/services/authorization/client/test/RoleServiceTest.java @@ -61,18 +61,18 @@ public class RoleServiceTest extends AbstractServiceTestImpl readWritePermList = new ArrayList(); private List tenantMgmntPermList = new ArrayList(); private List tenantMgmntPermRoleList = new ArrayList(); + private List adminPermList = new ArrayList(); private List adminPermRoleList = new ArrayList(); + private List readerPermList = new ArrayList(); private List readerPermRoleList = new ArrayList(); + private List adminRoles = new ArrayList(); private List readerRoles = new ArrayList(); + private Role cspaceTenantMgmntRole; - private Hashtable tenantBindings = - new Hashtable(); + private Hashtable tenantBindings = new Hashtable(); // // Store the list of default roles, perms, and roleperms // @@ -112,11 +115,14 @@ public class AuthorizationGen { */ public void createDefaultPermissions(JPATransactionContext jpaTransactionContext) { for (String tenantId : tenantBindings.keySet()) { - List adminPerms = createDefaultAdminPermissions(tenantId, AUTHZ_IS_ENTITY_PROXY); + List adminPerms = createDefaultAdminPermissions(tenantId, AUTHZ_IS_ENTITY_PROXY); // CRUDL perms adminPermList.addAll(adminPerms); - List readerPerms = createDefaultReaderPermissions(tenantId, AUTHZ_IS_ENTITY_PROXY); + List readerPerms = createDefaultReaderPermissions(tenantId, AUTHZ_IS_ENTITY_PROXY); // RL perms readerPermList.addAll(readerPerms); + + List readWritePerms = createDefaultReadWritePermissions(tenantId, AUTHZ_IS_ENTITY_PROXY); // CRUL perms + readWritePermList.addAll(readWritePerms); } List tenantMgmntPerms = createDefaultTenantMgmntPermissions(); @@ -178,19 +184,48 @@ public class AuthorizationGen { return perm; } - private Permission buildAdminPermission(String tenantId, String resourceName) { - String description = "Generated admin permission."; - return AuthorizationCommon.createPermission(tenantId, resourceName, description, AuthorizationCommon.ACTIONGROUP_CRUDL_NAME); - } + /** + * createDefaultReadWritePermissions creates read-write (CRUL) permissions for all services + * used by the given tenant + * @param tenantId + * @return + */ + public List createDefaultReadWritePermissions(String tenantId, boolean isEntityProxy) { + ArrayList apcList = new ArrayList(); + TenantBindingType tbinding = tenantBindings.get(tenantId); + for (ServiceBindingType sbinding : tbinding.getServiceBindings()) { + //add permissions for the main path + String resourceName = sbinding.getName().toLowerCase().trim(); + if (isEntityProxy == true) { + resourceName = SecurityUtils.getResourceEntity(resourceName); + } + Permission perm = buildReadWritePermission(tbinding.getId(), resourceName); + apcList.add(perm); + + //add permissions for alternate paths + if (isEntityProxy == false) { + List uriPaths = sbinding.getUriPath(); + for (String uriPath : uriPaths) { + perm = buildReadWritePermission(tbinding.getId(), uriPath.toLowerCase()); + apcList.add(perm); + } + } + } + + return apcList; + } + /** * createDefaultReaderPermissions creates read only permissions for all services * used by the given tenant + * * @param tenantId * @return */ public List createDefaultReaderPermissions(String tenantId, boolean isEntityProxy) { ArrayList apcList = new ArrayList(); + TenantBindingType tbinding = tenantBindings.get(tenantId); for (ServiceBindingType sbinding : tbinding.getServiceBindings()) { //add permissions for the main path @@ -198,27 +233,35 @@ public class AuthorizationGen { if (isEntityProxy == true) { resourceName = SecurityUtils.getResourceEntity(resourceName); } - Permission perm = buildReaderPermission(tbinding.getId(), - resourceName); + Permission perm = buildReaderPermission(tbinding.getId(), resourceName); apcList.add(perm); //add permissions for alternate paths if (isEntityProxy == false) { List uriPaths = sbinding.getUriPath(); for (String uriPath : uriPaths) { - perm = buildReaderPermission(tbinding.getId(), - uriPath.toLowerCase()); + perm = buildReaderPermission(tbinding.getId(), uriPath.toLowerCase()); apcList.add(perm); } } } + return apcList; - } + private Permission buildAdminPermission(String tenantId, String resourceName) { + String description = "Generated admin permission."; + return AuthorizationCommon.createPermission(tenantId, resourceName, description, AuthorizationCommon.ACTIONGROUP_CRUDL_NAME, true); + } + private Permission buildReaderPermission(String tenantId, String resourceName) { - String description = "Generated read-only permission."; - return AuthorizationCommon.createPermission(tenantId, resourceName, description, AuthorizationCommon.ACTIONGROUP_RL_NAME); + String description = "Generated read-only (RL) permission."; + return AuthorizationCommon.createPermission(tenantId, resourceName, description, AuthorizationCommon.ACTIONGROUP_RL_NAME, true); + } + + private Permission buildReadWritePermission(String tenantId, String resourceName) { + String description = "Generated read-write (CRUL) permission."; + return AuthorizationCommon.createPermission(tenantId, resourceName, description, AuthorizationCommon.ACTIONGROUP_CRUL_NAME, true); } public List getDefaultPermissions() { @@ -226,6 +269,7 @@ public class AuthorizationGen { allPermList = new ArrayList(); allPermList.addAll(adminPermList); allPermList.addAll(readerPermList); + allPermList.addAll(readWritePermList); allPermList.addAll(tenantMgmntPermList); } return allPermList; @@ -315,7 +359,8 @@ public class AuthorizationGen { /* for (Permission p : adminPermList) { PermissionRole permCAdmRole = associatePermissionRoles(p, roles, false); adminPermRoleList.add(permCAdmRole); - } */ + } */ + // Now associate the tenant management perms to the role for (Permission p : tenantMgmntPermList) { // Note we enforce tenant, as should all be tenant 0 (the special one) diff --git a/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java b/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java index bd849dc15..7ac92c819 100644 --- a/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java +++ b/services/authorization-mgt/import/src/main/java/org/collectionspace/services/authorization/importer/AuthorizationSeed.java @@ -106,7 +106,7 @@ public class AuthorizationSeed { } for (PermissionRole pr : permRoleList) { if (pr.getPermission().get(0).getPermissionId().equals(p.getCsid())) { - AuthorizationCommon.addPermissionsForUri(p, pr); + AuthorizationCommon.addPermissionsForUri(jpaTransactionContext, p, pr); } } } diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/RoleResource.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/RoleResource.java index 0ae23e998..17bd0f7b5 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/RoleResource.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/RoleResource.java @@ -30,6 +30,7 @@ import org.collectionspace.services.common.ServiceMessages; import org.collectionspace.services.common.context.RemoteServiceContextFactory; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.context.ServiceContextFactory; +import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.storage.StorageClient; import org.collectionspace.services.common.storage.TransactionContext; import org.collectionspace.services.common.storage.jpa.JpaStorageClientImpl; @@ -57,7 +58,7 @@ import javax.ws.rs.core.UriInfo; @Consumes("application/xml") @Produces("application/xml") @SuppressWarnings("unchecked") -public class RoleResource extends SecurityResourceBase { +public class RoleResource extends SecurityResourceBase { final Logger logger = LoggerFactory.getLogger(RoleResource.class); final StorageClient storageClient = new JpaStorageClientImpl(); @@ -78,12 +79,12 @@ public class RoleResource extends SecurityResourceBase { } @Override - public ServiceContextFactory getServiceContextFactory() { + public ServiceContextFactory getServiceContextFactory() { return RemoteServiceContextFactory.get(); } @Override - public StorageClient getStorageClient(ServiceContext ctx) { + public StorageClient getStorageClient(ServiceContext ctx) { //FIXME use ctx to identify storage client return storageClient; } @@ -114,7 +115,7 @@ public class RoleResource extends SecurityResourceBase { AccountRoleSubResource subResource = new AccountRoleSubResource(AccountRoleSubResource.ACCOUNT_ACCOUNTROLE_SERVICE); //get relationships for a role - result = subResource.getAccountRole((ServiceContext)null, accCsid, SubjectType.ACCOUNT); + result = subResource.getAccountRole((ServiceContext)null, accCsid, SubjectType.ACCOUNT); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED, accCsid); } @@ -164,15 +165,19 @@ public class RoleResource extends SecurityResourceBase { // If marked as metadata immutable, do not delete if (RoleClient.IMMUTABLE.equals(role.getMetadataProtection())) { Response response = - Response.status(Response.Status.FORBIDDEN).entity("Role: "+csid+" is immutable.").type("text/plain").build(); + Response.status(Response.Status.FORBIDDEN).entity("Role: "+csid+" is immutable.").type("text/plain").build(); // FIXME: Should be status code 423 (resource locked) return response; } // - // delete all the permission/role relationships + // delete all the permission/role relationships (if any) // PermissionRoleSubResource permRoleResource = new PermissionRoleSubResource(PermissionRoleSubResource.ROLE_PERMROLE_SERVICE); - permRoleResource.deletePermissionRole(ctx, csid, SubjectType.PERMISSION); + try { + permRoleResource.deletePermissionRole(ctx, csid, SubjectType.PERMISSION); + } catch (DocumentNotFoundException dnf) { + // consume exception, not a problem. Just means no relationships exist + } // //delete all the account/role relationships associate with this role // @@ -182,7 +187,7 @@ public class RoleResource extends SecurityResourceBase { // //finally, delete the role itself // - ((JpaStorageClientImpl) getStorageClient(ctx)).deleteWhere(ctx, csid); + ((JpaStorageClientImpl) getStorageClient(ctx)).deleteWhere(ctx, csid); // FIXME: We should/could get rid the SID in Spring Security table as well transactionContext.commitTransaction(); } catch(Exception e) { transactionContext.markForRollback(); @@ -198,26 +203,31 @@ public class RoleResource extends SecurityResourceBase { @Path("{csid}/permroles") public Response createRolePermission(@QueryParam("_method") String method, @PathParam("csid") String roleCsid, PermissionRole input) { - if (method != null) { + if (method != null) { // FIXME: Not sure how method could every be "delete" if ("delete".equalsIgnoreCase(method)) { return deleteRolePermission(roleCsid, input); } } - + logger.debug("createRolePermission with roleCsid=" + roleCsid); ensureCSID(roleCsid, ServiceMessages.PUT_FAILED + "permroles role "); Response response = null; try { Role role = (Role)get(roleCsid, Role.class); - // If marked as metadata immutable, do not delete + // + // If marked as metadata immutable, do not change + // if (RoleClient.IMMUTABLE.equals(role.getPermsProtection())) { response = Response.status(Response.Status.FORBIDDEN).entity("Role: "+roleCsid+" is immutable.").type("text/plain").build(); return response; } + // + // Create new role-permission relationships + // PermissionRoleSubResource subResource = new PermissionRoleSubResource(PermissionRoleSubResource.ROLE_PERMROLE_SERVICE); - String permrolecsid = subResource.createPermissionRole((ServiceContext)null, input, SubjectType.PERMISSION); + String permrolecsid = subResource.createPermissionRole((ServiceContext)null, input, SubjectType.PERMISSION); UriBuilder path = UriBuilder.fromResource(RoleResource.class); path.path(roleCsid + "/permroles/" + permrolecsid); response = Response.created(path.build()).build(); @@ -240,7 +250,7 @@ public class RoleResource extends SecurityResourceBase { PermissionRoleSubResource subResource = new PermissionRoleSubResource(PermissionRoleSubResource.ROLE_PERMROLE_SERVICE); //get relationships for a role - result = subResource.getPermissionRole((ServiceContext)null, roleCsid, SubjectType.PERMISSION); + result = subResource.getPermissionRole((ServiceContext)null, roleCsid, SubjectType.PERMISSION); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED, roleCsid); } @@ -262,7 +272,7 @@ public class RoleResource extends SecurityResourceBase { PermissionRoleSubResource subResource = new PermissionRoleSubResource(PermissionRoleSubResource.ROLE_PERMROLE_SERVICE); //get relationships for a role - result = subResource.getPermissionRoleRel((ServiceContext)null, roleCsid, SubjectType.PERMISSION, permrolecsid); + result = subResource.getPermissionRoleRel((ServiceContext)null, roleCsid, SubjectType.PERMISSION, permrolecsid); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED, roleCsid); } @@ -287,7 +297,7 @@ public class RoleResource extends SecurityResourceBase { PermissionRoleSubResource subResource = new PermissionRoleSubResource(PermissionRoleSubResource.ROLE_PERMROLE_SERVICE); //delete all relationships for a permission - subResource.deletePermissionRole((ServiceContext)null, roleCsid, SubjectType.PERMISSION, input); + subResource.deletePermissionRole((ServiceContext)null, roleCsid, SubjectType.PERMISSION, input); result = Response.status(HttpResponseCodes.SC_OK).build(); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.DELETE_FAILED, roleCsid); @@ -314,7 +324,7 @@ public class RoleResource extends SecurityResourceBase { PermissionRoleSubResource subResource = new PermissionRoleSubResource(PermissionRoleSubResource.ROLE_PERMROLE_SERVICE); //delete all relationships for a permission - subResource.deletePermissionRole((ServiceContext)null, roleCsid, SubjectType.PERMISSION); + subResource.deletePermissionRole((ServiceContext)null, roleCsid, SubjectType.PERMISSION); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.DELETE_FAILED, roleCsid); } diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java.txt b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java.txt index 6c0ea8e81..9be3c7b6e 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java.txt +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java.txt @@ -1,262 +1 @@ -/** - * This document is a part of the source code and related artifacts - * for CollectionSpace, an open source collections management system - * for museums and related institutions: - - * http://www.collectionspace.org - * http://wiki.collectionspace.org - - * Copyright 2009 University of California at Berkeley - - * Licensed under the Educational Community License (ECL), Version 2.0. - * You may not use this file except in compliance with this License. - - * You may obtain a copy of the ECL 2.0 License at - - * https://source.collectionspace.org/collection-space/LICENSE.txt - - * 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.collectionspace.services.authorization.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import org.collectionspace.services.authorization.perms.ActionType; -import org.collectionspace.services.authorization.CSpaceAction; -import org.collectionspace.services.authorization.perms.Permission; -import org.collectionspace.services.authorization.perms.PermissionAction; -import org.collectionspace.services.authorization.perms.PermissionsList; -import org.collectionspace.services.authorization.URIResourceImpl; - -import org.collectionspace.services.common.document.BadRequestException; -import org.collectionspace.services.common.document.DocumentFilter; -import org.collectionspace.services.common.document.DocumentWrapper; -import org.collectionspace.services.common.document.JaxbUtils; -import org.collectionspace.services.common.security.SecurityUtils; -import org.collectionspace.services.common.storage.jpa.JpaDocumentHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Document handler for Permission - * @author - */ -public class PermissionDocumentHandler - extends JpaDocumentHandler { - - private final Logger logger = LoggerFactory.getLogger(PermissionDocumentHandler.class); - private Permission permission; - private PermissionsList permissionsList; - - public CSpaceAction getAction(ActionType action) { - if (ActionType.CREATE.name().equals(action.name())) { - return CSpaceAction.CREATE; - } else if (ActionType.READ.equals(action)) { - return CSpaceAction.READ; - } else if (ActionType.UPDATE.equals(action)) { - return CSpaceAction.UPDATE; - } else if (ActionType.DELETE.equals(action)) { - return CSpaceAction.DELETE; - } else if (ActionType.SEARCH.equals(action)) { - return CSpaceAction.SEARCH; - } else if (ActionType.ADMIN.equals(action)) { - return CSpaceAction.ADMIN; - } else if (ActionType.START.equals(action)) { - return CSpaceAction.START; - } else if (ActionType.STOP.equals(action)) { - return CSpaceAction.STOP; - } - // - // We could not find a match, so we need to throw an exception. - // - throw new IllegalArgumentException("action = " + action.toString()); - } - - /* - * Add the ACE hashed ID to the permission action so we can map the permission to the Spring Security - * tables. - */ - private void handlePermissionActions(Permission perm) { - //FIXME: REM - Having Java class loader issues with ActionType class. Not sure of the cause. - try { - List permActions = perm.getAction(); - for (PermissionAction permAction : permActions) { - CSpaceAction action = getAction(permAction.getName()); - URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(), - perm.getResourceName(), action); - permAction.setObjectIdentity(uriRes.getHashedId().toString()); - permAction.setObjectIdentityResource(uriRes.getId()); - //PermissionActionUtil.update(perm, permAction); - } - } catch (Exception x) { - x.printStackTrace(); - } - } - - @Override - public void handleCreate(DocumentWrapper wrapDoc) throws Exception { - String id = UUID.randomUUID().toString(); - Permission permission = wrapDoc.getWrappedObject(); - permission.setCsid(id); - setTenant(permission); - handlePermissionActions(permission); - } - - @Override - public void completeCreate(DocumentWrapper wrapDoc) throws Exception { - } - - @Override - public void handleUpdate(DocumentWrapper wrapDoc) throws Exception { - Permission permissionFound = wrapDoc.getWrappedObject(); - Permission permissionReceived = getCommonPart(); - merge(permissionReceived, permissionFound); - } - - /** - * merge manually merges the from from to the to permission - * -this method is created due to inefficiency of JPA EM merge - * @param from - * @param to - * @return merged permission - */ - private Permission merge(Permission from, Permission to) throws Exception { - if (!(from.getResourceName().equalsIgnoreCase(to.getResourceName()))) { - String msg = "Resource name cannot be changed " + to.getResourceName(); - logger.error(msg); - throw new BadRequestException(msg); - } - //resource name, attribute cannot be changed - - if (from.getDescription() != null) { - to.setDescription(from.getDescription()); - } - if (from.getEffect() != null) { - to.setEffect(from.getEffect()); - } - List fromActions = from.getAction(); - if (!fromActions.isEmpty()) { - //override the whole list, no reconcilliation by design - to.setAction(fromActions); - } - - if (logger.isDebugEnabled()) { - logger.debug("merged permission=" + JaxbUtils.toString(to, Permission.class)); - } - - handlePermissionActions(to); - return to; - } - - @Override - public void completeUpdate(DocumentWrapper wrapDoc) throws Exception { - Permission upAcc = wrapDoc.getWrappedObject(); - getServiceContext().setOutput(upAcc); - sanitize(upAcc); - //FIXME update lower-layer authorization (acls) - //will require deleting old permissions for this resource and adding - //new based on new actions and effect - } - - @Override - public void handleGet(DocumentWrapper wrapDoc) throws Exception { - setCommonPart(extractCommonPart(wrapDoc)); - sanitize(getCommonPart()); - getServiceContext().setOutput(permission); - } - - @Override - public void handleGetAll(DocumentWrapper wrapDoc) throws Exception { - PermissionsList permissionsList = extractCommonPartList(wrapDoc); - setCommonPartList(permissionsList); - getServiceContext().setOutput(getCommonPartList()); - } - - @Override - public void completeDelete(DocumentWrapper wrapDoc) throws Exception { - } - - @Override - public Permission extractCommonPart( - DocumentWrapper wrapDoc) - throws Exception { - return wrapDoc.getWrappedObject(); - } - - @Override - public void fillCommonPart(Permission obj, DocumentWrapper wrapDoc) - throws Exception { - throw new UnsupportedOperationException("operation not relevant for AccountDocumentHandler"); - } - - @Override - public PermissionsList extractCommonPartList( - DocumentWrapper wrapDoc) - throws Exception { - - PermissionsList permissionsList = new PermissionsList(); - List list = new ArrayList(); - permissionsList.setPermission(list); - for (Object obj : wrapDoc.getWrappedObject()) { - Permission permission = (Permission) obj; - sanitize(permission); - list.add(permission); - } - return permissionsList; - } - - @Override - public Permission getCommonPart() { - return permission; - } - - @Override - public void setCommonPart(Permission permission) { - this.permission = permission; - } - - @Override - public PermissionsList getCommonPartList() { - return permissionsList; - } - - @Override - public void setCommonPartList(PermissionsList permissionsList) { - this.permissionsList = permissionsList; - } - - @Override - public String getQProperty( - String prop) { - return null; - } - - @Override - public DocumentFilter createDocumentFilter() { - DocumentFilter filter = new PermissionJpaFilter(this.getServiceContext()); - return filter; - } - - /** - * sanitize removes data not needed to be sent to the consumer - * @param permission - */ - private void sanitize(Permission permission) { - if (!SecurityUtils.isCSpaceAdmin()) { - permission.setTenantId(null); - } - } - - private void setTenant(Permission permission) { - //set tenant only if not available from input - if (permission.getTenantId() == null || permission.getTenantId().isEmpty()) { - permission.setTenantId(getServiceContext().getTenantId()); - } - } -} +The PermissionDocumentHandler.java file has been move to the "org.collectionspace.services.common" module. \ No newline at end of file diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java index c9628487c..0e4534813 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/PermissionValidatorHandler.java @@ -26,6 +26,8 @@ package org.collectionspace.services.authorization.storage; import java.util.List; +import javax.xml.bind.JAXBElement; + import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionAction; import org.collectionspace.services.client.PermissionClient; @@ -33,6 +35,7 @@ import org.collectionspace.services.common.ServiceMessages; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentHandler.Action; import org.collectionspace.services.common.document.InvalidDocumentException; +import org.collectionspace.services.common.document.JaxbUtils; import org.collectionspace.services.common.document.ValidatorHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,12 +44,12 @@ import org.slf4j.LoggerFactory; * PermissionValidatorHandler executes validation rules for permission * @author */ -public class PermissionValidatorHandler implements ValidatorHandler { +public class PermissionValidatorHandler implements ValidatorHandler { final Logger logger = LoggerFactory.getLogger(PermissionValidatorHandler.class); @Override - public void validate(Action action, ServiceContext ctx) + public void validate(Action action, ServiceContext ctx) throws InvalidDocumentException { if (logger.isDebugEnabled()) { logger.debug("validate() action=" + action.name()); @@ -57,21 +60,27 @@ public class PermissionValidatorHandler implements ValidatorHandler { boolean invalid = false; if (action.equals(Action.CREATE)) { - //create specific validation here if (permission.getResourceName() == null || permission.getResourceName().isEmpty()) { invalid = true; msgBldr.append("\nThe resource name for creating a new permission resource is missing or empty."); - } else { - invalid = !validateActionFields(permission); } + if (validateActionFields(action, permission) == false) { + invalid = true; + msgBldr.append("\nAction info is missing or inconsistent."); + } + if (permission.getEffect() == null) { + invalid = true; + msgBldr.append("\n'effect' elment is missing from the payload or is not set to either PERMIT or DENY."); + } } else if (action.equals(Action.UPDATE)) { - //update specific validation here if (permission.getResourceName() == null || permission.getResourceName().isEmpty()) { invalid = true; msgBldr.append("\nThe resource name for updating an existing permission is missing or empty."); - } else { - invalid = !validateActionFields(permission); } + if (validateActionFields(action, permission) == false) { + invalid = true; + msgBldr.append("\nAction info is missing or inconsistent."); + } } if (invalid) { @@ -86,7 +95,7 @@ public class PermissionValidatorHandler implements ValidatorHandler { } } - private boolean validateActionFields(Permission permission) { + private boolean validateActionFields(Action action, Permission permission) { boolean result = true; List permActionList = permission.getAction(); @@ -106,7 +115,15 @@ public class PermissionValidatorHandler implements ValidatorHandler { // if the action list field is not set, but the action group is set then set the action actionL permission.setAction(PermissionClient.getActionList(permActionGroup)); } else { - // both action fields are not set, we don't care. + if (action.equals(Action.CREATE)) { + result = false; + org.collectionspace.services.authorization.perms.ObjectFactory objectFactory = + new org.collectionspace.services.authorization.perms.ObjectFactory(); + JAXBElement permJaxbElement = objectFactory.createPermission(permission); + String msg = String.format("Either (or both) the 'action' or 'actiongroup' element needs to be set: %s", + JaxbUtils.toString(permJaxbElement, Permission.class)); + logger.error(msg); + } } return result; diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java index cd777062f..8e436a4fd 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleDocumentHandler.java @@ -45,6 +45,7 @@ import org.collectionspace.services.common.document.DocumentFilter; import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.common.document.JaxbUtils; import org.collectionspace.services.common.security.SecurityUtils; +import org.collectionspace.services.common.storage.TransactionContext; import org.collectionspace.services.common.storage.jpa.JpaDocumentHandler; import org.slf4j.Logger; @@ -164,6 +165,13 @@ public class RoleDocumentHandler // List permValueList = role.getPermission(); if (permValueList != null && permValueList.size() > 0) { + // + // To prevent new Permissions being created (especially low-level Spring Security perms), we'll first flush the current + // JPA context to ensure our Role can be successfully persisted. + // + TransactionContext jpaTransactionContext = this.getServiceContext().getCurrentTransactionContext(); + jpaTransactionContext.flush(); + // create and persist a permrole instance // The caller of this method needs to ensure a valid and active EM (EntityManager) instance is in the Service context RoleValue roleValue = RoleFactory.createRoleValueInstance(role); diff --git a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleValidatorHandler.java b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleValidatorHandler.java index 4e239b32d..75b542baf 100644 --- a/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleValidatorHandler.java +++ b/services/authorization-mgt/service/src/main/java/org/collectionspace/services/authorization/storage/RoleValidatorHandler.java @@ -26,6 +26,7 @@ package org.collectionspace.services.authorization.storage; import org.collectionspace.services.authorization.Role; +import org.collectionspace.services.client.RoleClient; import org.collectionspace.services.common.ServiceMessages; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentHandler.Action; @@ -38,35 +39,38 @@ import org.slf4j.LoggerFactory; * RoleValidatorHandler executes validation rules for role * @author */ -public class RoleValidatorHandler implements ValidatorHandler { +public class RoleValidatorHandler implements ValidatorHandler { final Logger logger = LoggerFactory.getLogger(RoleValidatorHandler.class); @Override - public void validate(Action action, ServiceContext ctx) + public void validate(Action action, ServiceContext ctx) throws InvalidDocumentException { if (logger.isDebugEnabled()) { logger.debug("validate() action=" + action.name()); } try { - Role role = (Role) ctx.getInput(); + Role role = ctx.getInput(); StringBuilder msgBldr = new StringBuilder(ServiceMessages.VALIDATION_FAILURE); boolean invalid = false; if (action.equals(Action.CREATE)) { - - //create specific validation here if (role.getRoleName() == null || role.getRoleName().isEmpty()) { invalid = true; msgBldr.append("\nroleName : missing or empty"); + } else { + if (role.getRoleName().startsWith(RoleClient.BACKEND_ROLE_PREFIX)) { + invalid = true; + msgBldr.append(String.format("\nroleName : cannot beging with '%s'", RoleClient.BACKEND_ROLE_PREFIX)); + } } } else if (action.equals(Action.UPDATE)) { - //update specific validation here if (role.getRoleName() == null || role.getRoleName().isEmpty()) { invalid = true; msgBldr.append("\nroleName : cannot be missing or empty"); } } + if (invalid) { String msg = msgBldr.toString(); logger.error(msg); diff --git a/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql b/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql index c5cb4b10f..eb9f87fc1 100644 --- a/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql +++ b/services/authorization/pstore/src/main/resources/db/postgresql/authorization.sql @@ -5,10 +5,24 @@ DROP TABLE IF EXISTS permissions_actions CASCADE; DROP TABLE IF EXISTS permissions_roles CASCADE; DROP TABLE IF EXISTS roles CASCADE; DROP SEQUENCE IF EXISTS hibernate_sequence; -create table accounts_roles (HJID int8 not null, account_id varchar(128) not null, created_at timestamp not null, role_id varchar(128) not null, role_name varchar(255), screen_name varchar(255), user_id varchar(128) not null, primary key (HJID), unique (account_id, role_id)); -create table permissions (csid varchar(128) not null, action_group varchar(128), attribute_name varchar(128), created_at timestamp not null, description varchar(255), effect varchar(32) not null, resource_name varchar(128) not null, tenant_id varchar(128) not null, updated_at timestamp, primary key (csid), unique (resource_name, action_group, tenant_id)); -create table permissions_actions (HJID int8 not null, name varchar(128) not null, objectIdentity varchar(128) not null, objectIdentityResource varchar(128) not null, ACTION__PERMISSION_CSID varchar(128), primary key (HJID)); -create table permissions_roles (HJID int8 not null, actionGroup varchar(255), created_at timestamp not null, permission_id varchar(128) not null, permission_resource varchar(255), role_id varchar(128) not null, role_name varchar(255), primary key (HJID), unique (permission_id, role_id)); -create table roles (csid varchar(128) not null, created_at timestamp not null, description varchar(255), displayname varchar(200) not null, rolegroup varchar(255), rolename varchar(200) not null, tenant_id varchar(128) not null, metadata_protection varchar(255), perms_protection varchar(255), updated_at timestamp, primary key (csid), unique (rolename, tenant_id), unique (displayname, tenant_id)); + +create table accounts_roles (HJID int8 not null, account_id varchar(128) not null, created_at timestamp not null, role_id varchar(128) not null, + role_name varchar(255) not null, screen_name varchar(255), user_id varchar(128) not null, primary key (HJID), unique (account_id, role_id)); + +create table permissions (csid varchar(128) not null, action_group varchar(128), attribute_name varchar(128), created_at timestamp not null, description varchar(255), effect varchar(32) not null, + metadata_protection varchar(255), actions_protection varchar(255), + resource_name varchar(128) not null, tenant_id varchar(128) not null, + updated_at timestamp, primary key (csid)); + +create table permissions_actions (HJID int8 not null, name varchar(128) not null, objectIdentity varchar(128) not null, objectIdentityResource varchar(128) not null, + ACTION__PERMISSION_CSID varchar(128), primary key (HJID)); + + create table permissions_roles (HJID int8 not null, actionGroup varchar(255), created_at timestamp not null, permission_id varchar(128) not null, permission_resource varchar(255), role_id varchar(128) not null, role_name varchar(255), primary key (HJID), unique (permission_id, role_id)); + +create table roles (csid varchar(128) not null, created_at timestamp not null, description varchar(255), displayname varchar(200) not null, rolegroup varchar(255), + rolename varchar(200) not null, tenant_id varchar(128) not null, + metadata_protection varchar(255), perms_protection varchar(255), + updated_at timestamp, primary key (csid), unique (rolename, tenant_id), unique (displayname, tenant_id)); + alter table permissions_actions add constraint FK85F82042E2DC84FD foreign key (ACTION__PERMISSION_CSID) references permissions; create sequence hibernate_sequence; diff --git a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java index 58e7ac8de..016954d53 100644 --- a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java +++ b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/AuthZ.java @@ -28,12 +28,13 @@ import java.util.HashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.collectionspace.authentication.AuthN; import org.collectionspace.authentication.CSpaceTenant; import org.collectionspace.authentication.CSpaceUser; -import org.collectionspace.authentication.spi.AuthNContext; import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.spi.CSpaceAuthorizationProvider; + import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -107,8 +108,7 @@ public class AuthZ { if (logger.isDebugEnabled()) { logger.debug("reading beanConfig=" + beanConfig); } - ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext( - new String[]{beanConfig}); + ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[]{beanConfig}); // FIXME: This is never used. Keep it for debugging? provider = (CSpaceAuthorizationProvider) appContext.getBean("cspaceAuthorizationProvider"); if (logger.isDebugEnabled()) { logger.debug("initialized the authz provider"); @@ -127,7 +127,7 @@ public class AuthZ { try { for (CSpaceResource res : resources) { CSpaceAction action = res.getAction(); - addPermissions(res, action, principals, grant); + addPermission(res, action, principals, grant); } provider.commitTransaction(status); } catch (Throwable t) { @@ -143,7 +143,7 @@ public class AuthZ { * @param principals * @param grant true to grant false to deny */ - private void addPermissions(CSpaceResource res, CSpaceAction action, String[] principals, boolean grant) + private void addPermission(CSpaceResource res, CSpaceAction action, String[] principals, boolean grant) throws PermissionException { provider.getPermissionManager().addPermissionsToRoles(res, action, principals, grant); provider.clearAclCache(); @@ -156,14 +156,14 @@ public class AuthZ { * @param res * @param principals */ - public void deletePermissionsFromRoles(CSpaceResource[] resources, String[] principals) + public void deletePermissionsFromRoles(CSpaceResource[] resources, String[] principals) // FIXME: # Can tx move one level up? throws PermissionNotFoundException, PermissionException { TransactionStatus status = provider.beginTransaction("deletePermssions"); try { for (CSpaceResource res : resources) { CSpaceAction action = res.getAction(); - deletePermissionsFromRoles(res, action, principals); + deletePermissionFromRoles(res, action, principals); } provider.commitTransaction(status); } catch (Throwable t) { @@ -179,9 +179,9 @@ public class AuthZ { * @param action * @param principals */ - private void deletePermissionsFromRoles(CSpaceResource res, CSpaceAction action, String[] principals) + private void deletePermissionFromRoles(CSpaceResource res, CSpaceAction action, String[] principals) throws PermissionNotFoundException, PermissionException { - provider.getPermissionManager().deletePermissionsFromRoles(res, action, principals); + provider.getPermissionManager().deletePermissionFromRoles(res, action, principals); provider.clearAclCache(); } diff --git a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spi/CSpacePermissionManager.java b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spi/CSpacePermissionManager.java index b8f0f30de..c0dd99aa3 100644 --- a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spi/CSpacePermissionManager.java +++ b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spi/CSpacePermissionManager.java @@ -57,7 +57,7 @@ public interface CSpacePermissionManager { * @see CSpaceResource * @see CSpaceAction */ - public void deletePermissionsFromRoles(CSpaceResource res, CSpaceAction action, String[] principals) + public void deletePermissionFromRoles(CSpaceResource res, CSpaceAction action, String[] principals) throws PermissionNotFoundException, PermissionException; /** diff --git a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringPermissionManager.java b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringPermissionManager.java index 70e5f0b62..ef2b1074f 100644 --- a/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringPermissionManager.java +++ b/services/authorization/service/src/main/java/org/collectionspace/services/authorization/spring/SpringPermissionManager.java @@ -137,7 +137,7 @@ public class SpringPermissionManager implements CSpacePermissionManager { * @throws PermissionException */ @Override - public void deletePermissionsFromRoles(CSpaceResource res, CSpaceAction action, String[] principals) + public void deletePermissionFromRoles(CSpaceResource res, CSpaceAction action, String[] principals) throws PermissionNotFoundException, PermissionException { ObjectIdentity oid = SpringAuthorizationProvider.getObjectIdentity(res); Sid[] sids = SpringAuthorizationProvider.getSids(principals); @@ -181,14 +181,6 @@ public class SpringPermissionManager implements CSpacePermissionManager { throw new PermissionException(msg, ex); } } - if (log.isDebugEnabled()) { - log.debug("deletedpermissions(res,action,prin[]), success for " - + " res=" + res.toString() - + " action=" + action.toString() - + " oid=" + oid.toString() - + " perm=" + p.toString() - + " sids=" + sids.toString()); - } } /** @@ -360,7 +352,7 @@ public class SpringPermissionManager implements CSpacePermissionManager { + " found " + aces + " aces"); } ArrayList foundAces = new ArrayList(); - Iterator iter = acel.listIterator(); + Iterator iter = acel.listIterator(); //not possible to delete while iterating while (iter.hasNext()) { AccessControlEntry ace = (AccessControlEntry) iter.next(); @@ -376,11 +368,17 @@ public class SpringPermissionManager implements CSpacePermissionManager { } i++; } + + boolean updateNeeded = false; for (int j = foundAces.size() - 1; j >= 0; j--) { //the following operation does not work while iterating in the while loop acl.deleteAce(foundAces.get(j)); //autobox + updateNeeded = true; + } + + if (updateNeeded) { + provider.getProviderAclService().updateAcl(acl); } - provider.getProviderAclService().updateAcl(acl); if (log.isDebugEnabled()) { log.debug("deletePermissions: for acl oid=" + oid.toString() diff --git a/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java b/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java index 427228b76..43746e853 100644 --- a/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java +++ b/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java @@ -25,8 +25,10 @@ package org.collectionspace.services.common.api; import java.io.File; import java.io.InputStream; +import java.util.List; import java.util.Properties; import java.util.regex.Pattern; + import java.util.regex.Matcher; /** General utility methods. @@ -402,4 +404,12 @@ public class Tools { return result; } + + public static boolean isEmpty(List theList) { + if (theList != null && theList.size() > 0) { + return false; + } else { + return true; + } + } } diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java index f331ef5f2..d13775052 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionResource.java @@ -27,24 +27,35 @@ import org.collectionspace.services.authorization.PermissionRole; import org.collectionspace.services.authorization.PermissionRoleRel; import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.authorization.perms.Permission; +import org.collectionspace.services.authorization.perms.PermissionAction; import org.collectionspace.services.authorization.perms.PermissionsList; -import org.collectionspace.services.authorization.storage.AuthorizationDelegate; +import org.collectionspace.services.authorization.storage.PermissionDocumentHandler; import org.collectionspace.services.client.CollectionSpaceClientUtils; import org.collectionspace.services.client.PayloadOutputPart; import org.collectionspace.services.client.PermissionClient; +import org.collectionspace.services.common.CSWebApplicationException; import org.collectionspace.services.common.SecurityResourceBase; import org.collectionspace.services.common.ServiceMessages; +import org.collectionspace.services.common.api.Tools; +import org.collectionspace.services.common.authorization_mgt.PermissionRoleUtil; import org.collectionspace.services.common.context.RemoteServiceContextFactory; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.context.ServiceContextFactory; +import org.collectionspace.services.common.document.DocumentException; +import org.collectionspace.services.common.document.DocumentHandler; +import org.collectionspace.services.common.document.DocumentHandler.Action; +import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.storage.StorageClient; import org.collectionspace.services.common.storage.TransactionContext; import org.collectionspace.services.common.storage.jpa.JPATransactionContext; import org.collectionspace.services.common.storage.jpa.JpaStorageClientImpl; + import org.jboss.resteasy.util.HttpResponseCodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -62,7 +73,7 @@ import javax.ws.rs.core.UriInfo; @Path(PermissionClient.SERVICE_PATH) @Consumes("application/xml") @Produces("application/xml") -public class PermissionResource extends SecurityResourceBase { +public class PermissionResource extends SecurityResourceBase { final Logger logger = LoggerFactory.getLogger(PermissionResource.class); final StorageClient storageClient = new JpaStorageClientImpl(); @@ -89,7 +100,7 @@ public class PermissionResource extends SecurityResourceBase { } @Override - public StorageClient getStorageClient(@SuppressWarnings("rawtypes") ServiceContext ctx) { + public StorageClient getStorageClient(ServiceContext ctx) { //FIXME use ctx to identify storage client return storageClient; } @@ -110,7 +121,7 @@ public class PermissionResource extends SecurityResourceBase { Response response = createPermission(jpaTransactionContext, input); if (response.getStatus() == Response.Status.CREATED.getStatusCode()) { permCsid = CollectionSpaceClientUtils.extractId(response); - result = (Permission)get(jpaTransactionContext, permCsid, Permission.class); + result = (Permission)get(jpaTransactionContext, permCsid, Permission.class); // return the result of a full lookup of what we just persisted } return result; @@ -134,42 +145,187 @@ public class PermissionResource extends SecurityResourceBase { return result; } + /** + * Updates a permission by first deleting it and all of it's relationships (with roles, and perm-actions) and then + * recreating it. Unfortunately, we can't seem to be able to just update the perm-actions because of an issue with JPA. + * + * @param ui + * @param csid + * @param theUpdate + * @return + * @throws Exception + */ @PUT @Path("{csid}") - public Permission updatePermission(@PathParam("csid") String csid,Permission theUpdate) { - return (Permission)update(csid, theUpdate, Permission.class); + synchronized public Permission updatePermission(@Context UriInfo ui, @PathParam("csid") String csid, Permission theUpdate) throws Exception { + Permission result = null; + ensureCSID(csid, ServiceMessages.UPDATE_FAILED + "permission "); + + ServiceContext ctx = createServiceContext(null, Permission.class); + PermissionDocumentHandler docHandler = (PermissionDocumentHandler) createDocumentHandler(ctx); + + TransactionContext transactionContext = ctx.openConnection(); + try { + transactionContext.beginTransaction(); + // + // Get a copy of the currently persisted resource + // + Permission original = (Permission) get(transactionContext, csid, Permission.class); + if (original == null) { + throw new DocumentNotFoundException(String.format("The Permission resource CSID=%s could not be found.", csid)); + } else if (isImmutable(original) == true) { + String msg = String.format("Permission resource CSID=%s is immutable and cannot be updated.", csid); + throw new DocumentException(msg); + } + + Permission perm = copyForUpdate(original); // If we upgrade to JPA 2.0, we could just "detach" the original instead of needing to copy it. + + // + // Before we start the update process, verify the payload is valid + // + ctx.setInput(theUpdate); + docHandler.prepare(Action.UPDATE); + + // + // Get a copy of the permission-role relationships + // + PermissionRole permRole = getPermissionRole(ctx, csid); + + // + // Delete the Permission resource (and related perm-actions, and perm-roles) from storage and remove from current JPA context + // + Response deletedRes = deletePermission(ctx, csid); + if (deletedRes.getStatus() != Response.Status.OK.getStatusCode()) { + throw new DocumentException(String.format("Could not update Permission resource CSID=%s", csid)); + } + + // + // Merge the "update" payload with the corresponding Permission resource payload + // + perm = docHandler.merge(perm, theUpdate); + + // + // Recreate the Permission resource (and related perm-actions) using the same CSID and updated Permission object + // + ctx.setInput(perm); + ctx.setProperty(PermissionClient.PERMISSION_UPDATE_CSID, csid); + Response res = create(ctx, perm); + if (res.getStatus() != Response.Status.CREATED.getStatusCode()) { + throw new DocumentException(String.format("Could not update Permission resource CSID=%s", csid)); + } + + // + // Recreate the permission-role relationships + // + if (PermissionRoleUtil.isEmpty(permRole) == false) { + Response permRoleRes = createPermissionRole(ctx, csid, permRole); + if (permRoleRes.getStatus() != Response.Status.CREATED.getStatusCode()) { + throw new DocumentException(String.format("Could not update Permission resource CSID=%s", csid)); + } + } + + transactionContext.commitTransaction(); + result = perm; + } catch (Exception e) { + transactionContext.markForRollback(); + throw bigReThrow(e, ServiceMessages.UPDATE_FAILED, csid); + } finally { + if (result == null) { + // + // + // + } + ctx.closeConnection(); + } + + return result; } - @SuppressWarnings("unchecked") + /** + * Return true if the permission is immutable. + * + * @param original + * @return + */ + private boolean isImmutable(Permission original) { + boolean result = false; + + if ((!Tools.isEmpty(original.getMetadataProtection()) && original.getMetadataProtection().equals(PermissionClient.IMMUTABLE)) + || (!Tools.isEmpty(original.getActionsProtection()) && original.getActionsProtection().equals(PermissionClient.IMMUTABLE))) { + result = true; + } + + return result; + } + + private Permission copyForUpdate(Permission theOriginal) throws DocumentException { + Permission result = null; + + if (theOriginal != null) { + result = new Permission(); + result.setAttributeName(theOriginal.getAttributeName()); + result.setDescription(theOriginal.getDescription()); + result.setEffect(theOriginal.getEffect()); + result.setResourceName(theOriginal.getResourceName()); + result.setTenantId(theOriginal.getTenantId()); + result.setActionGroup(theOriginal.getActionGroup()); + + for (PermissionAction permissionAction : theOriginal.getAction()) { + result.getAction().add(copyForUpdate(permissionAction)); + } + } + + return result; + } + + private PermissionAction copyForUpdate(PermissionAction permissionAction) { + PermissionAction result = new PermissionAction(); + + result.setName(permissionAction.getName()); + result.setObjectIdentity(permissionAction.getObjectIdentity()); + result.setObjectIdentityResource(permissionAction.getObjectIdentityResource()); + + return result; + } + + /** + * Deletes the Permission resource and its relationship(s) with any role(s). Does not delete the actual low-level permission-action tuples. + * See https://issues.collectionspace.org/browse/DRYD-223 + * + * @param csid + * @return + * @throws Exception + */ @DELETE @Path("{csid}") - synchronized public Response deletePermission(@PathParam("csid") String csid) throws Exception { + public Response deletePermission(@PathParam("csid") String csid) throws Exception { logger.debug("deletePermission with csid=" + csid); ensureCSID(csid, ServiceMessages.DELETE_FAILED + "permission "); ServiceContext ctx = createServiceContext((Permission) null, Permission.class); + return deletePermission(ctx, csid); + } + + synchronized public Response deletePermission(ServiceContext ctx, String csid) throws Exception { + DocumentHandler docHandler = createDocumentHandler(ctx); + TransactionContext transactionContext = ctx.openConnection(); try { transactionContext.beginTransaction(); // // First, delete the relationships between the Permission resource and any Role resources. // - PermissionRoleSubResource subResource = - new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); - subResource.deletePermissionRole(ctx, csid, SubjectType.ROLE); - // - // Next, delete the low-level (Spring Security) permissions. - // - // NOTE: For deletePermission() in the authz provider at the PermissionRoleSubResource/DocHandler level, there is no visibility - // if permission is deleted, so do it here. - // - // WARNING: This operation deletes the Spring ACL (not the ACEs). It's possible the ACL might be needed for other ACEs roles... - // - AuthorizationDelegate.deletePermissions((JPATransactionContext)transactionContext, csid); // Deletes the low-level (Spring Security) permissions + try { + PermissionRoleSubResource subResource = + new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + subResource.deletePermissionRole(ctx, csid, SubjectType.ROLE); + } catch (DocumentNotFoundException dnf) { + // ignore, just means we didn't find any relationships to delete + } // // Lastly, delete the Permission resource itself and commit the transaction // - getStorageClient(ctx).delete(ctx, csid); + getStorageClient(ctx).delete(ctx, csid, docHandler); transactionContext.commitTransaction(); } catch (Exception e) { transactionContext.markForRollback(); @@ -180,31 +336,40 @@ public class PermissionResource extends SecurityResourceBase { return Response.status(HttpResponseCodes.SC_OK).build(); } - + @POST @Path("{csid}/permroles") - public Response createPermissionRole(@QueryParam("_method") String method, + public Response createPermissionRole( + @QueryParam("_method") String method, @PathParam("csid") String permCsid, - PermissionRole input) { - if (method != null) { - if ("delete".equalsIgnoreCase(method)) { - return deletePermissionRole(permCsid, input); - } - } - logger.debug("createPermissionRole with permCsid=" + permCsid); - ensureCSID(permCsid, ServiceMessages.POST_FAILED + "permroles permission "); - try { - PermissionRoleSubResource subResource = - new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); - String permrolecsid = subResource.createPermissionRole((ServiceContext)null, input, SubjectType.ROLE); - UriBuilder path = UriBuilder.fromResource(PermissionResource.class); - path.path(permCsid + "/permroles/" + permrolecsid); - Response response = Response.created(path.build()).build(); - return response; - } catch (Exception e) { - throw bigReThrow(e, ServiceMessages.POST_FAILED, permCsid); - } - } + PermissionRole input) { + if (method != null) { + if ("delete".equalsIgnoreCase(method)) { // FIXME: How could 'method' ever equal "delete" + return deletePermissionRole(permCsid, input); + } + } + logger.debug("createPermissionRole with permCsid=" + permCsid); + ensureCSID(permCsid, ServiceMessages.POST_FAILED + "permroles permission "); + + return createPermissionRole((ServiceContext)null, permCsid, input); + } + + protected Response createPermissionRole( + ServiceContext ctx, + String permCsid, + PermissionRole input) { + try { + PermissionRoleSubResource subResource = new PermissionRoleSubResource( + PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); + String permrolecsid = subResource.createPermissionRole(ctx, input, SubjectType.ROLE); + UriBuilder path = UriBuilder.fromResource(PermissionResource.class); + path.path(permCsid + "/permroles/" + permrolecsid); + Response response = Response.created(path.build()).build(); + return response; + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.POST_FAILED, permCsid); + } + } @GET @Path("{csid}/permroles/{id}") @@ -228,24 +393,37 @@ public class PermissionResource extends SecurityResourceBase { @GET @Path("{csid}/permroles") - public PermissionRole getPermissionRole( - @PathParam("csid") String permCsid) { + public PermissionRole getPermissionRole(@PathParam("csid") String permCsid) { logger.debug("getPermissionRole with permCsid=" + permCsid); ensureCSID(permCsid, ServiceMessages.GET_FAILED + "permroles permission "); + + PermissionRole result = getPermissionRole((ServiceContext)null, permCsid); + + if (PermissionRoleUtil.isEmpty(result)) { + String msg = String.format("Could not find any permission-role relationships for Permission resource CSID=%s", permCsid); + Response response = Response.status(Response.Status.NOT_FOUND).entity(msg).type("text/plain").build(); + throw new CSWebApplicationException(response); + } + + return result; + } + + private PermissionRole getPermissionRole(ServiceContext ctx, String permCsid) { + ensureCSID(permCsid, ServiceMessages.GET_FAILED + "permroles permission "); PermissionRole result = null; + try { PermissionRoleSubResource subResource = new PermissionRoleSubResource(PermissionRoleSubResource.PERMISSION_PERMROLE_SERVICE); - //get relationships for a permission - result = subResource.getPermissionRole((ServiceContext)null, permCsid, SubjectType.ROLE); + result = subResource.getPermissionRole(ctx, permCsid, SubjectType.ROLE); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED, permCsid); } - checkResult(result, permCsid, ServiceMessages.GET_FAILED); + return result; - } - - public Response deletePermissionRole(String permCsid, PermissionRole input) { + } + + private Response deletePermissionRole(String permCsid, PermissionRole input) { logger.debug("Delete payload of permrole relationships with permission permCsid=" + permCsid); ensureCSID(permCsid, ServiceMessages.DELETE_FAILED + "permroles permission "); try { diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java index cc38e6a01..887962d58 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/PermissionRoleSubResource.java @@ -30,6 +30,7 @@ import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.storage.PermissionRoleDocumentHandler; import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl; +import org.collectionspace.services.common.authorization_mgt.PermissionRoleUtil; import org.collectionspace.services.common.context.RemoteServiceContextFactory; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.context.ServiceContextFactory; @@ -51,6 +52,8 @@ import org.slf4j.LoggerFactory; @SuppressWarnings("rawtypes") public class PermissionRoleSubResource extends AbstractCollectionSpaceResourceImpl { + /** The logger. */ + final Logger logger = LoggerFactory.getLogger(PermissionRoleSubResource.class); public final static String ROLE_PERMROLE_SERVICE = "authorization/roles/permroles"; public final static String PERMISSION_PERMROLE_SERVICE = "authorization/permissions/permroles"; @@ -58,8 +61,6 @@ public class PermissionRoleSubResource //service name to identify binding /** The service name. */ private String serviceName = "authorization/permroles"; - /** The logger. */ - final Logger logger = LoggerFactory.getLogger(PermissionRoleSubResource.class); /** The storage client. */ final StorageClient storageClient = new JpaRelationshipStorageClient(); /** @@ -247,15 +248,32 @@ public class PermissionRoleSubResource if (logger.isDebugEnabled()) { logger.debug("deletePermissionRole with csid=" + csid); } + PermissionRole permRole = getPermissionRole(parentCtx, csid, subject); - if (permRole != null) { + if (PermissionRoleUtil.isEmpty(permRole) == false) { deletePermissionRole(parentCtx, csid, subject, permRole); } else { - String msg = String.format("The permission CSID=%s is missing or not related to any roles.", csid); + String msg = String.format("The %s CSID=%s is missing or not related to any objects.", + getInverse(subject).toString().toLowerCase(), csid); throw new DocumentNotFoundException(msg); } } - + + /* + * Returns the inverse of the subject (the object) + */ + private SubjectType getInverse(SubjectType subject) { + SubjectType result; + + if (subject.equals(SubjectType.PERMISSION)) { + result = SubjectType.ROLE; + } else { + result = SubjectType.PERMISSION; + } + + return result; + } + /** * deletePermissionRole deletes permission-role relationships using given * csid of object (permission/role) and subject (role/permission) diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java index ddc9c5584..812ddb488 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/AuthorizationDelegate.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; import org.collectionspace.authentication.AuthN; + import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.AuthZ; import org.collectionspace.services.authorization.CSpaceAction; @@ -34,18 +35,20 @@ import org.collectionspace.services.authorization.CSpaceResource; import org.collectionspace.services.authorization.perms.EffectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionAction; -import org.collectionspace.services.authorization.PermissionException; import org.collectionspace.services.authorization.PermissionRole; import org.collectionspace.services.authorization.PermissionValue; import org.collectionspace.services.authorization.Role; import org.collectionspace.services.authorization.RoleValue; import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.authorization.URIResourceImpl; + import org.collectionspace.services.common.authorization_mgt.PermissionRoleUtil; import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.storage.jpa.JPATransactionContext; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,13 +63,14 @@ public class AuthorizationDelegate { private static final Logger logger = LoggerFactory.getLogger(AuthorizationDelegate.class); /** - * addPermissions add permissions represented given PermissionRole + * Add low-level Spring permissions represented by the given PermissionRole instance + * * @param ctx * @param pr permission role * @throws Exception * @see PermissionRole */ - public static void addRelationships(ServiceContext ctx, PermissionRole pr) throws Exception { + public static void addRelationships(ServiceContext ctx, PermissionRole pr) throws Exception { JPATransactionContext jpaTransactionContext = (JPATransactionContext) ctx.getCurrentTransactionContext(); SubjectType subject = PermissionRoleUtil.getRelationSubject(ctx, pr); @@ -83,6 +87,7 @@ public class AuthorizationDelegate { String[] roles = getRoles(jpaTransactionContext, pr.getRole()); boolean grant = permission.getEffect().equals(EffectType.PERMIT) ? true : false; authz.addPermissions(resources, roles, grant); + jpaTransactionContext.setAclTablesUpdateFlag(true); // Tell the containing JPA transaction that we've committed changes to the Spring Tables } else if (SubjectType.PERMISSION.equals(subject)) { RoleValue rv = pr.getRole().get(0); Role role = getRole(jpaTransactionContext, rv.getRoleId()); @@ -97,14 +102,13 @@ public class AuthorizationDelegate { for (PermissionValue pv : pr.getPermission()) { Permission p = getPermission(jpaTransactionContext, pv.getPermissionId()); if (p == null) { - String msg = "addPermissions: No permission resource found for csid=" + pv.getPermissionId(); - logger.error(msg); - //TODO: would be nice contiue to still send 400 back - continue; + String msg = "addRelationships: No permission resource found for csid=" + pv.getPermissionId(); + throw new DocumentException(msg); } CSpaceResource[] resources = getResources(p); boolean grant = p.getEffect().equals(EffectType.PERMIT) ? true : false; authz.addPermissions(resources, roles, grant); + jpaTransactionContext.setAclTablesUpdateFlag(true); // Tell the containing JPA transaction that we've committed changes to the Spring Tables } } } @@ -112,53 +116,57 @@ public class AuthorizationDelegate { /** * deletePermissions delete all permissions associated with given permission role * @param ctx - * @param pr permissionrole + * @param permRole permissionrole * @throws Exception */ - public static void deletePermissionsFromRoles(ServiceContext ctx, PermissionRole pr) + public static void deletePermissionsFromRoles(ServiceContext ctx, PermissionRole permRole) throws Exception { JPATransactionContext jpaTransactionContext = (JPATransactionContext) ctx.getCurrentTransactionContext(); - SubjectType subject = PermissionRoleUtil.getRelationSubject(ctx, pr); + SubjectType subject = PermissionRoleUtil.getRelationSubject(ctx, permRole); AuthZ authz = AuthZ.get(); if (subject.equals(SubjectType.ROLE)) { - List permissionValues = pr.getPermission(); - if (permissionValues != null & permissionValues.size() > 0) { - PermissionValue pv = permissionValues.get(0); - Permission p = getPermission(jpaTransactionContext, pv.getPermissionId()); + List permissionValues = permRole.getPermission(); + if (permissionValues != null && permissionValues.size() == 1) { + PermissionValue permValue = permissionValues.get(0); + Permission p = getPermission(jpaTransactionContext, permValue.getPermissionId()); if (p == null) { - String msg = "deletePermissions: No permission found for id=" + pv.getPermissionId(); + String msg = "deletePermissions: No permission found for id=" + permValue.getPermissionId(); logger.error(msg); throw new DocumentNotFoundException(msg); } CSpaceResource[] resources = getResources(p); - String[] roles = getRoles(jpaTransactionContext, pr.getRole()); + String[] roles = getRoles(jpaTransactionContext, permRole.getRole()); authz.deletePermissionsFromRoles(resources, roles); + jpaTransactionContext.setAclTablesUpdateFlag(true); // Tell the containing JPA transaction that we've committed changes to the Spring Tables + } else { + throw new DocumentException("When the subject of a permrole is ROLE, there should be only ONE permission specified."); } } else if (SubjectType.PERMISSION.equals(subject)) { - List roleValues = pr.getRole(); - if (roleValues != null && roleValues.size() > 0) { - RoleValue rv = roleValues.get(0); - Role r = getRole(jpaTransactionContext, rv.getRoleId()); - if (r == null) { - String msg = "deletePermissions: No role found for id=" + rv.getRoleId(); + List roleValues = permRole.getRole(); + if (roleValues != null && roleValues.size() == 1) { + RoleValue roleValue = roleValues.get(0); + Role role = getRole(jpaTransactionContext, roleValue.getRoleId()); + if (role == null) { + String msg = "deletePermissions: No role found for id=" + roleValue.getRoleId(); logger.error(msg); throw new DocumentNotFoundException(msg); } - //using r not rv ensures we're getting the "ROLE" prefix/qualified name + // Using role not roleValue ensures we're getting the "ROLE" prefix/qualified name // This needs to use the qualified name, not the display name - String[] roles = {r.getRoleName()}; - for (PermissionValue pv : pr.getPermission()) { - Permission p = getPermission(jpaTransactionContext, pv.getPermissionId()); - if (p == null) { + String[] roles = {role.getRoleName()}; + for (PermissionValue pv : permRole.getPermission()) { + Permission perm = getPermission(jpaTransactionContext, pv.getPermissionId()); + if (perm == null) { String msg = "deletePermissions: No permission found for id=" + pv.getPermissionId(); - logger.error(msg); - //TODO: would be nice contiue to still send 400 back - continue; + throw new DocumentException(msg); } - CSpaceResource[] resources = getResources(p); + CSpaceResource[] resources = getResources(perm); authz.deletePermissionsFromRoles(resources, roles); + jpaTransactionContext.setAclTablesUpdateFlag(true); // Tell the containing JPA transaction that we've committed changes to the Spring Tables } + } else { + throw new DocumentException("When the subject of a permrole is PERMISSION, there should be only ONE role specified."); } } } @@ -182,6 +190,7 @@ public class AuthorizationDelegate { CSpaceResource[] resources = getResources(p); AuthZ.get().deletePermissions(resources); + jpaTransactionContext.setAclTablesUpdateFlag(true); // Tell the containing JPA transaction that we've committed changes to the Spring Tables } /** diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java index d618ea453..28e43efe2 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionDocumentHandler.java @@ -39,7 +39,7 @@ import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionAction; import org.collectionspace.services.authorization.perms.PermissionsList; import org.collectionspace.services.authorization.URIResourceImpl; - +import org.collectionspace.services.common.api.Tools; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.BadRequestException; import org.collectionspace.services.common.document.DocumentException; @@ -63,7 +63,7 @@ import org.slf4j.LoggerFactory; public class PermissionDocumentHandler extends JpaDocumentHandler> { - private final Logger logger = LoggerFactory.getLogger(PermissionDocumentHandler.class); + private final Logger logger = LoggerFactory.getLogger(PermissionDocumentHandler.class); private Permission permission; private PermissionsList permissionsList; @@ -131,7 +131,6 @@ public class PermissionDocumentHandler URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(), perm.getResourceName(), action); permAction.setObjectIdentity(uriRes.getHashedId().toString()); permAction.setObjectIdentityResource(uriRes.getId()); - //PermissionActionUtil.update(perm, permAction); } } @@ -165,11 +164,18 @@ public class PermissionDocumentHandler // // First check to see if an equivalent permission exists // + ServiceContext ctx = getServiceContext(); Permission permission = wrapDoc.getWrappedObject(); Permission existingPermission = findExistingPermission(permission); if (existingPermission == null) { - String id = UUID.randomUUID().toString(); + // + // If our call originates from an UPDATE/PUT request, then we can find a CSID in the service context + // + String id = (String)ctx.getProperty(PermissionClient.PERMISSION_UPDATE_CSID); + if (Tools.isEmpty(id) == true) { + id = UUID.randomUUID().toString(); + } permission.setCsid(id); setTenant(permission); handlePermissionActions(permission); @@ -185,59 +191,63 @@ public class PermissionDocumentHandler public void completeCreate(DocumentWrapper wrapDoc) throws Exception { } + /** + * Not used. Due to an issue with the JPA 1.0 update mechanism, we had to perform the update process + * in the PermissionResource class. Look there for more details. + */ + @Deprecated @Override public void handleUpdate(DocumentWrapper wrapDoc) throws Exception { - Permission permissionFound = wrapDoc.getWrappedObject(); - Permission permissionReceived = getCommonPart(); - merge(permissionReceived, permissionFound); } - /** - * merge manually merges the from from to the to permission - * -this method is created due to inefficiency of JPA EM merge - * @param from - * @param to - * @return merged permission + /* + * Merge two Permission resources for an update/put request. */ - private Permission merge(Permission from, Permission to) throws Exception { - if (!(from.getResourceName().equalsIgnoreCase(to.getResourceName()))) { - String msg = "Resource name cannot be changed " + to.getResourceName(); - logger.error(msg); - throw new BadRequestException(msg); + public Permission merge(Permission perm, Permission theUpdate) throws DocumentException { + Permission result = perm; + + if (!Tools.isEmpty(theUpdate.getResourceName()) && !theUpdate.getResourceName().equalsIgnoreCase(perm.getResourceName())) { + String msg = String.format("Failed attempt to change Permission's (CSID='%S') resource name from '%s' to '%s'.", + perm.getCsid(), perm.getResourceName(), theUpdate.getResourceName()); + throw new DocumentException(msg); } - //resource name, attribute cannot be changed - if (from.getDescription() != null) { - to.setDescription(from.getDescription()); + if (theUpdate.getDescription() != null) { + perm.setDescription(theUpdate.getDescription()); } - if (from.getEffect() != null) { - to.setEffect(from.getEffect()); + if (theUpdate.getEffect() != null) { + perm.setEffect(theUpdate.getEffect()); } - List fromActions = from.getAction(); - if (!fromActions.isEmpty()) { - // Override the whole list, no reconciliation by design - to.setAction(fromActions); - // Update the actionGroup field to reflect the new action list - to.setActionGroup(PermissionClient.getActionGroup(fromActions)); + // + // Override the whole perm-action list, no reconciliation by design. We've + // already cleaned-up and removed all the old perm-role relationships + // + // If the update didn't provide any new perm-actions, then we leave the + // existing ones alone. + // + if (Tools.isEmpty(theUpdate.getAction()) == false) { + perm.setAction(theUpdate.getAction()); + perm.setActionGroup(PermissionClient.getActionGroup(theUpdate.getAction())); } - + if (logger.isDebugEnabled()) { - logger.debug("merged permission=" + JaxbUtils.toString(to, Permission.class)); + logger.debug("merged permission=" + JaxbUtils.toString(perm, Permission.class)); } - - handlePermissionActions(to); - return to; + + return result; } - + + /** + * Because of issues with JPA 1.0 not being able to propagate updates from the 'permissions' table to the related 'permissions_actions' + * table, we need to handle updates in the PermissionResource class by deleting and creating the Permission resource + */ @SuppressWarnings("unchecked") @Override + @Deprecated public void completeUpdate(DocumentWrapper wrapDoc) throws Exception { - Permission upAcc = wrapDoc.getWrappedObject(); - getServiceContext().setOutput(upAcc); - sanitize(upAcc); - //FIXME update lower-layer authorization (acls) - //will require deleting old permissions for this resource and adding - //new based on new actions and effect + Permission updatedPerm = wrapDoc.getWrappedObject(); + getServiceContext().setOutput(updatedPerm); + sanitize(updatedPerm); } @SuppressWarnings("unchecked") diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java index a3614ef1b..0e6ea03d9 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionRoleDocumentHandler.java @@ -38,8 +38,11 @@ import org.collectionspace.services.authorization.SubjectType; import org.collectionspace.services.common.authorization_mgt.AuthorizationRoleRel; import org.collectionspace.services.common.authorization_mgt.PermissionRoleUtil; import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentFilter; +import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.document.DocumentWrapper; +import org.collectionspace.services.common.document.JaxbUtils; import org.collectionspace.services.common.document.TransactionException; import org.collectionspace.services.common.storage.jpa.JPATransactionContext; import org.collectionspace.services.common.storage.jpa.JpaDocumentFilter; @@ -204,50 +207,64 @@ public class PermissionRoleDocumentHandler AuthorizationDelegate.deletePermissionsFromRoles(getServiceContext(), pr); } - /* (non-Javadoc) + /* + * Turns a list of permission-role rows from the database into a PermissionRole object. The list of rows + * was the result of a query where the subject was either a Role or a Permission. + * + * (non-Javadoc) * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#extractCommonPart(org.collectionspace.services.common.document.DocumentWrapper) */ @Override - public PermissionRole extractCommonPart( - DocumentWrapper> wrapDoc) + public PermissionRole extractCommonPart(DocumentWrapper> wrapDoc) throws Exception { - List prrl = wrapDoc.getWrappedObject(); - PermissionRole pr = new PermissionRole(); - SubjectType subject = PermissionRoleUtil.getRelationSubject(getServiceContext()); - if (prrl.size() == 0) { - return pr; + PermissionRole result = new PermissionRole(); + + List permissionRoleRel = wrapDoc.getWrappedObject(); + if (permissionRoleRel.size() == 0) { + return result; } - PermissionRoleRel prr0 = prrl.get(0); + + SubjectType subject = PermissionRoleUtil.getRelationSubject(getServiceContext()); + result.setSubject(subject); + + PermissionRoleRel prr0 = permissionRoleRel.get(0); if (SubjectType.ROLE.equals(subject)) { - - List pvs = new ArrayList(); - pr.setPermission(pvs); + // + // Since ROLE is the subject, they'll be just one Permission + // + List permissionValueList = new ArrayList(); + result.setPermission(permissionValueList); PermissionValue pv = AuthorizationRoleRel.buildPermissionValue(prr0); - pvs.add(pv); - - //add roles - List rvs = new ArrayList(); - pr.setRole(rvs); - for (PermissionRoleRel prr : prrl) { + permissionValueList.add(pv); + // + // Add role values + // + List roleValueList = new ArrayList(); + result.setRole(roleValueList); + for (PermissionRoleRel prr : permissionRoleRel) { RoleValue rv = AuthorizationRoleRel.buildRoleValue(prr); - rvs.add(rv); + roleValueList.add(rv); } } else if (SubjectType.PERMISSION.equals(subject)) { - - List rvs = new ArrayList(); - pr.setRole(rvs); + // + // Since PERMISSION is the subject, they'll be just one Role and one or more Permissions + // + List roleValueList = new ArrayList(); + result.setRole(roleValueList); RoleValue rv = AuthorizationRoleRel.buildRoleValue(prr0); - rvs.add(rv); - - //add permssions - List pvs = new ArrayList(); - pr.setPermission(pvs); - for (PermissionRoleRel prr : prrl) { + roleValueList.add(rv); + // + // Add permssions values + // + List permissionValueList = new ArrayList(); + result.setPermission(permissionValueList); + for (PermissionRoleRel prr : permissionRoleRel) { PermissionValue pv = AuthorizationRoleRel.buildPermissionValue(prr); - pvs.add(pv); + permissionValueList.add(pv); } } - return pr; + + return result; } /** @@ -274,7 +291,13 @@ public class PermissionRoleDocumentHandler ServiceContext ctx = this.getServiceContext(); String tenantId = ctx.getTenantId(); - PermissionRoleUtil.buildPermissionRoleRel(ctx, pr, subject, prrl, handleDelete, tenantId); + try { + PermissionRoleUtil.buildPermissionRoleRel(ctx, pr, subject, prrl, handleDelete, tenantId); + } catch (DocumentNotFoundException dnf) { + String msg = String.format("The following perm-role payload references permissions and/or roles that do not exist: \n%s", + JaxbUtils.toString(pr, PermissionRole.class)); + throw new DocumentException(msg); + } } /* (non-Javadoc) diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java index 0924ff63d..dd722d2b5 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/PermissionStorageConstants.java @@ -34,6 +34,9 @@ public class PermissionStorageConstants { final public static String Q_RESOURCE_NAME = "res"; final public static String Q_ACTION_GROUP = "actGrp"; + + final public static String ID = "csid"; + final public static String TENANT_ID = "tenant_id"; final public static String RESOURCE_NAME = "resourceName"; final public static String ACTION_GROUP = "actionGroup"; public static final String PERMREL_ROLE_ID = "roleId"; diff --git a/services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java b/services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java index ffc216f1c..8d428063d 100644 --- a/services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java +++ b/services/common/src/main/java/org/collectionspace/services/authorization/storage/RoleStorageConstants.java @@ -25,7 +25,7 @@ package org.collectionspace.services.authorization.storage; /** - * RoleStorageConstants declares query params, etc. + * RoleStorageConstants declares JPA query params, etc. See JAX-B file roles.xsd for details * @author */ public class RoleStorageConstants { @@ -33,6 +33,8 @@ public class RoleStorageConstants { final public static String Q_ROLE_NAME = "r"; final public static String ROLE_NAME = "roleName"; + final public static String ROLE_ID = "csid"; + final public static String ROLE_TENANT_ID = "tenant_id"; final public static String PERM_ROLE_REL_ROLE_ID = "roleId"; final public static String PERM_ROLE_REL_PERM_ID = "permissionId"; diff --git a/services/common/src/main/java/org/collectionspace/services/common/SecurityResourceBase.java b/services/common/src/main/java/org/collectionspace/services/common/SecurityResourceBase.java index 05f0c893d..df5a9fa78 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/SecurityResourceBase.java +++ b/services/common/src/main/java/org/collectionspace/services/common/SecurityResourceBase.java @@ -3,6 +3,7 @@ package org.collectionspace.services.common; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentFilter; import org.collectionspace.services.common.document.DocumentHandler; +import org.collectionspace.services.common.storage.TransactionContext; import org.collectionspace.services.common.storage.jpa.JPATransactionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,16 +13,29 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; -@SuppressWarnings({ "unchecked", "rawtypes" }) -public abstract class SecurityResourceBase extends AbstractCollectionSpaceResourceImpl { +@SuppressWarnings("rawtypes") +public abstract class SecurityResourceBase extends AbstractCollectionSpaceResourceImpl { - final Logger logger = LoggerFactory.getLogger(SecurityResourceBase.class); + @SuppressWarnings("hiding") + final Logger logger = LoggerFactory.getLogger(SecurityResourceBase.class); - public Response create(Object input) { + public Response create(IT input) { + Response response = null; + + try { + ServiceContext ctx = createServiceContext(input, input.getClass()); + response = create(ctx, input); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.POST_FAILED+"create in "+this.getClass().getName()); + } + + return response; + } + + protected Response create(ServiceContext ctx, IT input) { Response response = null; try { - ServiceContext ctx = createServiceContext(input, input.getClass()); DocumentHandler handler = createDocumentHandler(ctx); String csid = getStorageClient(ctx).create(ctx, handler); UriBuilder path = UriBuilder.fromResource(this.getClass()); @@ -34,11 +48,11 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour return response; } - public Response create(JPATransactionContext jpaTransactionContext, Object input) { + public Response create(JPATransactionContext jpaTransactionContext, IT input) { Response response = null; try { - ServiceContext ctx = createServiceContext(jpaTransactionContext, input, input.getClass()); + ServiceContext ctx = createServiceContext(jpaTransactionContext, input, input.getClass()); DocumentHandler handler = createDocumentHandler(ctx); String csid = getStorageClient(ctx).create(ctx, handler); UriBuilder path = UriBuilder.fromResource(this.getClass()); @@ -51,9 +65,9 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour return response; } - private ServiceContext createServiceContext(JPATransactionContext jpaTransactionContext, Object input, + private ServiceContext createServiceContext(JPATransactionContext jpaTransactionContext, IT input, Class clazz) throws Exception { - ServiceContext result = createServiceContext(input, clazz); + ServiceContext result = createServiceContext(input, clazz); if (jpaTransactionContext != null) { result.setTransactionContext(jpaTransactionContext); @@ -71,7 +85,7 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour ensureCSID(csid, ServiceMessages.GET_FAILED + "csid"); Object result = null; try { - ServiceContext ctx = createServiceContext((Object) null, objectClass, ui); + ServiceContext ctx = createServiceContext((IT) null, objectClass, ui); DocumentHandler handler = createDocumentHandler(ctx); getStorageClient(ctx).get(ctx, csid, handler); result = ctx.getOutput(); @@ -82,12 +96,13 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour return result; } - public Object get(JPATransactionContext jpaTransactionContext, String csid, Class objectClass) { + protected Object get(TransactionContext transactionContext, String csid, Class objectClass) { logger.debug("get with csid=" + csid); + JPATransactionContext jpaTransactionContext = (JPATransactionContext)transactionContext; ensureCSID(csid, ServiceMessages.GET_FAILED + "csid"); Object result = null; try { - ServiceContext ctx = createServiceContext(jpaTransactionContext, (Object) null, objectClass); + ServiceContext ctx = createServiceContext(jpaTransactionContext, (IT) null, objectClass); DocumentHandler handler = createDocumentHandler(ctx); getStorageClient(ctx).get(ctx, csid, handler); result = ctx.getOutput(); @@ -100,7 +115,7 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour public Object getList(UriInfo ui, Class objectClass) { try { - ServiceContext ctx = createServiceContext((Object) null, objectClass, ui); + ServiceContext ctx = createServiceContext((IT) null, objectClass, ui); DocumentHandler handler = createDocumentHandler(ctx); MultivaluedMap queryParams = (ui != null ? ui.getQueryParameters() : null); DocumentFilter myFilter = handler.createDocumentFilter(); @@ -116,17 +131,17 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour } } - public Object update(String csid, Object theUpdate, Class objectClass) { + public Object update(String csid, IT theUpdate, Class objectClass) { return update((UriInfo)null, csid, theUpdate, objectClass); } - public Object update(UriInfo ui, String csid, Object theUpdate, Class objectClass) { + public Object update(UriInfo ui, String csid, IT theUpdate, Class objectClass) { if (logger.isDebugEnabled()) { logger.debug("updateRole with csid=" + csid); } ensureCSID(csid, ServiceMessages.PUT_FAILED + this.getClass().getName()); try { - ServiceContext ctx = createServiceContext(theUpdate, objectClass, ui); + ServiceContext ctx = createServiceContext(theUpdate, objectClass, ui); DocumentHandler handler = createDocumentHandler(ctx); getStorageClient(ctx).update(ctx, csid, handler); return ctx.getOutput(); @@ -134,4 +149,37 @@ public abstract class SecurityResourceBase extends AbstractCollectionSpaceResour throw bigReThrow(e, ServiceMessages.PUT_FAILED, csid); } } + + public Object update(ServiceContext parentCtx, UriInfo ui, String csid, IT theUpdate, Class objectClass) { + if (logger.isDebugEnabled()) { + logger.debug("updateRole with csid=" + csid); + } + ensureCSID(csid, ServiceMessages.PUT_FAILED + this.getClass().getName()); + + try { + ServiceContext ctx = createServiceContext(parentCtx, theUpdate, objectClass, ui); + DocumentHandler handler = createDocumentHandler(ctx); + getStorageClient(ctx).update(ctx, csid, handler); + return ctx.getOutput(); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.PUT_FAILED, csid); + } + } + + protected ServiceContext createServiceContext( + ServiceContext parentCtx, + IT input, + Class theClass, + UriInfo uriInfo) throws Exception { + ServiceContext ctx = createServiceContext(input, theClass, uriInfo); + JPATransactionContext parentTransactionContext = parentCtx != null ? (JPATransactionContext)parentCtx.getCurrentTransactionContext() : null; + // + // If the parent context has an active JPA connection then we'll use it. + // + if (parentTransactionContext != null) { + ctx.setTransactionContext(parentTransactionContext); + } + + return ctx; + } } diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java index 3ba8a36dc..66e3acfd5 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationCommon.java @@ -14,10 +14,7 @@ import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.UUID; - import javax.naming.NamingException; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; import org.collectionspace.authentication.AuthN; import org.collectionspace.services.account.AccountListItem; @@ -38,7 +35,7 @@ import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.perms.EffectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionAction; - +import org.collectionspace.services.client.PermissionClient; import org.collectionspace.services.client.Profiler; import org.collectionspace.services.client.RoleClient; import org.collectionspace.services.client.workflow.WorkflowClient; @@ -46,6 +43,7 @@ import org.collectionspace.services.client.workflow.WorkflowClient; import org.collectionspace.services.common.config.ServiceConfigUtils; import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; import org.collectionspace.services.common.context.ServiceBindingUtils; +import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentHandler; import org.collectionspace.services.common.security.SecurityUtils; import org.collectionspace.services.common.storage.DatabaseProductType; @@ -65,7 +63,6 @@ import org.collectionspace.services.lifecycle.TransitionDefList; //import org.mortbay.log.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.security.acls.model.AlreadyExistsException; public class AuthorizationCommon { @@ -76,8 +73,6 @@ public class AuthorizationCommon { // For token generation and password reset // final private static String DEFAULT_PASSWORD_RESET_EMAIL_MESSAGE = "Hello {{greeting}},\n\r\n\rYou've started the process to reset your CollectionSpace account password. To finish resetting your password, go to the Reset Password page {{link}} on CollectionSpace.\n\r\n\rIf clicking the link doesn't work, copy and paste the following link into your browser address bar and click Go.\n\r\n\r{{link}}\n\r Thanks,\n\r\n\r CollectionSpace Administrator\n\r\n\rPlease do not reply to this email. This mailbox is not monitored and you will not receive a response. For assistance, contact your CollectionSpace Administrator directly."; - final private static String tokensalt = "74102328UserDetailsReset"; - final private static int TIME_SCALAR = 100000; private static final String DEFAULT_PASSWORD_RESET_EMAIL_SUBJECT = "Password reset for CollectionSpace account"; // @@ -89,14 +84,18 @@ public class AuthorizationCommon { // ActionGroup labels/constants // - // for READ-WRITE + // for READ-WRITE-DELETE final public static String ACTIONGROUP_CRUDL_NAME = "CRUDL"; final public static ActionType[] ACTIONSET_CRUDL = {ActionType.CREATE, ActionType.READ, ActionType.UPDATE, ActionType.DELETE, ActionType.SEARCH}; + // for READ-WRITE + final public static String ACTIONGROUP_CRUL_NAME = "CRUL"; + final public static ActionType[] ACTIONSET_CRUL = {ActionType.CREATE, ActionType.READ, ActionType.UPDATE, ActionType.SEARCH}; // for READ-ONLY final public static String ACTIONGROUP_RL_NAME = "RL"; final public static ActionType[] ACTIONSET_RL = {ActionType.READ, ActionType.SEARCH}; static ActionGroup ACTIONGROUP_CRUDL; + static ActionGroup ACTIONGROUP_CRUL; static ActionGroup ACTIONGROUP_RL; // A static block to initialize the predefined action groups @@ -109,7 +108,10 @@ public class AuthorizationCommon { ACTIONGROUP_RL = new ActionGroup(); ACTIONGROUP_RL.name = ACTIONGROUP_RL_NAME; ACTIONGROUP_RL.actions = ACTIONSET_RL; - + // For read-write + ACTIONGROUP_CRUL = new ActionGroup(); + ACTIONGROUP_CRUL.name = ACTIONGROUP_CRUL_NAME; + ACTIONGROUP_CRUL.actions = ACTIONSET_CRUL; } final static Logger logger = LoggerFactory.getLogger(AuthorizationCommon.class); @@ -163,17 +165,7 @@ public class AuthorizationCommon { public static String setTenantConfigMD5Hash(String tenantId, String md5hash) { return tenantConfigMD5HashTable.put(tenantId, md5hash); } - - @Deprecated - public static Role xgetRole(String tenantId, String displayName) { - Role role = null; - - String roleName = AuthorizationCommon.getQualifiedRoleName(tenantId, displayName); - //role = AuthorizationStore.getRoleByName(roleName, tenantId); - - return role; - } - + public static Role getRole(JPATransactionContext jpaTransactionContext, String tenantId, String displayName) { Role role = null; @@ -183,11 +175,15 @@ public class AuthorizationCommon { return role; } - - public static Role createRole(String tenantId, String name, String description) { - return createRole(tenantId, name, description, false /* mutable by default */); - } - + /** + * Create a new role instance to be persisted later. + * + * @param tenantId + * @param name + * @param description + * @param immutable + * @return + */ public static Role createRole(String tenantId, String name, String description, boolean immutable) { Role role = new Role(); @@ -212,7 +208,8 @@ public class AuthorizationCommon { * with assumption that resource is of type URI * @param permission configuration */ - public static void addPermissionsForUri(Permission perm, + public static void addPermissionsForUri(JPATransactionContext jpaTransactionContext, + Permission perm, PermissionRole permRole) throws PermissionException { // // First check the integrity of the incoming arguments. @@ -238,6 +235,7 @@ public class AuthorizationCommon { resources.add(uriRes); } AuthZ.get().addPermissions(resources.toArray(new CSpaceResource[0]), principals.toArray(new String[0]), grant); // CSPACE-4967 + jpaTransactionContext.setAclTablesUpdateFlag(true); // Tell the containing JPA transaction that we've committed changes to the Spring Tables } private static Connection getConnection(String databaseName) throws NamingException, SQLException { @@ -267,6 +265,8 @@ public class AuthorizationCommon { result = ACTIONGROUP_CRUDL; } else if (actionGroupStr.equalsIgnoreCase(ACTIONGROUP_RL_NAME)) { result = ACTIONGROUP_RL; + } else if (actionGroupStr.equalsIgnoreCase(ACTIONGROUP_CRUL_NAME)) { + result = ACTIONGROUP_CRUL; } return result; @@ -275,11 +275,12 @@ public class AuthorizationCommon { public static Permission createPermission(String tenantId, String resourceName, String description, - String actionGroupStr) { + String actionGroupStr, + boolean immutable) { Permission result = null; ActionGroup actionGroup = getActionGroup(actionGroupStr); - result = createPermission(tenantId, resourceName, description, actionGroup); + result = createPermission(tenantId, resourceName, description, actionGroup, immutable); return result; } @@ -287,7 +288,8 @@ public class AuthorizationCommon { private static Permission createPermission(String tenantId, String resourceName, String description, - ActionGroup actionGroup) { + ActionGroup actionGroup, + boolean immutable) { String id = tenantId + "-" + resourceName.replace('/', '_') // Remove the slashes so the ID can be used in a URI/URL + "-" + actionGroup.name; @@ -307,13 +309,19 @@ public class AuthorizationCommon { pas.add(permAction); } + if (immutable) { + perm.setMetadataProtection(PermissionClient.IMMUTABLE); + perm.setActionsProtection(PermissionClient.IMMUTABLE); + } + return perm; } private static Permission createWorkflowPermission(TenantBindingType tenantBinding, ServiceBindingType serviceBinding, String transitionVerb, - ActionGroup actionGroup) + ActionGroup actionGroup, + boolean immutable) { Permission result = null; String workFlowServiceSuffix; @@ -332,7 +340,7 @@ public class AuthorizationCommon { + workFlowServiceSuffix + transitionName; String description = "A generated workflow permission for actiongroup " + actionGroup.name; - result = createPermission(tenantId, resourceName, description, actionGroup); + result = createPermission(tenantId, resourceName, description, actionGroup, immutable); if (logger.isDebugEnabled() == true) { logger.debug("Generated a workflow permission: " @@ -348,15 +356,18 @@ public class AuthorizationCommon { private static PermissionRole createPermissionRole( Permission permission, Role role, - boolean enforceTenancy) throws Exception + boolean enforceTenancy) throws DocumentException { PermissionRole permRole = new PermissionRole(); + + // // Check to see if the tenant ID of the permission and the tenant ID of the role match + // boolean tenantIdsMatch = role.getTenantId().equalsIgnoreCase(permission.getTenantId()); if (tenantIdsMatch == false && enforceTenancy == false) { tenantIdsMatch = true; // If we don't need to enforce tenancy then we'll just consider them matched. } - + if (tenantIdsMatch == true) { permRole.setSubject(SubjectType.ROLE); // @@ -382,13 +393,13 @@ public class AuthorizationCommon { } else { String errMsg = "The tenant ID of the role: " + role.getTenantId() + " did not match the tenant ID of the permission: " + permission.getTenantId(); - throw new Exception(errMsg); + throw new DocumentException(errMsg); } return permRole; } - private static Hashtable getTenantNamesFromConfig(TenantBindingConfigReaderImpl tenantBindingConfigReader) { + private static Hashtable getTenantNamesFromConfig(TenantBindingConfigReaderImpl tenantBindingConfigReader) { // Note that this only handles tenants not marked as "createDisabled" Hashtable tenantBindings = @@ -664,84 +675,92 @@ public class AuthorizationCommon { } } - + /** + * Creates the default Admin and Reader roles for all the configured tenants. + * + * Returns the CSID of the Spring Admin role. + * + * @param conn + * @param tenantInfo + * @param tenantAdminRoleCSIDs + * @param tenantReaderRoleCSIDs + * @return + * @throws SQLException + * @throws Exception + */ private static String findOrCreateDefaultRoles(Connection conn, Hashtable tenantInfo, Hashtable tenantAdminRoleCSIDs, Hashtable tenantReaderRoleCSIDs) throws SQLException, Exception { - // Fifth, fetch and save the default roles + String springAdminRoleCSID = null; Statement stmt = null; PreparedStatement pstmt = null; try { - final String querySpringRole = - "SELECT csid from roles WHERE rolename='"+AuthN.ROLE_SPRING_ADMIN_NAME+"'"; + // + // Look for the Spring Security admin role. If not found, create it. + // + final String querySpringRole = String.format("SELECT csid from roles WHERE rolename='%s'", AuthN.ROLE_SPRING_ADMIN_NAME); stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(querySpringRole); - if(rs.next()) { + if (rs.next()) { springAdminRoleCSID = rs.getString(1); if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts found Spring Admin role: " - +springAdminRoleCSID); + logger.debug("createDefaultAccounts found Spring Admin role: " + springAdminRoleCSID); } } else { - final String insertSpringAdminRoleSQL = - "INSERT INTO roles (csid, rolename, displayName, rolegroup, created_at, tenant_id) " - + "VALUES ('-1', 'ROLE_SPRING_ADMIN', 'SPRING_ADMIN', 'Spring Security Administrator', now(), '0')"; + final String insertSpringAdminRoleSQL = String.format( + "INSERT INTO roles (csid, rolename, displayName, rolegroup, created_at, tenant_id) VALUES ('%s', '%s', '%s', '%s', now(), '%s')", + AuthN.ROLE_SPRING_ADMIN_ID, AuthN.ROLE_SPRING_ADMIN_NAME, AuthN.SPRING_ADMIN_USER, AuthN.ROLE_SPRING_GROUP_NAME, AuthN.ADMIN_TENANT_ID); stmt.executeUpdate(insertSpringAdminRoleSQL); - springAdminRoleCSID = "-1"; - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts CREATED Spring Admin role: " - +springAdminRoleCSID); - } + springAdminRoleCSID = AuthN.ROLE_SPRING_ADMIN_ID; } rs.close(); - final String getRoleCSIDSql = - "SELECT csid from roles WHERE tenant_id=? and rolename=?"; - pstmt = conn.prepareStatement(getRoleCSIDSql); // create a statement rs = null; - for(String tId : tenantInfo.keySet()) { - pstmt.setString(1, tId); // set tenant_id param - pstmt.setString(2, getDefaultAdminRole(tId)); // set rolename param + + // + // Look for and save each tenants default Admin and Reader roles + // + final String getRoleCSIDSql = "SELECT csid from roles WHERE tenant_id=? and rolename=?"; + pstmt = conn.prepareStatement(getRoleCSIDSql); // create a statement + for (String tenantId : tenantInfo.keySet()) { + // + // Look for the default Admin role + // + pstmt.setString(1, tenantId); + pstmt.setString(2, getDefaultAdminRole(tenantId)); rs = pstmt.executeQuery(); // extract data from the ResultSet - if(!rs.next()) { - throw new RuntimeException("Cannot find role: "+getDefaultAdminRole(tId) - +" for tenant id: "+tId+" in roles!"); - } - String tenantAdminRoleCSID = rs.getString(1); - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts found role: " - +getDefaultAdminRole(tId)+"("+tenantAdminRoleCSID - +") for tenant id: "+tId); + if (!rs.next()) { + throw new RuntimeException("Cannot find role: " + getDefaultAdminRole(tenantId) + + " for tenant id: " + tenantId + " in roles!"); } - tenantAdminRoleCSIDs.put(tId, tenantAdminRoleCSID); - pstmt.setString(1, tId); // set tenant_id param - pstmt.setString(2, getDefaultReaderRole(tId)); // set rolename param + String tenantAdminRoleCSID = rs.getString(1); // First column (#1) is the CSID + tenantAdminRoleCSIDs.put(tenantId, tenantAdminRoleCSID); rs.close(); + rs = null; + // + // Look for the default Reader role + // + pstmt.setString(1, tenantId); // set tenant_id param + pstmt.setString(2, getDefaultReaderRole(tenantId)); // set rolename param rs = pstmt.executeQuery(); // extract data from the ResultSet - if(!rs.next()) { - throw new RuntimeException("Cannot find role: "+getDefaultReaderRole(tId) - +" for tenant id: "+tId+" in roles!"); + if (!rs.next()) { + throw new RuntimeException("Cannot find role: " + getDefaultReaderRole(tenantId) + + " for tenant id: " + tenantId + " in roles!"); } String tenantReaderRoleCSID = rs.getString(1); - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts found role: " - +getDefaultReaderRole(tId)+"("+tenantReaderRoleCSID - +") for tenant id: "+tId); - } - tenantReaderRoleCSIDs.put(tId, tenantReaderRoleCSID); + tenantReaderRoleCSIDs.put(tenantId, tenantReaderRoleCSID); rs.close(); } pstmt.close(); } catch(Exception e) { throw e; } finally { - if(stmt!=null) - stmt.close(); - if(pstmt!=null) - pstmt.close(); + if (stmt != null) stmt.close(); + if (pstmt != null) pstmt.close(); } + return springAdminRoleCSID; } @@ -786,55 +805,38 @@ public class AuthorizationCommon { PreparedStatement pstmt = null; try { String insertAccountRoleSQL; - if (databaseProductType == DatabaseProductType.MYSQL) { - insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_MYSQL; - } else if (databaseProductType == DatabaseProductType.POSTGRESQL) { + if (databaseProductType == DatabaseProductType.POSTGRESQL) { insertAccountRoleSQL = INSERT_ACCOUNT_ROLE_SQL_POSTGRES; } else { throw new Exception("Unrecognized database system."); } - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts binding accounts to roles with SQL:\n" - +insertAccountRoleSQL); - } + pstmt = conn.prepareStatement(insertAccountRoleSQL); // create a statement - for(String tId : tenantInfo.keySet()) { - String adminUserId = getDefaultAdminUserID(tenantInfo.get(tId)); - if(!usersInRepo.contains(adminUserId)) { + for (String tId : tenantInfo.keySet()) { + String adminUserId = getDefaultAdminUserID(tenantInfo.get(tId)); + if (!usersInRepo.contains(adminUserId)) { String adminAcct = tenantAdminAcctCSIDs.get(tId); String adminRoleId = tenantAdminRoleCSIDs.get(tId); pstmt.setString(1, adminAcct); // set acct CSID param pstmt.setString(2, adminUserId); // set user_id param pstmt.setString(3, adminRoleId); // set role_id param pstmt.setString(4, getDefaultAdminRole(tId)); // set rolename param - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts binding account: " - +adminUserId+" to Admin role("+adminRoleId - +") for tenant id: "+tId); - } pstmt.executeUpdate(); + // // Now add the Spring Admin Role to the admin accounts + // pstmt.setString(3, springAdminRoleCSID); // set role_id param pstmt.setString(4, AuthN.ROLE_SPRING_ADMIN_NAME); // set rolename param - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts binding account: " - +adminUserId+" to Spring Admin role: "+springAdminRoleCSID); - } pstmt.executeUpdate(); } String readerUserId = getDefaultReaderUserID(tenantInfo.get(tId)); - if(!usersInRepo.contains(readerUserId)) { + if (!usersInRepo.contains(readerUserId)) { String readerAcct = tenantReaderAcctCSIDs.get(tId); String readerRoleId = tenantReaderRoleCSIDs.get(tId); pstmt.setString(1, readerAcct); // set acct CSID param pstmt.setString(2, readerUserId); // set user_id param pstmt.setString(3, readerRoleId); // set role_id param pstmt.setString(4, getDefaultReaderRole(tId)); // set rolename param - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts binding account: " - +readerUserId+" to Reader role("+readerRoleId - +") for tenant id: "+tId); - } pstmt.executeUpdate(); } } @@ -842,8 +844,9 @@ public class AuthorizationCommon { } catch(Exception e) { throw e; } finally { - if(pstmt!=null) + if (pstmt!=null) { pstmt.close(); + } } } @@ -869,20 +872,18 @@ public class AuthorizationCommon { pstmt.setString(2, tenantManagerUserID); // set user_id param pstmt.setString(3, tenantManagerRoleID); // set role_id param pstmt.setString(4, tenantManagerRoleName); // set rolename param - if (logger.isDebugEnabled()) { - logger.debug("bindTenantManagerAccountRole binding user: " - +tenantManagerUserID+" to Admin role("+tenantManagerRoleName+")"); - } pstmt.executeUpdate(); + /* At this point, tenant manager should not need the Spring Admin Role - pstmt.setString(3, springAdminRoleCSID); // set role_id param - pstmt.setString(4, SPRING_ADMIN_ROLE); // set rolename param - if (logger.isDebugEnabled()) { - logger.debug("createDefaultAccounts binding account: " - +adminUserId+" to Spring Admin role: "+springAdminRoleCSID); - } - pstmt.executeUpdate(); + pstmt.setString(3, springAdminRoleCSID); // set role_id param + pstmt.setString(4, SPRING_ADMIN_ROLE); // set rolename param + if (logger.isDebugEnabled()) { + logger.debug("createDefaultAccounts binding account: " + +adminUserId+" to Spring Admin role: "+springAdminRoleCSID); + } + pstmt.executeUpdate(); */ + pstmt.close(); } catch(Exception e) { throw e; @@ -914,8 +915,9 @@ public class AuthorizationCommon { throw e; } finally { try { - if (conn != null) + if (conn != null) { conn.close(); + } } catch (SQLException sqle) { if (logger.isDebugEnabled()) { logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage()); @@ -971,7 +973,7 @@ public class AuthorizationCommon { tenantAdminAcctCSIDs, tenantReaderAcctCSIDs); boolean createdTenantMgrAccount = findOrCreateTenantManagerUserAndAccount(conn); - if(createdTenantMgrAccount) { + if (createdTenantMgrAccount) { // If we created the account, we need to create the bindings. Otherwise, assume they // are all set (from previous initialization). String tenantManagerRoleCSID = findTenantManagerRole(conn); @@ -984,8 +986,9 @@ public class AuthorizationCommon { throw e; } finally { try { - if (conn != null) + if (conn != null) { conn.close(); + } } catch (SQLException sqle) { if (logger.isDebugEnabled()) { logger.debug("SQL Exception closing statement/connection: " + sqle.getLocalizedMessage()); @@ -995,7 +998,7 @@ public class AuthorizationCommon { } private static String getDefaultAdminRole(String tenantId) { - return ROLE_PREFIX+tenantId+TENANT_ADMIN_ROLE_SUFFIX; + return ROLE_PREFIX + tenantId + TENANT_ADMIN_ROLE_SUFFIX; } private static String getDefaultReaderRole(String tenantId) { @@ -1003,14 +1006,14 @@ public class AuthorizationCommon { } private static String getDefaultAdminUserID(String tenantName) { - return TENANT_ADMIN_ACCT_PREFIX+tenantName; + return TENANT_ADMIN_ACCT_PREFIX + tenantName; } private static String getDefaultReaderUserID(String tenantName) { - return TENANT_READER_ACCT_PREFIX+tenantName; + return TENANT_READER_ACCT_PREFIX + tenantName; } - static public PermissionAction createPermissionAction(Permission perm, + static private PermissionAction createPermissionAction(Permission perm, ActionType actionType) { PermissionAction pa = new PermissionAction(); @@ -1023,18 +1026,6 @@ public class AuthorizationCommon { return pa; } - - static public PermissionAction update(Permission perm, PermissionAction permAction) { - PermissionAction pa = new PermissionAction(); - - CSpaceAction action = URIResourceImpl.getAction(permAction.getName()); - URIResourceImpl uriRes = new URIResourceImpl(perm.getTenantId(), - perm.getResourceName(), action); - pa.setObjectIdentity(uriRes.getHashedId().toString()); - pa.setObjectIdentityResource(uriRes.getId()); - - return pa; - } private static HashSet getTransitionVerbList(TenantBindingType tenantBinding, ServiceBindingType serviceBinding) { HashSet result = new HashSet(); @@ -1053,7 +1044,9 @@ public class AuthorizationCommon { TransitionDefList result = null; try { String serviceObjectName = serviceBinding.getObject().getName(); - DocumentHandler docHandler = ServiceConfigUtils.createDocumentHandlerInstance( + + @SuppressWarnings("rawtypes") + DocumentHandler docHandler = ServiceConfigUtils.createDocumentHandlerInstance( tenantBinding, serviceBinding); Lifecycle lifecycle = docHandler.getLifecycle(serviceObjectName); if (lifecycle != null) { @@ -1083,6 +1076,7 @@ public class AuthorizationCommon { } /** + * Creates the immutable workflow permission sets for the default admin and reader roles. * * @param tenantBindingConfigReader * @param databaseProductType @@ -1093,7 +1087,7 @@ public class AuthorizationCommon { JPATransactionContext jpaTransactionContext, TenantBindingConfigReaderImpl tenantBindingConfigReader, DatabaseProductType databaseProductType, - String cspaceDatabaseName) throws Exception //FIXME: REM - 4/11/2012 - Rename to createWorkflowPermissions + String cspaceDatabaseName) throws Exception { java.util.logging.Logger logger = java.util.logging.Logger.getAnonymousLogger(); @@ -1122,16 +1116,15 @@ public class AuthorizationCommon { if (prop == null ? true : Boolean.parseBoolean(prop)) { try { jpaTransactionContext.beginTransaction(); - TransitionDefList transitionDefList = getTransitionDefList(tenantBinding, serviceBinding); HashSet transitionVerbList = getTransitionVerbList(tenantBinding, serviceBinding); for (String transitionVerb : transitionVerbList) { // // Create the permission for the admin role - Permission adminPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_CRUDL); + Permission adminPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_CRUDL, true); persist(jpaTransactionContext, adminPerm, adminRole, true, ACTIONGROUP_CRUDL); // // Create the permission for the read-only role - Permission readonlyPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_RL); + Permission readonlyPerm = createWorkflowPermission(tenantBinding, serviceBinding, transitionVerb, ACTIONGROUP_RL, true); persist(jpaTransactionContext, readonlyPerm, readonlyRole, true, ACTIONGROUP_RL); // Persist/store the permission and permrole records and related Spring Security info } jpaTransactionContext.commitTransaction(); @@ -1188,7 +1181,6 @@ public class AuthorizationCommon { public static String getPersistedMD5Hash(String tenantId, String cspaceDatabaseName) throws Exception { String result = null; - ArrayList existingTenants = new ArrayList(); // First find or create the tenants final String queryTenantSQL = String.format("SELECT id, name, config_md5hash FROM tenants WHERE id = '%s'", tenantId); @@ -1263,7 +1255,7 @@ public class AuthorizationCommon { Profiler profiler = new Profiler(AuthorizationCommon.class, 2); profiler.start(); // Add a corresponding entry in the Spring Security Tables - addPermissionsForUri(permission, permRole); + addPermissionsForUri(jpaTransactionContext, permission, permRole); profiler.stop(); logger.debug("Finished full perm generation for " + ":" + permission.getTenantId() diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationRoleRel.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationRoleRel.java index 80759dead..9ce462ee0 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationRoleRel.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/AuthorizationRoleRel.java @@ -5,6 +5,7 @@ import org.collectionspace.services.authorization.PermissionRoleRel; import org.collectionspace.authentication.AuthN; import org.collectionspace.services.authorization.AccountRoleRel; import org.collectionspace.services.authorization.PermissionValue; +import org.collectionspace.services.authorization.Role; import org.collectionspace.services.authorization.RoleValue; public class AuthorizationRoleRel { @@ -23,6 +24,24 @@ public class AuthorizationRoleRel { return av; } + /** + * Builds the role value. + * + * @param arr the arr + * @return the role account value + */ + static public RoleValue buildRoleValue(Role role) { + RoleValue rv = null; + + rv = new RoleValue(); + rv.setRoleId(role.getCsid()); + rv.setRoleName(role.getRoleName()); + rv.setDisplayName(role.getDisplayName()); + rv.setTenantId(role.getTenantId()); + + return rv; + } + /** * Builds the role value. * diff --git a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java index d73f37656..9a66032eb 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java +++ b/services/common/src/main/java/org/collectionspace/services/common/authorization_mgt/PermissionRoleUtil.java @@ -26,27 +26,31 @@ package org.collectionspace.services.common.authorization_mgt; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; import javax.persistence.NoResultException; import org.collectionspace.services.common.document.DocumentException; import org.collectionspace.services.common.document.DocumentNotFoundException; -import org.collectionspace.services.common.document.TransactionException; +import org.collectionspace.services.common.document.JaxbUtils; +import org.collectionspace.services.common.api.Tools; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.context.ServiceContextProperties; import org.collectionspace.services.common.storage.jpa.JPATransactionContext; import org.collectionspace.services.common.storage.jpa.JpaStorageUtils; +import org.collectionspace.services.client.RoleClient; +import org.collectionspace.authentication.AuthN; + import org.collectionspace.services.authorization.perms.ActionType; import org.collectionspace.services.authorization.perms.EffectType; import org.collectionspace.services.authorization.perms.Permission; import org.collectionspace.services.authorization.perms.PermissionAction; import org.collectionspace.services.authorization.storage.PermissionStorageConstants; +import org.collectionspace.services.authorization.storage.RoleStorageConstants; import org.collectionspace.services.authorization.PermissionResource; import org.collectionspace.services.authorization.PermissionRole; import org.collectionspace.services.authorization.PermissionRoleRel; import org.collectionspace.services.authorization.PermissionValue; +import org.collectionspace.services.authorization.Role; import org.collectionspace.services.authorization.RoleValue; import org.collectionspace.services.authorization.SubjectType; @@ -69,7 +73,7 @@ public class PermissionRoleUtil { * @param ctx the ctx * @return the relation subject */ - static public SubjectType getRelationSubject(ServiceContext ctx) { + static public SubjectType getRelationSubject(ServiceContext ctx) { Object o = ctx.getProperty(ServiceContextProperties.SUBJECT); if (o == null) { throw new IllegalArgumentException(ServiceContextProperties.SUBJECT @@ -86,7 +90,7 @@ public class PermissionRoleUtil { * @param pr the pr * @return the relation subject */ - static public SubjectType getRelationSubject(ServiceContext ctx, PermissionRole pr) { + static public SubjectType getRelationSubject(ServiceContext ctx, PermissionRole pr) { SubjectType subject = pr.getSubject(); if (subject == null) { //it is not required to give subject as URI determines the subject @@ -101,39 +105,45 @@ public class PermissionRoleUtil { * * @param pr permissionrole * @param subject the subject - * @param prrl persistent entities built are inserted into this list + * @param permRoleRelationshipList persistent entities built are inserted into this list * @param toDelete the to delete */ static public void buildPermissionRoleRel(JPATransactionContext jpaTransactionContext, PermissionRole pr, SubjectType subject, - List prrl, + List permRoleRelationshipList, boolean handleDelete, String tenantId) throws Exception { if (subject.equals(SubjectType.ROLE)) { List permissionValues = pr.getPermission(); - if (permissionValues != null && permissionValues.size() > 0) { + if (permissionValues != null && permissionValues.size() == 1) { PermissionValue pv = permissionValues.get(0); for (RoleValue rv : pr.getRole()) { - PermissionRoleRel prr = buildPermissonRoleRel(jpaTransactionContext, pv, rv, subject, handleDelete, tenantId); - prrl.add(prr); + PermissionRoleRel permRoleRelationship = buildPermissonRoleRel(jpaTransactionContext, pv, rv, subject, handleDelete, tenantId); + permRoleRelationshipList.add(permRoleRelationship); } + } else { + String msg = "There must be one and only one Permission supplied in the payload when creating this Permission-Roles relationshiop."; + throw new DocumentException(msg); } } else if (subject.equals(SubjectType.PERMISSION)) { List roleValues = pr.getRole(); - if (roleValues != null && roleValues.size() > 0) { + if (roleValues != null && roleValues.size() == 1) { RoleValue rv = roleValues.get(0); for (PermissionValue pv : pr.getPermission()) { PermissionRoleRel prr = buildPermissonRoleRel(jpaTransactionContext, pv, rv, subject, handleDelete, tenantId); - prrl.add(prr); + permRoleRelationshipList.add(prr); } + } else { + String msg = "There must be one and only one Role supplied in the payload when creating this Role-Permissions relationshiop."; + throw new DocumentException(msg); } } } static public void buildPermissionRoleRel( - ServiceContext ctx, + ServiceContext ctx, PermissionRole pr, SubjectType subject, List prrl, @@ -160,7 +170,7 @@ public class PermissionRoleUtil { * Try to find a persisted Permission record using a PermissionValue instance. * */ - static private Permission lookupPermission(JPATransactionContext jpaTransactionContext, PermissionValue permissionValue, String tenantId) throws TransactionException { + static private Permission lookupPermission(JPATransactionContext jpaTransactionContext, PermissionValue permissionValue, String tenantId) throws DocumentException { Permission result = null; String actionGroup = permissionValue.getActionGroup() != null ? permissionValue.getActionGroup().trim() : null; @@ -171,19 +181,23 @@ public class PermissionRoleUtil { // if (permissionId != null && !permissionId.isEmpty()) { try { - result = (Permission)JpaStorageUtils.getEntity(jpaTransactionContext, permissionId, Permission.class); + result = (Permission)JpaStorageUtils.getEntityByDualKeys( + jpaTransactionContext, + Permission.class.getName(), + PermissionStorageConstants.ID, permissionId, + PermissionStorageConstants.TENANT_ID, tenantId); } catch (Throwable e) { String msg = String.format("Searched for but couldn't find a permission with CSID='%s'.", permissionId); logger.trace(msg); } - } else if ((resourceName != null && !resourceName.isEmpty()) && - (actionGroup != null && !actionGroup.isEmpty())) { + } else if (Tools.notBlank(resourceName) && Tools.notBlank(actionGroup)) { // // If there was no permission ID, then we can try to find the permission with the resource name and action group tuple // try { - result = (Permission)JpaStorageUtils.getEntityByDualKeys(jpaTransactionContext, + result = (Permission)JpaStorageUtils.getEntityByDualKeys( + jpaTransactionContext, Permission.class.getName(), PermissionStorageConstants.RESOURCE_NAME, permissionValue.getResourceName(), PermissionStorageConstants.ACTION_GROUP, permissionValue.getActionGroup(), @@ -196,11 +210,31 @@ public class PermissionRoleUtil { } else { String errMsg = String.format("Couldn't perform lookup of permission. Not enough information provided. Lookups requires a permission CSID or a resource name and action group tuple. The provided information was permission ID='%s', resourceName='%s', and actionGroup='%s'.", permissionId, resourceName, actionGroup); - logger.warn(errMsg); + throw new DocumentException(errMsg); + } + + if (result == null) { + throw new DocumentNotFoundException(String.format("Could not find Permission resource with CSID='%s', actionGroup='%s', resourceName='%s'.", + permissionId, actionGroup, resourceName)); } return result; } + /** + * Ensure the Role's permission relationships can be changed. + * + * @param role + * @return + */ + static private boolean canRoleRelatedTo(Role role) { + boolean result = true; + + if (RoleClient.IMMUTABLE.equals(role.getPermsProtection()) && !AuthN.get().isSystemAdmin()) { + result = false; + } + + return result; + } /** * Builds a permisson role relationship for either 'create' or 'delete' @@ -218,8 +252,18 @@ public class PermissionRoleUtil { String tenantId) throws DocumentException { PermissionRoleRel result = null; + Role role = lookupRole(jpaTransactionContext, roleValue, tenantId); + // + // Ensure we can change the Role's permissions-related relationships. + // + if (canRoleRelatedTo(role) == false) { + String msg = String.format("Role with CSID='%s' cannot have its associated permissions changed.", role.getCsid()); + throw new DocumentException(msg); + } + // + // Get the permission info + // Permission permission = lookupPermission(jpaTransactionContext, permissionValue, tenantId); - // // If we couldn't find an existing permission and we're not processing a DELETE request, we need to create // a new permission. @@ -236,6 +280,10 @@ public class PermissionRoleUtil { String errMsg = "Could not create new permission for new permission-role relationship."; throw new DocumentException(errMsg); } + } else if (permission == null && handleDelete == true) { + String msg = String.format("Could not find an existing permission that matches this: %s", + JaxbUtils.toString(permissionValue, PermissionValue.class)); + throw new DocumentException(msg); } // @@ -271,8 +319,46 @@ public class PermissionRoleUtil { return result; } + + public static RoleValue fetchRoleValue(ServiceContext ctx, String roleId) throws DocumentNotFoundException { + RoleValue result = null; + + JPATransactionContext jpaTransactionContext = (JPATransactionContext) ctx.getCurrentTransactionContext(); + Role role = lookupRole(jpaTransactionContext, roleId, ctx.getTenantId()); + result = AuthorizationRoleRel.buildRoleValue(role); + + return result; + } - private static Permission createPermission(JPATransactionContext jpaTransactionContext, Permission permission) { + private static Role lookupRole(JPATransactionContext jpaTransactionContext, RoleValue roleValue, String tenantId) throws DocumentNotFoundException { + return lookupRole(jpaTransactionContext, roleValue.getRoleId(), tenantId); + } + + private static Role lookupRole(JPATransactionContext jpaTransactionContext, String roleId, String tenantId) throws DocumentNotFoundException { + Role result = null; + + try { + result = (Role)JpaStorageUtils.getEntityByDualKeys( + jpaTransactionContext, + Role.class.getName(), + RoleStorageConstants.ROLE_ID, roleId, + RoleStorageConstants.ROLE_TENANT_ID, tenantId); + } catch (Throwable e) { + String msg = String.format("Searched for but couldn't find a role with CSID='%s'.", + roleId); + logger.trace(msg); + } + + if (result == null) { + String msg = String.format("Could not find Role resource with CSID='%s'", roleId); + throw new DocumentNotFoundException(msg); + } + + return result; + } + + + private static Permission createPermission(JPATransactionContext jpaTransactionContext, Permission permission) { Permission result = null; PermissionResource permissionResource = new PermissionResource(); // Get the PermissionResource singleton instance (RESTEasy ensures it is a singleton) @@ -322,6 +408,17 @@ public class PermissionRoleUtil { return result; } + + static public boolean isEmpty(PermissionRole permRole) { + boolean result = true; + + if (permRole != null && !Tools.isEmpty(permRole.getPermission()) + && !Tools.isEmpty(permRole.getRole()) && permRole.getSubject() != null) { + result = false; + } + + return result; + } /** * Checks if is invalid tenant. diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/RemoteServiceContextImpl.java b/services/common/src/main/java/org/collectionspace/services/common/context/RemoteServiceContextImpl.java index d10ab1caf..17ba6aea3 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/RemoteServiceContextImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/RemoteServiceContextImpl.java @@ -55,6 +55,7 @@ public class RemoteServiceContextImpl /** The logger. */ final Logger logger = LoggerFactory.getLogger(RemoteServiceContextImpl.class); + //input stores original content as received over the wire /** The input. */ private IT input; @@ -163,12 +164,12 @@ public class RemoteServiceContextImpl */ @Override public void setInput(IT input) { - //for security reasons, do not allow to set input again (from handlers) - if (this.input != null) { - String msg = "Resetting or changing an context's input is not allowed."; - logger.error(msg); - throw new IllegalStateException(msg); - } + if (logger.isDebugEnabled()) { + if (this.input != null) { + String msg = "\n#\n# Resetting or changing an context's input is not advised.\n#"; + logger.warn(msg); + } + } this.input = input; } @@ -195,7 +196,8 @@ public class RemoteServiceContextImpl * @return * @throws Exception */ - public CollectionSpaceResource getResource(ServiceContext ctx) throws Exception { + @SuppressWarnings("unchecked") + public CollectionSpaceResource getResource(ServiceContext ctx) throws Exception { CollectionSpaceResource result = null; ResourceMap resourceMap = ctx.getResourceMap(); @@ -232,7 +234,8 @@ public class RemoteServiceContextImpl /* (non-Javadoc) * @see org.collectionspace.services.common.context.RemoteServiceContext#getLocalContext(java.lang.String) */ - @Override + @SuppressWarnings("unchecked") + @Override public ServiceContext getLocalContext(String localContextClassName) throws Exception { ClassLoader cloader = Thread.currentThread().getContextClassLoader(); Class ctxClass = cloader.loadClass(localContextClassName); @@ -278,8 +281,10 @@ public class RemoteServiceContextImpl // // If it's a shared connection, we can't close it. Just reduce the refcount by 1 // - String warnMsg = "Attempted to release a shared storage connection. Only the originator can release the connection"; - logger.warn(warnMsg); + if (logger.isTraceEnabled()) { + String traceMsg = "Attempted to release a shared storage connection. Only the originator can release the connection"; + logger.trace(traceMsg); + } transactionConnectionRefCount--; } else { TransactionContext transactionCtx = getCurrentTransactionContext(); diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java index 36bb6a2a6..5adce86df 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java @@ -38,7 +38,7 @@ import org.collectionspace.services.common.document.TransactionException; import org.collectionspace.services.common.document.ValidatorHandler; import org.collectionspace.services.common.security.SecurityContext; import org.collectionspace.services.common.storage.TransactionContext; -import org.collectionspace.services.common.storage.jpa.JPATransactionContext; + import org.collectionspace.services.config.ClientType; import org.collectionspace.services.config.service.ObjectPartType; import org.collectionspace.services.config.service.ServiceBindingType; diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/AbstractDocumentHandlerImpl.java b/services/common/src/main/java/org/collectionspace/services/common/document/AbstractDocumentHandlerImpl.java index 770289760..f6af6159b 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/AbstractDocumentHandlerImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/AbstractDocumentHandlerImpl.java @@ -45,6 +45,7 @@ import org.slf4j.LoggerFactory; * @param * @param */ +@SuppressWarnings({"unchecked", "rawtypes"}) public abstract class AbstractDocumentHandlerImpl implements DocumentHandler { @@ -77,7 +78,7 @@ public abstract class AbstractDocumentHandlerImpl /* (non-Javadoc) * @see org.collectionspace.services.common.document.DocumentHandler#getServiceContext() */ - @Override + @Override public ServiceContext getServiceContext() { return serviceContext; } @@ -311,7 +312,7 @@ public abstract class AbstractDocumentHandlerImpl /* (non-Javadoc) * @see org.collectionspace.services.common.document.DocumentHandler#complete(org.collectionspace.services.common.document.DocumentHandler.Action, org.collectionspace.services.common.document.DocumentWrapper) */ - @Override + @Override final public void complete(Action action, DocumentWrapper wrapDoc) throws Exception { switch (action) { case CREATE: diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/InconsistentStateException.java b/services/common/src/main/java/org/collectionspace/services/common/document/InconsistentStateException.java new file mode 100644 index 000000000..fb333decb --- /dev/null +++ b/services/common/src/main/java/org/collectionspace/services/common/document/InconsistentStateException.java @@ -0,0 +1,23 @@ +package org.collectionspace.services.common.document; + +public class InconsistentStateException extends TransactionException { + private static final long serialVersionUID = 11L; + + public InconsistentStateException() { + super(TRANSACTION_FAILED_MSG); + setErrorCode(HTTP_CODE); + } + + public InconsistentStateException(String msg) { + super(msg); + } + + public InconsistentStateException(String message, Throwable cause) { + super(message, cause); + } + + public InconsistentStateException(Throwable cause) { + super(cause); + } + +} diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/JaxbUtils.java b/services/common/src/main/java/org/collectionspace/services/common/document/JaxbUtils.java index 0c8d4b377..366634d27 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/JaxbUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/JaxbUtils.java @@ -32,8 +32,12 @@ import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; +import javax.xml.namespace.QName; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,17 +55,42 @@ public class JaxbUtils { * @param clazz class of the jaxb object * @return */ - public static String toString(Object o, Class clazz) { + public static String toString(Object o, Class clazz) { StringWriter sw = new StringWriter(); + try { JAXBContext jc = JAXBContext.newInstance(clazz); Marshaller m = jc.createMarshaller(); - m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, - Boolean.TRUE); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.marshal(o, sw); - } catch (Exception e) { - e.printStackTrace(); - } + } catch (javax.xml.bind.MarshalException e) { + // + // If the JAX-B object we're trying to marshal doesn't have an @XmlRootElement, then we need another + // approach. + // + return marshalWithoutRoot(o, clazz); + } catch (JAXBException e) { + logger.error(e.getMessage()); + } + + return sw.toString(); + } + + /* + * Use this to marshal a JAX-B object that has no @XmlRootElement + */ + private static String marshalWithoutRoot(Object o, Class clazz) { + StringWriter sw = new StringWriter(); + + try { + JAXBContext jc = JAXBContext.newInstance(clazz); + Marshaller marshaller = jc.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.marshal(new JAXBElement(new QName("uri","local"), clazz, o), sw); + } catch (Exception e) { + logger.debug(e.getMessage()); + } + return sw.toString(); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java b/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java index 7f979b164..a2c099f7f 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java @@ -22,10 +22,6 @@ package org.collectionspace.services.common.document; * */ public class TransactionException extends DocumentException { - - /** - * - */ private static final long serialVersionUID = 1L; // Custom HTTP status code, per the extensibility offered via RFC-2616 diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/StorageClient.java b/services/common/src/main/java/org/collectionspace/services/common/storage/StorageClient.java index d39fbb970..168f64d71 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/StorageClient.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/StorageClient.java @@ -161,4 +161,7 @@ public interface StorageClient { throws DocumentNotFoundException, TransactionException, DocumentException; + boolean delete(ServiceContext ctx, Object entityFound, DocumentHandler handler) + throws DocumentNotFoundException, DocumentException; + } diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/TransactionContext.java b/services/common/src/main/java/org/collectionspace/services/common/storage/TransactionContext.java index 09f3516a0..bbcf4bb0e 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/TransactionContext.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/TransactionContext.java @@ -36,4 +36,7 @@ public abstract class TransactionContext { abstract public void remove(Object entity); abstract public Object merge(Object entity); + + abstract public void flush(); + } diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JPATransactionContext.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JPATransactionContext.java index 5954593e9..dbdf2483a 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JPATransactionContext.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JPATransactionContext.java @@ -3,10 +3,13 @@ package org.collectionspace.services.common.storage.jpa; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Query; +import javax.persistence.RollbackException; import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.InconsistentStateException; import org.collectionspace.services.common.document.TransactionException; import org.collectionspace.services.common.storage.TransactionContext; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +19,7 @@ public class JPATransactionContext extends TransactionContext { private final Logger logger = LoggerFactory.getLogger(TransactionContext.class); private int transactionRefCount = 0; - private Boolean commitSuccessful = null; + private boolean aclTablesUpdatedFlag = false; EntityManagerFactory emf; EntityManager em; @@ -39,6 +42,23 @@ public class JPATransactionContext extends TransactionContext { protected EntityManager getEntityManager() { return em; } + + /** + * Set to 'true' if (and only if) a change has been made AND successfully committed to the Spring Security tables. + * + * Since we can't include Spring Security table changes and JPA changes in a single transaction, we + * keep track of changes to the Spring Security tables here. We'll use this flag to log a critical error if + * we think there is a chance the JPA tables and Spring Security tables get out of sync. + * + * @param flag + */ + public void setAclTablesUpdateFlag(boolean flag) { + aclTablesUpdatedFlag = flag; + } + + protected boolean getAclTablesUpdateFlag() { + return aclTablesUpdatedFlag; + } @Override public ServiceContext getServiceContext() { @@ -58,13 +78,40 @@ public class JPATransactionContext extends TransactionContext { @Override public void close() throws TransactionException { if (em.getTransaction().isActive() == true && em.getTransaction().getRollbackOnly() == true) { - em.getTransaction().rollback(); + if (getAclTablesUpdateFlag() == false) { + // + // Since there were no changes committed to the Spring Security tables, we can just rollback and continue + // + em.getTransaction().rollback(); + } else { + String msg = handleInconsistentState(); + throw new InconsistentStateException(msg); + } } else if (em.getTransaction().isActive() == true) { - throw new JPATransactionException("There is an active transaction. You must commit the active transaction prior to calling this close method."); + markForRollback(); + close(); // NOTE: Recursive call. + throw new JPATransactionException("There was an active transaction. You must commit the active transaction prior to calling this close method."); } - + + em.close(); + JpaStorageUtils.releaseEntityManagerFactory(emf); + } + + private String handleInconsistentState() throws InconsistentStateException { + // + // If we've modified the Spring Tables and need to rollback this JPA transaction, we now have a potentially critical inconsistent state in the system + // + String msg = "\n#\n# CRITICAL: The Spring Security tables just became inconsistent with CollectionSpace JPA AuthN and AuthZ tables. Contact your CollectionSpace administrator immediately.\n#"; + + // + // Finish by rolling back the JPA transaction, closing the connection, and throwing an exception + // + logger.error(msg); + em.getTransaction().rollback(); em.close(); JpaStorageUtils.releaseEntityManagerFactory(emf); + + return msg; } @Override @@ -85,11 +132,13 @@ public class JPATransactionContext extends TransactionContext { return em.merge(entity); } + @SuppressWarnings("unchecked") @Override public Object find(Class entityClass, Object primaryKey) { return em.find(entityClass, primaryKey); } + @SuppressWarnings("unchecked") @Override public Object find(Class entityClass, String id) { return em.find(entityClass, id); @@ -104,6 +153,16 @@ public class JPATransactionContext extends TransactionContext { public void remove(Object entity) { em.remove(entity); } + + @Override + public boolean isTransactionActive() { + return em.getTransaction().isActive(); + } + + @Override + public void flush() { + em.flush(); + } @Override public void commitTransaction() throws TransactionException { @@ -112,12 +171,6 @@ public class JPATransactionContext extends TransactionContext { } if (--transactionRefCount == 0) { em.getTransaction().commit(); - commitSuccessful = Boolean.TRUE; } } - - @Override - public boolean isTransactionActive() { - return em.getTransaction().isActive(); - } } diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java index 49bc98036..d2a92aa74 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java @@ -66,21 +66,6 @@ import org.slf4j.LoggerFactory; public class JpaRelationshipStorageClient extends JpaStorageClientImpl { private final Logger logger = LoggerFactory.getLogger(JpaRelationshipStorageClient.class); - - public static PermissionValue createPermissionValue(Permission permission) { - PermissionValue result = new PermissionValue(); - result.setPermissionId(permission.getCsid()); - result.setResourceName(permission.getResourceName()); - result.setActionGroup(permission.getActionGroup()); - return result; - } - - public static RoleValue createRoleValue(Role role) { - RoleValue result = new RoleValue(); - result.setRoleId(role.getCsid()); - result.setRoleName(role.getRoleName()); - return result; - } public JpaRelationshipStorageClient() { //empty @@ -107,30 +92,28 @@ public class JpaRelationshipStorageClient extends JpaStorageClientImpl { try { jpaTransactionContext.beginTransaction(); handler.prepare(Action.CREATE); - List rl = new ArrayList(); - DocumentWrapper> wrapDoc = - new DocumentWrapperImpl>(rl); + List relationshipList = new ArrayList(); + DocumentWrapper> wrapDoc = new DocumentWrapperImpl>(relationshipList); handler.handle(Action.CREATE, wrapDoc); - for (T r : rl) { - JaxbUtils.setValue(r, "setCreatedAtItem", Date.class, new Date()); - jpaTransactionContext.persist(r); + for (T relationship : relationshipList) { + JaxbUtils.setValue(relationship, "setCreatedAtItem", Date.class, new Date()); + jpaTransactionContext.persist(relationship); } - handler.complete(Action.CREATE, wrapDoc); + handler.complete(Action.CREATE, wrapDoc); jpaTransactionContext.commitTransaction(); - result = "-1"; // meaningless result + result = "0"; // meaningless result } catch (BadRequestException bre) { - jpaTransactionContext.markForRollback(); throw bre; } catch (PersistenceException pe) { - jpaTransactionContext.markForRollback(); throw pe; + } catch (DocumentException de) { + throw de; } catch (Exception e) { - jpaTransactionContext.markForRollback(); - if (logger.isDebugEnabled()) { - logger.debug("Caught exception ", e); - } throw new DocumentException(e); } finally { + if (result == null) { + jpaTransactionContext.markForRollback(); // If result == null, we failed and must mark the current tx for rollback + } ctx.closeConnection(); } @@ -159,7 +142,6 @@ public class JpaRelationshipStorageClient extends JpaStorageClientImpl { } String objectId = getObjectId(ctx); - Class objectClass = getObjectClass(ctx); DocumentFilter docFilter = handler.getDocumentFilter(); if (docFilter == null) { docFilter = handler.createDocumentFilter(); @@ -193,19 +175,9 @@ public class JpaRelationshipStorageClient extends JpaStorageClientImpl { try { relList = q.getResultList(); } catch (NoResultException nre) { - String msg = "get(1): " + " could not find relationships for object class=" - + objectClass.getName() + " id=" + id; - if (logger.isDebugEnabled()) { - logger.debug(msg, nre); - } - } - if (relList.size() == 0) { - String msg = "get(2): " + " could not find relationships for object class=" - + objectClass.getName() + " id=" + id; - if (logger.isDebugEnabled()) { - logger.debug(msg); - } + // Quietly consume. relList will just be an empty list } + DocumentWrapper> wrapDoc = new DocumentWrapperImpl>(relList); handler.handle(Action.GET, wrapDoc); handler.complete(Action.GET, wrapDoc); @@ -354,14 +326,14 @@ public class JpaRelationshipStorageClient extends JpaStorageClientImpl { try { jpaTransactionContext.beginTransaction(); handler.prepare(Action.DELETE); - List rl = new ArrayList(); - DocumentWrapper> wrapDoc = new DocumentWrapperImpl>(rl); + List relationshipList = new ArrayList(); + DocumentWrapper> wrapDoc = new DocumentWrapperImpl>(relationshipList); handler.handle(Action.DELETE, wrapDoc); // //the following could be much more efficient if done with a single sql/jql // - for (T r : rl) { - jpaTransactionContext.remove(getRelationship(jpaTransactionContext, r)); + for (T relationship : relationshipList) { + jpaTransactionContext.remove(getRelationship(jpaTransactionContext, relationship)); } handler.complete(Action.DELETE, wrapDoc); // Delete from the Spring Security tables. Would be better if this was part of the earlier transaction. jpaTransactionContext.commitTransaction(); diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java index 613e7d017..b0665fc8d 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java @@ -115,8 +115,8 @@ public class JpaStorageClientImpl implements StorageClient { try { handler.handle(Action.CREATE, wrapDoc); JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date()); - jpaConnectionContext.persist(entity); - } catch (EntityExistsException ee) { + jpaConnectionContext.persist(entity); + } catch (EntityExistsException ee) { // FIXME: No, don't allow duplicates // // We found an existing matching entity in the store, so we don't need to create one. Just update the transient 'entity' instance with the existing persisted entity we found. // An entity's document handler class will throw this exception only if attempting to create (but not actually creating) duplicate is ok -e.g., Permission records. @@ -410,28 +410,55 @@ public class JpaStorageClientImpl implements StorageClient { * cost: a get before delete * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String) */ - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "rawtypes" }) @Override public boolean delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException, DocumentException { - boolean result = true; + boolean result = false; JPATransactionContext jpaConnectionContext = (JPATransactionContext)ctx.openConnection(); try { jpaConnectionContext.beginTransaction(); - handler.prepare(Action.DELETE); Object entityFound = getEntity(ctx, id); if (entityFound == null) { - jpaConnectionContext.markForRollback(); String msg = "delete(ctx, ix, handler) could not find entity with id=" + id; logger.error(msg); throw new DocumentNotFoundException(msg); } - DocumentWrapper wrapDoc = new DocumentWrapperImpl(entityFound); + result = delete(ctx, entityFound, handler); + jpaConnectionContext.commitTransaction(); + } catch (DocumentException de) { + jpaConnectionContext.markForRollback(); + throw de; + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("delete(ctx, ix, handler): Caught exception ", e); + } + jpaConnectionContext.markForRollback(); + throw new DocumentException(e); + } finally { + ctx.closeConnection(); + } + + return result; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean delete(ServiceContext ctx, Object entity, DocumentHandler handler) + throws DocumentNotFoundException, DocumentException { + boolean result = false; + + JPATransactionContext jpaConnectionContext = (JPATransactionContext)ctx.openConnection(); + try { + jpaConnectionContext.beginTransaction(); + handler.prepare(Action.DELETE); + DocumentWrapper wrapDoc = new DocumentWrapperImpl(entity); handler.handle(Action.DELETE, wrapDoc); - jpaConnectionContext.remove(entityFound); + jpaConnectionContext.remove(entity); handler.complete(Action.DELETE, wrapDoc); jpaConnectionContext.commitTransaction(); + result = true; } catch (DocumentException de) { jpaConnectionContext.markForRollback(); throw de; @@ -446,7 +473,7 @@ public class JpaStorageClientImpl implements StorageClient { } return result; - } + } /** * Gets the entityReceived name. @@ -501,9 +528,9 @@ public class JpaStorageClientImpl implements StorageClient { throws DocumentNotFoundException, TransactionException { Object entityFound = null; - JPATransactionContext jpaTransactionConnection = (JPATransactionContext)ctx.openConnection(); + JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection(); try { - entityFound = JpaStorageUtils.getEntity(jpaTransactionConnection.getEntityManager(), id, entityClazz); + entityFound = JpaStorageUtils.getEntity(jpaTransactionContext, id, entityClazz); // FIXME: # Should be qualifying with the tenant ID if (entityFound == null) { String msg = "could not find entity of type=" + entityClazz.getName() + " with id=" + id; diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java index 56f7f791b..705cd756e 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageUtils.java @@ -440,6 +440,7 @@ public class JpaStorageUtils { if (useTenantId == true) { q.setParameter("tenantId", tenantId); } + result = q.getSingleResult(); return result; diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryClientImpl.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryClientImpl.java index 2293bb966..e95c958da 100644 --- a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryClientImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryClientImpl.java @@ -2221,4 +2221,13 @@ public class RepositoryClientImpl implements RepositoryClient - - - - resource_name - action_group - tenant_id - - + @@ -81,8 +74,7 @@ - + @@ -91,10 +83,8 @@ - - + + @@ -103,6 +93,24 @@ + + + + + + + + + + + + + + + + + + -- 2.47.3