diff --git a/changelog.txt b/changelog.txt index 9fcca8345e89f9e287b1fa4136f465a54406aa9d..012e7793ea5af172eaa1a19050261c4924ad115f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ eDelivery SMP 5.1 -- Added the HTTP parameter 'Resource-Owner' as alternative to ServiceGroup-Owner +- added new HTTP headers when creating a new resource: + 'Resource-Owner' as alternative to ServiceGroup-Owner + 'Resource-Group': the name of the group: to define to which group in domain the resource belongs to. The group must be in one of the admin's groups. If not given the first group from the list is used. + 'Resource-Visibility': The visibility of the resource: PUBLIC, PRIVATE. The default value is PUBLIC. Resource visibility can be set only at creation time. To change value of the existing resource it must be done via the UI. - added new properties: smp.instance.name: The SMP instance name smp.credentials.reset_request.url: The URL to reset the user password diff --git a/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql b/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql index b7127990eedfdf0f1a769a33c48e736a87d104f2..bba97b98b4159619ccaf6c798eea753380e891b9 100644 --- a/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql +++ b/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql @@ -56,7 +56,8 @@ insert into SMP_DOMAIN_RESOURCE_DEF (ID, FK_RESOURCE_DEF_ID, FK_DOMAIN_ID,CREATE (2, 2, 1, NOW(), NOW()); insert into SMP_GROUP (ID, FK_DOMAIN_ID, NAME, VISIBILITY, CREATED_ON, LAST_UPDATED_ON) values -(1, 1, 'test group', 'PUBLIC', NOW(), NOW()); +(1, 1, 'Group001', 'PUBLIC', NOW(), NOW()), +(2, 1, 'Group002', 'PUBLIC', NOW(), NOW()); insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values (1, 1, 2, 'ADMIN', NOW(), NOW()), diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java index 29f449c985e9247c9656bb8c0369fa07ba8e6a69..2255619028304e3a2717388f0149a62f1594bee9 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java @@ -48,17 +48,6 @@ import static org.apache.commons.lang3.StringUtils.trim; @Repository public class GroupDao extends BaseDao<DBGroup> { - /** - * Returns domain records from smp_domain table. - * - * @return the list of domain records from smp_domain table - * @throws IllegalStateException if no domain is configured - */ - public List<DBGroup> getAllGroups() { - TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_ALL, DBGroup.class); - return query.getResultList(); - } - /** * Get group list for domains * diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java index eab03e722f44d23a7a8b743f7b2c6ab896a0144e..63d8038a971348874b5f74db503fe41ff1fef567 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java @@ -39,5 +39,21 @@ public enum VisibilityType { /** * Access to the domain, group or resource is possible only if you are only direct or un-direct member of the domain, group or resource */ - PRIVATE + PRIVATE; + + /** + * Returns the VisibilityType from the string case-insensitive value. If value is blank or null, null is returned. + * + * @param value the value + * @return the VisibilityType + * @IllegalArgumentException if the value is not valid. + */ + public static VisibilityType fromString(String value) { + + String sValue = value == null ? null : value.trim(); + if (sValue == null || sValue.isEmpty()) { + return null; + } + return valueOf(sValue.toUpperCase()); + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java index a23e88bbeb9e998b82adb27f7b899b6855f4662f..b1c08892925c2b52c7e4a9165add6f36bf6d24a7 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -19,14 +19,16 @@ package eu.europa.ec.edelivery.smp.services.resource; import eu.europa.ec.edelivery.smp.auth.SMPUserDetails; -import eu.europa.ec.edelivery.smp.services.IdentifierService; import eu.europa.ec.edelivery.smp.data.dao.*; +import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.DBGroup; import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument; import eu.europa.ec.edelivery.smp.data.model.doc.DBResource; import eu.europa.ec.edelivery.smp.data.model.doc.DBSubresource; import eu.europa.ec.edelivery.smp.data.model.ext.DBResourceDef; import eu.europa.ec.edelivery.smp.data.model.ext.DBSubresourceDef; +import eu.europa.ec.edelivery.smp.data.model.user.DBUser; import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.identifiers.Identifier; @@ -34,6 +36,7 @@ import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.security.ResourceGuard; import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.services.IdentifierService; import eu.europa.ec.edelivery.smp.servlet.ResourceAction; import eu.europa.ec.edelivery.smp.servlet.ResourceRequest; import org.apache.commons.lang3.StringUtils; @@ -46,8 +49,7 @@ import java.util.Optional; import static eu.europa.ec.edelivery.smp.exceptions.ErrorCode.SML_INVALID_IDENTIFIER; import static eu.europa.ec.edelivery.smp.logging.SMPLogger.SECURITY_MARKER; -import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase; -import static org.apache.commons.lang3.StringUtils.join; +import static org.apache.commons.lang3.StringUtils.*; /** @@ -66,6 +68,7 @@ public class ResourceResolverService { final ConfigurationService configurationService; final IdentifierService identifierService; final DomainDao domainDao; + final GroupDao groupDao; final ResourceDefDao resourceDefinitionDao; final DomainResourceDefDao domainResourceDefDao; final ResourceDao resourceDao; @@ -76,6 +79,7 @@ public class ResourceResolverService { ConfigurationService configurationService, IdentifierService identifierService, DomainDao domainDao, + GroupDao groupDao, DomainResourceDefDao domainResourceDefDao, ResourceDefDao resourceDefinitionDao, ResourceDao resourceDao, @@ -85,6 +89,7 @@ public class ResourceResolverService { this.configurationService = configurationService; this.identifierService = identifierService; this.domainDao = domainDao; + this.groupDao = groupDao; this.domainResourceDefDao = domainResourceDefDao; this.resourceDefinitionDao = resourceDefinitionDao; this.resourceDao = resourceDao; @@ -134,6 +139,12 @@ public class ResourceResolverService { throw new SMPRuntimeException(ErrorCode.SG_NOT_EXISTS, resourceId.getValue(), resourceId.getScheme()); } resource = createNewResource(resourceId, resourceDef, domain); + // determine the group for the resource + DBGroup group = resolveResourceGroup(user.getUser(), domain, + trimToNull(resourceRequest.getResourceGroupParameter())); + + resource.setVisibility(resourceRequest.getResourceVisibilityParameter()); + resource.setGroup(group); } locationVector.setResource(resource); @@ -282,6 +293,41 @@ public class ResourceResolverService { return optResource.orElse(null); } + /** + * Method resolves the group for the given domain, admin user and group name. + * If the group name is null/not given, the first group is returned. If the group name is provided + * but the user is not admin for the group, the exception is thrown. + * + * @param user admin user creating the resource + * @param domain domain where the resource is created + * @param domainGroup group name + * @return DBGroup for the given domain, user and group name. + * @throws SMPRuntimeException if the user is not admin authorized to create the resource for the given group/domain + */ + protected DBGroup resolveResourceGroup(DBUser user, DBDomain domain, String domainGroup) { + LOG.debug("Resolve group for domain [{}] and user [{}] and group [{}]", domain.getDomainCode(), + user.getUsername(), + domainGroup); + List<DBGroup> adminListGroup = + groupDao.getGroupsByDomainUserIdAndGroupRoles(domain.getId(), user.getId(), MembershipRoleType.ADMIN); + if (adminListGroup.isEmpty()) { + throw new SMPRuntimeException(ErrorCode.UNAUTHORIZED, + "User [" + user.getUsername() + "] is not admin for any group in domain [" + domain.getDomainCode() + "]"); + } + if (domainGroup == null) { + LOG.debug("Set first/default group [{}] for domain [{}]", adminListGroup.get(0).getGroupName(), + domain.getDomainCode()); + return adminListGroup.get(0); + } + return groupDao.getGroupsByDomainUserIdAndGroupRoles(domain.getId(), user.getId(), MembershipRoleType.ADMIN) + .stream() + .filter(group -> equalsIgnoreCase(group.getGroupName(), domainGroup)) + .findFirst() + .orElseThrow(() -> new SMPRuntimeException(ErrorCode.UNAUTHORIZED, + "User [" + user.getUsername() + "] is not authorized for group [" + + domainGroup + "] in domain [" + domain.getDomainCode() + "]")); + } + /** * Resolve subresource for given resource , subresource context and subresouce Identifier * diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java index fdd6059052cb56bc9ef3d6d7d5e123bdab024a05..0e7719dbc2c791ee4d26ab0a309e2dcf4f328c87 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -18,6 +18,7 @@ */ package eu.europa.ec.edelivery.smp.servlet; +import eu.europa.ec.edelivery.smp.data.enums.VisibilityType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; @@ -37,7 +38,6 @@ import static org.apache.commons.lang3.StringUtils.trim; * * @author Joze Rihtarsic * @since 5.0 - * */ public class ResourceRequest { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(ResourceRequest.class); @@ -88,6 +88,22 @@ public class ResourceRequest { return getHeader(WebConstants.HTTP_PARAM_RESOURCE_TYPE); } + /** + * Returns the visibility of the resource. If value can not be parsed, + * the IllegalArgumentException is thrown. + * If the visibility is not set, the default value is PUBLIC. + * @return the visibility of the resource + * @throws IllegalArgumentException if the value can not be parsed. + */ + public VisibilityType getResourceVisibilityParameter() { + VisibilityType visibility = VisibilityType.fromString(getHeader(WebConstants.HTTP_PARAM_RESOURCE_VISIBILITY)); + return visibility == null ? VisibilityType.PUBLIC : visibility; + } + + public String getResourceGroupParameter() { + return getHeader(WebConstants.HTTP_PARAM_RESOURCE_GROUP); + } + public List<String> getUrlPathParameters() { return urlPathParameters; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java index 0b42b7206997b1661167a10a8782f9bb1e249e73..6b7313b39deab3d30dfb7b1bb9b8e2e40c8cf4bb 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java @@ -31,6 +31,8 @@ public class WebConstants { public static final String HTTP_PARAM_RESOURCE_TYPE = "Resource-Type"; public static final String HTTP_PARAM_OWNER_OBSOLETE = "ServiceGroup-Owner"; public static final String HTTP_PARAM_OWNER = "Resource-Owner"; + public static final String HTTP_PARAM_RESOURCE_GROUP = "Resource-Group"; + public static final String HTTP_PARAM_RESOURCE_VISIBILITY = "Resource-Visibility"; private WebConstants() { } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java index 7b4ab905edded51ec2d8c76fcbc7b44347bdb786..32fe3f8599dab21c68836e5b4cb6aae2b5809902 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java @@ -70,6 +70,8 @@ public class ResourceController { private static final List<String> SUPPORTED_HEADERS = Arrays.asList(lowerCase(HTTP_PARAM_DOMAIN), lowerCase(HTTP_PARAM_OWNER), lowerCase(HTTP_PARAM_OWNER_OBSOLETE), + lowerCase(HTTP_PARAM_RESOURCE_GROUP), + lowerCase(HTTP_PARAM_RESOURCE_VISIBILITY), lowerCase(HTTP_PARAM_RESOURCE_TYPE)); final ResourceService resourceService; final DomainGuard domainGuard; diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java index 1f58f212b7bdd5f23e702bc9b9522b327eef002a..d4ac2abef468cf625cf719752ed64b788b1fe55b 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java @@ -54,7 +54,8 @@ public class ResourceControllerSingleDomainTest extends AbstractControllerTest { private static final String SERVICE_GROUP_INPUT_BODY = getSampleServiceGroupBodyWithScheme(IDENTIFIER_SCHEME); private static final String HTTP_HEADER_KEY_DOMAIN = "Domain"; private static final String HTTP_HEADER_KEY_SERVICE_GROUP_OWNER = "ServiceGroup-Owner"; - + private static final String HTTP_HEADER_KEY_RESOURCE_GROUP = "Resource-Group"; + private static final String HTTP_HEADER_KEY_RESOURCE_VISIBILITY = "Resource-Visibility"; private static final String OTHER_OWNER_NAME = "CN=EHEALTH_SMP_TEST_BRAZIL,O=European Commission,C=BE:48b681ee8e0dcc08"; @BeforeEach @@ -190,4 +191,57 @@ public class ResourceControllerSingleDomainTest extends AbstractControllerTest { .content(SERVICE_GROUP_INPUT_BODY)) .andExpect(status().isCreated()); } + + @ParameterizedTest + @CsvSource({"Null group,,201", + "Empty group, '',201", + "Existing group:,'domain group',201", + "Existing group 2:,'Third group',201", + "Not authorized for domain:,'Second group',401", + "Not authorized for non existing domain:,'Not exists group',401"}) + void createResourceForGroup(String testDesc, String groupName, int httpCode) throws Exception { + + LOG.info(testDesc); + // create service group by group admin + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, OTHER_OWNER_NAME); + httpHeaders.add(HTTP_HEADER_KEY_DOMAIN, "domain"); + if (groupName != null) { + httpHeaders.add(HTTP_HEADER_KEY_RESOURCE_GROUP, groupName); + } + + mvc.perform(put(URL_PATH) + .with(ADMIN_CREDENTIALS) + .contentType(APPLICATION_XML_VALUE) + .headers(httpHeaders) + .content(SERVICE_GROUP_INPUT_BODY)) + .andExpect( status().is(httpCode) ); + } + + @ParameterizedTest + @CsvSource({"Null Visibility,,201", + "Empty Visibility, '',201", + "Public Visibility:,'PUBLIC',201", + "Private Visibility:,'PRIVATE',201", + "Case insensitive Visibility:,'PRiVaTE',201", + "Invalid Visibility:,'NotOKValue',400", + }) + void createResourceWithVisibility(String testDesc, String visibility, int httpCode) throws Exception { + + LOG.info(testDesc); + // create service group by group admin + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, OTHER_OWNER_NAME); + httpHeaders.add(HTTP_HEADER_KEY_DOMAIN, "domain"); + if (visibility != null) { + httpHeaders.add(HTTP_HEADER_KEY_RESOURCE_VISIBILITY, visibility); + } + + mvc.perform(put(URL_PATH) + .with(ADMIN_CREDENTIALS) + .contentType(APPLICATION_XML_VALUE) + .headers(httpHeaders) + .content(SERVICE_GROUP_INPUT_BODY)) + .andExpect( status().is(httpCode) ); + } } diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java index 1fe33961e7e52fec253b232376e0a76fd7880f94..76e168b882b6b275eb4281cf954b0192cdff2eb4 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java @@ -62,8 +62,8 @@ class GroupEditControllerIT extends AbstractControllerTest { @ParameterizedTest @CsvSource({ - ", 2", - "'', 2", + ", 3", + "'', 3", "group-admin, 1", "resource-admin, 1", "group-viewer, 0", diff --git a/smp-webapp/src/test/resources/webapp_integration_test_data.sql b/smp-webapp/src/test/resources/webapp_integration_test_data.sql index 1688a5596e7c594510c4f16803c9a7c476f0ef35..9c726ef123a286caa3ee6e4df93f0a46c5b65e66 100644 --- a/smp-webapp/src/test/resources/webapp_integration_test_data.sql +++ b/smp-webapp/src/test/resources/webapp_integration_test_data.sql @@ -86,7 +86,8 @@ insert into SMP_DOMAIN (ID, VISIBILITY, DOMAIN_CODE, SML_SUBDOMAIN, SML_SMP_ID, insert into SMP_GROUP (ID, FK_DOMAIN_ID, NAME, VISIBILITY, CREATED_ON, LAST_UPDATED_ON) values (1, 1, 'domain group', 'PUBLIC', NOW(), NOW()), -(2, 1, 'Second group', 'PUBLIC', NOW(), NOW()); +(2, 1, 'Second group', 'PUBLIC', NOW(), NOW()), +(3, 1, 'Third group', 'PUBLIC', NOW(), NOW()); -- -------------- -- configure extension and document types service group and servicemetadata @@ -127,7 +128,8 @@ insert into SMP_DOMAIN_MEMBER (ID, FK_DOMAIN_ID, FK_USER_ID, MEMBERSHIP_ROLE, CR insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values (1, 1, 1, 'ADMIN', NOW(), NOW()), -(2, 1, 3, 'ADMIN', NOW(), NOW()); +(2, 3, 1, 'ADMIN', NOW(), NOW()), +(3, 1, 3, 'ADMIN', NOW(), NOW()); -- set ownership insert into SMP_RESOURCE_MEMBER (ID, FK_RESOURCE_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values (-1, -1, 1, 'ADMIN', NOW(), NOW()),