Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit 5e4b2f4a authored by Joze RIHTARSIC's avatar Joze RIHTARSIC
Browse files

Fix REST API delete sub-resource permission

parent 3dec94ad
No related branches found
No related tags found
No related merge requests found
Pipeline #121547 passed with warnings
Showing
with 360 additions and 250 deletions
eDelivery SMP 5.1
- Added the HTTP parameter 'Resource-Owner' as alternative to ServiceGroup-Owner
eDelivery SMP 5.0
- removed: bdmsl.participant.multidomain.enabled
- environment properties have now 'smp.' prefix
......
......@@ -37,16 +37,17 @@ public class DomainGuard {
/**
* Method resolves the domain and authorize the user for the action on the domain
*
* @param resourceRequest a resource request
* @param user a user trying to execute the action on the resource
* @param user a user trying to execute the action on the resource
* @return the DBDomain
*/
public DBDomain resolveAndAuthorizeForDomain(ResourceRequest resourceRequest, SMPUserDetails user){
public DBDomain resolveAndAuthorizeForDomain(ResourceRequest resourceRequest, SMPUserDetails user) {
DBDomain domain = domainResolverService.resolveDomain(
resourceRequest.getDomainHttpParameter(),
resourceRequest.getUrlPathParameter(0));
if (isUserIsAuthorizedForDomainResourceAction(domain, user, resourceRequest.getAction())){
if (isUserIsAuthorizedForDomainResourceAction(domain, user, resourceRequest.getAction())) {
resourceRequest.setAuthorizedDomain(domain);
return domain;
}
......@@ -55,10 +56,11 @@ public class DomainGuard {
}
/**
* Purpose of the method is to guard domain resources. It validates if users has any "rights to" execute the action
* on the domain resources and subresources
* Purpose of the method is to guard domain resources and sub-resources. It validates if users has any
* "permission to" execute the http action on the domain resources and subresources. More accurate check is done
* when the resource and/or subresource are resolved.
*
* @param user user to be authorized
* @param user user to be authorized
* @param action action to be executed
* @param domain domain to be authorized
* @return true if user is authorized to execute the action on the domain
......@@ -82,9 +84,9 @@ public class DomainGuard {
/**
* Method validates of the user can read resources on the domain!
*
* @param user
* @param domain
* @return
* @param user user to be authorized for READ action
* @param domain domain to be authorized
* @return true if user is authorized to execute the action on the domain, else it returns false
*/
public boolean canRead(SMPUserDetails user, DBDomain domain) {
LOG.info(SMPLogger.SECURITY_MARKER, "User: [{}] is trying to read domain: [{}]", user, domain);
......@@ -112,7 +114,7 @@ public class DomainGuard {
* Method validates of the user can delete resources on the domain! Only users with group admin role can delete
* domain resources
*
* @param user user to be authorized
* @param user user to be authorized
* @param domain domain to be authorized
* @return true if user is authorized to execute the action on the domain
*/
......@@ -124,7 +126,8 @@ public class DomainGuard {
return false;
}
// to be able to delete domain resources it must be member of any group on domain
boolean isAuthorized = groupMemberDao.isUserAnyDomainGroupResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN);
boolean isAuthorized = groupMemberDao.isUserAnyDomainGroupResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN)
|| resourceMemberDao.isUserAnyDomainResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN);
LOG.info(SMPLogger.SECURITY_MARKER, "User: [{}] is authorized:[{}] to read resources from Domain: [{}]", user, isAuthorized, domain);
return isAuthorized;
}
......@@ -135,7 +138,7 @@ public class DomainGuard {
*
* @param user user to be authorized
* @param domain domain to be authorized
* @return true if user is authorized to execute the action on the domain
* @return true if user is authorized to execute the action on the domain
*/
public boolean canCreateUpdate(SMPUserDetails user, DBDomain domain) {
LOG.info(SMPLogger.SECURITY_MARKER, "User: [{}] is trying to create/update resource from domain: [{}]", user, domain);
......@@ -145,12 +148,10 @@ public class DomainGuard {
return false;
}
// to be able to delete domain resources it must be member of any group on domain
boolean isAuthorized = groupMemberDao.isUserAnyDomainGroupResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN)
|| resourceMemberDao.isUserAnyDomainResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN);
boolean isAuthorized = groupMemberDao.isUserAnyDomainGroupResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN)
|| resourceMemberDao.isUserAnyDomainResourceMemberWithRole(user.getUser(), domain, MembershipRoleType.ADMIN);
LOG.info(SMPLogger.SECURITY_MARKER, "User: [{}] is authorized:[{}] to create/update resources from Domain: [{}]", user, isAuthorized, domain);
return isAuthorized;
}
}
......@@ -44,7 +44,7 @@ public class ResourceGuard {
* @param user user trying to execute the action
* @param action resource action
* @param resource target resource
* @return
* @return true if user is not authorized for the http action on the resource, else false.
*/
public boolean userIsNotAuthorizedForAction(SMPUserDetails user, ResourceAction action, DBResource resource, DBDomain domain) {
return !userIsAuthorizedForAction(user, action, resource, domain);
......@@ -66,6 +66,8 @@ public class ResourceGuard {
switch (action) {
case READ:
return canRead(user, subresource);
case CREATE_UPDATE:
return canCreateUpdate(user, subresource);
case DELETE:
return canDelete(user, subresource);
}
......@@ -154,4 +156,10 @@ public class ResourceGuard {
// Subresource can be created by the resource admin, the same as for update
return canUpdate(user, subresource);
}
public boolean canCreateUpdate(SMPUserDetails user, DBSubresource subresource) {
LOG.debug(SMPLogger.SECURITY_MARKER, "User [{}] is trying to delete resource [{}]", user, subresource);
// Subresource can be created by the resource admin, the same as for update
return canUpdate(user, subresource);
}
}
......@@ -108,7 +108,7 @@ public class ResourceResolverService {
validateResourceIdentifier(resourceId);
DBResource resource = resolveResourceIdentifier(domain, resourceDef, resourceId);
if (resource == null) {
// the resource must be found because it is not create action nor the last parameter to be resolved
// the resource must be found because if action is not "create" action nor the last parameter to be resolved
if (resourceRequest.getAction() != ResourceAction.CREATE_UPDATE
|| pathParameters.size() > iParameterIndex + 1) {
throw new SMPRuntimeException(ErrorCode.SG_NOT_EXISTS, resourceId.getValue(), resourceId.getScheme());
......@@ -117,40 +117,51 @@ public class ResourceResolverService {
}
locationVector.setResource(resource);
if (resourceGuard.userIsNotAuthorizedForAction(user, resourceRequest.getAction(), resource, domain)) {
LOG.info(SECURITY_MARKER, "User [{}] is NOT authorized for action [{}] on the resource [{}]", getUsername(user), resourceRequest.getAction(), resource);
throw new SMPRuntimeException(ErrorCode.UNAUTHORIZED);
} else {
LOG.info(SECURITY_MARKER, "User: [{}] is authorized for action [{}] on the resource [{}]", getUsername(user), resourceRequest.getAction(), resource);
}
if (pathParameters.size() == ++iParameterIndex) {
locationVector.setResolved(true);
// check if resource is resolved - no more parameters to be resolved
locationVector.setResolved(pathParameters.size() == ++iParameterIndex);
if (locationVector.isResolved()) {
// validate if user is authorized for action
if (resourceGuard.userIsNotAuthorizedForAction(user, resourceRequest.getAction(), resource, domain)) {
LOG.info(SECURITY_MARKER, "User [{}] is NOT authorized for action [{}] on the resource [{}]",
getUsername(user), resourceRequest.getAction(), resource);
throw new SMPRuntimeException(ErrorCode.UNAUTHORIZED);
}
return locationVector;
}
if (pathParameters.size() == iParameterIndex + 2) {
String subResourceDefUrl = pathParameters.get(iParameterIndex);
// test if subresourceDef exists
DBSubresourceDef subresourceDef = getSubresource(resourceDef, subResourceDefUrl);
Identifier subResourceId = identifierService.normalizeDocumentIdentifier(pathParameters.get(++iParameterIndex));
DBSubresource subresource = resolveSubResourceIdentifier(resource, subResourceDefUrl, subResourceId);
LOG.debug("Got subresource [{}]", subresource);
if (subresource == null) {
if (resourceRequest.getAction() != ResourceAction.CREATE_UPDATE) {
throw new SMPRuntimeException(ErrorCode.METADATA_NOT_EXISTS, resource.getIdentifierValue(), resource.getIdentifierScheme(), subResourceId.getValue(), subResourceId.getScheme());
}
subresource = createNewSubResource(subResourceId, resource, subresourceDef);
// resolve subresource - expected exactly two parameters
if (pathParameters.size() != iParameterIndex + 2) {
throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, join(pathParameters, ","),
"Invalid remaining subresource parameters (expected only subresourceDef and subresource identifier)");
}
String subResourceDefUrl = pathParameters.get(iParameterIndex);
// test if subresourceDef exists
DBSubresourceDef subresourceDef = getSubresource(resourceDef, subResourceDefUrl);
Identifier subResourceId = identifierService.normalizeDocumentIdentifier(pathParameters.get(++iParameterIndex));
DBSubresource subresource = resolveSubResourceIdentifier(resource, subResourceDefUrl, subResourceId);
LOG.debug("Got subresource [{}]", subresource);
if (subresource == null) {
if (resourceRequest.getAction() != ResourceAction.CREATE_UPDATE) {
throw new SMPRuntimeException(ErrorCode.METADATA_NOT_EXISTS,
resource.getIdentifierValue(), resource.getIdentifierScheme(),
subResourceId.getValue(), subResourceId.getScheme());
}
subresource = createNewSubResource(subResourceId, resource, subresourceDef);
}
locationVector.setSubresource(subresource);
locationVector.setSubResourceDef(subresourceDef);
locationVector.setResolved(true);
return locationVector;
if (!resourceGuard.userIsAuthorizedForAction(user, resourceRequest.getAction(), subresource)) {
LOG.info(SECURITY_MARKER, "User [{}] is NOT authorized for action [{}] on the subresource resource [{}]",
getUsername(user), resourceRequest.getAction(), subresource);
throw new SMPRuntimeException(ErrorCode.UNAUTHORIZED);
}
throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, join(pathParameters, ","), "Invalid remaining subresource parameters (expected only subresourceDef and subresource identifier)");
locationVector.setSubresource(subresource);
locationVector.setSubResourceDef(subresourceDef);
locationVector.setResolved(true);
return locationVector;
}
/**
......@@ -229,11 +240,17 @@ public class ResourceResolverService {
optResDef = resourceDefs.stream().filter(resdef ->
equalsIgnoreCase(resdef.getIdentifier(), domain.getDefaultResourceTypeIdentifier())).findFirst();
if (optResDef.isPresent()) {
LOG.debug("Located default ResourceDef [{}] for domain [{}] by the path parameter [{}]", domain.getDefaultResourceTypeIdentifier(), domain.getDomainCode());
LOG.debug("Located default ResourceDef [{}] for domain [{}] by the path parameter [{}]",
domain.getDefaultResourceTypeIdentifier(),
domain.getDomainCode(),
pathParameter);
return optResDef.get();
}
// return first
LOG.info("Return first (default) ResourceDef [{}] for domain [{}] by the path parameter [{}]", resourceDefs.get(0).getDomainResourceDefs(), domain.getDomainCode());
LOG.info("Return first (default) ResourceDef [{}] for domain [{}] by the path parameter [{}]",
resourceDefs.get(0).getDomainResourceDefs(),
domain.getDomainCode(),
pathParameter);
return resourceDefs.get(0);
}
......@@ -297,8 +314,7 @@ public class ResourceResolverService {
}
}
public String getUsername(UserDetails user){
return user ==null? "Anonymous":user.getUsername();
public String getUsername(UserDetails user) {
return user == null ? "Anonymous" : user.getUsername();
}
}
......@@ -2,6 +2,7 @@ package eu.europa.ec.edelivery.smp.servlet;
import eu.europa.ec.edelivery.smp.data.model.DBDomain;
import eu.europa.ec.edelivery.smp.services.resource.ResolvedData;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.List;
......@@ -42,7 +43,9 @@ public class ResourceRequest {
public String getOwnerHttpParameter() {
String owner = getHeader(WebConstants.HTTP_PARAM_OWNER);
if (StringUtils.isBlank(owner)) {
owner = getHeader(WebConstants.HTTP_PARAM_OWNER_OBSOLETE);
}
return owner;
}
......
......@@ -7,13 +7,13 @@ package eu.europa.ec.edelivery.smp.servlet;
* @since 4.1
*/
public class WebConstants {
public static final int HTTP_RESPONSE_CODE_CREATED= 201;
public static final int HTTP_RESPONSE_CODE_UPDATED= 200;
public static final String HTTP_PARAM_DOMAIN="Domain";
public static final String HTTP_PARAM_RESOURCE_TYPE="Resource-Type";
public static final String HTTP_PARAM_OWNER="ServiceGroup-Owner";
public static final int HTTP_RESPONSE_CODE_CREATED = 201;
public static final int HTTP_RESPONSE_CODE_UPDATED = 200;
public static final String HTTP_PARAM_DOMAIN = "Domain";
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";
private WebConstants() {
}
}
......@@ -3,11 +3,8 @@ package eu.europa.ec.edelivery.smp.security;
import eu.europa.ec.edelivery.smp.auth.SMPUserDetails;
import eu.europa.ec.edelivery.smp.data.dao.AbstractJunit5BaseDao;
import eu.europa.ec.edelivery.smp.data.enums.VisibilityType;
import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
import eu.europa.ec.edelivery.smp.servlet.ResourceAction;
import eu.europa.ec.edelivery.smp.servlet.ResourceRequest;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
......@@ -22,8 +19,6 @@ class ResourceGuardTest extends AbstractJunit5BaseDao {
@Autowired
ResourceGuard testInstance;
ResourceRequest resourceRequest = Mockito.mock(ResourceRequest.class);
SMPUserDetails userDetails = Mockito.mock(SMPUserDetails.class);
@BeforeEach
......@@ -57,18 +52,6 @@ class ResourceGuardTest extends AbstractJunit5BaseDao {
assertTrue(result);
}
@ParameterizedTest
@ValueSource(strings = {"CREATE_UPDATE"})
void testUserIsAuthorizedForActionNotSupported(ResourceAction action) {
// given - user is authorized - see the createResourceMemberships
when(userDetails.getUser()).thenReturn(testUtilsDao.getUser1());
SMPRuntimeException result = assertThrows(SMPRuntimeException.class,
() -> testInstance.userIsAuthorizedForAction(userDetails, action, testUtilsDao.getSubresourceD1G1RD1_S1()));
// then
MatcherAssert.assertThat(result.getMessage(), CoreMatchers.containsString("Action not supported"));
}
@Test
void testCanReadResourceForPrivateDomainOK() {
// given - user is authorized - see the createResourceMemberships
......
......@@ -51,6 +51,7 @@ public class ResourceController {
// set them to lower case for fast comparing with the http headers
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_TYPE));
final ResourceService resourceService;
final DomainGuard domainGuard;
......@@ -131,7 +132,6 @@ public class ResourceController {
}
// resolve domain and test authorization for the domain.
domainGuard.resolveAndAuthorizeForDomain(resourceRequest, user);
return user;
}
......
......@@ -13,149 +13,99 @@
package eu.europa.ec.edelivery.smp.controllers;
import eu.europa.ec.edelivery.smp.test.SmpTestWebAppConfig;
import eu.europa.ec.edelivery.smp.test.testutils.X509CertificateTestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import eu.europa.ec.edelivery.smp.ui.AbstractControllerTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import java.io.IOException;
import static eu.europa.ec.edelivery.smp.ServiceGroupBodyUtil.*;
import static eu.europa.ec.edelivery.smp.ServiceGroupBodyUtil.getSampleServiceGroupBodyWithScheme;
import static java.lang.String.format;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Created by gutowpa on 02/08/2017.
* @author gutowpa
* @since 3.0
*/
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {SmpTestWebAppConfig.class})
@Sql(scripts = {"classpath:/cleanup-database.sql",
"classpath:/webapp_integration_test_data.sql"},
executionPhase = BEFORE_TEST_METHOD)
public class ResourceControllerSingleDomainTest {
private static final String IDENTIFIER_SCHEME = "ehealth-participantid-qns";
private static final String PARTICIPANT_ID = "urn:poland:ncpb";
private static final String DOCUMENT_SCHEME = "doctype";
private static final String DOCUMENT_ID = "invoice";
public class ResourceControllerSingleDomainTest extends AbstractControllerTest {
private static final String URL_PATH = format("/%s::%s", IDENTIFIER_SCHEME, PARTICIPANT_ID);
private static final String URL_DOC_PATH = format("%s/services/%s::%s", URL_PATH, DOCUMENT_SCHEME, DOCUMENT_ID);
public static final Logger LOG = LoggerFactory.getLogger(ResourceControllerSingleDomainTest.class);
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 OTHER_OWNER_NAME = "CN=EHEALTH_SMP_TEST_BRAZIL,O=European Commission,C=BE:48b681ee8e0dcc08";
private static final RequestPostProcessor ADMIN_CREDENTIALS = httpBasic("pat_smp_admin", "123456");
@Autowired
private WebApplicationContext webAppContext;
private MockMvc mvc;
@Before
public void setup() throws IOException {
X509CertificateTestUtils.reloadKeystores();
mvc = MockMvcBuilders.webAppContextSetup(webAppContext)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
initServletContext();
}
private void initServletContext() {
MockServletContext sc = new MockServletContext("");
ServletContextListener listener = new ContextLoaderListener(webAppContext);
ServletContextEvent event = new ServletContextEvent(sc);
listener.contextInitialized(event);
@BeforeEach
void initApplication() throws IOException {
super.setup();
}
@Test
public void adminCanCreateServiceGroupNoDomain() throws Exception {
void adminCanCreateServiceGroupNoDomain() throws Exception {
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isCreated());
}
@Test
public void adminCanUpdateServiceGroupNoDomain() throws Exception {
mvc.perform(put(URL_PATH)
/**
* Test update permissions for resource with different creation parameters. The user data match
* the data in the database: webapp_integration_test_data.sql
*/
@ParameterizedTest
@CsvSource({"'Default owner is admin: OK', 200, pat_smp_admin, 123456,''",
"'Default owner Admin, but user updates: Fail', 401, test_pat_hashed_pass, 123456,''",
"'Default owner is admin, bad credentials: Fail', 401, pat_smp_admin, 000000,''",
"'Set owner is same group admin: OK', 200, pat_smp_admin, 123456,'pat_smp_admin'",
"'Set owner user: OK', 200, test_pat_hashed_pass, 123456,'test_pat_hashed_pass'",
"'Set owner username: OK', 200, test_pat_hashed_pass, 123456,'test_user_hashed_pass'",
"'Set owner user, but admin updates: Fail', 401, test_pat_hashed_pass, 123456,'pat_smp_admin'",
})
void groupAdminCanUpdateServiceGroupNoDomain(String desc, int expectedStatus,
String resourceAdminATId, String groupResourceATSecret,
String resourceOwnerId) throws Exception {
LOG.info(desc);
// create service group by group admin
HttpHeaders httpHeaders = new HttpHeaders();
if (StringUtils.isNotBlank(resourceOwnerId)) {
httpHeaders.add(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, resourceOwnerId);
}
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.headers(httpHeaders)
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isCreated());
// update service group by owner (if not given then owner is the same as creator)
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isOk());
.with(httpBasic(resourceAdminATId, groupResourceATSecret))
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().is(expectedStatus));
}
@Test
public void existingServiceMetadataCanBeRetrievedByEverybodyNoDomain() throws Exception {
String xmlSG = getSampleServiceGroupBody(IDENTIFIER_SCHEME, PARTICIPANT_ID);
String xmlMD = generateServiceMetadata(PARTICIPANT_ID, IDENTIFIER_SCHEME, DOCUMENT_ID, DOCUMENT_SCHEME, "test");
// crate service group
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(xmlSG))
.andExpect(status().isCreated());
// add service metadata
mvc.perform(put(URL_DOC_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(xmlMD))
.andExpect(status().isCreated());
MvcResult mr = mvc.perform(get(URL_PATH).header("X-Forwarded-Host", "ec.test.eu")
.header("X-Forwarded-Port", "443")
.header("X-Forwarded-Proto", "https")).andReturn();
System.out.println(mr.getResponse().getContentAsString());
mvc.perform(get(URL_PATH))
.andExpect(content().xml("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><ServiceGroup xmlns=\"http://docs.oasis-open.org/bdxr/ns/SMP/2016/05\" xmlns:ns2=\"http://www.w3.org/2000/09/xmldsig#\"><ParticipantIdentifier scheme=\"ehealth-participantid-qns\">urn:poland:ncpb</ParticipantIdentifier><ServiceMetadataReferenceCollection><ServiceMetadataReference href=\"http://localhost/ehealth-participantid-qns%3A%3Aurn%3Apoland%3Ancpb/services/doctype%3A%3Ainvoice\"/></ServiceMetadataReferenceCollection></ServiceGroup>"));
}
@Test
public void anonymousUserCannotCreateServiceGroup() throws Exception {
void anonymousUserCannotCreateServiceGroup() throws Exception {
mvc.perform(put(URL_PATH)
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
.contentType(APPLICATION_XML_VALUE)
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isUnauthorized());
mvc.perform(get(URL_PATH))
......@@ -163,56 +113,56 @@ public class ResourceControllerSingleDomainTest {
}
@Test
public void malformedInputReturnsBadRequestNoDomain() throws Exception {
void malformedInputReturnsBadRequestNoDomain() throws Exception {
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content("malformed input XML"))
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content("malformed input XML"))
.andExpect(status().isBadRequest());
}
@Test
public void invalidParticipantSchemeReturnsBadRequestNoDomain() throws Exception {
void invalidParticipantSchemeReturnsBadRequestNoDomain() throws Exception {
String scheme = "length-exceeeeeeds-25chars";
String urlPath = format("/%s::%s", scheme, PARTICIPANT_ID);
mvc.perform(put(urlPath)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(getSampleServiceGroupBodyWithScheme(scheme)))
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(getSampleServiceGroupBodyWithScheme(scheme)))
.andExpect(status().isBadRequest());
}
@Test
public void creatingServiceGroupUnderBadFormatedDomainReturnsBadRequestNoDomain() throws Exception {
void creatingServiceGroupUnderBadFormattedDomainReturnsBadRequestNoDomain() throws Exception {
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.header(HTTP_HEADER_KEY_DOMAIN, "not-existing-domain")
.content(SERVICE_GROUP_INPUT_BODY))
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.header(HTTP_HEADER_KEY_DOMAIN, "not-existing-domain")
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isBadRequest())
.andExpect(content().string(stringContainsInOrder("FORMAT_ERROR")));
}
@Test
public void creatingServiceGroupUnderNotExistingDomainReturnsBadRequestNoDomain() throws Exception {
void creatingServiceGroupUnderNotExistingDomainReturnsBadRequestNoDomain() throws Exception {
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.header(HTTP_HEADER_KEY_DOMAIN, "notExistingDomain")
.content(SERVICE_GROUP_INPUT_BODY))
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.header(HTTP_HEADER_KEY_DOMAIN, "notExistingDomain")
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isNotFound())
.andExpect(content().string(stringContainsInOrder("NOT_FOUND")));
}
@Test
public void adminCanAssignNewServiceGroupToOtherOwnerNoDomain() throws Exception {
void adminCanAssignNewServiceGroupToOtherOwnerNoDomain() throws Exception {
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.header(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, OTHER_OWNER_NAME)
.content(SERVICE_GROUP_INPUT_BODY))
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.header(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, OTHER_OWNER_NAME)
.content(SERVICE_GROUP_INPUT_BODY))
.andExpect(status().isCreated());
}
}
package eu.europa.ec.edelivery.smp.controllers;
import eu.europa.ec.edelivery.smp.servlet.WebConstants;
import eu.europa.ec.edelivery.smp.ui.AbstractControllerTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.MvcResult;
import java.io.IOException;
import static eu.europa.ec.edelivery.smp.ServiceGroupBodyUtil.generateServiceMetadata;
import static eu.europa.ec.edelivery.smp.ServiceGroupBodyUtil.getSampleServiceGroupBody;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class ResourceControllerSubResourceTest extends AbstractControllerTest {
public static final Logger LOG = LoggerFactory.getLogger(ResourceControllerSingleDomainTest.class);
private static final String IDENTIFIER_SCHEME = "ehealth-participantid-qns";
private static final String DOCUMENT_SCHEME = "doctype";
@BeforeEach
public void setup() throws IOException {
super.setup();
}
/**
* Test update permissions for resource with different creation parameters. The user data match
* the data in the database: webapp_integration_test_data.sql
*/
@ParameterizedTest
@CsvSource({"'Resource owner is admin: OK', 201, pat_smp_admin, 123456,''",
"'Resource owner, but user updates: Fail', 401, test_pat_hashed_pass, 123456,''",
"'Default owner is admin, bad credentials: Fail', 401, pat_smp_admin, 000000,''",
"'Set owner is same group admin: OK', 201, pat_smp_admin, 123456,'pat_smp_admin'",
"'Set owner user: OK', 201, test_pat_hashed_pass, 123456,'test_pat_hashed_pass'",
"'Set owner username: OK', 201, test_pat_hashed_pass, 123456,'test_user_hashed_pass'",
"'Set owner user, but admin updates: Fail', 401, test_pat_hashed_pass, 123456,'pat_smp_admin'",
})
void createSubResourcePermissions(String desc, int expectedStatus,
String resourceAdminATId, String resourceATSecret,
String resourceOwnerId) throws Exception {
LOG.info(desc);
String xmlSG = getSampleServiceGroupBody(IDENTIFIER_SCHEME, PARTICIPANT_ID);
String xmlMD = generateServiceMetadata(PARTICIPANT_ID, IDENTIFIER_SCHEME, DOCUMENT_ID, DOCUMENT_SCHEME, "test");
// owner headers
HttpHeaders httpHeaders = new HttpHeaders();
if (StringUtils.isNotBlank(resourceOwnerId)) {
httpHeaders.add(WebConstants.HTTP_PARAM_OWNER, resourceOwnerId);
}
// crate service group
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.headers(httpHeaders)
.contentType(APPLICATION_XML_VALUE)
.content(xmlSG))
.andExpect(status().isCreated());
// add subresource/service-metadata
mvc.perform(put(URL_DOC_PATH)
.with(httpBasic(resourceAdminATId, resourceATSecret))
.contentType(APPLICATION_XML_VALUE)
.content(xmlMD))
.andExpect(status().is(expectedStatus));
}
/**
* Test update permissions for resource with different creation parameters. The user data match
* the data in the database: webapp_integration_test_data.sql
*/
@ParameterizedTest
@CsvSource({"'Resource owner is admin: OK', 200, pat_smp_admin, 123456, pat_smp_admin, 123456, ''",
"'Admin is Resource owner, but user deletes: Fail', 401, pat_smp_admin, 123456, test_pat_hashed_pass, 123456,''",
"'Default owner is admin, bad credentials: Fail', 401, pat_smp_admin, 123456, pat_smp_admin, 000000,''",
"'Set owner is same group admin: OK', 200, pat_smp_admin, 123456, pat_smp_admin, 123456,'pat_smp_admin'",
"'Set resource owner user: OK', 200, test_pat_hashed_pass, 123456, test_pat_hashed_pass, 123456,'test_pat_hashed_pass'",
"'Set resource owner user: OK', 200, test_pat_hashed_pass, 123456, test_pat_hashed_pass, 123456,'test_user_hashed_pass'",
"'Set owner user, but admin deletets: Fail', 400, test_pat_hashed_pass, 123456, pat_smp_admin, 123456, 'test_pat_hashed_pass'",
})
void deleteSubResourcePermissions(String desc, int expectedStatus,
String resourceAdminCreateATId, String resourceCreateATSecret,
String deleteAdminCreateATId, String deleteCreateATSecret,
String resourceOwnerId) throws Exception {
LOG.info(desc);
String xmlSG = getSampleServiceGroupBody(IDENTIFIER_SCHEME, PARTICIPANT_ID);
String xmlMD = generateServiceMetadata(PARTICIPANT_ID, IDENTIFIER_SCHEME, DOCUMENT_ID, DOCUMENT_SCHEME, "test");
// owner headers
HttpHeaders httpHeaders = new HttpHeaders();
if (StringUtils.isNotBlank(resourceOwnerId)) {
httpHeaders.add(WebConstants.HTTP_PARAM_OWNER, resourceOwnerId);
}
// crate service group
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.headers(httpHeaders)
.contentType(APPLICATION_XML_VALUE)
.content(xmlSG))
.andExpect(status().isCreated());
// add subresource/service-metadata with appropriate owner
mvc.perform(put(URL_DOC_PATH)
.with(httpBasic(resourceAdminCreateATId, resourceCreateATSecret))
.contentType(APPLICATION_XML_VALUE)
.content(xmlMD))
.andExpect(status().isCreated());
// delete subresource/service-metadata with test owner
mvc.perform(delete(URL_DOC_PATH)
.with(httpBasic(deleteAdminCreateATId, deleteCreateATSecret)))
.andExpect(status().is(expectedStatus));
}
@Test
void existingSubResourceCanBeRetrievedByEverybodyNoDomain() throws Exception {
String xmlSG = getSampleServiceGroupBody(IDENTIFIER_SCHEME, PARTICIPANT_ID);
String xmlMD = generateServiceMetadata(PARTICIPANT_ID, IDENTIFIER_SCHEME, DOCUMENT_ID, DOCUMENT_SCHEME, "test");
// crate service group
mvc.perform(put(URL_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(xmlSG))
.andExpect(status().isCreated());
// add service metadata
mvc.perform(put(URL_DOC_PATH)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
.content(xmlMD))
.andExpect(status().isCreated());
MvcResult mr = mvc.perform(get(URL_PATH).header("X-Forwarded-Host", "ec.test.eu")
.header("X-Forwarded-Port", "443")
.header("X-Forwarded-Proto", "https")).andReturn();
System.out.println(mr.getResponse().getContentAsString());
mvc.perform(get(URL_PATH))
.andExpect(content().xml("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><ServiceGroup xmlns=\"http://docs.oasis-open.org/bdxr/ns/SMP/2016/05\" xmlns:ns2=\"http://www.w3.org/2000/09/xmldsig#\"><ParticipantIdentifier scheme=\"ehealth-participantid-qns\">urn:poland:ncpb</ParticipantIdentifier><ServiceMetadataReferenceCollection><ServiceMetadataReference href=\"http://localhost/ehealth-participantid-qns%3A%3Aurn%3Apoland%3Ancpb/services/doctype%3A%3Ainvoice\"/></ServiceMetadataReferenceCollection></ServiceGroup>"));
}
}
......@@ -23,7 +23,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
import java.io.IOException;
......@@ -35,7 +34,6 @@ import static eu.europa.ec.edelivery.smp.ServiceGroupBodyUtil.getSampleServiceGr
import static java.lang.String.format;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
......@@ -50,9 +48,6 @@ public class ResourceControllerTest extends AbstractControllerTest {
private static final String DOCUMENT_TYPE_URL = "smp-1";
private static final String IDENTIFIER_SCHEME = "ehealth-participantid-qns";
private static final String DOCUMENT_SCHEME = "doctype";
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_DOMAIN_VALUE = "domain";
......@@ -60,8 +55,6 @@ public class ResourceControllerTest extends AbstractControllerTest {
private static final String OTHER_OWNER_NAME_URL_ENCODED = "CN=utf-8_%C5%BC_SMP,O=EC,C=BE:0000000000000666";
private static final RequestPostProcessor ADMIN_CREDENTIALS = httpBasic("pat_smp_admin", "123456");
@Autowired
ForwardedHeaderTransformer forwardedHeaderTransformer;
......@@ -368,7 +361,6 @@ public class ResourceControllerTest extends AbstractControllerTest {
public void malformedInputReturnsBadRequest() throws Exception {
String participantId = UUID.randomUUID().toString();
String resourceExample = getSampleServiceGroupBody(IDENTIFIER_SCHEME, participantId);
String urlPath = format("/%s::%s", IDENTIFIER_SCHEME, participantId);
mvc.perform(put(urlPath)
......@@ -476,7 +468,7 @@ public class ResourceControllerTest extends AbstractControllerTest {
.andExpect(status().isCreated());
// add service metadata
LOG.info("create service metadata: [{}]", docUrlPath);
ResultActions actions = mvc.perform(put(docUrlPath)
mvc.perform(put(docUrlPath)
.header(HTTP_HEADER_KEY_DOMAIN, HTTP_DOMAIN_VALUE)
.with(ADMIN_CREDENTIALS)
.contentType(APPLICATION_XML_VALUE)
......
......@@ -19,18 +19,19 @@ import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao;
import eu.europa.ec.edelivery.smp.test.SmpTestWebAppConfig;
import eu.europa.ec.edelivery.smp.test.testutils.MockMvcUtils;
import eu.europa.ec.edelivery.smp.test.testutils.X509CertificateTestUtils;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
......@@ -50,15 +51,16 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
/**
* Created by gutowpa on 20/02/2017.
*/
@RunWith(Parameterized.class)
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = {SmpTestWebAppConfig.class})
@DirtiesContext
@Sql(scripts = {
"classpath:/cleanup-database.sql",
"classpath:/webapp_integration_test_data.sql"},
executionPhase = BEFORE_TEST_METHOD)
public class SecurityConfigurationClientCertTest {
public static final Logger LOG = LoggerFactory.getLogger(SecurityConfigurationClientCertTest.class);
//Jul++9+23:59:00+2019+GMT"
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMM dd HH:mm:ss yyyy 'GMT'");
......@@ -133,18 +135,9 @@ public class SecurityConfigurationClientCertTest {
"C=DE, O=T-Systems International GmbH, OU=T-Systems Trust Center, ST=Nordrhein Westfalen, postalCode=57250, L=Netphen, street=Untere Industriestr. 20, CN=Internal Business CA 2",
"f71ee8b11cb3b787",
},
});
}
// because we are using Parameterized instead of SpringJUnit4ClassRunner we need to declare
// SpringClassRule and SpringMethodRule manually
@ClassRule
public static final SpringClassRule scr = new SpringClassRule();
@Rule
public final SpringMethodRule smr = new SpringMethodRule();
@Autowired
private WebApplicationContext context;
......@@ -153,7 +146,7 @@ public class SecurityConfigurationClientCertTest {
MockMvc mvc;
@Before
@BeforeEach
public void setup() throws IOException {
configurationDao.setPropertyToDatabase(SMPPropertyEnum.EXTERNAL_TLS_AUTHENTICATION_CLIENT_CERT_HEADER_ENABLED, "true", "");
configurationDao.setPropertyToDatabase(SMPPropertyEnum.CLIENT_CERT_HEADER_ENABLED_DEPRECATED, "true", "");
......@@ -161,30 +154,23 @@ public class SecurityConfigurationClientCertTest {
X509CertificateTestUtils.reloadKeystores();
mvc = MockMvcUtils.initializeMockMvc(context);
}
@Parameterized.Parameter()
public String testName;
@Parameterized.Parameter(1)
public String expectedCertificateId;
@Parameterized.Parameter(2)
public String certificateDn;
@Parameterized.Parameter(3)
public String serialNumber;
@Test
public void validClientCertHeaderAuthorizedForPutTest() throws Exception {
System.out.println("Test: " + testName);
@ParameterizedTest
@MethodSource("data")
public void validClientCertHeaderAuthorizedForPutTest(
String testName,
String expectedCertificateId,
String certificateDn,
String serialNumber
) throws Exception {
LOG.info("Test: [{}]", testName);
String clientCert = buildClientCert(serialNumber, certificateDn);
System.out.println("Client-Cert: " + clientCert);
HttpHeaders headers = new HttpHeaders();
headers.add("Client-Cert", clientCert);
mvc.perform(MockMvcRequestBuilders.put(RETURN_LOGGED_USER_PATH)
.headers(headers).with(csrf()))
.headers(headers).with(csrf()))
.andExpect(status().isOk())
.andExpect(content().string(containsString(expectedCertificateId)))
.andReturn().getResponse().getContentAsString();
......
......@@ -21,6 +21,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.web.context.WebApplicationContext;
import java.io.IOException;
......@@ -31,7 +32,9 @@ import java.util.stream.Collectors;
import static eu.europa.ec.edelivery.smp.test.testutils.MockMvcUtils.getObjectFromResponse;
import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*;
import static java.lang.String.format;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
......@@ -47,6 +50,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
executionPhase = BEFORE_TEST_METHOD)
abstract public class AbstractControllerTest {
// the webapp_integration_test_data data
public static final String IDENTIFIER_SCHEME = "ehealth-participantid-qns";
public static final String DOCUMENT_SCHEME = "doctype";
public static final String PARTICIPANT_ID = "urn:poland:ncpb";
public static final String DOCUMENT_ID = "invoice";
public static final RequestPostProcessor ADMIN_CREDENTIALS = httpBasic("pat_smp_admin", "123456");
// Oasis SMP 1.0 URL paths
public static final String URL_PATH = format("/%s::%s", IDENTIFIER_SCHEME, PARTICIPANT_ID);
public static final String URL_DOC_PATH = format("%s/services/%s::%s", URL_PATH, DOCUMENT_SCHEME, DOCUMENT_ID);
protected ObjectMapper mapper = null;
protected MockMvc mvc;
@Autowired
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment