diff --git a/app/src/main/resources/swagger/api-docs.json b/app/src/main/resources/swagger/api-docs.json index b7dd6691d..4d7ad34c0 100644 --- a/app/src/main/resources/swagger/api-docs.json +++ b/app/src/main/resources/swagger/api-docs.json @@ -51,7 +51,7 @@ "description" : "UserGroups operations" } ], "paths" : { - "/delegations" : { + "/v1/delegations" : { "post" : { "tags" : [ "delegations" ], "summary" : "createDelegation", @@ -116,7 +116,7 @@ } ] } }, - "/institutions" : { + "/v1/institutions" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitutions", @@ -223,7 +223,7 @@ } ] } }, - "/institutions/products" : { + "/v1/institutions/products" : { "get" : { "tags" : [ "institutions" ], "summary" : "getProductsTree", @@ -289,7 +289,7 @@ } ] } }, - "/institutions/{institutionId}" : { + "/v1/institutions/{institutionId}" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitution", @@ -432,7 +432,7 @@ } ] } }, - "/institutions/{institutionId}/geographicTaxonomy" : { + "/v1/institutions/{institutionId}/geographicTaxonomy" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitutionGeographicTaxonomy", @@ -571,7 +571,7 @@ } ] } }, - "/institutions/{institutionId}/institutions" : { + "/v1/institutions/{institutionId}/institutions" : { "get" : { "tags" : [ "institutions" ], "summary" : "Retrieve list of delegation using to", @@ -656,7 +656,7 @@ } ] } }, - "/institutions/{institutionId}/logo" : { + "/v1/institutions/{institutionId}/logo" : { "put" : { "tags" : [ "institutions" ], "summary" : "saveInstitutionLogo", @@ -741,7 +741,7 @@ } ] } }, - "/institutions/{institutionId}/partners" : { + "/v1/institutions/{institutionId}/partners" : { "get" : { "tags" : [ "institutions" ], "summary" : "Retrieve institution's delegations", @@ -826,7 +826,7 @@ } ] } }, - "/institutions/{institutionId}/products" : { + "/v1/institutions/{institutionId}/products" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitutionProducts", @@ -903,7 +903,7 @@ } ] } }, - "/institutions/{institutionId}/products/{productId}/users" : { + "/v1/institutions/{institutionId}/products/{productId}/users" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitutionProductUsers", @@ -1087,7 +1087,7 @@ } ] } }, - "/institutions/{institutionId}/products/{productId}/users/{userId}" : { + "/v1/institutions/{institutionId}/products/{productId}/users/{userId}" : { "put" : { "tags" : [ "institutions" ], "summary" : "addUserProductRoles", @@ -1170,7 +1170,7 @@ } ] } }, - "/institutions/{institutionId}/users" : { + "/v1/institutions/{institutionId}/users" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitutionUsers", @@ -1276,7 +1276,7 @@ } ] } }, - "/institutions/{institutionId}/users/{userId}" : { + "/v1/institutions/{institutionId}/users/{userId}" : { "get" : { "tags" : [ "institutions" ], "summary" : "getInstitutionUser", @@ -1358,7 +1358,7 @@ } ] } }, - "/onboarding-requests/approve/{tokenId}" : { + "/v1/onboarding-requests/approve/{tokenId}" : { "post" : { "tags" : [ "onboarding" ], "summary" : "approveOnboardingRequest", @@ -1415,7 +1415,7 @@ } ] } }, - "/onboarding-requests/reject/{tokenId}" : { + "/v1/onboarding-requests/reject/{tokenId}" : { "delete" : { "tags" : [ "onboarding" ], "summary" : "rejectOnboardingRequest", @@ -1472,7 +1472,7 @@ } ] } }, - "/onboarding-requests/{tokenId}" : { + "/v1/onboarding-requests/{tokenId}" : { "get" : { "tags" : [ "onboarding" ], "summary" : "retrieveOnboardingRequest", @@ -1546,7 +1546,7 @@ } ] } }, - "/pnPGInstitutions/{institutionId}/products" : { + "/v1/pnPGInstitutions/{institutionId}/products" : { "get" : { "tags" : [ "pnPGInstitutions" ], "summary" : "getPnPGInstitutionProducts", @@ -1622,7 +1622,7 @@ } ] } }, - "/products/{productId}/back-office" : { + "/v1/products/{productId}/back-office" : { "get" : { "tags" : [ "products" ], "summary" : "retrieveProductBackoffice", @@ -1714,7 +1714,7 @@ } ] } }, - "/products/{productId}/brokers/{institutionType}" : { + "/v1/products/{productId}/brokers/{institutionType}" : { "get" : { "tags" : [ "products" ], "summary" : "getProductBrokers", @@ -1800,7 +1800,7 @@ } ] } }, - "/products/{productId}/roles" : { + "/v1/products/{productId}/roles" : { "get" : { "tags" : [ "products" ], "summary" : "getProductRoles", @@ -1876,7 +1876,7 @@ } ] } }, - "/relationships/{relationshipId}" : { + "/v1/relationships/{relationshipId}" : { "delete" : { "tags" : [ "relationships" ], "summary" : "deleteRelationshipById", @@ -1932,7 +1932,7 @@ } ] } }, - "/relationships/{relationshipId}/activate" : { + "/v1/relationships/{relationshipId}/activate" : { "post" : { "tags" : [ "relationships" ], "summary" : "activateRelationship", @@ -1988,7 +1988,7 @@ } ] } }, - "/relationships/{relationshipId}/suspend" : { + "/v1/relationships/{relationshipId}/suspend" : { "post" : { "tags" : [ "relationships" ], "summary" : "suspendRelationship", @@ -2044,7 +2044,7 @@ } ] } }, - "/support" : { + "/v1/support" : { "post" : { "tags" : [ "external-v2", "support" ], "summary" : "sendSupportRequest", @@ -2147,7 +2147,7 @@ } ] } }, - "/token/exchange" : { + "/v1/token/exchange" : { "get" : { "tags" : [ "token" ], "summary" : "exchange", @@ -2238,7 +2238,7 @@ } ] } }, - "/token/exchange/fatturazione" : { + "/v1/token/exchange/fatturazione" : { "get" : { "tags" : [ "token" ], "summary" : "billingToken", @@ -2321,7 +2321,7 @@ } ] } }, - "/users" : { + "/v1/users" : { "post" : { "tags" : [ "user" ], "summary" : "saveUser", @@ -2393,7 +2393,7 @@ } ] } }, - "/users/search" : { + "/v1/users/search" : { "post" : { "tags" : [ "user" ], "summary" : "search", @@ -2475,7 +2475,7 @@ } ] } }, - "/users/{id}" : { + "/v1/users/{id}" : { "get" : { "tags" : [ "user" ], "summary" : "getUserByInternalId", @@ -2686,7 +2686,7 @@ } ] } }, - "/user-groups" : { + "/v1/user-groups" : { "get" : { "tags" : [ "user-groups" ], "summary" : "getUserGroups", @@ -2813,7 +2813,7 @@ } ] } }, - "/user-groups/" : { + "/v1/user-groups/" : { "post" : { "tags" : [ "user-groups" ], "summary" : "createUserGroup", @@ -2878,7 +2878,7 @@ } ] } }, - "/user-groups/{id}" : { + "/v1/user-groups/{id}" : { "get" : { "tags" : [ "user-groups" ], "summary" : "getUserGroupById", @@ -3080,7 +3080,7 @@ } ] } }, - "/user-groups/{id}/activate" : { + "/v1/user-groups/{id}/activate" : { "post" : { "tags" : [ "user-groups" ], "summary" : "activateUserGroup", @@ -3136,7 +3136,7 @@ } ] } }, - "/user-groups/{id}/members/{userId}" : { + "/v1/user-groups/{id}/members/{userId}" : { "post" : { "tags" : [ "user-groups" ], "summary" : "addMemberToUserGroup", @@ -3202,7 +3202,7 @@ } ] } }, - "/user-groups/{id}/suspend" : { + "/v1/user-groups/{id}/suspend" : { "post" : { "tags" : [ "user-groups" ], "summary" : "suspendUserGroup", @@ -3258,7 +3258,7 @@ } ] } }, - "/user-groups/{userGroupId}/members/{userId}" : { + "/v1/user-groups/{userGroupId}/members/{userId}" : { "delete" : { "tags" : [ "user-groups" ], "summary" : "deleteMemberFromUserGroup", diff --git a/web/src/main/java/it/pagopa/selfcare/dashboard/web/config/MethodSecurityConfig.java b/web/src/main/java/it/pagopa/selfcare/dashboard/web/config/MethodSecurityConfig.java index b1599a8ba..29bb42986 100644 --- a/web/src/main/java/it/pagopa/selfcare/dashboard/web/config/MethodSecurityConfig.java +++ b/web/src/main/java/it/pagopa/selfcare/dashboard/web/config/MethodSecurityConfig.java @@ -1,5 +1,6 @@ package it.pagopa.selfcare.dashboard.web.config; +import it.pagopa.selfcare.dashboard.connector.api.MsCoreConnector; import it.pagopa.selfcare.dashboard.web.security.SelfCarePermissionEvaluator; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; @@ -11,10 +12,16 @@ @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { + private final MsCoreConnector msCoreConnector; + + public MethodSecurityConfig(MsCoreConnector msCoreConnector) { + this.msCoreConnector = msCoreConnector; + } + @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - expressionHandler.setPermissionEvaluator(new SelfCarePermissionEvaluator()); + expressionHandler.setPermissionEvaluator(new SelfCarePermissionEvaluator(msCoreConnector)); return expressionHandler; } diff --git a/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/RelationshipController.java b/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/RelationshipController.java index dcbcedcf3..9d7170fa9 100644 --- a/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/RelationshipController.java +++ b/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/RelationshipController.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @Slf4j @@ -28,6 +29,7 @@ public RelationshipController(RelationshipService relationshipService) { @PostMapping(value = "/{relationshipId}/suspend") @ResponseStatus(HttpStatus.NO_CONTENT) @ApiOperation(value = "", notes = "${swagger.dashboard.institutions.api.suspendUser}") + @PreAuthorize("hasPermission(#relationshipId, 'relationshipId', 'ADMIN')") public void suspendRelationship(@ApiParam("${swagger.dashboard.user.model.relationshipId}") @PathVariable("relationshipId") String relationshipId) { @@ -43,6 +45,7 @@ public void suspendRelationship(@ApiParam("${swagger.dashboard.user.model.relati @PostMapping(value = "/{relationshipId}/activate") @ResponseStatus(HttpStatus.NO_CONTENT) @ApiOperation(value = "", notes = "${swagger.dashboard.institutions.api.activateUser}") + @PreAuthorize("hasPermission(#relationshipId, 'relationshipId', 'ADMIN')") public void activateRelationship(@ApiParam("${swagger.dashboard.user.model.relationshipId}") @PathVariable("relationshipId") String relationshipId) { @@ -57,6 +60,7 @@ public void activateRelationship(@ApiParam("${swagger.dashboard.user.model.relat @DeleteMapping(value = "/{relationshipId}") @ResponseStatus(HttpStatus.NO_CONTENT) @ApiOperation(value = "", notes = "${swagger.dashboard.institutions.api.deleteUser}") + @PreAuthorize("hasPermission(#relationshipId, 'relationshipId', 'ADMIN')") public void deleteRelationshipById(@ApiParam("${swagger.dashboard.user.model.relationshipId}") @PathVariable("relationshipId") String relationshipId) { diff --git a/web/src/main/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluator.java b/web/src/main/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluator.java index a15fb6b0d..e7d893a61 100644 --- a/web/src/main/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluator.java +++ b/web/src/main/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluator.java @@ -2,11 +2,14 @@ import it.pagopa.selfcare.commons.base.logging.LogUtils; import it.pagopa.selfcare.commons.base.security.SelfCareGrantedAuthority; +import it.pagopa.selfcare.dashboard.connector.api.MsCoreConnector; +import it.pagopa.selfcare.dashboard.connector.model.user.UserInfo; import it.pagopa.selfcare.dashboard.web.model.InstitutionResource; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import java.io.Serializable; @@ -14,6 +17,12 @@ public class SelfCarePermissionEvaluator implements PermissionEvaluator { public static final String ANY_PERMISSION = "ANY"; + public static final String RELATIONSHIP_ID = "relationshipId"; + private final MsCoreConnector msCoreConnector; + + public SelfCarePermissionEvaluator(MsCoreConnector msCoreConnector) { + this.msCoreConnector = msCoreConnector; + } @Override @@ -64,6 +73,21 @@ public boolean hasPermission(Authentication authentication, Serializable targetI .anyMatch(grantedAuthority -> ANY_PERMISSION.equals(permission) || permission.equals(grantedAuthority.getAuthority())); } + if (targetId != null && RELATIONSHIP_ID.equals(targetType)) { + UserInfo userInfo = msCoreConnector.getUser(targetId.toString()); + if(!CollectionUtils.isEmpty(userInfo.getProducts()) && userInfo.getProducts().size() == 1) { + String productId = userInfo.getProducts().keySet().stream().toList().get(0); + result = authentication.getAuthorities() + .stream() + .filter(grantedAuthority -> SelfCareGrantedAuthority.class.isAssignableFrom(grantedAuthority.getClass())) + .map(SelfCareGrantedAuthority.class::cast) + .filter(grantedAuthority -> grantedAuthority.getInstitutionId().equals(userInfo.getInstitutionId())) + .filter(grantedAuthority -> grantedAuthority.getRoleOnProducts().containsKey(productId)) + .map(grantedAuthority -> grantedAuthority.getRoleOnProducts().get(productId)) + .anyMatch(grantedAuthority -> ANY_PERMISSION.equals(permission) || permission.equals(grantedAuthority.getAuthority())); + } + } + log.debug("hasPermission result = {}", result); log.trace("hasPermission end"); return result; diff --git a/web/src/test/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluatorTest.java b/web/src/test/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluatorTest.java index 82d52dad3..d9abdede6 100644 --- a/web/src/test/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluatorTest.java +++ b/web/src/test/java/it/pagopa/selfcare/dashboard/web/security/SelfCarePermissionEvaluatorTest.java @@ -2,26 +2,43 @@ import it.pagopa.selfcare.commons.base.security.ProductGrantedAuthority; import it.pagopa.selfcare.commons.base.security.SelfCareGrantedAuthority; +import it.pagopa.selfcare.dashboard.connector.api.MsCoreConnector; +import it.pagopa.selfcare.dashboard.connector.model.user.ProductInfo; +import it.pagopa.selfcare.dashboard.connector.model.user.UserInfo; import it.pagopa.selfcare.dashboard.web.model.InstitutionResource; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.Executable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; import java.io.Serializable; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static it.pagopa.selfcare.commons.base.security.PartyRole.MANAGER; import static it.pagopa.selfcare.commons.base.security.PartyRole.OPERATOR; import static it.pagopa.selfcare.commons.base.security.SelfCareAuthority.ADMIN; import static it.pagopa.selfcare.commons.base.security.SelfCareAuthority.LIMITED; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; - +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {SelfCarePermissionEvaluator.class}) class SelfCarePermissionEvaluatorTest { - private final SelfCarePermissionEvaluator permissionEvaluator = new SelfCarePermissionEvaluator(); + @MockBean + MsCoreConnector msCoreConnector; + + @Autowired + SelfCarePermissionEvaluator permissionEvaluator; @Test @@ -312,4 +329,48 @@ void hasPermission_withTargetId_targetTypeInstitutionResource__anyPermittedAsLim assertTrue(hasPermission); } + @Test + void hasPermission_withTargetId_targetTypeRelationshipId_notPermitted_invalidRole() { + // given + Serializable targetId = "relationshipId"; + String targetType = "relationshipId"; + Object permission = LIMITED.toString(); + UserInfo userInfo = getUserInfo(); + when(msCoreConnector.getUser(anyString())).thenReturn(userInfo); + List roleOnProducts = List.of(new ProductGrantedAuthority(MANAGER, "productRole", "productId")); + List authorities = List.of(new SelfCareGrantedAuthority(targetId.toString(), roleOnProducts)); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("username", "password", authorities); + // when + boolean hasPermission = permissionEvaluator.hasPermission(authentication, targetId, targetType, permission); + // then + assertFalse(hasPermission); + } + + + @Test + void hasPermission_withTargetId_targetTypeRelationshipId_permitted() { + // given + Serializable targetId = "relationshipId"; + String targetType = "relationshipId"; + Object permission = ADMIN.toString(); + UserInfo userInfo = getUserInfo(); + when(msCoreConnector.getUser(anyString())).thenReturn(userInfo); + List roleOnProducts = List.of(new ProductGrantedAuthority(MANAGER, "productRole", "productId")); + List authorities = List.of(new SelfCareGrantedAuthority("institutionId", roleOnProducts)); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("username", "password", authorities); + // when + boolean hasPermission = permissionEvaluator.hasPermission(authentication, targetId, targetType, permission); + // then + assertTrue(hasPermission); + } + + private static UserInfo getUserInfo() { + UserInfo userInfo = new UserInfo(); + userInfo.setInstitutionId("institutionId"); + Map map = new HashMap<>(); + map.put("productId", new ProductInfo()); + userInfo.setProducts(map); + return userInfo; + } + } \ No newline at end of file