From cc459a344a0777b9ab45c96555eeb91ce8cb8e6c Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Tue, 23 Jul 2024 15:24:55 +0200 Subject: [PATCH 01/19] feat : add getRbac in RBACConfiguration --- .../rmes/config/auth/RBACConfiguration.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java index 1d692217a..4997993d7 100644 --- a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java @@ -49,4 +49,20 @@ private static ModuleAccessPrivileges.Privilege toPrivilege(Map.Entry>> getRbac() { + return allModulesAccessPrivileges.stream() + .collect(Collectors.toMap( + privilege -> privilege.roleName().role(), + privilege -> privilege.privileges().stream() + .collect(Collectors.toMap( + ModuleAccessPrivileges::application, + moduleAccess -> moduleAccess.privileges().stream() + .collect(Collectors.toMap( + ModuleAccessPrivileges.Privilege::privilege, + ModuleAccessPrivileges.Privilege::strategy + )) + )) + )); + } + } From 0df5404b4fe596f7646a5924c906f3b6701ddad6 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Tue, 23 Jul 2024 15:25:20 +0200 Subject: [PATCH 02/19] feat : add AccessPrivileges class --- .../services/rbac/AccessPrivileges.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java new file mode 100644 index 000000000..2b8af89c0 --- /dev/null +++ b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java @@ -0,0 +1,50 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.model.rbac.RBAC; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class AccessPrivileges { + private final Map> privileges; + private RBAC.Privilege action; + private RBAC.Module resource; + + public AccessPrivileges(Map> privileges) { + this.privileges = privileges; + } + + public AccessPrivileges isGranted(RBAC.Privilege action) { + this.action = action; + return this; + } + + public AccessPrivileges on(RBAC.Module resource) { + this.resource = resource; + return this; + } + + public boolean withId(String id) { + return checkPrivileges(id); + } + + private boolean checkPrivileges(String id) { + Map resourcePrivileges = privileges.get(resource); + if (resourcePrivileges == null) { + return false; + } + + RBAC.Strategy strategy = resourcePrivileges.get(action); + if (strategy == null) { + return false; + } + + return strategy == RBAC.Strategy.ALL || (strategy == RBAC.Strategy.STAMP && checkStamp(id)); + } + + private boolean checkStamp(String id) { + // Implémentez la logique pour vérifier le stamp + return true; // Exemple simplifié + } +} From e27882aeee23645f7b17de527f60e53d307f3bf1 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Tue, 23 Jul 2024 15:26:12 +0200 Subject: [PATCH 03/19] feat : add canUpdateSerie and getAccessPrivileges --- .../SecurityExpressionRootForBauhaus.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 5c15d49ff..c571960bb 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -3,10 +3,15 @@ import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; import fr.insee.rmes.config.auth.roles.Roles; import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; +import fr.insee.rmes.external.services.rbac.AccessPrivileges; +import fr.insee.rmes.external.services.rbac.RBACService; +import fr.insee.rmes.model.rbac.RBAC; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; import org.springframework.security.access.expression.SecurityExpressionOperations; import org.springframework.security.access.expression.SecurityExpressionRoot; @@ -20,6 +25,12 @@ public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressionOperations, SecurityExpressionOperations { + @Autowired + RBACService rbacService; + + @Autowired + UserDecoder userDecoder; + private static final Logger logger = LoggerFactory.getLogger(SecurityExpressionRootForBauhaus.class); private final MethodSecurityExpressionOperations methodSecurityExpressionOperations; @@ -229,4 +240,12 @@ private Optional getStamp() { return this.stampFromPrincipal.findStamp(methodSecurityExpressionRoot.getPrincipal()); } -} + public boolean canUpdateSerie(String serieId) throws RmesException{ + return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); + } + + private AccessPrivileges getAccessPrivileges() throws RmesException { + return rbacService.computeRbac(userDecoder.fromPrincipal(methodSecurityExpressionRoot.getPrincipal()).get().roles()); + } + +} \ No newline at end of file From 64d47db692cf3f330d1e2e477f8d86ca280eed05 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Tue, 23 Jul 2024 15:27:03 +0200 Subject: [PATCH 04/19] fix : change return type of getUserInformation --- src/main/java/fr/insee/rmes/webservice/UserResources.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/insee/rmes/webservice/UserResources.java b/src/main/java/fr/insee/rmes/webservice/UserResources.java index fd0909964..1958a7c91 100644 --- a/src/main/java/fr/insee/rmes/webservice/UserResources.java +++ b/src/main/java/fr/insee/rmes/webservice/UserResources.java @@ -5,6 +5,7 @@ import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.authentication.stamps.StampsService; +import fr.insee.rmes.external.services.rbac.AccessPrivileges; import fr.insee.rmes.external.services.rbac.RBACService; import fr.insee.rmes.model.rbac.RBAC; import io.swagger.v3.oas.annotations.Operation; @@ -76,7 +77,7 @@ public UserResources(StampsService stampsService, RBACService rbacService, UserD @ApiResponse(content = @Content(mediaType = "application/json")) } ) - public Map> getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { + public AccessPrivileges getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { User user = this.userDecoder.fromPrincipal(principal).get(); return rbacService.computeRbac(user.roles()); } From bcc38c94a76e669f94ead3dfa436bef0a9d98234 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Tue, 23 Jul 2024 15:27:43 +0200 Subject: [PATCH 05/19] feat : change computeRbac --- .../external/services/rbac/RBACService.java | 5 ++++- .../services/rbac/RBACServiceImpl.java | 21 +++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java index fbc86b8db..73733a9ac 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java @@ -7,5 +7,8 @@ public interface RBACService { - Map> computeRbac(List roles); +// Map> computeRbac(List roles); + AccessPrivileges computeRbac(List roles); + + } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java index 2ad038fd8..1f03950c6 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java @@ -4,9 +4,7 @@ import fr.insee.rmes.model.rbac.RBAC; import org.springframework.stereotype.Service; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Service public class RBACServiceImpl implements RBACService { @@ -18,24 +16,23 @@ public RBACServiceImpl(RBACConfiguration configuration) { } @Override - public Map> computeRbac(List roles) { - /*if(roles.isEmpty()){ - return Map.of(); + public AccessPrivileges computeRbac(List roles) { + if (roles.isEmpty()) { + return new AccessPrivileges(Collections.emptyMap()); } - Map>> rbac = configuration.getRbac(); + Map>> rbac = configuration.getRbac(); - Map> results = new HashMap<>(); + Map> results = new HashMap<>(); for (String role : roles) { - Map> rolePrivileges = rbac.get(role); + Map> rolePrivileges = rbac.get(role); if (rolePrivileges != null) { mergePrivileges(results, rolePrivileges); } } - return results;*/ - return Map.of(); + return new AccessPrivileges(results); } private void mergePrivileges(Map> target, @@ -60,4 +57,6 @@ private void mergePrivileges(Map }); } } + + } From cd86452c8321e0e947719926f9bf9f8874212805 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Wed, 24 Jul 2024 09:30:23 +0200 Subject: [PATCH 06/19] feat : add PreAuthorize to DatasetResources --- .../classifications/ClassificationUtils.java | 2 +- .../datasets/DatasetServiceImpl.java | 4 ++-- .../distribution/DistributionQueries.java | 2 ++ .../SecurityExpressionRootForBauhaus.java | 16 ++++++++++++++++ .../restrictions/StampsRestrictionsService.java | 1 + .../external/services/rbac/AccessPrivileges.java | 3 +-- src/main/java/fr/insee/rmes/model/rbac/RBAC.java | 3 ++- .../webservice/dataset/DatasetResources.java | 10 +++++----- 8 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/classifications/ClassificationUtils.java b/src/main/java/fr/insee/rmes/bauhaus_services/classifications/ClassificationUtils.java index 2964ddff8..f7c188f19 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/classifications/ClassificationUtils.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/classifications/ClassificationUtils.java @@ -21,6 +21,7 @@ import org.eclipse.rdf4j.model.vocabulary.SKOS; import org.json.JSONArray; import org.json.JSONObject; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Repository; import java.util.ArrayList; @@ -32,7 +33,6 @@ public void updateClassification(Classification classification, String uri) thro Model model = new LinkedHashModel(); this.validate(classification); - Resource graph = RdfUtils.codesListGraph(classification.getId()); IRI classificationIri = RdfUtils.createIRI(uri); diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java index 8bc030a01..34a61e223 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java @@ -4,7 +4,6 @@ import fr.insee.rmes.bauhaus_services.operations.series.SeriesUtils; import fr.insee.rmes.bauhaus_services.rdf_utils.RdfService; import fr.insee.rmes.bauhaus_services.rdf_utils.RdfUtils; -import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.exceptions.ErrorCodes; import fr.insee.rmes.exceptions.RmesBadRequestException; import fr.insee.rmes.exceptions.RmesException; @@ -17,7 +16,6 @@ import fr.insee.rmes.persistance.ontologies.INSEE; import fr.insee.rmes.utils.DateUtils; import fr.insee.rmes.utils.Deserializer; -import fr.insee.rmes.utils.IdGenerator; import org.eclipse.rdf4j.model.*; import org.eclipse.rdf4j.model.impl.LinkedHashModel; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; @@ -206,6 +204,8 @@ private String update(String datasetId, Dataset dataset) throws RmesException { return this.persist(dataset); } + + @Override public String update(String datasetId, String body) throws RmesException { Dataset dataset = Deserializer.deserializeBody(body, Dataset.class); diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/distribution/DistributionQueries.java b/src/main/java/fr/insee/rmes/bauhaus_services/distribution/DistributionQueries.java index e7b469c72..c5730df70 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/distribution/DistributionQueries.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/distribution/DistributionQueries.java @@ -50,4 +50,6 @@ public static String getContributorsByDistributionUri(String uri) throws RmesExc params.put("DISTRIBUTION_GRAPH_URI", uri); return FreeMarkerUtils.buildRequest(ROOT_DIRECTORY, "getDistributionContributorsByUriQuery.ftlh", params); } + + } \ No newline at end of file diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index c571960bb..61c2bec80 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -244,6 +244,22 @@ public boolean canUpdateSerie(String serieId) throws RmesException{ return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); } + public boolean canDeleteDataset(String datasetId) throws RmesException{ + return getAccessPrivileges().isGranted(RBAC.Privilege.DELETE).on(RBAC.Module.DATASET).withId(datasetId); + } + + public boolean canUpdateDataset(String datasetId) throws RmesException{ + return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.DATASET).withId(datasetId); + } + + public boolean canCreateDataset(String datasetId) throws RmesException{ + return getAccessPrivileges().isGranted(RBAC.Privilege.CREATE).on(RBAC.Module.DATASET).withId(datasetId); + } + + public boolean canPublishDataset(String datasetId) throws RmesException{ + return getAccessPrivileges().isGranted(RBAC.Privilege.PUBLISH).on(RBAC.Module.DATASET).withId(datasetId); + } + private AccessPrivileges getAccessPrivileges() throws RmesException { return rbacService.computeRbac(userDecoder.fromPrincipal(methodSecurityExpressionRoot.getPrincipal()).get().roles()); } diff --git a/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java b/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java index d7d456f25..3159893ef 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java @@ -19,6 +19,7 @@ public interface StampsRestrictionsService { boolean isConceptsOrCollectionsOwner(List uris) throws RmesException; + boolean canCreateConcept() throws RmesException; boolean canModifyConcept(IRI uri) throws RmesException; diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java index 2b8af89c0..b5a1afc89 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java @@ -2,9 +2,8 @@ import fr.insee.rmes.model.rbac.RBAC; -import java.util.Collections; import java.util.Map; -import java.util.Set; + public class AccessPrivileges { private final Map> privileges; diff --git a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java index b9f91b7c5..8df0708d8 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java +++ b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java @@ -9,7 +9,8 @@ public enum Module { OPERATION, INDICATOR, SIMS, - CLASSIFICATION + CLASSIFICATION, + DATASET } public enum Privilege { diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index ac283f0eb..c69de7965 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -59,7 +59,7 @@ public String getDistributionsByDataset(@PathVariable(Constants.ID) String id) t return this.datasetService.getDistributions(id); } - @PreAuthorize("isAdmin() || isDatasetContributor()") + @PreAuthorize("canCreateDataset(#datasetId)") @PostMapping(value = "", consumes = APPLICATION_JSON_VALUE) @Operation(operationId = "createDataset", summary = "Create a dataset") @ResponseStatus(HttpStatus.CREATED) @@ -68,7 +68,7 @@ public String setDataset( return this.datasetService.create(body); } - @PreAuthorize("isAdmin() || isDatasetContributorWithStamp(#datasetId)") + @PreAuthorize("canUpdateDataset(#datasetId)") @PutMapping(value = "/{id}", consumes = APPLICATION_JSON_VALUE) @Operation(operationId = "updateDataset", summary = "Update a dataset") public String setDataset( @@ -78,7 +78,7 @@ public String setDataset( return this.datasetService.update(datasetId, body); } - @PreAuthorize("isAdmin() || isDatasetContributorWithStamp(#datasetId)") + @PreAuthorize("canPublishDataset(#datasetId)") @PutMapping("/{id}/validate") @Operation(operationId = "publishDataset", summary = "Publish a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Distribution.class))))}) @@ -92,7 +92,7 @@ public String getArchivageUnits() throws RmesException { return this.datasetService.getArchivageUnits(); } - @PreAuthorize("isAdmin() || isDatasetContributorWithStamp(#datasetId)") + @PreAuthorize("canUpdateDataset(#datasetId)") @PatchMapping(value = "/{id}", consumes = APPLICATION_JSON_VALUE) @Operation(operationId = "patchDataset", summary = "Update a dataset") public void patchDataset( @@ -102,7 +102,7 @@ public void patchDataset( this.datasetService.patchDataset(datasetId, dataset); } - @PreAuthorize("isAdmin() || isDatasetContributorWithStamp(#datasetId)") + @PreAuthorize("canDeleteDataset(#datasetId)") @DeleteMapping("/{id}") @Operation( operationId = "deleteDataset", From 1e1a74ac2c6ae1eff21fb773fd41887be090c5cd Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Thu, 1 Aug 2024 10:31:44 +0200 Subject: [PATCH 07/19] feat : add correct getAccessPrivileges() --- .../SecurityExpressionRootForBauhaus.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 61c2bec80..645afaf81 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -7,6 +7,7 @@ import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; import fr.insee.rmes.external.services.rbac.AccessPrivileges; import fr.insee.rmes.external.services.rbac.RBACService; +import fr.insee.rmes.external.services.rbac.RBACServiceImpl; import fr.insee.rmes.model.rbac.RBAC; import org.json.JSONObject; import org.slf4j.Logger; @@ -17,19 +18,19 @@ import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.annotation.AuthenticationPrincipal; -import java.util.Optional; +import java.util.*; import java.util.function.BiPredicate; import static java.util.Objects.requireNonNull; public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressionOperations, SecurityExpressionOperations { - @Autowired - RBACService rbacService; - @Autowired - UserDecoder userDecoder; + private final RBACService rbacService; + private static final Logger logger = LoggerFactory.getLogger(SecurityExpressionRootForBauhaus.class); @@ -38,15 +39,15 @@ public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressio private final StampFromPrincipal stampFromPrincipal; private final SecurityExpressionRoot methodSecurityExpressionRoot; - private SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal) { - this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; - this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; + public SecurityExpressionRootForBauhaus(RBACService rbacService, StampAuthorizationChecker stampAuthorizationChecker, SecurityExpressionRoot methodSecurityExpressionRoot, StampFromPrincipal stampFromPrincipal, MethodSecurityExpressionOperations methodSecurityExpressionOperations) { this.stampAuthorizationChecker = stampAuthorizationChecker; + this.methodSecurityExpressionRoot = methodSecurityExpressionRoot; this.stampFromPrincipal = stampFromPrincipal; + this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; } public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal) { - return new SecurityExpressionRootForBauhaus(requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal)); + return new SecurityExpressionRootForBauhaus( requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal)); } @Override @@ -240,6 +241,7 @@ private Optional getStamp() { return this.stampFromPrincipal.findStamp(methodSecurityExpressionRoot.getPrincipal()); } + public boolean canUpdateSerie(String serieId) throws RmesException{ return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); } @@ -261,7 +263,13 @@ public boolean canPublishDataset(String datasetId) throws RmesException{ } private AccessPrivileges getAccessPrivileges() throws RmesException { - return rbacService.computeRbac(userDecoder.fromPrincipal(methodSecurityExpressionRoot.getPrincipal()).get().roles()); + Optional stamp = this.getStamp(); + Collection collectionRole = this.getAuthentication().getAuthorities(); + List listerole = new ArrayList<>(collectionRole); + List strings = listerole.stream() + .map(object -> Objects.toString(object, null)) + .toList(); + return rbacService.computeRbac(strings); } } \ No newline at end of file From 391ce0d9d5bcf94f1df2816f5ef7619fcdd1ace8 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Fri, 2 Aug 2024 10:17:25 +0200 Subject: [PATCH 08/19] rollback --- .../SecurityExpressionRootForBauhaus.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 645afaf81..5bcc809d7 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -7,7 +7,6 @@ import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; import fr.insee.rmes.external.services.rbac.AccessPrivileges; import fr.insee.rmes.external.services.rbac.RBACService; -import fr.insee.rmes.external.services.rbac.RBACServiceImpl; import fr.insee.rmes.model.rbac.RBAC; import org.json.JSONObject; import org.slf4j.Logger; @@ -19,7 +18,6 @@ import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import java.util.*; import java.util.function.BiPredicate; @@ -28,25 +26,30 @@ public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressionOperations, SecurityExpressionOperations { - - private final RBACService rbacService; - - private static final Logger logger = LoggerFactory.getLogger(SecurityExpressionRootForBauhaus.class); private final MethodSecurityExpressionOperations methodSecurityExpressionOperations; private final StampAuthorizationChecker stampAuthorizationChecker; private final StampFromPrincipal stampFromPrincipal; private final SecurityExpressionRoot methodSecurityExpressionRoot; + private final RBACService rbacService; public SecurityExpressionRootForBauhaus(RBACService rbacService, StampAuthorizationChecker stampAuthorizationChecker, SecurityExpressionRoot methodSecurityExpressionRoot, StampFromPrincipal stampFromPrincipal, MethodSecurityExpressionOperations methodSecurityExpressionOperations) { this.stampAuthorizationChecker = stampAuthorizationChecker; - this.methodSecurityExpressionRoot = methodSecurityExpressionRoot; + this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; this.stampFromPrincipal = stampFromPrincipal; this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; + this.rbacService = rbacService; + } + + public SecurityExpressionRootForBauhaus( MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker,StampFromPrincipal stampFromPrincipal) { + this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; + this.stampFromPrincipal = stampFromPrincipal; + this.stampAuthorizationChecker = stampAuthorizationChecker; + this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; } - public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal) { + public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { return new SecurityExpressionRootForBauhaus( requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal)); } From 154dca173d11602bea67936c1b74b6f675ed0f42 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Fri, 2 Aug 2024 10:48:32 +0200 Subject: [PATCH 09/19] feat : add canReadDataset & RbacService in constructor & Dataset role --- .../BauhausMethodSecurityExpressionHandler.java | 7 +++++-- .../SecurityExpressionRootForBauhaus.java | 17 +++++++---------- .../external/services/rbac/RBACService.java | 6 ++---- .../webservice/dataset/DatasetResources.java | 7 +++++-- src/main/resources/rbac.yml | 9 +++++++++ 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java b/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java index a037a90f9..33cf22b96 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java @@ -1,6 +1,7 @@ package fr.insee.rmes.config.auth.security; import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.external.services.rbac.RBACService; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +25,11 @@ public class BauhausMethodSecurityExpressionHandler extends DefaultMethodSecurit private final StampAuthorizationChecker stampAuthorizationChecker; private final StampFromPrincipal stampFromPrincipal; + private final RBACService rbacService; @Autowired - public BauhausMethodSecurityExpressionHandler(StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal) { + public BauhausMethodSecurityExpressionHandler(StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { + this.rbacService = rbacService; logger.trace("Initializing GlobalMethodSecurityConfiguration with BauhausMethodSecurityExpressionHandler and DefaultRolePrefix = {}", DEFAULT_ROLE_PREFIX); this.stampAuthorizationChecker = requireNonNull(stampAuthorizationChecker); this.stampFromPrincipal = requireNonNull(stampFromPrincipal); @@ -37,7 +40,7 @@ public BauhausMethodSecurityExpressionHandler(StampAuthorizationChecker stampAut public EvaluationContext createEvaluationContext(Supplier authentication, MethodInvocation mi) { StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi); MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue(); - context.setRootObject(SecurityExpressionRootForBauhaus.enrich(delegate, this.stampAuthorizationChecker, this.stampFromPrincipal)); + context.setRootObject(SecurityExpressionRootForBauhaus.enrich(delegate, this.stampAuthorizationChecker, this.stampFromPrincipal, this.rbacService)); return context; } } \ No newline at end of file diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 5bcc809d7..ed9ef1638 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -11,7 +11,6 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; import org.springframework.security.access.expression.SecurityExpressionOperations; import org.springframework.security.access.expression.SecurityExpressionRoot; @@ -34,23 +33,17 @@ public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressio private final SecurityExpressionRoot methodSecurityExpressionRoot; private final RBACService rbacService; - public SecurityExpressionRootForBauhaus(RBACService rbacService, StampAuthorizationChecker stampAuthorizationChecker, SecurityExpressionRoot methodSecurityExpressionRoot, StampFromPrincipal stampFromPrincipal, MethodSecurityExpressionOperations methodSecurityExpressionOperations) { - this.stampAuthorizationChecker = stampAuthorizationChecker; - this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; - this.stampFromPrincipal = stampFromPrincipal; - this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; - this.rbacService = rbacService; - } - public SecurityExpressionRootForBauhaus( MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker,StampFromPrincipal stampFromPrincipal) { + public SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; this.stampFromPrincipal = stampFromPrincipal; this.stampAuthorizationChecker = stampAuthorizationChecker; this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; + this.rbacService = rbacService; } public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { - return new SecurityExpressionRootForBauhaus( requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal)); + return new SecurityExpressionRootForBauhaus( requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal),requireNonNull(rbacService)); } @Override @@ -265,6 +258,10 @@ public boolean canPublishDataset(String datasetId) throws RmesException{ return getAccessPrivileges().isGranted(RBAC.Privilege.PUBLISH).on(RBAC.Module.DATASET).withId(datasetId); } + public boolean canReadDataset(String datasetId) throws RmesException{ + return getAccessPrivileges().isGranted(RBAC.Privilege.READ).on(RBAC.Module.DATASET).withId(datasetId); + } + private AccessPrivileges getAccessPrivileges() throws RmesException { Optional stamp = this.getStamp(); Collection collectionRole = this.getAuthentication().getAuthorities(); diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java index 73733a9ac..80c8d130d 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java @@ -1,14 +1,12 @@ package fr.insee.rmes.external.services.rbac; -import fr.insee.rmes.model.rbac.RBAC; + +import org.springframework.stereotype.Service; import java.util.List; -import java.util.Map; public interface RBACService { -// Map> computeRbac(List roles); AccessPrivileges computeRbac(List roles); - } diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index c69de7965..b3f7f9337 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -19,6 +19,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -38,6 +39,7 @@ public DatasetResources(DatasetService datasetService) { this.datasetService = datasetService; } +// @PreAuthorize("canReadDataset(#datasetId)") @GetMapping(produces = "application/json") @Operation(operationId = "getDatasets", summary = "List of datasets", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -45,6 +47,7 @@ public String getDatasets() throws RmesException { return this.datasetService.getDatasets(); } +// @PreAuthorize("canReadDataset(#datasetId)") @GetMapping(value = "/{id}", produces = "application/json") @Operation(operationId = "getDataset", summary = "Get a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -52,6 +55,7 @@ public String getDataset(@PathVariable(Constants.ID) String id) throws RmesExcep return this.datasetService.getDatasetByID(id); } +// @PreAuthorize("canReadDataset(#datasetId)") @GetMapping("/{id}/distributions") @Operation(operationId = "getDistributionsByDataset", summary = "List of distributions for a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -73,8 +77,7 @@ public String setDataset( @Operation(operationId = "updateDataset", summary = "Update a dataset") public String setDataset( @PathVariable("id") String datasetId, - @Parameter(description = "Dataset", required = true) @RequestBody String body) throws RmesException { - + @Parameter(description = "Dataset", required = true) @RequestBody String body ) throws RmesException { return this.datasetService.update(datasetId, body); } diff --git a/src/main/resources/rbac.yml b/src/main/resources/rbac.yml index 1483f51e5..3f78c780f 100644 --- a/src/main/resources/rbac.yml +++ b/src/main/resources/rbac.yml @@ -57,6 +57,13 @@ rbac: delete: ALL publish: ALL validate: ALL + dataset: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL Utilisateur_RMESGNCS: concept: read: ALL @@ -74,6 +81,8 @@ rbac: read: ALL classification: read: ALL + dataset: + read: ALL Gestionnaire_concept_RMESGNCS: concept: create: STAMP From 180f09565bd30a96867e880cd53e44fbb0298e5f Mon Sep 17 00:00:00 2001 From: Fabrice B Date: Thu, 8 Aug 2024 16:11:45 +0200 Subject: [PATCH 10/19] refactor (craft Rbac) WIP --- .../rmes/config/auth/RBACConfiguration.java | 8 ++- .../SecurityExpressionRootForBauhaus.java | 4 +- .../services/rbac/AccessPrivileges.java | 49 -------------- .../rbac/ApplicationAccessPrivileges.java | 66 +++++++++++++++++++ .../external/services/rbac/RBACService.java | 4 +- .../services/rbac/RBACServiceImpl.java | 12 +++- .../model/rbac/AllModuleAccessPrivileges.java | 9 --- .../model/rbac/ModuleAccessPrivileges.java | 27 +++++++- .../java/fr/insee/rmes/model/rbac/RBAC.java | 7 +- .../insee/rmes/webservice/UserResources.java | 7 +- 10 files changed, 116 insertions(+), 77 deletions(-) delete mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java create mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java delete mode 100644 src/main/java/fr/insee/rmes/model/rbac/AllModuleAccessPrivileges.java diff --git a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java index 4997993d7..1e139df71 100644 --- a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java @@ -1,6 +1,6 @@ package fr.insee.rmes.config.auth; -import fr.insee.rmes.model.rbac.AllModuleAccessPrivileges; +import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; import fr.insee.rmes.model.rbac.ModuleAccessPrivileges; import fr.insee.rmes.model.rbac.RBAC; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -12,7 +12,7 @@ @ConfigurationProperties("rbac") -public record RBACConfiguration (Set allModulesAccessPrivileges){ +public record RBACConfiguration (Map applicationAccessPrivilegesByRoles){ @ConstructorBinding public RBACConfiguration(Map>> config){ @@ -65,4 +65,8 @@ public Map>> getRbac )); } + public ApplicationAccessPrivileges accessPrivilegesForRole(RoleName roleName) { + } + + public record RoleName(String role) {} } diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index ed9ef1638..55afaf35c 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -5,7 +5,7 @@ import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; -import fr.insee.rmes.external.services.rbac.AccessPrivileges; +import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; import fr.insee.rmes.external.services.rbac.RBACService; import fr.insee.rmes.model.rbac.RBAC; import org.json.JSONObject; @@ -262,7 +262,7 @@ public boolean canReadDataset(String datasetId) throws RmesException{ return getAccessPrivileges().isGranted(RBAC.Privilege.READ).on(RBAC.Module.DATASET).withId(datasetId); } - private AccessPrivileges getAccessPrivileges() throws RmesException { + private ApplicationAccessPrivileges getAccessPrivileges() throws RmesException { Optional stamp = this.getStamp(); Collection collectionRole = this.getAuthentication().getAuthorities(); List listerole = new ArrayList<>(collectionRole); diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java deleted file mode 100644 index b5a1afc89..000000000 --- a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivileges.java +++ /dev/null @@ -1,49 +0,0 @@ -package fr.insee.rmes.external.services.rbac; - -import fr.insee.rmes.model.rbac.RBAC; - -import java.util.Map; - - -public class AccessPrivileges { - private final Map> privileges; - private RBAC.Privilege action; - private RBAC.Module resource; - - public AccessPrivileges(Map> privileges) { - this.privileges = privileges; - } - - public AccessPrivileges isGranted(RBAC.Privilege action) { - this.action = action; - return this; - } - - public AccessPrivileges on(RBAC.Module resource) { - this.resource = resource; - return this; - } - - public boolean withId(String id) { - return checkPrivileges(id); - } - - private boolean checkPrivileges(String id) { - Map resourcePrivileges = privileges.get(resource); - if (resourcePrivileges == null) { - return false; - } - - RBAC.Strategy strategy = resourcePrivileges.get(action); - if (strategy == null) { - return false; - } - - return strategy == RBAC.Strategy.ALL || (strategy == RBAC.Strategy.STAMP && checkStamp(id)); - } - - private boolean checkStamp(String id) { - // Implémentez la logique pour vérifier le stamp - return true; // Exemple simplifié - } -} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java b/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java new file mode 100644 index 000000000..7bb69c346 --- /dev/null +++ b/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java @@ -0,0 +1,66 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.model.rbac.ModuleAccessPrivileges; +import fr.insee.rmes.model.rbac.RBAC; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * An instance of AccessPrivileges gathers strategysByPrivileges for all modules of the application. + * + */ +public record ApplicationAccessPrivileges(EnumMap privilegesByModules) { + public static final ApplicationAccessPrivileges NO_PRIVILEGE = new ApplicationAccessPrivileges(new EnumMap<>(RBAC.Module.class)); + private RBAC.Privilege action; + private RBAC.Module resource; + + public ApplicationAccessPrivileges(Map> privileges) { + this.privileges = privileges; + } + + public ApplicationAccessPrivileges isGranted(RBAC.Privilege action) { + this.action = action; + return this; + } + + public ApplicationAccessPrivileges on(RBAC.Module resource) { + this.resource = resource; + return this; + } + + public boolean withId(String id) { + return checkPrivileges(id); + } + + private boolean checkPrivileges(String id) { + Map resourcePrivileges = privileges.get(resource); + if (resourcePrivileges == null) { + return false; + } + + RBAC.Strategy strategy = resourcePrivileges.get(action); + if (strategy == null) { + return false; + } + + return strategy == RBAC.Strategy.ALL || (strategy == RBAC.Strategy.STAMP && checkStamp(id)); + } + + private boolean checkStamp(String id) { + // Implémentez la logique pour vérifier le stamp + return true; // Exemple simplifié + } + + public ApplicationAccessPrivileges merge(ApplicationAccessPrivileges other) { + Objects.requireNonNull(other); + var mergedMap = new EnumMap(RBAC.Module.class); + for (RBAC.Module module : RBAC.Module.values()) { + var moduleMergedPrivileges = ModuleAccessPrivileges.merge(privilegesByModules.get(module), other.privilegesByModules.get(module)); + moduleMergedPrivileges.ifPresent(privilege->mergedMap.put(module,privilege)); + } + return new ApplicationAccessPrivileges(mergedMap); + } +} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java index 80c8d130d..40c8be08c 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java @@ -1,12 +1,10 @@ package fr.insee.rmes.external.services.rbac; -import org.springframework.stereotype.Service; - import java.util.List; public interface RBACService { - AccessPrivileges computeRbac(List roles); + ApplicationAccessPrivileges computeRbac(List roles); } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java index 1f03950c6..492937469 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java @@ -16,9 +16,15 @@ public RBACServiceImpl(RBACConfiguration configuration) { } @Override - public AccessPrivileges computeRbac(List roles) { + public ApplicationAccessPrivileges computeRbac(List roles) { + + return roles.stream() + .map(configuration::accessPrivilegesForRole) + .reduce(ApplicationAccessPrivileges::merge) + .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); + if (roles.isEmpty()) { - return new AccessPrivileges(Collections.emptyMap()); + return new ApplicationAccessPrivileges(Collections.emptyMap()); } Map>> rbac = configuration.getRbac(); @@ -32,7 +38,7 @@ public AccessPrivileges computeRbac(List roles) { } } - return new AccessPrivileges(results); + return new ApplicationAccessPrivileges(results); } private void mergePrivileges(Map> target, diff --git a/src/main/java/fr/insee/rmes/model/rbac/AllModuleAccessPrivileges.java b/src/main/java/fr/insee/rmes/model/rbac/AllModuleAccessPrivileges.java deleted file mode 100644 index 1b4e49587..000000000 --- a/src/main/java/fr/insee/rmes/model/rbac/AllModuleAccessPrivileges.java +++ /dev/null @@ -1,9 +0,0 @@ -package fr.insee.rmes.model.rbac; - -import java.util.Set; - -public record AllModuleAccessPrivileges(RoleName roleName, Set privileges) { - - - public record RoleName(String role) {} -} diff --git a/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java b/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java index 50435ef36..6b4e284b6 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java @@ -1,10 +1,31 @@ package fr.insee.rmes.model.rbac; -import java.util.Set; +import java.util.EnumMap; +import java.util.Optional; -public record ModuleAccessPrivileges(RBAC.Module application, Set privileges) { +public record ModuleAccessPrivileges(EnumMap strategysByPrivileges) { - public record Privilege(RBAC.Privilege privilege, RBAC.Strategy strategy) { + public static Optional merge(ModuleAccessPrivileges moduleAccessPrivileges1, ModuleAccessPrivileges moduleAccessPrivileges2) { + if(moduleAccessPrivileges1!=null){ + return Optional.of(moduleAccessPrivileges1.merge(moduleAccessPrivileges2)); + } + if(moduleAccessPrivileges2!=null){ + return Optional.of(moduleAccessPrivileges2.merge(moduleAccessPrivileges1)); + } + return Optional.empty(); } + private ModuleAccessPrivileges merge(ModuleAccessPrivileges other) { + if (other == null){ + return this; + } + EnumMap mergedPrivileges = new EnumMap<>(RBAC.Privilege.class); + for (RBAC.Privilege privilege : RBAC.Privilege.values()) { + RBAC.Strategy.merge(strategysByPrivileges.get(privilege), other.strategysByPrivileges.get(privilege)) + .ifPresent(s->mergedPrivileges.put(privilege, s)); + } + return new ModuleAccessPrivileges(mergedPrivileges); + } + + } diff --git a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java index 8df0708d8..228229457 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java +++ b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java @@ -1,5 +1,7 @@ package fr.insee.rmes.model.rbac; +import java.util.Optional; + public class RBAC { public enum Module { CONCEPT, @@ -23,6 +25,9 @@ public enum Privilege { } public enum Strategy { - ALL, STAMP + ALL, STAMP; + + public static Optional merge(Strategy strategy, Strategy strategy1) { + } } } diff --git a/src/main/java/fr/insee/rmes/webservice/UserResources.java b/src/main/java/fr/insee/rmes/webservice/UserResources.java index 1958a7c91..16ac68b2f 100644 --- a/src/main/java/fr/insee/rmes/webservice/UserResources.java +++ b/src/main/java/fr/insee/rmes/webservice/UserResources.java @@ -5,9 +5,8 @@ import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.authentication.stamps.StampsService; -import fr.insee.rmes.external.services.rbac.AccessPrivileges; +import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; import fr.insee.rmes.external.services.rbac.RBACService; -import fr.insee.rmes.model.rbac.RBAC; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -25,8 +24,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; - /** * WebService class for resources of Concepts * @@ -77,7 +74,7 @@ public UserResources(StampsService stampsService, RBACService rbacService, UserD @ApiResponse(content = @Content(mediaType = "application/json")) } ) - public AccessPrivileges getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { + public ApplicationAccessPrivileges getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { User user = this.userDecoder.fromPrincipal(principal).get(); return rbacService.computeRbac(user.roles()); } From 165c84d55121690eb6183d037646c5ddac364ca4 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Fri, 9 Aug 2024 10:58:05 +0200 Subject: [PATCH 11/19] refactor(crat Rbac) --- .../StampAuthorizationChecker.java | 20 +++++- .../stamps/StampsRestrictionServiceImpl.java | 27 ++++---- .../insee/rmes/config/LogRequestFilter.java | 2 +- .../rmes/config/auth/RBACConfiguration.java | 65 +++--------------- .../SecurityExpressionRootForBauhaus.java | 66 ++++++++++--------- .../fr/insee/rmes/config/auth/user/User.java | 2 +- .../services/rbac/AccessPrivilegeChecker.java | 32 +++++++++ .../rbac/ApplicationAccessPrivileges.java | 55 +++++----------- .../services/rbac/CheckAccessPrivilege.java | 12 ++++ .../external/services/rbac/RBACService.java | 5 +- .../services/rbac/RBACServiceImpl.java | 66 ++++--------------- .../external/services/rbac/StampChecker.java | 7 ++ .../model/rbac/ModuleAccessPrivileges.java | 23 ++++--- .../java/fr/insee/rmes/model/rbac/RBAC.java | 20 +++++- .../insee/rmes/webservice/UserResources.java | 9 ++- .../distribution/DistributionResources.java | 3 +- .../StampAuthorizationCheckerTest.java | 23 +++++++ .../OpenIDConnectSecurityContextTest.java | 4 +- .../stubs/StampAuthorizationCheckerStub.java | 18 +++++ .../UserResourcesEnvHorsProdTest.java | 4 +- .../webservice/UserResourcesTestEnvProd.java | 2 +- 21 files changed, 246 insertions(+), 219 deletions(-) create mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java create mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java create mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java create mode 100644 src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java create mode 100644 src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java b/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java index 699c3cd65..ef549075a 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java @@ -10,6 +10,8 @@ import fr.insee.rmes.config.auth.user.AuthorizeMethodDecider; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.RmesException; +import fr.insee.rmes.external.services.rbac.StampChecker; +import fr.insee.rmes.model.rbac.RBAC; import fr.insee.rmes.persistance.ontologies.QB; import fr.insee.rmes.persistance.sparql_queries.code_list.CodeListQueries; import fr.insee.rmes.persistance.sparql_queries.structures.StructureQueries; @@ -24,7 +26,7 @@ import static java.util.Objects.requireNonNull; @Component -public class StampAuthorizationChecker extends StampsRestrictionServiceImpl { +public class StampAuthorizationChecker extends StampsRestrictionServiceImpl implements StampChecker { private static final Logger logger = LoggerFactory.getLogger(StampAuthorizationChecker.class); public static final String CHECKING_AUTHORIZATION_ERROR_MESSAGE = "Error while checking authorization for user with stamp {} to modify or delete {}"; public static final String ERROR_AUTHORIZATION = "Error while checking authorization for user with stamp {} to modify {}"; @@ -145,5 +147,21 @@ private IRI findComponentIRI(String componentId) throws RmesException { } } + @Override + public boolean userStampIsAuthorizedForResource(RBAC.Module module, String id) { + return switch (module){ + case CONCEPT -> false; + case COLLECTION -> false; + case FAMILY -> false; + case SERIE -> false; + case OPERATION -> false; + case INDICATOR -> false; + case SIMS -> false; + case CLASSIFICATION -> false; + case DATASET -> isDatasetManagerWithStamp(id, getStamp()); + case null -> false; + }; + } + } diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java index 5461deb56..b0b82e793 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java @@ -5,6 +5,7 @@ import fr.insee.rmes.config.auth.UserProvider; import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; import fr.insee.rmes.config.auth.user.AuthorizeMethodDecider; +import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.persistance.sparql_queries.concepts.ConceptsQueries; @@ -65,7 +66,7 @@ public boolean isConceptsOrCollectionsOwner(List uris) throws RmesException } private boolean isConceptOwner(List uris) throws RmesException { - return isOwnerForModule(getStamp(), uris, ConceptsQueries::getOwner, Constants.OWNER); + return isOwnerForModule(getStampAsString(), uris, ConceptsQueries::getOwner, Constants.OWNER); } public boolean isSeriesManagerWithStamp(IRI iri, String stamp) throws RmesException { @@ -73,14 +74,14 @@ public boolean isSeriesManagerWithStamp(IRI iri, String stamp) throws RmesExcept } protected boolean isSeriesManager(IRI iri) throws RmesException { - return isSeriesManagerWithStamp(iri, getStamp()); + return isSeriesManagerWithStamp(iri, getStampAsString()); } private boolean isIndicatorCreator(IRI iri) throws RmesException { - return isOwnerForModule(getStamp(), List.of(iri), IndicatorsQueries::getCreatorsByIndicatorUri, Constants.CREATORS); + return isOwnerForModule(getStampAsString(), List.of(iri), IndicatorsQueries::getCreatorsByIndicatorUri, Constants.CREATORS); } private boolean isConceptManager(IRI uri) throws RmesException { - return isManagerForModule(getStamp(), uri, ConceptsQueries::getManager, Constants.MANAGER); + return isManagerForModule(getStampAsString(), uri, ConceptsQueries::getManager, Constants.MANAGER); } protected boolean isManagerForModule(String stamp, IRI uri, QueryGenerator queryGenerator, String stampKey) throws RmesException { logger.trace("Check management access for {} with stamp {}",uri, stampKey); @@ -103,10 +104,14 @@ private boolean checkResponsabilityForModule(String stamp, List uris, Query ); } - protected String getStamp() { - return getUser().getStamp(); + protected String getStampAsString() { + return getUser().getStampAsString(); } + protected Stamp getStamp(){ + return getUser().stamp(); + } + private Object findStamp(Object o, String stampKey) { if (o instanceof JSONObject jsonObject) { return jsonObject.get(stampKey); @@ -134,7 +139,7 @@ public boolean canValidateIndicator(IRI uri) throws RmesException { @Override public boolean canModifySims(IRI seriesOrIndicatorUri) throws RmesException { - return (authorizeMethodDecider.isAdmin() || authorizeMethodDecider.isCnis() || (isSeriesManagerWithStamp(seriesOrIndicatorUri, getStamp()) && authorizeMethodDecider.isSeriesContributor()) + return (authorizeMethodDecider.isAdmin() || authorizeMethodDecider.isCnis() || (isSeriesManagerWithStamp(seriesOrIndicatorUri, getStampAsString()) && authorizeMethodDecider.isSeriesContributor()) || (isIndicatorCreator(seriesOrIndicatorUri) && authorizeMethodDecider.isIndicatorContributor())); } @@ -164,12 +169,12 @@ public boolean canCreateIndicator() { @Override public boolean canCreateOperation(IRI seriesURI) throws RmesException { - return (authorizeMethodDecider.isAdmin() || (isSeriesManagerWithStamp(seriesURI, getStamp()) && authorizeMethodDecider.isSeriesContributor())); + return (authorizeMethodDecider.isAdmin() || (isSeriesManagerWithStamp(seriesURI, getStampAsString()) && authorizeMethodDecider.isSeriesContributor())); } @Override public boolean canCreateSims(IRI seriesOrIndicatorUri) throws RmesException { - return (authorizeMethodDecider.isAdmin() || (isSeriesManagerWithStamp(seriesOrIndicatorUri, getStamp()) && authorizeMethodDecider.isSeriesContributor()) + return (authorizeMethodDecider.isAdmin() || (isSeriesManagerWithStamp(seriesOrIndicatorUri, getStampAsString()) && authorizeMethodDecider.isSeriesContributor()) || (isIndicatorCreator(seriesOrIndicatorUri) && authorizeMethodDecider.isIndicatorContributor())); } @@ -186,7 +191,7 @@ public boolean canModifyConcept(IRI uri) throws RmesException { @Override public boolean canModifySeries(IRI uri) throws RmesException { - return ((isSeriesManagerWithStamp(uri, getStamp()) && authorizeMethodDecider.isSeriesContributor()) || authorizeMethodDecider.isAdmin() || authorizeMethodDecider.isCnis() ); + return ((isSeriesManagerWithStamp(uri, getStampAsString()) && authorizeMethodDecider.isSeriesContributor()) || authorizeMethodDecider.isAdmin() || authorizeMethodDecider.isCnis() ); } @Override @@ -196,7 +201,7 @@ public boolean canModifyOperation(IRI seriesURI) throws RmesException { @Override public boolean canValidateSeries(IRI uri) throws RmesException { - return (authorizeMethodDecider.isAdmin() || (isSeriesManagerWithStamp(uri, getStamp()) && authorizeMethodDecider.isSeriesContributor())); + return (authorizeMethodDecider.isAdmin() || (isSeriesManagerWithStamp(uri, getStampAsString()) && authorizeMethodDecider.isSeriesContributor())); } @Override diff --git a/src/main/java/fr/insee/rmes/config/LogRequestFilter.java b/src/main/java/fr/insee/rmes/config/LogRequestFilter.java index 8d362af32..64ccd7bd1 100644 --- a/src/main/java/fr/insee/rmes/config/LogRequestFilter.java +++ b/src/main/java/fr/insee/rmes/config/LogRequestFilter.java @@ -57,7 +57,7 @@ private String getIdUser() { logger.error("while authenticating user", e); currentUser = empty(); } - return currentUser.map(user -> user.id() + " " + user.getStamp()).orElse("No authentication needed"); + return currentUser.map(user -> user.id() + " " + user.getStampAsString()).orElse("No authentication needed"); } } diff --git a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java index 1e139df71..e40ad009d 100644 --- a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java @@ -1,71 +1,24 @@ package fr.insee.rmes.config.auth; import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; -import fr.insee.rmes.model.rbac.ModuleAccessPrivileges; import fr.insee.rmes.model.rbac.RBAC; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.ConstructorBinding; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; @ConfigurationProperties("rbac") -public record RBACConfiguration (Map applicationAccessPrivilegesByRoles){ - - @ConstructorBinding - public RBACConfiguration(Map>> config){ - this(toSetOfAllModulesAccessPrivileges(config)); - } - - private static Set toSetOfAllModulesAccessPrivileges(Map>> rbac) { - return rbac.entrySet().stream() - .map(RBACConfiguration::toAllModuleAccessPrivileges) - .collect(Collectors.toSet()); - } - - private static AllModuleAccessPrivileges toAllModuleAccessPrivileges(Map.Entry>> entry) { - return new AllModuleAccessPrivileges(new AllModuleAccessPrivileges.RoleName(entry.getKey()), toSetOfModuleAccessPrivileges(entry.getValue())); - } - - private static Set toSetOfModuleAccessPrivileges(Map> privilegesForOneRole) { - return privilegesForOneRole.entrySet().stream() - .map(RBACConfiguration::toModuleAccessPrivileges) - .collect(Collectors.toSet()); - } - - private static ModuleAccessPrivileges toModuleAccessPrivileges(Map.Entry> entry) { - return new ModuleAccessPrivileges(entry.getKey(), toSetOfPrivileges(entry.getValue())); - } - - private static Set toSetOfPrivileges(Map privilegesForOneModule) { - return privilegesForOneModule.entrySet().stream() - .map(RBACConfiguration::toPrivilege) - .collect(Collectors.toSet()); - } - - private static ModuleAccessPrivileges.Privilege toPrivilege(Map.Entry entry) { - return new ModuleAccessPrivileges.Privilege(entry.getKey(), entry.getValue()); - } - - public Map>> getRbac() { - return allModulesAccessPrivileges.stream() - .collect(Collectors.toMap( - privilege -> privilege.roleName().role(), - privilege -> privilege.privileges().stream() - .collect(Collectors.toMap( - ModuleAccessPrivileges::application, - moduleAccess -> moduleAccess.privileges().stream() - .collect(Collectors.toMap( - ModuleAccessPrivileges.Privilege::privilege, - ModuleAccessPrivileges.Privilege::strategy - )) - )) - )); - } - - public ApplicationAccessPrivileges accessPrivilegesForRole(RoleName roleName) { +public record RBACConfiguration (Map>> config){ + + public Map toMapOfApplicationAccessPrivilegesByRoles() { + return config.entrySet().stream() + .collect(Collectors.toMap(entry->new RoleName(entry.getKey()), + entry -> ApplicationAccessPrivileges.of(entry.getValue()), + ApplicationAccessPrivileges::merge + )); } public record RoleName(String role) {} diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 55afaf35c..2a60e3727 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -1,11 +1,12 @@ package fr.insee.rmes.config.auth.security; import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.config.auth.RBACConfiguration; import fr.insee.rmes.config.auth.roles.Roles; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; -import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; +import fr.insee.rmes.external.services.rbac.CheckAccessPrivilege; import fr.insee.rmes.external.services.rbac.RBACService; import fr.insee.rmes.model.rbac.RBAC; import org.json.JSONObject; @@ -43,7 +44,7 @@ public SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations metho } public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { - return new SecurityExpressionRootForBauhaus( requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal),requireNonNull(rbacService)); + return new SecurityExpressionRootForBauhaus(requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal), requireNonNull(rbacService)); } @Override @@ -103,12 +104,12 @@ public boolean isFullyAuthenticated() { @Override public boolean hasPermission(Object target, Object permission) { - return this.methodSecurityExpressionRoot.hasPermission(target,permission); + return this.methodSecurityExpressionRoot.hasPermission(target, permission); } @Override public boolean hasPermission(Object targetId, String targetType, Object permission) { - return this.methodSecurityExpressionRoot.hasPermission(targetId,targetType,permission); + return this.methodSecurityExpressionRoot.hasPermission(targetId, targetType, permission); } @Override @@ -146,12 +147,12 @@ public boolean isDatasetContributor() { return hasRole(Roles.DATASET_CONTRIBUTOR); } - public boolean isDatasetContributorWithStamp(String datasetId){ + public boolean isDatasetContributorWithStamp(String datasetId) { logger.trace("Check if {} is contributor for dataset {}", methodSecurityExpressionRoot.getPrincipal(), datasetId); return isDatasetContributor() && isManagerForDatasetId(datasetId); } - public boolean isDistributionContributorWithStamp(String distributionId){ + public boolean isDistributionContributorWithStamp(String distributionId) { logger.trace("Check if {} is contributor for distribution {}", methodSecurityExpressionRoot.getPrincipal(), distributionId); return isDatasetContributor() && isManagerForDistributionId(distributionId); } @@ -161,16 +162,16 @@ public boolean isContributorOfSerie(String seriesId) { return hasRole(Roles.SERIES_CONTRIBUTOR) && isManagerForSerieId(seriesId); } -// for PUT and DELETE CodesList - public boolean isContributorOfCodesList(String codesListId){ + // for PUT and DELETE CodesList + public boolean isContributorOfCodesList(String codesListId) { logger.trace("Check if {} is contributor for codes list {}", methodSecurityExpressionRoot.getPrincipal(), codesListId); return hasRole(Roles.CODESLIST_CONTRIBUTOR) && isManagerForCodesListId(codesListId); } -// for POST CodesList + // for POST CodesList public boolean isCodesListContributor(String body) { logger.trace("Check if {} can create the codes list", methodSecurityExpressionRoot.getPrincipal()); - return hasRole(Roles.CODESLIST_CONTRIBUTOR)&& checkStampIsContributor(body); + return hasRole(Roles.CODESLIST_CONTRIBUTOR) && checkStampIsContributor(body); } private boolean checkStampIsContributor(String body) { @@ -183,26 +184,26 @@ private boolean checkStampIsContributor(String body) { } //for PUT and DELETE structure - public boolean isStructureContributor(String structureId){ + public boolean isStructureContributor(String structureId) { logger.trace("Check if {} is contributor for structure {}", methodSecurityExpressionRoot.getPrincipal(), structureId); return hasRole(Roles.STRUCTURES_CONTRIBUTOR) && isManagerForStructureId(structureId); } -// for POST structure or component + // for POST structure or component public boolean isStructureAndComponentContributor(String body) { logger.trace("Check if {} can create the structure or component", methodSecurityExpressionRoot.getPrincipal()); - return hasRole(Roles.STRUCTURES_CONTRIBUTOR)&& checkStampIsContributor(body); + return hasRole(Roles.STRUCTURES_CONTRIBUTOR) && checkStampIsContributor(body); } //for PUT and DELETE component - public boolean isComponentContributor(String componentId){ + public boolean isComponentContributor(String componentId) { logger.trace("Check if {} is contributor for component {}", methodSecurityExpressionRoot.getPrincipal(), componentId); return hasRole(Roles.STRUCTURES_CONTRIBUTOR) && isManagerForComponentId(componentId); } - private boolean userHasStampWichManageResource(String resourceId, BiPredicate stampIsManager){ - if (resourceId==null){ + private boolean userHasStampWichManageResource(String resourceId, BiPredicate stampIsManager) { + if (resourceId == null) { throw new RmesRuntimeBadRequestException("id must be not null"); } var stamp = getStamp(); @@ -221,6 +222,7 @@ private boolean isManagerForCodesListId(String codesListId) { private boolean isManagerForDatasetId(String datasetId) { return userHasStampWichManageResource(datasetId, this.stampAuthorizationChecker::isDatasetManagerWithStamp); } + private boolean isManagerForDistributionId(String distributionId) { return userHasStampWichManageResource(distributionId, this.stampAuthorizationChecker::isDistributionManagerWithStamp); } @@ -232,44 +234,46 @@ private boolean isManagerForStructureId(String structureId) { private boolean isManagerForComponentId(String componentId) { return userHasStampWichManageResource(componentId, this.stampAuthorizationChecker::isComponentManagerWithStamp); } - + private Optional getStamp() { return this.stampFromPrincipal.findStamp(methodSecurityExpressionRoot.getPrincipal()); } - public boolean canUpdateSerie(String serieId) throws RmesException{ + public boolean canUpdateSerie(String serieId) throws RmesException { return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); } - public boolean canDeleteDataset(String datasetId) throws RmesException{ + public boolean canDeleteDataset(String datasetId) throws RmesException { return getAccessPrivileges().isGranted(RBAC.Privilege.DELETE).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canUpdateDataset(String datasetId) throws RmesException{ + public boolean canUpdateDataset(String datasetId) throws RmesException { return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canCreateDataset(String datasetId) throws RmesException{ + public boolean canCreateDataset(String datasetId) throws RmesException { return getAccessPrivileges().isGranted(RBAC.Privilege.CREATE).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canPublishDataset(String datasetId) throws RmesException{ + public boolean canPublishDataset(String datasetId) throws RmesException { return getAccessPrivileges().isGranted(RBAC.Privilege.PUBLISH).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canReadDataset(String datasetId) throws RmesException{ + public boolean canReadDataset(String datasetId) throws RmesException { return getAccessPrivileges().isGranted(RBAC.Privilege.READ).on(RBAC.Module.DATASET).withId(datasetId); } - private ApplicationAccessPrivileges getAccessPrivileges() throws RmesException { - Optional stamp = this.getStamp(); - Collection collectionRole = this.getAuthentication().getAuthorities(); - List listerole = new ArrayList<>(collectionRole); - List strings = listerole.stream() - .map(object -> Objects.toString(object, null)) - .toList(); - return rbacService.computeRbac(strings); + private CheckAccessPrivilege getAccessPrivileges() { + return rbacService.computeRbac(this.getAuthentication().getAuthorities().stream() + .filter(Objects::nonNull) + .map(SecurityExpressionRootForBauhaus::toRoleName) + .toList() + ); + } + + private static RBACConfiguration.RoleName toRoleName(GrantedAuthority grantedAuthority) { + return new RBACConfiguration.RoleName(grantedAuthority.getAuthority()); } } \ No newline at end of file diff --git a/src/main/java/fr/insee/rmes/config/auth/user/User.java b/src/main/java/fr/insee/rmes/config/auth/user/User.java index 14984da30..318126760 100644 --- a/src/main/java/fr/insee/rmes/config/auth/user/User.java +++ b/src/main/java/fr/insee/rmes/config/auth/user/User.java @@ -19,7 +19,7 @@ public boolean hasRole(String role) { return roles.contains(role); } - public String getStamp(){ + public String getStampAsString(){ return stamp.stamp(); } } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java new file mode 100644 index 000000000..d86f3a5d6 --- /dev/null +++ b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java @@ -0,0 +1,32 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.model.rbac.RBAC; + +public record AccessPrivilegeChecker(ApplicationAccessPrivileges applicationAccessPrivileges, StampChecker stampChecker, RBAC.Privilege privilege, RBAC.Module module) { + + public AccessPrivilegeChecker(ApplicationAccessPrivileges applicationAccessPrivileges, StampChecker stampChecker, RBAC.Privilege privilege) { + this(applicationAccessPrivileges, stampChecker, privilege, null); + } + + public AccessPrivilegeChecker on(RBAC.Module module) { + return withModule(module); + } + + private AccessPrivilegeChecker withModule(RBAC.Module module) { + return new AccessPrivilegeChecker(this.applicationAccessPrivileges, this.stampChecker, this.privilege, module); + } + + public boolean withId(String id) { + var strategy = applicationAccessPrivileges.privilegesForModule(module).strategyFor(privilege); + if (strategy.isEmpty()){ + return false; + } + return strategy.get().isAllStampAuthorized() || checkStampFor(id); + } + + private boolean checkStampFor(String id) { + return id != null && stampChecker.userStampIsAuthorizedForResource(module, id); + } + + +} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java b/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java index 7bb69c346..d9dcb6e9a 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java @@ -6,7 +6,7 @@ import java.util.EnumMap; import java.util.Map; import java.util.Objects; -import java.util.Set; +import java.util.stream.Collectors; /** * An instance of AccessPrivileges gathers strategysByPrivileges for all modules of the application. @@ -14,53 +14,28 @@ */ public record ApplicationAccessPrivileges(EnumMap privilegesByModules) { public static final ApplicationAccessPrivileges NO_PRIVILEGE = new ApplicationAccessPrivileges(new EnumMap<>(RBAC.Module.class)); - private RBAC.Privilege action; - private RBAC.Module resource; - public ApplicationAccessPrivileges(Map> privileges) { - this.privileges = privileges; - } - - public ApplicationAccessPrivileges isGranted(RBAC.Privilege action) { - this.action = action; - return this; - } - - public ApplicationAccessPrivileges on(RBAC.Module resource) { - this.resource = resource; - return this; - } - - public boolean withId(String id) { - return checkPrivileges(id); - } - - private boolean checkPrivileges(String id) { - Map resourcePrivileges = privileges.get(resource); - if (resourcePrivileges == null) { - return false; - } - - RBAC.Strategy strategy = resourcePrivileges.get(action); - if (strategy == null) { - return false; - } - - return strategy == RBAC.Strategy.ALL || (strategy == RBAC.Strategy.STAMP && checkStamp(id)); - } - - private boolean checkStamp(String id) { - // Implémentez la logique pour vérifier le stamp - return true; // Exemple simplifié + public static ApplicationAccessPrivileges of(Map> privilegesByModules) { + return new ApplicationAccessPrivileges(new EnumMap<>(privilegesByModules.entrySet().stream() + .collect( + Collectors.toMap(Map.Entry::getKey, + entry -> new ModuleAccessPrivileges(entry.getValue()), + ModuleAccessPrivileges::merge + ) + ))); } public ApplicationAccessPrivileges merge(ApplicationAccessPrivileges other) { Objects.requireNonNull(other); var mergedMap = new EnumMap(RBAC.Module.class); for (RBAC.Module module : RBAC.Module.values()) { - var moduleMergedPrivileges = ModuleAccessPrivileges.merge(privilegesByModules.get(module), other.privilegesByModules.get(module)); - moduleMergedPrivileges.ifPresent(privilege->mergedMap.put(module,privilege)); + mergedMap.put(module, ModuleAccessPrivileges.merge(privilegesByModules.get(module), other.privilegesByModules.get(module))); } return new ApplicationAccessPrivileges(mergedMap); } + + public ModuleAccessPrivileges privilegesForModule(RBAC.Module module) { + var privileges = privilegesByModules.get(module); + return privileges==null?ModuleAccessPrivileges.NO_PRIVILEGE:privileges; + } } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java new file mode 100644 index 000000000..2026c7d02 --- /dev/null +++ b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java @@ -0,0 +1,12 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.model.rbac.RBAC; + +import java.util.Map; + +public record CheckAccessPrivilege(ApplicationAccessPrivileges applicationAccessPrivileges, StampChecker stampChecker) { + + public AccessPrivilegeChecker isGranted(RBAC.Privilege privilege) { + return new AccessPrivilegeChecker(applicationAccessPrivileges, stampChecker, privilege); + } +} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java index 40c8be08c..e80ec4dd0 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java @@ -1,10 +1,11 @@ package fr.insee.rmes.external.services.rbac; +import fr.insee.rmes.config.auth.RBACConfiguration; + import java.util.List; public interface RBACService { - ApplicationAccessPrivileges computeRbac(List roles); - + CheckAccessPrivilege computeRbac(List roles); } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java index 492937469..a1b2ae655 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java @@ -1,68 +1,26 @@ package fr.insee.rmes.external.services.rbac; import fr.insee.rmes.config.auth.RBACConfiguration; -import fr.insee.rmes.model.rbac.RBAC; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.List; +import java.util.Map; @Service -public class RBACServiceImpl implements RBACService { +public record RBACServiceImpl(Map applicationAccessPrivilegesByRoles, StampChecker stampChecker) implements RBACService { - private final RBACConfiguration configuration; - - public RBACServiceImpl(RBACConfiguration configuration) { - this.configuration = configuration; + @Autowired + public RBACServiceImpl(RBACConfiguration configuration, StampChecker stampChecker) { + this(configuration.toMapOfApplicationAccessPrivilegesByRoles(), stampChecker); } @Override - public ApplicationAccessPrivileges computeRbac(List roles) { - - return roles.stream() - .map(configuration::accessPrivilegesForRole) + public CheckAccessPrivilege computeRbac(List roles) { + return new CheckAccessPrivilege(roles.stream() + .map(applicationAccessPrivilegesByRoles::get) .reduce(ApplicationAccessPrivileges::merge) - .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); - - if (roles.isEmpty()) { - return new ApplicationAccessPrivileges(Collections.emptyMap()); - } - - Map>> rbac = configuration.getRbac(); - - Map> results = new HashMap<>(); - - for (String role : roles) { - Map> rolePrivileges = rbac.get(role); - if (rolePrivileges != null) { - mergePrivileges(results, rolePrivileges); - } - } - - return new ApplicationAccessPrivileges(results); + .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE), + this.stampChecker); } - - private void mergePrivileges(Map> target, - Map> source) { - for (Map.Entry> entry : source.entrySet()) { - RBAC.Module app = entry.getKey(); - Map sourcePrivileges = entry.getValue(); - - target.merge(app, new HashMap<>(sourcePrivileges), (targetPrivileges, newPrivileges) -> { - for (Map.Entry privilegeEntry : newPrivileges.entrySet()) { - RBAC.Privilege privilege = privilegeEntry.getKey(); - RBAC.Strategy strategy = privilegeEntry.getValue(); - - targetPrivileges.merge(privilege, strategy, (existingStrategy, newStrategy) -> { - if (existingStrategy == RBAC.Strategy.ALL || newStrategy == RBAC.Strategy.ALL) { - return RBAC.Strategy.ALL; - } - return existingStrategy; - }); - } - return targetPrivileges; - }); - } - } - - } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java b/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java new file mode 100644 index 000000000..0b211c039 --- /dev/null +++ b/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java @@ -0,0 +1,7 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.model.rbac.RBAC; + +public interface StampChecker { + boolean userStampIsAuthorizedForResource(RBAC.Module module, String id); +} diff --git a/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java b/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java index 6b4e284b6..dd9ace7a3 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java @@ -1,18 +1,22 @@ package fr.insee.rmes.model.rbac; import java.util.EnumMap; +import java.util.Map; import java.util.Optional; public record ModuleAccessPrivileges(EnumMap strategysByPrivileges) { - public static Optional merge(ModuleAccessPrivileges moduleAccessPrivileges1, ModuleAccessPrivileges moduleAccessPrivileges2) { + public static final ModuleAccessPrivileges NO_PRIVILEGE = new ModuleAccessPrivileges(new EnumMap<>(RBAC.Privilege.class)); + + public ModuleAccessPrivileges(Map strategysByPrivileges){ + this(new EnumMap<>(strategysByPrivileges)); + } + + public static ModuleAccessPrivileges merge(ModuleAccessPrivileges moduleAccessPrivileges1, ModuleAccessPrivileges moduleAccessPrivileges2) { if(moduleAccessPrivileges1!=null){ - return Optional.of(moduleAccessPrivileges1.merge(moduleAccessPrivileges2)); + return moduleAccessPrivileges1.merge(moduleAccessPrivileges2); } - if(moduleAccessPrivileges2!=null){ - return Optional.of(moduleAccessPrivileges2.merge(moduleAccessPrivileges1)); - } - return Optional.empty(); + return moduleAccessPrivileges2; } private ModuleAccessPrivileges merge(ModuleAccessPrivileges other) { @@ -21,11 +25,12 @@ private ModuleAccessPrivileges merge(ModuleAccessPrivileges other) { } EnumMap mergedPrivileges = new EnumMap<>(RBAC.Privilege.class); for (RBAC.Privilege privilege : RBAC.Privilege.values()) { - RBAC.Strategy.merge(strategysByPrivileges.get(privilege), other.strategysByPrivileges.get(privilege)) - .ifPresent(s->mergedPrivileges.put(privilege, s)); + mergedPrivileges.put(privilege, RBAC.Strategy.merge(strategysByPrivileges.get(privilege), other.strategysByPrivileges.get(privilege))); } return new ModuleAccessPrivileges(mergedPrivileges); } - + public Optional strategyFor(RBAC.Privilege privilege) { + return Optional.ofNullable(strategysByPrivileges.get(privilege)); + } } diff --git a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java index 228229457..0317e5c02 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java +++ b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java @@ -1,7 +1,5 @@ package fr.insee.rmes.model.rbac; -import java.util.Optional; - public class RBAC { public enum Module { CONCEPT, @@ -27,7 +25,23 @@ public enum Privilege { public enum Strategy { ALL, STAMP; - public static Optional merge(Strategy strategy, Strategy strategy1) { + public static Strategy merge(Strategy strategy1, Strategy strategy2) { + if (strategy1 != null){ + return strategy1.merge(strategy2); + } + return strategy2; + } + + private Strategy merge(Strategy other) { + return switch (other){ + case ALL -> ALL; + case STAMP -> this; + case null -> this; + }; + } + + public boolean isAllStampAuthorized() { + return this == ALL; } } } diff --git a/src/main/java/fr/insee/rmes/webservice/UserResources.java b/src/main/java/fr/insee/rmes/webservice/UserResources.java index 16ac68b2f..7a36a84b1 100644 --- a/src/main/java/fr/insee/rmes/webservice/UserResources.java +++ b/src/main/java/fr/insee/rmes/webservice/UserResources.java @@ -1,8 +1,8 @@ package fr.insee.rmes.webservice; +import fr.insee.rmes.config.auth.RBACConfiguration; import fr.insee.rmes.config.auth.security.UserDecoder; import fr.insee.rmes.config.auth.user.Stamp; -import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.authentication.stamps.StampsService; import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; @@ -24,6 +24,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Objects; + /** * WebService class for resources of Concepts * @@ -75,8 +77,9 @@ public UserResources(StampsService stampsService, RBACService rbacService, UserD } ) public ApplicationAccessPrivileges getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { - User user = this.userDecoder.fromPrincipal(principal).get(); - return rbacService.computeRbac(user.roles()); + return this.userDecoder.fromPrincipal(principal) + .map(user-> rbacService.computeRbac(user.roles().stream().filter(Objects::nonNull).map(RBACConfiguration.RoleName::new).toList()).applicationAccessPrivileges()) + .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); } /** diff --git a/src/main/java/fr/insee/rmes/webservice/distribution/DistributionResources.java b/src/main/java/fr/insee/rmes/webservice/distribution/DistributionResources.java index e5f4d6599..c5a5ca977 100644 --- a/src/main/java/fr/insee/rmes/webservice/distribution/DistributionResources.java +++ b/src/main/java/fr/insee/rmes/webservice/distribution/DistributionResources.java @@ -20,7 +20,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -77,7 +76,7 @@ public String getDatasetsForDistributionCreation(@AuthenticationPrincipal Object if (user.hasRole(Roles.ADMIN)) { return this.datasetService.getDatasets(); } - return this.datasetService.getDatasetsForDistributionCreation(user.getStamp()); + return this.datasetService.getDatasetsForDistributionCreation(user.getStampAsString()); } @PreAuthorize("isAdmin() || isDatasetContributor()") diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java new file mode 100644 index 000000000..27418f9b7 --- /dev/null +++ b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java @@ -0,0 +1,23 @@ +package fr.insee.rmes.bauhaus_services; + +import fr.insee.rmes.model.rbac.RBAC; +import fr.insee.rmes.stubs.StampAuthorizationCheckerStub; +import org.junit.jupiter.api.Test; + +import static fr.insee.rmes.stubs.StampAuthorizationCheckerStub.DATASET_STUB_ID; +import static org.assertj.core.api.Assertions.assertThat; + +class StampAuthorizationCheckerTest { + + private final StampAuthorizationChecker stampAuthorizationChecker=new StampAuthorizationCheckerStub(); + + @Test + void userStampIsAuthorizedForResource() { + + //Given + RBAC.Module module = RBAC.Module.DATASET; + String id = DATASET_STUB_ID; + //When then + assertThat(this.stampAuthorizationChecker.userStampIsAuthorizedForResource(module, id)).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContextTest.java b/src/test/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContextTest.java index fdd9a9c74..3e7ebf169 100644 --- a/src/test/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContextTest.java +++ b/src/test/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContextTest.java @@ -193,7 +193,7 @@ void testJwt() throws RmesException { User user = oidcContext.buildUserFromToken(jwtDecoded); assertThat(user.id()).isEqualTo(idep); - assertThat(user.getStamp()).isEqualTo(timbre); + assertThat(user.getStampAsString()).isEqualTo(timbre); assertThat(user.roles()).isEqualTo(roles); } @@ -205,7 +205,7 @@ void testJwt_sansTimbre() throws RmesException { User user = oidcContext.buildUserFromToken(jwtDecoded); assertThat(user.id()).isEqualTo(idep); - assertThat(user.getStamp()).isEqualTo(TIMBRE_ANONYME); + assertThat(user.getStampAsString()).isEqualTo(TIMBRE_ANONYME); assertThat(user.roles()).isEqualTo(roles); assertThat(logOutputStream.toString().trim()).hasToString(String.format(LOG_INFO_DEFAULT_STAMP.replace("{}", "%s"), idep)); logOutputStream.reset(); diff --git a/src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java b/src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java new file mode 100644 index 000000000..057a505f8 --- /dev/null +++ b/src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java @@ -0,0 +1,18 @@ +package fr.insee.rmes.stubs; + +import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.config.auth.user.Stamp; + +public class StampAuthorizationCheckerStub extends StampAuthorizationChecker { + + public static final String DATASET_STUB_ID = "1"; + + public StampAuthorizationCheckerStub() { + super(null, null, null, null); + } + + @Override + public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { + return DATASET_STUB_ID.equals(datasetId); + } +} diff --git a/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java b/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java index b31a06c9b..f2b492cd1 100644 --- a/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java +++ b/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java @@ -57,7 +57,7 @@ class UserResourcesEnvHorsProdTest { private static final String FAKE_STAMP_ANSWER = "{\"stamp\": \"fakeStampForDvAndQf\"}"; @Test - void getStamp_authent() throws Exception { + void getStamp_AsString_authent() throws Exception { String idep = "xxxxux"; String timbre = "XX59-YYY"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -69,7 +69,7 @@ void getStamp_authent() throws Exception { } @Test - void getStamp_anonymous() throws Exception { + void getStamp_AsString_anonymous() throws Exception { mvc.perform(get("/users/stamp") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) diff --git a/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java b/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java index 772e82929..2a8fef754 100644 --- a/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java +++ b/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java @@ -53,7 +53,7 @@ class UserResourcesEnvProdTest { private JwtDecoder jwtDecoder; @Test - void getStamp() throws Exception { + void getStampAsString() throws Exception { String idep = "xxxxux"; String timbre = "XX59-YYY"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); From 86bfa4860dfbaf6e4fc4c5ae82b19a143e615255 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Sat, 10 Aug 2024 19:46:41 +0200 Subject: [PATCH 12/19] refactor(change conception of Rbac + fix tests) - Limit dependencies to userProvider to security layer - Reduce dependency over deprecated class StampRestrictionServiceImpl --- .../StampAuthorizationChecker.java | 135 ++---------------- .../StampsRestrictionsVerifier.java | 29 ++++ .../StampsRestrictionsVerifierImpl.java | 129 +++++++++++++++++ .../datasets/DatasetServiceImpl.java | 5 +- .../stamps/StampsRestrictionServiceImpl.java | 60 ++------ .../utils/ComponentPublication.java | 7 +- .../utils/StructureComponentUtils.java | 31 +++- ...auhausMethodSecurityExpressionHandler.java | 15 +- .../security/CommonSecurityConfiguration.java | 12 -- .../SecurityExpressionRootForBauhaus.java | 77 ++++++---- .../auth/security/StampFromPrincipal.java | 11 -- .../StampsRestrictionsService.java | 5 + .../services/rbac/AccessPrivilegeChecker.java | 32 ----- .../rbac/ApplicationAccessPrivileges.java | 2 +- .../services/rbac/CheckAccessPrivilege.java | 12 -- .../rbac/CheckAccessPrivilegeForUser.java | 59 ++++++++ .../external/services/rbac/RBACService.java | 2 +- .../services/rbac/RBACServiceImpl.java | 25 ++-- .../external/services/rbac/StampChecker.java | 21 ++- .../java/fr/insee/rmes/utils/IRIUtils.java | 26 ++++ .../insee/rmes/webservice/UserResources.java | 6 +- .../webservice/dataset/DatasetResources.java | 1 - src/main/resources/rbac.yml | 8 ++ .../StampAuthorizationCheckerTest.java | 19 ++- .../StampsRestrictionServiceImplTest.java | 16 ++- .../auth/security/RBACConfigurationTest.java | 4 +- .../services/rbac/RBACServiceImplTest.java | 15 +- .../ConfigurationForTestWithAuth.java | 27 ++++ .../TestCodeListsResourcesEnvProd.java | 13 +- .../TestDatasetsResourcesEnvProd.java | 36 ++--- .../TestDistributionsResourcesEnvProd.java | 10 +- .../TestSeriesResourcesEnvProd.java | 11 +- .../TestStructuresResourcesEnvProd.java | 10 +- .../stubs/StampAuthorizationCheckerStub.java | 18 --- .../stubs/StampRestritionVerifierStub.java | 64 +++++++++ 35 files changed, 560 insertions(+), 393 deletions(-) create mode 100644 src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java create mode 100644 src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java delete mode 100644 src/main/java/fr/insee/rmes/config/auth/security/StampFromPrincipal.java delete mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java delete mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java create mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java create mode 100644 src/main/java/fr/insee/rmes/utils/IRIUtils.java create mode 100644 src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java delete mode 100644 src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java create mode 100644 src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java b/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java index ef549075a..f66337044 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java @@ -1,154 +1,48 @@ package fr.insee.rmes.bauhaus_services; -import fr.insee.rmes.bauhaus_services.datasets.DatasetQueries; -import fr.insee.rmes.bauhaus_services.distribution.DistributionQueries; -import fr.insee.rmes.bauhaus_services.rdf_utils.ObjectType; -import fr.insee.rmes.bauhaus_services.rdf_utils.RdfUtils; -import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; -import fr.insee.rmes.bauhaus_services.stamps.StampsRestrictionServiceImpl; -import fr.insee.rmes.config.auth.UserProvider; -import fr.insee.rmes.config.auth.user.AuthorizeMethodDecider; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.config.auth.user.Stamp; -import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.rbac.StampChecker; import fr.insee.rmes.model.rbac.RBAC; -import fr.insee.rmes.persistance.ontologies.QB; -import fr.insee.rmes.persistance.sparql_queries.code_list.CodeListQueries; -import fr.insee.rmes.persistance.sparql_queries.structures.StructureQueries; -import org.eclipse.rdf4j.model.IRI; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import static java.util.Objects.requireNonNull; - @Component -public class StampAuthorizationChecker extends StampsRestrictionServiceImpl implements StampChecker { - private static final Logger logger = LoggerFactory.getLogger(StampAuthorizationChecker.class); - public static final String CHECKING_AUTHORIZATION_ERROR_MESSAGE = "Error while checking authorization for user with stamp {} to modify or delete {}"; - public static final String ERROR_AUTHORIZATION = "Error while checking authorization for user with stamp {} to modify {}"; - private final String baseInternalUri; +public class StampAuthorizationChecker implements StampChecker { + + private final StampsRestrictionsVerifier stampsRestrictionsVerifier; @Autowired - public StampAuthorizationChecker(RepositoryGestion repoGestion, AuthorizeMethodDecider authorizeMethodDecider, UserProvider userProvider, @Value("${fr.insee.rmes.bauhaus.sesame.gestion.baseInternalURI}") String baseInternalUri) { - super(repoGestion, authorizeMethodDecider, userProvider); - this.baseInternalUri = baseInternalUri; + public StampAuthorizationChecker(StampsRestrictionsVerifier stampsRestrictionsVerifier) { + this.stampsRestrictionsVerifier = stampsRestrictionsVerifier; } public boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp) { - try { - return isSeriesManagerWithStamp(findIRI(requireNonNull(seriesId)), requireNonNull(stamp).stamp()); - } catch (RmesException e) { - logger.error(ERROR_AUTHORIZATION, stamp, seriesId); - return false; - } + return stampsRestrictionsVerifier.isSeriesManagerWithStamp(seriesId, stamp); } public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { - try { - return isDatasetManagerWithStamp(findDatasetIRI(requireNonNull(datasetId)), requireNonNull(stamp).stamp()); - } catch (RmesException e) { - logger.error(ERROR_AUTHORIZATION, stamp, datasetId); - return false; - } - } - - private boolean isDatasetManagerWithStamp(IRI iri, String stamp) throws RmesException { - return isManagerForModule(stamp, iri, DatasetQueries::getContributorsByDatasetUri, Constants.CONTRIBUTORS); + return stampsRestrictionsVerifier.isDatasetManagerWithStamp(datasetId, stamp); } public boolean isDistributionManagerWithStamp(String datasetId, Stamp stamp) { - try { - return isDistributionManagerWithStamp(findDistributionIRI(requireNonNull(datasetId)), requireNonNull(stamp).stamp()); - } catch (RmesException e) { - logger.error(ERROR_AUTHORIZATION, stamp, datasetId); - return false; - } - } - - private boolean isDistributionManagerWithStamp(IRI iri, String stamp) throws RmesException { - return isManagerForModule(stamp, iri, DistributionQueries::getContributorsByDistributionUri, Constants.CONTRIBUTORS); + return stampsRestrictionsVerifier.isDistributionManagerWithStamp(datasetId, stamp); } public boolean isCodesListManagerWithStamp(String codesListId, Stamp stamp) { - try { - return isCodesListManagerWithStamp(findCodesListIRI(requireNonNull(codesListId)), requireNonNull(stamp).stamp()); - } catch (RmesException e) { - logger.error(CHECKING_AUTHORIZATION_ERROR_MESSAGE, stamp, codesListId); - return false; - } + return stampsRestrictionsVerifier.isCodesListManagerWithStamp(codesListId, stamp); } public boolean isStructureManagerWithStamp(String structureId, Stamp stamp) { - try { - return isStructureManagerWithStamp(findStructureIRI(requireNonNull(structureId)), requireNonNull(stamp).stamp()); - } catch (RmesException e) { - logger.error(CHECKING_AUTHORIZATION_ERROR_MESSAGE, stamp, structureId); - return false; - } - } - - - public boolean isCodesListManagerWithStamp(IRI iri, String stamp) throws RmesException { - return isManagerForModule(stamp, iri, CodeListQueries::getContributorsByCodesListUri, Constants.CONTRIBUTORS); - } - - public boolean isComponentManagerWithStamp(IRI iri, String stamp) throws RmesException { - return isManagerForModule(stamp, iri, StructureQueries::getContributorsByComponentUri, Constants.CONTRIBUTORS); - } - - public boolean isStructureManagerWithStamp(IRI iri, String stamp) throws RmesException { - return isManagerForModule(stamp, iri, StructureQueries::getContributorsByStructureUri, Constants.CONTRIBUTORS); + return stampsRestrictionsVerifier.isStructureManagerWithStamp(structureId, stamp); } public boolean isComponentManagerWithStamp(String componentId, Stamp stamp) { - try { - return isComponentManagerWithStamp(findComponentIRI(requireNonNull(componentId)), requireNonNull(stamp).stamp()); - } catch (RmesException e) { - logger.error(CHECKING_AUTHORIZATION_ERROR_MESSAGE, stamp, componentId); - return false; - } - } - - private IRI findIRI(String seriesId) { - return RdfUtils.objectIRI(ObjectType.SERIES, seriesId); - } - - private IRI findCodesListIRI(String codesListId) throws RmesException { - JSONObject codeList = repoGestion.getResponseAsObject(CodeListQueries.getCodeListIRIByNotation(codesListId, baseInternalUri)); - String uriString = codeList.getString("iri"); - return RdfUtils.createIRI(uriString); - } - - private IRI findStructureIRI(String structureId) { - return RdfUtils.objectIRI(ObjectType.STRUCTURE, structureId); - } - - private IRI findDatasetIRI(String datasetId) { - return RdfUtils.objectIRI(ObjectType.DATASET, datasetId); - } - - private IRI findDistributionIRI(String distributionId) { - return RdfUtils.objectIRI(ObjectType.DISTRIBUTION, distributionId); - } - - private IRI findComponentIRI(String componentId) throws RmesException { - JSONObject type = repoGestion.getResponseAsObject(StructureQueries.getComponentType(componentId)); - String componentType = type.getString("type"); - if (componentType.equals(RdfUtils.toString(QB.ATTRIBUTE_PROPERTY))) { - return RdfUtils.structureComponentAttributeIRI(componentId); - } else if (componentType.equals(RdfUtils.toString(QB.DIMENSION_PROPERTY))) { - return RdfUtils.structureComponentDimensionIRI(componentId); - } else { - return RdfUtils.structureComponentMeasureIRI(componentId); - } + return stampsRestrictionsVerifier.isComponentManagerWithStamp(componentId,stamp); } @Override - public boolean userStampIsAuthorizedForResource(RBAC.Module module, String id) { + public boolean userStampIsAuthorizedForResource(RBAC.Module module, String id, Stamp stamp) { return switch (module){ case CONCEPT -> false; case COLLECTION -> false; @@ -158,10 +52,11 @@ public boolean userStampIsAuthorizedForResource(RBAC.Module module, String id) { case INDICATOR -> false; case SIMS -> false; case CLASSIFICATION -> false; - case DATASET -> isDatasetManagerWithStamp(id, getStamp()); + case DATASET -> isDatasetManagerWithStamp(id, stamp); case null -> false; }; } + } diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java new file mode 100644 index 000000000..115bee396 --- /dev/null +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java @@ -0,0 +1,29 @@ +package fr.insee.rmes.bauhaus_services.accesscontrol; + +import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; +import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.exceptions.RmesException; +import org.eclipse.rdf4j.model.IRI; + +import java.util.List; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public interface StampsRestrictionsVerifier { + boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp); + + boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp); + + boolean isDistributionManagerWithStamp(String distributionId, Stamp stamp); + + boolean isCodesListManagerWithStamp(String codesListId, Stamp stamp); + + boolean isStructureManagerWithStamp(String structureId, Stamp stamp); + + boolean isComponentManagerWithStamp(String componentId, Stamp stamp); + + boolean isManagerForModule(String stamp, IRI uri, StampsRestrictionsService.QueryGenerator queryGenerator, String stampKey) throws RmesException; + + boolean checkResponsabilityForModule(String stamp, List uris, StampsRestrictionsService.QueryGenerator queryGenerator, String stampKey, BiPredicate, Predicate> predicateMatcher) throws RmesException; +} diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java new file mode 100644 index 000000000..a5fd08975 --- /dev/null +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java @@ -0,0 +1,129 @@ +package fr.insee.rmes.bauhaus_services.accesscontrol; + +import fr.insee.rmes.bauhaus_services.Constants; +import fr.insee.rmes.bauhaus_services.datasets.DatasetQueries; +import fr.insee.rmes.bauhaus_services.distribution.DistributionQueries; +import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; +import fr.insee.rmes.bauhaus_services.structures.utils.StructureComponentUtils; +import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; +import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.exceptions.RmesException; +import fr.insee.rmes.persistance.sparql_queries.code_list.CodeListQueries; +import fr.insee.rmes.persistance.sparql_queries.operations.series.OpSeriesQueries; +import fr.insee.rmes.persistance.sparql_queries.structures.StructureQueries; +import fr.insee.rmes.utils.IRIUtils; +import org.eclipse.rdf4j.model.IRI; +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static fr.insee.rmes.utils.StringUtils.urisAsString; +import static java.util.Objects.requireNonNull; + +@Component +public record StampsRestrictionsVerifierImpl(IRIUtils iriUtils, RepositoryGestion repoGestion, StructureComponentUtils structureComponentUtils) implements StampsRestrictionsVerifier { + + private static final Logger logger = LoggerFactory.getLogger(StampsRestrictionsVerifierImpl.class); + + @Override + public boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp) { + try { + return isManagerForModule(requireNonNull(stamp).stamp(), iriUtils.findIRI(requireNonNull(seriesId)), OpSeriesQueries::getCreatorsBySeriesUri, Constants.CREATORS); + } catch (RmesException e) { + logger.error(errorMessage(stamp, seriesId), e); + return false; + } + } + + @Override + public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { + try { + return this.isManagerForModule(requireNonNull(stamp).stamp(), iriUtils.findDatasetIRI(requireNonNull(datasetId)), DatasetQueries::getContributorsByDatasetUri, Constants.CONTRIBUTORS); + } catch (RmesException e) { + logger.error(errorMessage(stamp, datasetId), e); + return false; + } + } + + @Override + public boolean isDistributionManagerWithStamp(String datasetId, Stamp stamp){ + try { + return isManagerForModule(requireNonNull(stamp).stamp(), iriUtils.findDistributionIRI(requireNonNull(datasetId)), DistributionQueries::getContributorsByDistributionUri, Constants.CONTRIBUTORS); + } catch (RmesException e) { + logger.error(errorMessage(stamp, datasetId), e); + return false; + } + } + + @Override + public boolean isCodesListManagerWithStamp(String codesListId, Stamp stamp) { + try { + return isManagerForModule(requireNonNull(stamp).stamp(), this.structureComponentUtils.findCodesListIRI(requireNonNull(codesListId)), CodeListQueries::getContributorsByCodesListUri, Constants.CONTRIBUTORS); + } catch (RmesException e) { + logger.error(errorMessage(stamp, codesListId), e); + return false; + } + } + + @Override + public boolean isStructureManagerWithStamp(String structureId, Stamp stamp) { + try { + return isManagerForModule(requireNonNull(stamp).stamp(), iriUtils.findStructureIRI(requireNonNull(structureId)), StructureQueries::getContributorsByStructureUri, Constants.CONTRIBUTORS); + } catch (RmesException e) { + logger.error(errorMessage(stamp, structureId), e); + return false; + } + } + + @Override + public boolean isComponentManagerWithStamp(String componentId, Stamp stamp) { + try { + return isManagerForModule(requireNonNull(stamp).stamp(), this.structureComponentUtils.findComponentIRI(requireNonNull(componentId)), StructureQueries::getContributorsByComponentUri, Constants.CONTRIBUTORS); + } catch (RmesException e) { + logger.error(errorMessage(stamp, componentId), e); + return false; + } + } + + private String errorMessage(Stamp stamp, String datasetId) { + return "Error while checking authorization for user with stamp "+stamp+" for resource "+datasetId; + } + + @Override + public boolean isManagerForModule(String stamp, IRI uri, StampsRestrictionsService.QueryGenerator queryGenerator, String stampKey) throws RmesException { + logger.trace("Check management access for {} with stamp {}",uri, stampKey); + return checkResponsabilityForModule(stamp, List.of(uri), queryGenerator, stampKey, Stream::anyMatch); + } + + @Override + public boolean checkResponsabilityForModule(String stamp, List uris, StampsRestrictionsService.QueryGenerator queryGenerator, String stampKey, BiPredicate, Predicate> predicateMatcher) throws RmesException { + JSONArray owners = repoGestion.getResponseAsArray(queryGenerator.generate(urisAsString(uris))); + return StringUtils.hasLength(stamp) && + predicateMatcher.test( + owners.toList().stream() + .map(o -> findStamp(o, stampKey)), + stamp::equals // apply predicate `stamp::equals` to the stream of stamps returned at the previous line + ); + } + + + private Object findStamp(Object o, String stampKey) { + if (o instanceof JSONObject jsonObject) { + return jsonObject.get(stampKey); + } + if (o instanceof Map map) { + return map.get(stampKey); + } + return null; + } + +} diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java index 46f53a6c6..c6de957ed 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java @@ -16,7 +16,10 @@ import fr.insee.rmes.persistance.ontologies.INSEE; import fr.insee.rmes.utils.DateUtils; import fr.insee.rmes.utils.Deserializer; -import org.eclipse.rdf4j.model.*; +import org.eclipse.rdf4j.model.BNode; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.impl.LinkedHashModel; import org.eclipse.rdf4j.model.vocabulary.*; import org.json.JSONArray; diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java index b0b82e793..5d307f0ea 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java @@ -1,34 +1,26 @@ package fr.insee.rmes.bauhaus_services.stamps; import fr.insee.rmes.bauhaus_services.Constants; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; import fr.insee.rmes.config.auth.UserProvider; import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; import fr.insee.rmes.config.auth.user.AuthorizeMethodDecider; -import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.persistance.sparql_queries.concepts.ConceptsQueries; import fr.insee.rmes.persistance.sparql_queries.operations.indicators.IndicatorsQueries; import fr.insee.rmes.persistance.sparql_queries.operations.series.OpSeriesQueries; import org.eclipse.rdf4j.model.IRI; -import org.json.JSONArray; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import java.util.List; -import java.util.Map; -import java.util.function.BiPredicate; -import java.util.function.Predicate; import java.util.stream.Stream; -import static fr.insee.rmes.utils.StringUtils.urisAsString; - @Service @Primary @@ -41,16 +33,18 @@ public class StampsRestrictionServiceImpl implements StampsRestrictionsService { protected final RepositoryGestion repoGestion; private final AuthorizeMethodDecider authorizeMethodDecider; - private final UserProvider userProvider; + private final UserProvider userProvider; + private final StampsRestrictionsVerifier stampsRestrictionsVerifier; private static final Logger logger = LoggerFactory.getLogger(StampsRestrictionServiceImpl.class); @Autowired - public StampsRestrictionServiceImpl(RepositoryGestion repoGestion, AuthorizeMethodDecider authorizeMethodDecider, UserProvider userProvider) { + public StampsRestrictionServiceImpl(RepositoryGestion repoGestion, AuthorizeMethodDecider authorizeMethodDecider, UserProvider userProvider, StampsRestrictionsVerifier stampsRestrictionsVerifier) { this.repoGestion = repoGestion; this.authorizeMethodDecider = authorizeMethodDecider; - this.userProvider = userProvider; - } + this.userProvider = userProvider; + this.stampsRestrictionsVerifier = stampsRestrictionsVerifier; + } @Override public boolean isConceptOrCollectionOwner(IRI uri) throws RmesException { @@ -70,7 +64,7 @@ private boolean isConceptOwner(List uris) throws RmesException { } public boolean isSeriesManagerWithStamp(IRI iri, String stamp) throws RmesException { - return isManagerForModule(stamp, iri, OpSeriesQueries::getCreatorsBySeriesUri, Constants.CREATORS); + return stampsRestrictionsVerifier.isManagerForModule(stamp, iri, OpSeriesQueries::getCreatorsBySeriesUri, Constants.CREATORS); } protected boolean isSeriesManager(IRI iri) throws RmesException { @@ -81,47 +75,21 @@ private boolean isIndicatorCreator(IRI iri) throws RmesException { return isOwnerForModule(getStampAsString(), List.of(iri), IndicatorsQueries::getCreatorsByIndicatorUri, Constants.CREATORS); } private boolean isConceptManager(IRI uri) throws RmesException { - return isManagerForModule(getStampAsString(), uri, ConceptsQueries::getManager, Constants.MANAGER); - } - protected boolean isManagerForModule(String stamp, IRI uri, QueryGenerator queryGenerator, String stampKey) throws RmesException { - logger.trace("Check management access for {} with stamp {}",uri, stampKey); - return checkResponsabilityForModule(stamp, List.of(uri), queryGenerator, stampKey, Stream::anyMatch); + return stampsRestrictionsVerifier.isManagerForModule(getStampAsString(), uri, ConceptsQueries::getManager, Constants.MANAGER); } + private boolean isOwnerForModule(String stamp, List uris, QueryGenerator queryGenerator, String stampKey) throws RmesException { logger.trace("Check ownership for {} with stamp {}",uris, stampKey); - return checkResponsabilityForModule(stamp, uris, queryGenerator, stampKey, Stream::allMatch); + return stampsRestrictionsVerifier.checkResponsabilityForModule(stamp, uris, queryGenerator, stampKey, Stream::allMatch); } - private boolean checkResponsabilityForModule(String stamp, List uris, QueryGenerator queryGenerator, String stampKey, BiPredicate, Predicate> predicateMatcher) throws RmesException { - JSONArray owners = repoGestion.getResponseAsArray(queryGenerator.generate(urisAsString(uris))); - return StringUtils.hasLength(stamp) && - predicateMatcher.test( - owners.toList().stream() - .map(o -> findStamp(o, stampKey)), - stamp::equals // apply predicate `stamp::equals` to the stream of stamps returned at the previous line - ); - } - protected String getStampAsString() { + private String getStampAsString() { return getUser().getStampAsString(); } - protected Stamp getStamp(){ - return getUser().stamp(); - } - - private Object findStamp(Object o, String stampKey) { - if (o instanceof JSONObject jsonObject) { - return jsonObject.get(stampKey); - } - if (o instanceof Map map) { - return map.get(stampKey); - } - return null; - } - private boolean canModifyOrValidateIndicator(IRI iri) throws RmesException { return (authorizeMethodDecider.isAdmin() || (isIndicatorCreator(iri) && authorizeMethodDecider.isIndicatorContributor())); @@ -223,8 +191,4 @@ public boolean isAdmin() { return authorizeMethodDecider.isAdmin(); } - - protected interface QueryGenerator { - String generate(String query) throws RmesException; - } } diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/ComponentPublication.java b/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/ComponentPublication.java index e877c2a19..1efa58b0d 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/ComponentPublication.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/ComponentPublication.java @@ -1,8 +1,7 @@ package fr.insee.rmes.bauhaus_services.structures.utils; import fr.insee.rmes.bauhaus_services.Constants; -import fr.insee.rmes.bauhaus_services.rdf_utils.RdfService; -import fr.insee.rmes.bauhaus_services.rdf_utils.RdfUtils; +import fr.insee.rmes.bauhaus_services.rdf_utils.*; import fr.insee.rmes.exceptions.RmesException; import org.apache.http.HttpStatus; import org.eclipse.rdf4j.model.IRI; @@ -16,7 +15,7 @@ import org.springframework.stereotype.Repository; @Repository -public class ComponentPublication extends RdfService { +public record ComponentPublication(RepositoryGestion repoGestion, PublicationUtils publicationUtils) { public void publishComponent(Resource component, IRI type) throws RmesException { @@ -57,7 +56,7 @@ public void publishComponent(Resource component, IRI type) throws RmesException con.close(); } Resource componentToPublishRessource = publicationUtils.tranformBaseURIToPublish(component); - repositoryPublication.publishResource(componentToPublishRessource, model, RdfUtils.toString(type)); + publicationUtils.repositoryPublication().publishResource(componentToPublishRessource, model, RdfUtils.toString(type)); } diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/StructureComponentUtils.java b/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/StructureComponentUtils.java index 7500c836c..f384d07a3 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/StructureComponentUtils.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/structures/utils/StructureComponentUtils.java @@ -3,8 +3,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import fr.insee.rmes.bauhaus_services.Constants; -import fr.insee.rmes.bauhaus_services.rdf_utils.RdfService; import fr.insee.rmes.bauhaus_services.rdf_utils.RdfUtils; +import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; +import fr.insee.rmes.config.Config; import fr.insee.rmes.exceptions.ErrorCodes; import fr.insee.rmes.exceptions.RmesBadRequestException; import fr.insee.rmes.exceptions.RmesException; @@ -27,14 +28,14 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Arrays; @Component -public class StructureComponentUtils extends RdfService { +public record StructureComponentUtils(Config config, RepositoryGestion repoGestion, ComponentPublication componentPublication, @Value("${fr.insee.rmes.bauhaus.sesame.gestion.baseInternalURI}") String baseInternalUri) { static final Logger logger = LoggerFactory.getLogger(StructureComponentUtils.class); private static final String MAX_LENGTH = "maxLength"; @@ -44,8 +45,6 @@ public class StructureComponentUtils extends RdfService { public static final String VALIDATED = "Validated"; public static final String MODIFIED = "Modified"; - @Autowired - ComponentPublication componentPublication; public JSONObject formatComponent(String id, JSONObject response) throws RmesException { response.put(Constants.ID, id); @@ -128,7 +127,7 @@ public String createComponent(MutualizedComponent component, JSONObject jsonComp return createComponent(component, id, jsonComponent); } - public String createComponent(MutualizedComponent component, String id, JSONObject jsonComponent) throws RmesException { + private String createComponent(MutualizedComponent component, String id, JSONObject jsonComponent) throws RmesException { validateComponent(component); component.setId(id); @@ -383,4 +382,24 @@ else if (type.equals(QB.DIMENSION_PROPERTY.stringValue())) { private boolean jsonObjecthasPropertyNullOrEmpty(JSONObject component, String property) { return component.isNull(property) || "".equals(component.getString(property)); } + + public IRI findComponentIRI(String componentId) throws RmesException { + JSONObject type = repoGestion.getResponseAsObject(StructureQueries.getComponentType(componentId)); + String componentType = type.getString("type"); + if (componentType.equals(RdfUtils.toString(QB.ATTRIBUTE_PROPERTY))) { + return RdfUtils.structureComponentAttributeIRI(componentId); + } else if (componentType.equals(RdfUtils.toString(QB.DIMENSION_PROPERTY))) { + return RdfUtils.structureComponentDimensionIRI(componentId); + } else { + return RdfUtils.structureComponentMeasureIRI(componentId); + } + } + + + public IRI findCodesListIRI(String codesListId) throws RmesException { + JSONObject codeList = repoGestion.getResponseAsObject(CodeListQueries.getCodeListIRIByNotation(codesListId, baseInternalUri)); + String uriString = codeList.getString("iri"); + return RdfUtils.createIRI(uriString); + } + } diff --git a/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java b/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java index 33cf22b96..f26ec5943 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java @@ -1,7 +1,7 @@ package fr.insee.rmes.config.auth.security; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; import fr.insee.rmes.external.services.rbac.RBACService; +import fr.insee.rmes.external.services.rbac.StampChecker; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,23 +16,22 @@ import java.util.function.Supplier; import static fr.insee.rmes.config.auth.security.CommonSecurityConfiguration.DEFAULT_ROLE_PREFIX; -import static java.util.Objects.requireNonNull; @Component public class BauhausMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { private static final Logger logger= LoggerFactory.getLogger(BauhausMethodSecurityExpressionHandler.class); - private final StampAuthorizationChecker stampAuthorizationChecker; - private final StampFromPrincipal stampFromPrincipal; private final RBACService rbacService; + private final UserDecoder userDecoder; + private final StampChecker stampChecker; @Autowired - public BauhausMethodSecurityExpressionHandler(StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { + public BauhausMethodSecurityExpressionHandler(RBACService rbacService, UserDecoder userDecoder, StampChecker stampChecker) { this.rbacService = rbacService; + this.userDecoder = userDecoder; + this.stampChecker = stampChecker; logger.trace("Initializing GlobalMethodSecurityConfiguration with BauhausMethodSecurityExpressionHandler and DefaultRolePrefix = {}", DEFAULT_ROLE_PREFIX); - this.stampAuthorizationChecker = requireNonNull(stampAuthorizationChecker); - this.stampFromPrincipal = requireNonNull(stampFromPrincipal); setDefaultRolePrefix(DEFAULT_ROLE_PREFIX); } @@ -40,7 +39,7 @@ public BauhausMethodSecurityExpressionHandler(StampAuthorizationChecker stampAut public EvaluationContext createEvaluationContext(Supplier authentication, MethodInvocation mi) { StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi); MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue(); - context.setRootObject(SecurityExpressionRootForBauhaus.enrich(delegate, this.stampAuthorizationChecker, this.stampFromPrincipal, this.rbacService)); + context.setRootObject(SecurityExpressionRootForBauhaus.enrich(delegate, this.rbacService, this.userDecoder, this.stampChecker)); return context; } } \ No newline at end of file diff --git a/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java index df1a59e6c..b4250572e 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java @@ -1,7 +1,5 @@ package fr.insee.rmes.config.auth.security; -import fr.insee.rmes.config.auth.user.User; -import fr.insee.rmes.exceptions.RmesException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -46,14 +44,4 @@ public CorsConfigurationSource corsConfigurationSource() { return source; } - @Bean - public StampFromPrincipal stampFromPrincipal(UserDecoder userDecoder){ - return principal -> { - try { - return userDecoder.fromPrincipal(principal).map(User::stamp); - } catch (RmesException e) { - return Optional.empty(); - } - }; - } } diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 2a60e3727..329b8018e 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -1,13 +1,14 @@ package fr.insee.rmes.config.auth.security; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; import fr.insee.rmes.config.auth.RBACConfiguration; import fr.insee.rmes.config.auth.roles.Roles; import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; -import fr.insee.rmes.external.services.rbac.CheckAccessPrivilege; +import fr.insee.rmes.external.services.rbac.CheckAccessPrivilegeForUser; import fr.insee.rmes.external.services.rbac.RBACService; +import fr.insee.rmes.external.services.rbac.StampChecker; import fr.insee.rmes.model.rbac.RBAC; import org.json.JSONObject; import org.slf4j.Logger; @@ -19,7 +20,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import java.util.*; +import java.util.Objects; +import java.util.Optional; import java.util.function.BiPredicate; import static java.util.Objects.requireNonNull; @@ -29,22 +31,22 @@ public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressio private static final Logger logger = LoggerFactory.getLogger(SecurityExpressionRootForBauhaus.class); private final MethodSecurityExpressionOperations methodSecurityExpressionOperations; - private final StampAuthorizationChecker stampAuthorizationChecker; - private final StampFromPrincipal stampFromPrincipal; private final SecurityExpressionRoot methodSecurityExpressionRoot; private final RBACService rbacService; + private final UserDecoder userDecoder; + private final StampChecker stampChecker; - public SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { + public SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations methodSecurityExpressionOperations, RBACService rbacService, UserDecoder userDecoder, StampChecker stampChecker) { this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; - this.stampFromPrincipal = stampFromPrincipal; - this.stampAuthorizationChecker = stampAuthorizationChecker; this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; this.rbacService = rbacService; + this.userDecoder = userDecoder; + this.stampChecker = stampChecker; } - public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, StampAuthorizationChecker stampAuthorizationChecker, StampFromPrincipal stampFromPrincipal, RBACService rbacService) { - return new SecurityExpressionRootForBauhaus(requireNonNull(methodSecurityExpressionOperations), requireNonNull(stampAuthorizationChecker), requireNonNull(stampFromPrincipal), requireNonNull(rbacService)); + public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, RBACService rbacService, UserDecoder userDecoder, StampChecker stampChecker) { + return new SecurityExpressionRootForBauhaus(requireNonNull(methodSecurityExpressionOperations), requireNonNull(rbacService), requireNonNull(userDecoder), requireNonNull(stampChecker)); } @Override @@ -212,63 +214,76 @@ private boolean userHasStampWichManageResource(String resourceId, BiPredicate getStamp() { - return this.stampFromPrincipal.findStamp(methodSecurityExpressionRoot.getPrincipal()); + return getUser().map(User::stamp); + } + + private Optional getUser() { + Object principal = methodSecurityExpressionRoot.getPrincipal(); + try { + return userDecoder.fromPrincipal(principal); + } catch (RmesException e) { + logger.error("Unable to convert principal " + principal + " to User", e); + return Optional.empty(); + } } - public boolean canUpdateSerie(String serieId) throws RmesException { - return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); + public boolean canUpdateSerie(String serieId) { + return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); } - public boolean canDeleteDataset(String datasetId) throws RmesException { - return getAccessPrivileges().isGranted(RBAC.Privilege.DELETE).on(RBAC.Module.DATASET).withId(datasetId); + public boolean canDeleteDataset(String datasetId) { + return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.DELETE).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canUpdateDataset(String datasetId) throws RmesException { - return getAccessPrivileges().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.DATASET).withId(datasetId); + public boolean canUpdateDataset(String datasetId) { + return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canCreateDataset(String datasetId) throws RmesException { - return getAccessPrivileges().isGranted(RBAC.Privilege.CREATE).on(RBAC.Module.DATASET).withId(datasetId); + public boolean canCreateDataset(String datasetId) { + return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.CREATE).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canPublishDataset(String datasetId) throws RmesException { - return getAccessPrivileges().isGranted(RBAC.Privilege.PUBLISH).on(RBAC.Module.DATASET).withId(datasetId); + public boolean canPublishDataset(String datasetId) { + return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.PUBLISH).on(RBAC.Module.DATASET).withId(datasetId); } - public boolean canReadDataset(String datasetId) throws RmesException { - return getAccessPrivileges().isGranted(RBAC.Privilege.READ).on(RBAC.Module.DATASET).withId(datasetId); + public boolean canReadDataset(String datasetId) { + return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.READ).on(RBAC.Module.DATASET).withId(datasetId); } - private CheckAccessPrivilege getAccessPrivileges() { - return rbacService.computeRbac(this.getAuthentication().getAuthorities().stream() + private CheckAccessPrivilegeForUser getAccessPrivilegesForUser() { + return new CheckAccessPrivilegeForUser(rbacService.computeRbac(this.getAuthentication().getAuthorities().stream() .filter(Objects::nonNull) .map(SecurityExpressionRootForBauhaus::toRoleName) .toList() + ), + getUser().orElse(User.EMPTY_USER), + this.stampChecker ); } diff --git a/src/main/java/fr/insee/rmes/config/auth/security/StampFromPrincipal.java b/src/main/java/fr/insee/rmes/config/auth/security/StampFromPrincipal.java deleted file mode 100644 index 22284f977..000000000 --- a/src/main/java/fr/insee/rmes/config/auth/security/StampFromPrincipal.java +++ /dev/null @@ -1,11 +0,0 @@ -package fr.insee.rmes.config.auth.security; - -import fr.insee.rmes.config.auth.user.Stamp; - -import java.util.Optional; - -public interface StampFromPrincipal { - - Optional findStamp(Object principal); - -} diff --git a/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java b/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java index 3159893ef..589c8db6a 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/restrictions/StampsRestrictionsService.java @@ -81,4 +81,9 @@ public interface StampsRestrictionsService { boolean canValidateClassification(IRI uri) throws RmesException; + boolean isSeriesManagerWithStamp(IRI iri, String stamp) throws RmesException; + + interface QueryGenerator { + String generate(String query) throws RmesException; + } } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java b/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java deleted file mode 100644 index d86f3a5d6..000000000 --- a/src/main/java/fr/insee/rmes/external/services/rbac/AccessPrivilegeChecker.java +++ /dev/null @@ -1,32 +0,0 @@ -package fr.insee.rmes.external.services.rbac; - -import fr.insee.rmes.model.rbac.RBAC; - -public record AccessPrivilegeChecker(ApplicationAccessPrivileges applicationAccessPrivileges, StampChecker stampChecker, RBAC.Privilege privilege, RBAC.Module module) { - - public AccessPrivilegeChecker(ApplicationAccessPrivileges applicationAccessPrivileges, StampChecker stampChecker, RBAC.Privilege privilege) { - this(applicationAccessPrivileges, stampChecker, privilege, null); - } - - public AccessPrivilegeChecker on(RBAC.Module module) { - return withModule(module); - } - - private AccessPrivilegeChecker withModule(RBAC.Module module) { - return new AccessPrivilegeChecker(this.applicationAccessPrivileges, this.stampChecker, this.privilege, module); - } - - public boolean withId(String id) { - var strategy = applicationAccessPrivileges.privilegesForModule(module).strategyFor(privilege); - if (strategy.isEmpty()){ - return false; - } - return strategy.get().isAllStampAuthorized() || checkStampFor(id); - } - - private boolean checkStampFor(String id) { - return id != null && stampChecker.userStampIsAuthorizedForResource(module, id); - } - - -} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java b/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java index d9dcb6e9a..d09f53a9d 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java @@ -18,7 +18,7 @@ public record ApplicationAccessPrivileges(EnumMap> privilegesByModules) { return new ApplicationAccessPrivileges(new EnumMap<>(privilegesByModules.entrySet().stream() .collect( - Collectors.toMap(Map.Entry::getKey, + Collectors.toMap(Map.Entry>::getKey, entry -> new ModuleAccessPrivileges(entry.getValue()), ModuleAccessPrivileges::merge ) diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java deleted file mode 100644 index 2026c7d02..000000000 --- a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilege.java +++ /dev/null @@ -1,12 +0,0 @@ -package fr.insee.rmes.external.services.rbac; - -import fr.insee.rmes.model.rbac.RBAC; - -import java.util.Map; - -public record CheckAccessPrivilege(ApplicationAccessPrivileges applicationAccessPrivileges, StampChecker stampChecker) { - - public AccessPrivilegeChecker isGranted(RBAC.Privilege privilege) { - return new AccessPrivilegeChecker(applicationAccessPrivileges, stampChecker, privilege); - } -} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java new file mode 100644 index 000000000..14b0b9c1a --- /dev/null +++ b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java @@ -0,0 +1,59 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.config.auth.user.User; +import fr.insee.rmes.model.rbac.RBAC; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicReference; + +public record CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, StampChecker stampChecker, AtomicReference privilege, AtomicReference module) { + + private static final Logger log = LoggerFactory.getLogger(CheckAccessPrivilegeForUser.class); + + public CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, StampChecker stampChecker) { + this(applicationAccessPrivileges, user, stampChecker, new AtomicReference<>(), new AtomicReference<>()); + } + + public CheckAccessPrivilegeForUser isGranted(RBAC.Privilege privilege) { + withPrivilege(privilege); + return this; + } + + private void withPrivilege(RBAC.Privilege privilege) { + this.privilege.set(privilege); + } + + public CheckAccessPrivilegeForUser on(RBAC.Module module) { + withModule(module); + return this; + } + + private void withModule(RBAC.Module module) { + this.module.set(module); + } + + public boolean withId(String id) { + var strategy = applicationAccessPrivileges.privilegesForModule(module.get()).strategyFor(privilege.get()); + if (strategy.isEmpty()){ + log.atDebug().log(()->debugAccess()+" : no privilege found for "+ user.roles()); + return false; + } + if (strategy.get().isAllStampAuthorized()){ + log.atDebug().log(()->debugAccess()+" : ALL privilege found"); + return true; + } + boolean authorized = checkStampFor(id); + log.atDebug().log(()->debugAccess()+" : STAMP privilege found : "+user.stamp()+" "+(authorized?"":"un")+"authorized for id"+id); + return authorized; + } + + private @NotNull String debugAccess() { + return "Check access for " + user + " for " + privilege + " in " + module; + } + + private boolean checkStampFor(String id) { + return id != null && stampChecker.userStampIsAuthorizedForResource(module.get(), id, user.stamp()); + } +} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java index e80ec4dd0..eaecd689f 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java @@ -7,5 +7,5 @@ public interface RBACService { - CheckAccessPrivilege computeRbac(List roles); + ApplicationAccessPrivileges computeRbac(List roles); } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java index a1b2ae655..120dede3d 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java @@ -1,6 +1,8 @@ package fr.insee.rmes.external.services.rbac; import fr.insee.rmes.config.auth.RBACConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -8,19 +10,26 @@ import java.util.Map; @Service -public record RBACServiceImpl(Map applicationAccessPrivilegesByRoles, StampChecker stampChecker) implements RBACService { +public record RBACServiceImpl(Map applicationAccessPrivilegesByRoles) implements RBACService { + + private static final Logger log = LoggerFactory.getLogger(RBACServiceImpl.class); @Autowired - public RBACServiceImpl(RBACConfiguration configuration, StampChecker stampChecker) { - this(configuration.toMapOfApplicationAccessPrivilegesByRoles(), stampChecker); + public RBACServiceImpl(RBACConfiguration configuration) { + this(configuration.toMapOfApplicationAccessPrivilegesByRoles()); } @Override - public CheckAccessPrivilege computeRbac(List roles) { - return new CheckAccessPrivilege(roles.stream() - .map(applicationAccessPrivilegesByRoles::get) + public ApplicationAccessPrivileges computeRbac(List roles) { + ApplicationAccessPrivileges applicationAccessPrivileges = roles.stream() + .map(this::getApplicationAccessPrivileges) .reduce(ApplicationAccessPrivileges::merge) - .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE), - this.stampChecker); + .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); + log.atTrace().log(()->"Privileges computed for roles "+roles+" : "+applicationAccessPrivileges); + return applicationAccessPrivileges; + } + + private ApplicationAccessPrivileges getApplicationAccessPrivileges(RBACConfiguration.RoleName roleName) { + return applicationAccessPrivilegesByRoles.getOrDefault(roleName, ApplicationAccessPrivileges.NO_PRIVILEGE); } } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java b/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java index 0b211c039..501e46d05 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java @@ -1,7 +1,26 @@ package fr.insee.rmes.external.services.rbac; +import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.model.rbac.RBAC; public interface StampChecker { - boolean userStampIsAuthorizedForResource(RBAC.Module module, String id); + boolean userStampIsAuthorizedForResource(RBAC.Module module, String id, Stamp stamp); + + @Deprecated + boolean isSeriesManagerWithStamp(String s, Stamp stamp); + + @Deprecated + boolean isCodesListManagerWithStamp(String s, Stamp stamp); + + @Deprecated + boolean isDatasetManagerWithStamp(String s, Stamp stamp); + + @Deprecated + boolean isDistributionManagerWithStamp(String s, Stamp stamp); + + @Deprecated + boolean isStructureManagerWithStamp(String s, Stamp stamp); + + @Deprecated + boolean isComponentManagerWithStamp(String s, Stamp stamp); } diff --git a/src/main/java/fr/insee/rmes/utils/IRIUtils.java b/src/main/java/fr/insee/rmes/utils/IRIUtils.java new file mode 100644 index 000000000..d88aab171 --- /dev/null +++ b/src/main/java/fr/insee/rmes/utils/IRIUtils.java @@ -0,0 +1,26 @@ +package fr.insee.rmes.utils; + +import fr.insee.rmes.bauhaus_services.rdf_utils.ObjectType; +import fr.insee.rmes.bauhaus_services.rdf_utils.RdfUtils; +import org.eclipse.rdf4j.model.IRI; +import org.springframework.stereotype.Component; + +@Component +public record IRIUtils() { + + public IRI findIRI(String seriesId) { + return RdfUtils.objectIRI(ObjectType.SERIES, seriesId); + } + + public IRI findStructureIRI(String structureId) { + return RdfUtils.objectIRI(ObjectType.STRUCTURE, structureId); + } + + public IRI findDatasetIRI(String datasetId) { + return RdfUtils.objectIRI(ObjectType.DATASET, datasetId); + } + + public IRI findDistributionIRI(String distributionId) { + return RdfUtils.objectIRI(ObjectType.DISTRIBUTION, distributionId); + } +} diff --git a/src/main/java/fr/insee/rmes/webservice/UserResources.java b/src/main/java/fr/insee/rmes/webservice/UserResources.java index 7a36a84b1..970b5a1cf 100644 --- a/src/main/java/fr/insee/rmes/webservice/UserResources.java +++ b/src/main/java/fr/insee/rmes/webservice/UserResources.java @@ -78,13 +78,11 @@ public UserResources(StampsService stampsService, RBACService rbacService, UserD ) public ApplicationAccessPrivileges getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { return this.userDecoder.fromPrincipal(principal) - .map(user-> rbacService.computeRbac(user.roles().stream().filter(Objects::nonNull).map(RBACConfiguration.RoleName::new).toList()).applicationAccessPrivileges()) + .map(user-> rbacService.computeRbac(user.roles().stream().filter(Objects::nonNull).map(RBACConfiguration.RoleName::new).toList())) .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); } - /** - * @deprecated - */ + @GetMapping(value = "/stamp", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(operationId = "getStamp", summary = "User's stamp", responses = {@ApiResponse(content = @Content(mediaType = "application/json", schema = @Schema(implementation = String.class)))}) diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index 351b3b3cd..1bbf2ca30 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -19,7 +19,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; diff --git a/src/main/resources/rbac.yml b/src/main/resources/rbac.yml index 3f78c780f..25754efaf 100644 --- a/src/main/resources/rbac.yml +++ b/src/main/resources/rbac.yml @@ -133,3 +133,11 @@ rbac: update: STAMP publish: STAMP validate: STAMP + Gestionnaire_jeu_donnees_RMESGNCS: + dataset: + delete: STAMP + update: STAMP + validate: STAMP + publish: STAMP + create: ALL + read: STAMP diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java index 27418f9b7..4c6b38800 100644 --- a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java +++ b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java @@ -1,23 +1,22 @@ package fr.insee.rmes.bauhaus_services; import fr.insee.rmes.model.rbac.RBAC; -import fr.insee.rmes.stubs.StampAuthorizationCheckerStub; -import org.junit.jupiter.api.Test; +import fr.insee.rmes.stubs.StampRestritionVerifierStub; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import static fr.insee.rmes.stubs.StampAuthorizationCheckerStub.DATASET_STUB_ID; +import static fr.insee.rmes.stubs.StampRestritionVerifierStub.*; import static org.assertj.core.api.Assertions.assertThat; class StampAuthorizationCheckerTest { - private final StampAuthorizationChecker stampAuthorizationChecker=new StampAuthorizationCheckerStub(); - - @Test - void userStampIsAuthorizedForResource() { - + private final StampAuthorizationChecker stampAuthorizationChecker=new StampAuthorizationChecker(new StampRestritionVerifierStub()); + @ParameterizedTest + @ValueSource(strings = {SERIES_STUB_ID, DATASET_STUB_ID, DISTRIBUTION_STUB_ID, COMPONENT_STUB_ID, STRUCTURE_STUB_ID, CODES_LISTES_STUB_ID}) + void userStampIsAuthorizedForResource(String id) { //Given RBAC.Module module = RBAC.Module.DATASET; - String id = DATASET_STUB_ID; //When then - assertThat(this.stampAuthorizationChecker.userStampIsAuthorizedForResource(module, id)).isTrue(); + assertThat(this.stampAuthorizationChecker.userStampIsAuthorizedForResource(module, id, null)).isTrue(); } } \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java index 1a8f6d804..f1b5737ef 100644 --- a/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java +++ b/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java @@ -1,5 +1,6 @@ package fr.insee.rmes.bauhaus_services.stamps; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifierImpl; import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; import fr.insee.rmes.config.ConfigStub; import fr.insee.rmes.config.auth.UserProvider; @@ -10,6 +11,7 @@ import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.json.JSONArray; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -32,6 +34,7 @@ class StampsRestrictionServiceImplTest { @Mock private RepositoryGestion repoGestion; + private StampsRestrictionsVerifierImpl stampsRestrictionsVerifier; @Mock private UserProvider userProvider; private final IRI iriToCheck = SimpleValueFactory.getInstance().createIRI("http://bauhaus/operations/serie/s2132"); @@ -52,6 +55,11 @@ static void configureOpSeriesQueries() { OpSeriesQueries.setConfig(new ConfigStub()); } + @BeforeEach + void injectRepositoryGestion() { + stampsRestrictionsVerifier = new StampsRestrictionsVerifierImpl(null, repoGestion, null); + } + @Test void isSeriesManager_OK_whenStampIsManagerWithMultipleCreators() throws RmesException { var owners=new JSONArray("[" + @@ -60,7 +68,7 @@ void isSeriesManager_OK_whenStampIsManagerWithMultipleCreators() throws RmesExce "]"); when(repoGestion.getResponseAsArray(anyString())).thenReturn(owners); when(userProvider.findUserDefaultToEmpty()).thenReturn(new User("", List.of(), timbre)); - var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider); + var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider, stampsRestrictionsVerifier); assertTrue(stampsRestrictionServiceImpl.isSeriesManager(this.iriToCheck)); } @@ -71,7 +79,7 @@ void isSeriesManager_OK_whenStampIsManagerWithOneCreator() throws RmesException "]"); when(repoGestion.getResponseAsArray(anyString())).thenReturn(owners); when(userProvider.findUserDefaultToEmpty()).thenReturn(new User("", List.of(), timbre)); - var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider); + var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider, stampsRestrictionsVerifier); assertTrue(stampsRestrictionServiceImpl.isSeriesManager(this.iriToCheck)); } @@ -82,7 +90,7 @@ void isSeriesManager_KO_whenStampIsNotManagerWithOneCreator() throws RmesExcepti "]"); when(repoGestion.getResponseAsArray(anyString())).thenReturn(owners); when(userProvider.findUserDefaultToEmpty()).thenReturn(new User("", List.of(), timbre)); - var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider); + var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider, stampsRestrictionsVerifier); assertFalse(stampsRestrictionServiceImpl.isSeriesManager(this.iriToCheck)); } @@ -94,7 +102,7 @@ void isSeriesManager_KO_whenStampIsNotManagerWithMultipleCreator() throws RmesEx "]"); when(repoGestion.getResponseAsArray(anyString())).thenReturn(owners); when(userProvider.findUserDefaultToEmpty()).thenReturn(new User("", List.of(), timbre)); - var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider); + var stampsRestrictionServiceImpl = new StampsRestrictionServiceImpl(repoGestion, null, userProvider, stampsRestrictionsVerifier); assertFalse(stampsRestrictionServiceImpl.isSeriesManager(this.iriToCheck)); } } \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java b/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java index cc826915e..5b3c83d30 100644 --- a/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java +++ b/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java @@ -11,13 +11,13 @@ @SpringBootTest(properties = "spring.config.additional-location=classpath:rbac.yml") @EnableConfigurationProperties(RBACConfiguration.class) -public class RBACConfigurationTest { +class RBACConfigurationTest { @Autowired private RBACConfiguration rbacConfiguration; @Test void testReadRbacConfig() { - assertThat(rbacConfiguration.allModulesAccessPrivileges()).isNotNull(); + assertThat(rbacConfiguration.toMapOfApplicationAccessPrivilegesByRoles()).isNotNull(); } } \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java index 77b17f143..58a6417be 100644 --- a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java +++ b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java @@ -1,16 +1,14 @@ package fr.insee.rmes.external.services.rbac; import fr.insee.rmes.config.auth.RBACConfiguration; -import fr.insee.rmes.model.rbac.RBAC; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import java.util.List; -import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class RBACServiceImplTest { @@ -18,18 +16,15 @@ class RBACServiceImplTest { @Autowired RBACServiceImpl rbacService; - @MockBean - RBACConfiguration configuration; - @Test void shouldReturnAnEmptyMapIfMissingRoles() { - Map> values = rbacService.computeRbac(List.of()); - assertTrue(values.isEmpty()); + ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of()); + assertThat(applicationAccessPrivileges).isEqualTo(ApplicationAccessPrivileges.NO_PRIVILEGE); } @Test void shouldReturnAnEmptyMapIfUnknownRole() { - Map> values = rbacService.computeRbac(List.of("UNKNOWN")); - assertTrue(values.isEmpty()); + ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("UNKNOWN"))); + assertThat(applicationAccessPrivileges).isEqualTo(ApplicationAccessPrivileges.NO_PRIVILEGE); } } \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java b/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java new file mode 100644 index 000000000..fc45c21ab --- /dev/null +++ b/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java @@ -0,0 +1,27 @@ +package fr.insee.rmes.integration.authorizations; + +import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.config.Config; +import fr.insee.rmes.config.auth.RBACConfiguration; +import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; +import fr.insee.rmes.config.auth.security.BauhausMethodSecurityExpressionHandler; +import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; +import fr.insee.rmes.config.auth.security.DefaultSecurityContext; +import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; +import fr.insee.rmes.external.services.rbac.RBACServiceImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Import; + +@TestConfiguration +@EnableConfigurationProperties(RBACConfiguration.class) +@Import({Config.class, + OpenIDConnectSecurityContext.class, + DefaultSecurityContext.class, + CommonSecurityConfiguration.class, + UserProviderFromSecurityContext.class, + BauhausMethodSecurityExpressionHandler.class, + RBACServiceImpl.class, + StampAuthorizationChecker.class}) +public class ConfigurationForTestWithAuth { +} diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java index 0858f7a3e..fa1a87dcb 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java @@ -3,10 +3,8 @@ import fr.insee.rmes.bauhaus_services.CodeListService; import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.config.Config; -import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.config.auth.roles.Roles; -import fr.insee.rmes.config.auth.security.*; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.model.ValidationStatus; import fr.insee.rmes.webservice.codesLists.CodeListsResources; @@ -41,12 +39,7 @@ "logging.level.fr.insee.rmes.config.auth=TRACE", "fr.insee.rmes.bauhaus.activeModules=codelists"} ) -@Import({Config.class, - OpenIDConnectSecurityContext.class, - DefaultSecurityContext.class, - CommonSecurityConfiguration.class, - UserProviderFromSecurityContext.class, - BauhausMethodSecurityExpressionHandler.class}) +@Import( ConfigurationForTestWithAuth.class) class TestCodeListsResourcesEnvProd { @Autowired @@ -59,6 +52,8 @@ class TestCodeListsResourcesEnvProd { protected OperationsDocumentationsService documentationsService; @MockBean StampAuthorizationChecker stampAuthorizationChecker; + @MockBean + StampsRestrictionsVerifier stampsRestrictionsVerifier; private final String idep = "xxxxxx"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java index 401127549..48f331ebd 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java @@ -1,6 +1,7 @@ package fr.insee.rmes.integration.authorizations; import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; @@ -10,11 +11,10 @@ import fr.insee.rmes.config.auth.security.DefaultSecurityContext; import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.external.services.rbac.RBACServiceImpl; import fr.insee.rmes.model.dataset.Dataset; import fr.insee.rmes.webservice.dataset.DatasetResources; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -40,27 +40,21 @@ "logging.level.org.springframework.security.web.access=TRACE", "logging.level.fr.insee.rmes.config.auth=TRACE", "logging.level.org.springframework.web=DEBUG", + "logging.level.fr.insee.rmes.external.services.rbac=DEBUG", "fr.insee.rmes.bauhaus.activeModules=datasets"} ) -@Import({Config.class, - OpenIDConnectSecurityContext.class, - DefaultSecurityContext.class, - CommonSecurityConfiguration.class, - UserProviderFromSecurityContext.class, - BauhausMethodSecurityExpressionHandler.class}) +@Import( ConfigurationForTestWithAuth.class) class TestDatasetsResourcesEnvProd { - - private static final Logger logger= LoggerFactory.getLogger(TestDatasetsResourcesEnvProd.class); - @Autowired private MockMvc mvc; @MockBean private JwtDecoder jwtDecoder; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; - @MockBean DatasetService datasetService; + @MockBean + StampsRestrictionsVerifier stampsRestrictionsVerifier; + private static Dataset dataset; @@ -194,7 +188,7 @@ void shouldUpdateADatasetIfAdmin() throws Exception { @Test void shouldUpdateADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(put("/datasets/" + datasetId).header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) @@ -205,7 +199,7 @@ void shouldUpdateADatasetIfDatasetContributorBasedOnStamp() throws Exception { @Test void shouldNotUpdateADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(put("/datasets/" + datasetId).header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) @@ -235,7 +229,7 @@ void shouldPublishADatasetIfAdmin() throws Exception { @Test void shouldPublishADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(put("/datasets/" + datasetId + "/validate").header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) @@ -245,7 +239,7 @@ void shouldPublishADatasetIfDatasetContributorBasedOnStamp() throws Exception { @Test void shouldNotPublishADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(put("/datasets/" + datasetId + "/validate").header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) @@ -275,7 +269,7 @@ void shouldDeleteADatasetIfAdmin() throws Exception { @Test void shouldDeleteADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); dataset=new Dataset(); dataset.setValidationState("Unpublished"); @@ -288,7 +282,7 @@ void shouldDeleteADatasetIfDatasetContributorBasedOnStamp() throws Exception { @Test void shouldNotDeleteADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(delete("/datasets/" + datasetId).header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) @@ -317,7 +311,7 @@ void shouldPatchADatasetIfAdmin() throws Exception { @Test void shouldPatchADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(patch("/datasets/" + datasetId).header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) @@ -328,7 +322,7 @@ void shouldPatchADatasetIfDatasetContributorBasedOnStamp() throws Exception { @Test void shouldNotPatchADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampAuthorizationChecker.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); + when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); mvc.perform(patch("/datasets/" + datasetId).header("Authorization", "Bearer toto") .contentType(MediaType.APPLICATION_JSON) diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java index 955fef25c..70253b9b1 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java @@ -2,6 +2,7 @@ import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; import fr.insee.rmes.bauhaus_services.distribution.DistributionService; import fr.insee.rmes.config.Config; @@ -40,12 +41,7 @@ "logging.level.fr.insee.rmes.config.auth=TRACE", "fr.insee.rmes.bauhaus.activeModules=datasets"} ) -@Import({Config.class, - OpenIDConnectSecurityContext.class, - DefaultSecurityContext.class, - CommonSecurityConfiguration.class, - UserProviderFromSecurityContext.class, - BauhausMethodSecurityExpressionHandler.class}) +@Import( ConfigurationForTestWithAuth.class) class TestDistributionsResourcesEnvProd { @Autowired @@ -58,6 +54,8 @@ class TestDistributionsResourcesEnvProd { private DistributionService distributionService; @MockBean StampAuthorizationChecker stampAuthorizationChecker; + @MockBean + StampsRestrictionsVerifier stampsRestrictionsVerifier; private final String idep = "xxxxxx"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java index faa269ed3..7a4d42140 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java @@ -3,6 +3,7 @@ import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.OperationsService; import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.roles.Roles; @@ -40,12 +41,7 @@ "logging.level.fr.insee.rmes.config.auth=TRACE", "fr.insee.rmes.bauhaus.activeModules=operations"} ) -@Import({Config.class, - OpenIDConnectSecurityContext.class, - DefaultSecurityContext.class, - CommonSecurityConfiguration.class, - UserProviderFromSecurityContext.class, - BauhausMethodSecurityExpressionHandler.class}) +@Import( ConfigurationForTestWithAuth.class) class TestSeriesResourcesEnvProd { @Autowired @@ -60,6 +56,9 @@ class TestSeriesResourcesEnvProd { @MockBean StampAuthorizationChecker stampAuthorizationChecker; + @MockBean + StampsRestrictionsVerifier stampsRestrictionsVerifier; + @MockBean private JwtDecoder jwtDecoder; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java index c625423cf..b66cf80b3 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java @@ -2,6 +2,7 @@ import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; import fr.insee.rmes.bauhaus_services.structures.StructureComponent; import fr.insee.rmes.bauhaus_services.structures.StructureService; import fr.insee.rmes.config.Config; @@ -44,12 +45,7 @@ "logging.level.fr.insee.rmes.config.auth=TRACE", "fr.insee.rmes.bauhaus.activeModules=structures"} ) -@Import({Config.class, - OpenIDConnectSecurityContext.class, - DefaultSecurityContext.class, - CommonSecurityConfiguration.class, - UserProviderFromSecurityContext.class, - BauhausMethodSecurityExpressionHandler.class}) +@Import( ConfigurationForTestWithAuth.class) class TestStructuresResourcesEnvProd { @Autowired private MockMvc mvc; @@ -63,6 +59,8 @@ class TestStructuresResourcesEnvProd { protected OperationsDocumentationsService documentationsService; @MockBean StampAuthorizationChecker stampAuthorizationChecker; + @MockBean + StampsRestrictionsVerifier stampsRestrictionsVerifier; private final String idep = "xxxxxx"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java b/src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java deleted file mode 100644 index 057a505f8..000000000 --- a/src/test/java/fr/insee/rmes/stubs/StampAuthorizationCheckerStub.java +++ /dev/null @@ -1,18 +0,0 @@ -package fr.insee.rmes.stubs; - -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.config.auth.user.Stamp; - -public class StampAuthorizationCheckerStub extends StampAuthorizationChecker { - - public static final String DATASET_STUB_ID = "1"; - - public StampAuthorizationCheckerStub() { - super(null, null, null, null); - } - - @Override - public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { - return DATASET_STUB_ID.equals(datasetId); - } -} diff --git a/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java b/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java new file mode 100644 index 000000000..1f63c3a5f --- /dev/null +++ b/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java @@ -0,0 +1,64 @@ +package fr.insee.rmes.stubs; + +import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; +import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.exceptions.RmesException; +import org.eclipse.rdf4j.model.IRI; + +import java.util.List; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class StampRestritionVerifierStub implements StampsRestrictionsVerifier { + + public static final String DATASET_STUB_ID = "datasetID"; + public static final String SERIES_STUB_ID = "seriesID"; + public static final String DISTRIBUTION_STUB_ID = "distributionID"; + public static final String CODES_LISTES_STUB_ID = "codesListesID"; + public static final String STRUCTURE_STUB_ID = "structureID"; + public static final String COMPONENT_STUB_ID = "componentID"; + + + + @Override + public boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp) { + return SERIES_STUB_ID.equals(seriesId); + } + + @Override + public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { + return DATASET_STUB_ID.equals(datasetId); + } + + @Override + public boolean isDistributionManagerWithStamp(String distributionId, Stamp stamp) { + return DISTRIBUTION_STUB_ID.equals(distributionId); + } + + @Override + public boolean isCodesListManagerWithStamp(String codesListId, Stamp stamp) { + return CODES_LISTES_STUB_ID.equals(codesListId); + } + + @Override + public boolean isStructureManagerWithStamp(String structureId, Stamp stamp) { + return STRUCTURE_STUB_ID.equals(structureId); + } + + @Override + public boolean isComponentManagerWithStamp(String componentId, Stamp stamp) { + return COMPONENT_STUB_ID.equals(componentId); + } + + @Override + public boolean isManagerForModule(String stamp, IRI uri, StampsRestrictionsService.QueryGenerator queryGenerator, String stampKey) throws RmesException { + return false; + } + + @Override + public boolean checkResponsabilityForModule(String stamp, List uris, StampsRestrictionsService.QueryGenerator queryGenerator, String stampKey, BiPredicate, Predicate> predicateMatcher) throws RmesException { + return false; + } +} From 31a83cf9606e63068a7aa7890d01768fb437bcc8 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 12 Aug 2024 19:31:10 +0200 Subject: [PATCH 13/19] refactor (rbac : architecture and tests) - less coupling - more tests case for access control - parametrized tests --- .../StampAuthorizationChecker.java | 62 --- ...onCheckerWithResourceOwnershipByStamp.java | 61 +++ ... => ResourceOwnershipByStampVerifier.java} | 2 +- ...ResourceOwnershipByStampVerifierImpl.java} | 4 +- .../stamps/StampsRestrictionServiceImpl.java | 14 +- .../auth/AccessControlConfiguration.java | 14 + .../rmes/config/auth/RBACConfiguration.java | 14 +- ...auhausMethodSecurityExpressionHandler.java | 14 +- .../SecurityExpressionRootForBauhaus.java | 45 ++- ...Checker.java => AuthorizationChecker.java} | 6 +- .../rbac/CheckAccessPrivilegeForUser.java | 20 +- .../external/services/rbac/RBACService.java | 22 +- .../services/rbac/RBACServiceImpl.java | 35 -- .../rbac/ApplicationAccessPrivileges.java | 24 +- .../java/fr/insee/rmes/model/rbac/Module.java | 13 + .../model/rbac/ModuleAccessPrivileges.java | 19 +- .../fr/insee/rmes/model/rbac/Privilege.java | 10 + .../java/fr/insee/rmes/model/rbac/RBAC.java | 47 +-- .../fr/insee/rmes/model/rbac/Strategy.java | 24 ++ .../insee/rmes/webservice/UserResources.java | 2 +- .../webservice/dataset/DatasetResources.java | 1 + .../StampAuthorizationCheckerTest.java | 7 +- .../StampsRestrictionServiceImplTest.java | 6 +- .../java/fr/insee/rmes/config/RBACTest.java | 19 + .../auth/security/RBACConfigurationTest.java | 8 +- .../services/rbac/RBACServiceImplTest.java | 30 -- .../services/rbac/RBACServiceTest.java | 112 ++++++ .../ConfigurationForTestWithAuth.java | 8 +- .../PublicResourcesAuthorizationsTest.java | 4 +- .../TestCodeListsResourcesEnvProd.java | 8 +- .../TestDatasetsResourcesEnvProd.java | 355 ++++-------------- .../TestDistributionsResourcesEnvProd.java | 14 +- ...ographyResourcesAuthorizationsEnvProd.java | 4 +- ...icatorsResourcesAuthorizationsEnvProd.java | 5 +- ...aReportResourcesAuthorizationsEnvProd.java | 50 ++- ...tSeriesResourcesAuthorizationsEnvProd.java | 4 +- .../TestSeriesResourcesEnvProd.java | 14 +- .../TestStructuresResourcesEnvProd.java | 14 +- .../stubs/StampRestritionVerifierStub.java | 4 +- .../rmes/webservice/PublicResourcesTest.java | 4 +- .../UnactiveStructureResourcesTest.java | 4 +- .../UserResourcesEnvHorsProdTest.java | 4 +- .../webservice/UserResourcesTestEnvProd.java | 4 +- src/test/resources/rbac-test.yml | 143 +++++++ 44 files changed, 647 insertions(+), 631 deletions(-) delete mode 100644 src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java create mode 100644 src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java rename src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/{StampsRestrictionsVerifier.java => ResourceOwnershipByStampVerifier.java} (95%) rename src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/{StampsRestrictionsVerifierImpl.java => ResourceOwnershipByStampVerifierImpl.java} (95%) create mode 100644 src/main/java/fr/insee/rmes/config/auth/AccessControlConfiguration.java rename src/main/java/fr/insee/rmes/external/services/rbac/{StampChecker.java => AuthorizationChecker.java} (77%) delete mode 100644 src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java rename src/main/java/fr/insee/rmes/{external/services => model}/rbac/ApplicationAccessPrivileges.java (58%) create mode 100644 src/main/java/fr/insee/rmes/model/rbac/Module.java create mode 100644 src/main/java/fr/insee/rmes/model/rbac/Privilege.java create mode 100644 src/main/java/fr/insee/rmes/model/rbac/Strategy.java create mode 100644 src/test/java/fr/insee/rmes/config/RBACTest.java delete mode 100644 src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java create mode 100644 src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java create mode 100644 src/test/resources/rbac-test.yml diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java b/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java deleted file mode 100644 index f66337044..000000000 --- a/src/main/java/fr/insee/rmes/bauhaus_services/StampAuthorizationChecker.java +++ /dev/null @@ -1,62 +0,0 @@ -package fr.insee.rmes.bauhaus_services; - -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; -import fr.insee.rmes.config.auth.user.Stamp; -import fr.insee.rmes.external.services.rbac.StampChecker; -import fr.insee.rmes.model.rbac.RBAC; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class StampAuthorizationChecker implements StampChecker { - - private final StampsRestrictionsVerifier stampsRestrictionsVerifier; - - @Autowired - public StampAuthorizationChecker(StampsRestrictionsVerifier stampsRestrictionsVerifier) { - this.stampsRestrictionsVerifier = stampsRestrictionsVerifier; - } - - public boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp) { - return stampsRestrictionsVerifier.isSeriesManagerWithStamp(seriesId, stamp); - } - - public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { - return stampsRestrictionsVerifier.isDatasetManagerWithStamp(datasetId, stamp); - } - - public boolean isDistributionManagerWithStamp(String datasetId, Stamp stamp) { - return stampsRestrictionsVerifier.isDistributionManagerWithStamp(datasetId, stamp); - } - - public boolean isCodesListManagerWithStamp(String codesListId, Stamp stamp) { - return stampsRestrictionsVerifier.isCodesListManagerWithStamp(codesListId, stamp); - } - - public boolean isStructureManagerWithStamp(String structureId, Stamp stamp) { - return stampsRestrictionsVerifier.isStructureManagerWithStamp(structureId, stamp); - } - - public boolean isComponentManagerWithStamp(String componentId, Stamp stamp) { - return stampsRestrictionsVerifier.isComponentManagerWithStamp(componentId,stamp); - } - - @Override - public boolean userStampIsAuthorizedForResource(RBAC.Module module, String id, Stamp stamp) { - return switch (module){ - case CONCEPT -> false; - case COLLECTION -> false; - case FAMILY -> false; - case SERIE -> false; - case OPERATION -> false; - case INDICATOR -> false; - case SIMS -> false; - case CLASSIFICATION -> false; - case DATASET -> isDatasetManagerWithStamp(id, stamp); - case null -> false; - }; - } - - -} - diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java new file mode 100644 index 000000000..b1a6a8c72 --- /dev/null +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java @@ -0,0 +1,61 @@ +package fr.insee.rmes.bauhaus_services.accesscontrol; + +import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.external.services.rbac.AuthorizationChecker; +import fr.insee.rmes.model.rbac.Module; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class AuthorizationCheckerWithResourceOwnershipByStamp implements AuthorizationChecker { + + private final ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; + + @Autowired + public AuthorizationCheckerWithResourceOwnershipByStamp(ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier) { + this.resourceOwnershipByStampVerifier = resourceOwnershipByStampVerifier; + } + + public boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp) { + return resourceOwnershipByStampVerifier.isSeriesManagerWithStamp(seriesId, stamp); + } + + public boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp) { + return resourceOwnershipByStampVerifier.isDatasetManagerWithStamp(datasetId, stamp); + } + + public boolean isDistributionManagerWithStamp(String datasetId, Stamp stamp) { + return resourceOwnershipByStampVerifier.isDistributionManagerWithStamp(datasetId, stamp); + } + + public boolean isCodesListManagerWithStamp(String codesListId, Stamp stamp) { + return resourceOwnershipByStampVerifier.isCodesListManagerWithStamp(codesListId, stamp); + } + + public boolean isStructureManagerWithStamp(String structureId, Stamp stamp) { + return resourceOwnershipByStampVerifier.isStructureManagerWithStamp(structureId, stamp); + } + + public boolean isComponentManagerWithStamp(String componentId, Stamp stamp) { + return resourceOwnershipByStampVerifier.isComponentManagerWithStamp(componentId,stamp); + } + + @Override + public boolean userStampIsAuthorizedForResource(Module module, String id, Stamp stamp) { + return switch (module){ + case CONCEPT -> false; + case COLLECTION -> false; + case FAMILY -> false; + case SERIE -> false; + case OPERATION -> false; + case INDICATOR -> false; + case SIMS -> false; + case CLASSIFICATION -> false; + case DATASET -> isDatasetManagerWithStamp(id, stamp); + case null -> false; + }; + } + + +} + diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/ResourceOwnershipByStampVerifier.java similarity index 95% rename from src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java rename to src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/ResourceOwnershipByStampVerifier.java index 115bee396..d232ee60c 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifier.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/ResourceOwnershipByStampVerifier.java @@ -10,7 +10,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; -public interface StampsRestrictionsVerifier { +public interface ResourceOwnershipByStampVerifier { boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp); boolean isDatasetManagerWithStamp(String datasetId, Stamp stamp); diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/ResourceOwnershipByStampVerifierImpl.java similarity index 95% rename from src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java rename to src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/ResourceOwnershipByStampVerifierImpl.java index a5fd08975..17b05e2da 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/StampsRestrictionsVerifierImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/ResourceOwnershipByStampVerifierImpl.java @@ -30,9 +30,9 @@ import static java.util.Objects.requireNonNull; @Component -public record StampsRestrictionsVerifierImpl(IRIUtils iriUtils, RepositoryGestion repoGestion, StructureComponentUtils structureComponentUtils) implements StampsRestrictionsVerifier { +public record ResourceOwnershipByStampVerifierImpl(IRIUtils iriUtils, RepositoryGestion repoGestion, StructureComponentUtils structureComponentUtils) implements ResourceOwnershipByStampVerifier { - private static final Logger logger = LoggerFactory.getLogger(StampsRestrictionsVerifierImpl.class); + private static final Logger logger = LoggerFactory.getLogger(ResourceOwnershipByStampVerifierImpl.class); @Override public boolean isSeriesManagerWithStamp(String seriesId, Stamp stamp) { diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java index 5d307f0ea..b42f40a32 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImpl.java @@ -1,7 +1,7 @@ package fr.insee.rmes.bauhaus_services.stamps; import fr.insee.rmes.bauhaus_services.Constants; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; import fr.insee.rmes.config.auth.UserProvider; import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; @@ -34,16 +34,16 @@ public class StampsRestrictionServiceImpl implements StampsRestrictionsService { protected final RepositoryGestion repoGestion; private final AuthorizeMethodDecider authorizeMethodDecider; private final UserProvider userProvider; - private final StampsRestrictionsVerifier stampsRestrictionsVerifier; + private final ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; private static final Logger logger = LoggerFactory.getLogger(StampsRestrictionServiceImpl.class); @Autowired - public StampsRestrictionServiceImpl(RepositoryGestion repoGestion, AuthorizeMethodDecider authorizeMethodDecider, UserProvider userProvider, StampsRestrictionsVerifier stampsRestrictionsVerifier) { + public StampsRestrictionServiceImpl(RepositoryGestion repoGestion, AuthorizeMethodDecider authorizeMethodDecider, UserProvider userProvider, ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier) { this.repoGestion = repoGestion; this.authorizeMethodDecider = authorizeMethodDecider; this.userProvider = userProvider; - this.stampsRestrictionsVerifier = stampsRestrictionsVerifier; + this.resourceOwnershipByStampVerifier = resourceOwnershipByStampVerifier; } @Override @@ -64,7 +64,7 @@ private boolean isConceptOwner(List uris) throws RmesException { } public boolean isSeriesManagerWithStamp(IRI iri, String stamp) throws RmesException { - return stampsRestrictionsVerifier.isManagerForModule(stamp, iri, OpSeriesQueries::getCreatorsBySeriesUri, Constants.CREATORS); + return resourceOwnershipByStampVerifier.isManagerForModule(stamp, iri, OpSeriesQueries::getCreatorsBySeriesUri, Constants.CREATORS); } protected boolean isSeriesManager(IRI iri) throws RmesException { @@ -75,14 +75,14 @@ private boolean isIndicatorCreator(IRI iri) throws RmesException { return isOwnerForModule(getStampAsString(), List.of(iri), IndicatorsQueries::getCreatorsByIndicatorUri, Constants.CREATORS); } private boolean isConceptManager(IRI uri) throws RmesException { - return stampsRestrictionsVerifier.isManagerForModule(getStampAsString(), uri, ConceptsQueries::getManager, Constants.MANAGER); + return resourceOwnershipByStampVerifier.isManagerForModule(getStampAsString(), uri, ConceptsQueries::getManager, Constants.MANAGER); } private boolean isOwnerForModule(String stamp, List uris, QueryGenerator queryGenerator, String stampKey) throws RmesException { logger.trace("Check ownership for {} with stamp {}",uris, stampKey); - return stampsRestrictionsVerifier.checkResponsabilityForModule(stamp, uris, queryGenerator, stampKey, Stream::allMatch); + return resourceOwnershipByStampVerifier.checkResponsabilityForModule(stamp, uris, queryGenerator, stampKey, Stream::allMatch); } diff --git a/src/main/java/fr/insee/rmes/config/auth/AccessControlConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/AccessControlConfiguration.java new file mode 100644 index 000000000..ba64c5473 --- /dev/null +++ b/src/main/java/fr/insee/rmes/config/auth/AccessControlConfiguration.java @@ -0,0 +1,14 @@ +package fr.insee.rmes.config.auth; + +import fr.insee.rmes.external.services.rbac.RBACService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AccessControlConfiguration { + + @Bean + public RBACService rbacService(RBACConfiguration rbacConfiguration){ + return new RBACService(rbacConfiguration.toRBAC()); + } +} diff --git a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java index e40ad009d..4bea82fc3 100644 --- a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java @@ -1,24 +1,22 @@ package fr.insee.rmes.config.auth; -import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; -import fr.insee.rmes.model.rbac.RBAC; -import org.apache.commons.lang3.tuple.Pair; +import fr.insee.rmes.model.rbac.*; +import fr.insee.rmes.model.rbac.Module; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; import java.util.Map; import java.util.stream.Collectors; @ConfigurationProperties("rbac") -public record RBACConfiguration (Map>> config){ +public record RBACConfiguration (Map>> config){ - public Map toMapOfApplicationAccessPrivilegesByRoles() { - return config.entrySet().stream() + public RBAC toRBAC() { + return new RBAC(config.entrySet().stream() .collect(Collectors.toMap(entry->new RoleName(entry.getKey()), entry -> ApplicationAccessPrivileges.of(entry.getValue()), ApplicationAccessPrivileges::merge - )); + ))); } public record RoleName(String role) {} diff --git a/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java b/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java index f26ec5943..4e7cc0454 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/BauhausMethodSecurityExpressionHandler.java @@ -1,7 +1,7 @@ package fr.insee.rmes.config.auth.security; +import fr.insee.rmes.external.services.rbac.AuthorizationChecker; import fr.insee.rmes.external.services.rbac.RBACService; -import fr.insee.rmes.external.services.rbac.StampChecker; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,18 +20,18 @@ @Component public class BauhausMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { - private static final Logger logger= LoggerFactory.getLogger(BauhausMethodSecurityExpressionHandler.class); + private static final Logger log = LoggerFactory.getLogger(BauhausMethodSecurityExpressionHandler.class); private final RBACService rbacService; private final UserDecoder userDecoder; - private final StampChecker stampChecker; + private final AuthorizationChecker authorizationChecker; @Autowired - public BauhausMethodSecurityExpressionHandler(RBACService rbacService, UserDecoder userDecoder, StampChecker stampChecker) { + public BauhausMethodSecurityExpressionHandler(RBACService rbacService, UserDecoder userDecoder, AuthorizationChecker authorizationChecker) { this.rbacService = rbacService; this.userDecoder = userDecoder; - this.stampChecker = stampChecker; - logger.trace("Initializing GlobalMethodSecurityConfiguration with BauhausMethodSecurityExpressionHandler and DefaultRolePrefix = {}", DEFAULT_ROLE_PREFIX); + this.authorizationChecker = authorizationChecker; + log.trace("Initializing GlobalMethodSecurityConfiguration with BauhausMethodSecurityExpressionHandler and DefaultRolePrefix = {}", DEFAULT_ROLE_PREFIX); setDefaultRolePrefix(DEFAULT_ROLE_PREFIX); } @@ -39,7 +39,7 @@ public BauhausMethodSecurityExpressionHandler(RBACService rbacService, UserDecod public EvaluationContext createEvaluationContext(Supplier authentication, MethodInvocation mi) { StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi); MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue(); - context.setRootObject(SecurityExpressionRootForBauhaus.enrich(delegate, this.rbacService, this.userDecoder, this.stampChecker)); + context.setRootObject(SecurityExpressionRootForBauhaus.enrich(delegate, this.rbacService, this.userDecoder, this.authorizationChecker)); return context; } } \ No newline at end of file diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 329b8018e..74aed14b8 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -7,9 +7,10 @@ import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.exceptions.RmesRuntimeBadRequestException; import fr.insee.rmes.external.services.rbac.CheckAccessPrivilegeForUser; +import fr.insee.rmes.external.services.rbac.AuthorizationChecker; import fr.insee.rmes.external.services.rbac.RBACService; -import fr.insee.rmes.external.services.rbac.StampChecker; -import fr.insee.rmes.model.rbac.RBAC; +import fr.insee.rmes.model.rbac.Module; +import fr.insee.rmes.model.rbac.Privilege; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,19 +35,19 @@ public class SecurityExpressionRootForBauhaus implements MethodSecurityExpressio private final SecurityExpressionRoot methodSecurityExpressionRoot; private final RBACService rbacService; private final UserDecoder userDecoder; - private final StampChecker stampChecker; + private final AuthorizationChecker authorizationChecker; - public SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations methodSecurityExpressionOperations, RBACService rbacService, UserDecoder userDecoder, StampChecker stampChecker) { + public SecurityExpressionRootForBauhaus(MethodSecurityExpressionOperations methodSecurityExpressionOperations, RBACService rbacService, UserDecoder userDecoder, AuthorizationChecker authorizationChecker) { this.methodSecurityExpressionRoot = (SecurityExpressionRoot) methodSecurityExpressionOperations; this.methodSecurityExpressionOperations = methodSecurityExpressionOperations; this.rbacService = rbacService; this.userDecoder = userDecoder; - this.stampChecker = stampChecker; + this.authorizationChecker = authorizationChecker; } - public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, RBACService rbacService, UserDecoder userDecoder, StampChecker stampChecker) { - return new SecurityExpressionRootForBauhaus(requireNonNull(methodSecurityExpressionOperations), requireNonNull(rbacService), requireNonNull(userDecoder), requireNonNull(stampChecker)); + public static MethodSecurityExpressionOperations enrich(MethodSecurityExpressionOperations methodSecurityExpressionOperations, RBACService rbacService, UserDecoder userDecoder, AuthorizationChecker authorizationChecker) { + return new SecurityExpressionRootForBauhaus(requireNonNull(methodSecurityExpressionOperations), requireNonNull(rbacService), requireNonNull(userDecoder), requireNonNull(authorizationChecker)); } @Override @@ -214,27 +215,27 @@ private boolean userHasStampWichManageResource(String resourceId, BiPredicate getStamp() { @@ -253,27 +254,31 @@ private Optional getUser() { public boolean canUpdateSerie(String serieId) { - return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.SERIE).withId(serieId); + return getAccessPrivilegesForUser().isGranted(Privilege.UPDATE).on(Module.SERIE).withId(serieId); } public boolean canDeleteDataset(String datasetId) { - return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.DELETE).on(RBAC.Module.DATASET).withId(datasetId); + return getAccessPrivilegesForUser().isGranted(Privilege.DELETE).on(Module.DATASET).withId(datasetId); } public boolean canUpdateDataset(String datasetId) { - return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.UPDATE).on(RBAC.Module.DATASET).withId(datasetId); + return getAccessPrivilegesForUser().isGranted(Privilege.UPDATE).on(Module.DATASET).withId(datasetId); } public boolean canCreateDataset(String datasetId) { - return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.CREATE).on(RBAC.Module.DATASET).withId(datasetId); + return getAccessPrivilegesForUser().isGranted(Privilege.CREATE).on(Module.DATASET).withId(datasetId); } public boolean canPublishDataset(String datasetId) { - return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.PUBLISH).on(RBAC.Module.DATASET).withId(datasetId); + return getAccessPrivilegesForUser().isGranted(Privilege.PUBLISH).on(Module.DATASET).withId(datasetId); } public boolean canReadDataset(String datasetId) { - return getAccessPrivilegesForUser().isGranted(RBAC.Privilege.READ).on(RBAC.Module.DATASET).withId(datasetId); + return getAccessPrivilegesForUser().isGranted(Privilege.READ).on(Module.DATASET).withId(datasetId); + } + + public boolean canReadAllDataset(){ + return getAccessPrivilegesForUser().isGranted(Privilege.READ).on(Module.DATASET).withId(null); } private CheckAccessPrivilegeForUser getAccessPrivilegesForUser() { @@ -283,7 +288,7 @@ private CheckAccessPrivilegeForUser getAccessPrivilegesForUser() { .toList() ), getUser().orElse(User.EMPTY_USER), - this.stampChecker + this.authorizationChecker ); } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java b/src/main/java/fr/insee/rmes/external/services/rbac/AuthorizationChecker.java similarity index 77% rename from src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java rename to src/main/java/fr/insee/rmes/external/services/rbac/AuthorizationChecker.java index 501e46d05..f9acd2dd6 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/StampChecker.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/AuthorizationChecker.java @@ -1,10 +1,10 @@ package fr.insee.rmes.external.services.rbac; import fr.insee.rmes.config.auth.user.Stamp; -import fr.insee.rmes.model.rbac.RBAC; +import fr.insee.rmes.model.rbac.Module; -public interface StampChecker { - boolean userStampIsAuthorizedForResource(RBAC.Module module, String id, Stamp stamp); +public interface AuthorizationChecker { + boolean userStampIsAuthorizedForResource(Module module, String id, Stamp stamp); @Deprecated boolean isSeriesManagerWithStamp(String s, Stamp stamp); diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java index 14b0b9c1a..29f14fe2e 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java @@ -1,36 +1,38 @@ package fr.insee.rmes.external.services.rbac; import fr.insee.rmes.config.auth.user.User; -import fr.insee.rmes.model.rbac.RBAC; +import fr.insee.rmes.model.rbac.ApplicationAccessPrivileges; +import fr.insee.rmes.model.rbac.Module; +import fr.insee.rmes.model.rbac.Privilege; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicReference; -public record CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, StampChecker stampChecker, AtomicReference privilege, AtomicReference module) { +public record CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, AuthorizationChecker authorizationChecker, AtomicReference privilege, AtomicReference module) { private static final Logger log = LoggerFactory.getLogger(CheckAccessPrivilegeForUser.class); - public CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, StampChecker stampChecker) { - this(applicationAccessPrivileges, user, stampChecker, new AtomicReference<>(), new AtomicReference<>()); + public CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, AuthorizationChecker authorizationChecker) { + this(applicationAccessPrivileges, user, authorizationChecker, new AtomicReference<>(), new AtomicReference<>()); } - public CheckAccessPrivilegeForUser isGranted(RBAC.Privilege privilege) { + public CheckAccessPrivilegeForUser isGranted(Privilege privilege) { withPrivilege(privilege); return this; } - private void withPrivilege(RBAC.Privilege privilege) { + private void withPrivilege(Privilege privilege) { this.privilege.set(privilege); } - public CheckAccessPrivilegeForUser on(RBAC.Module module) { + public CheckAccessPrivilegeForUser on(Module module) { withModule(module); return this; } - private void withModule(RBAC.Module module) { + private void withModule(Module module) { this.module.set(module); } @@ -54,6 +56,6 @@ public boolean withId(String id) { } private boolean checkStampFor(String id) { - return id != null && stampChecker.userStampIsAuthorizedForResource(module.get(), id, user.stamp()); + return id != null && authorizationChecker.userStampIsAuthorizedForResource(module.get(), id, user.stamp()); } } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java index eaecd689f..e5d5d9f1a 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/RBACService.java @@ -1,11 +1,27 @@ package fr.insee.rmes.external.services.rbac; - import fr.insee.rmes.config.auth.RBACConfiguration; +import fr.insee.rmes.model.rbac.ApplicationAccessPrivileges; +import fr.insee.rmes.model.rbac.RBAC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; -public interface RBACService { +public record RBACService(RBAC rbac){ + + private static final Logger log = LoggerFactory.getLogger(RBACService.class); + + public ApplicationAccessPrivileges computeRbac(List roles) { + ApplicationAccessPrivileges applicationAccessPrivileges = roles.stream() + .map(this::getApplicationAccessPrivileges) + .reduce(ApplicationAccessPrivileges::merge) + .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); + log.atTrace().log(()->"Privileges computed for roles "+roles+" : "+applicationAccessPrivileges); + return applicationAccessPrivileges; + } - ApplicationAccessPrivileges computeRbac(List roles); + private ApplicationAccessPrivileges getApplicationAccessPrivileges(RBACConfiguration.RoleName roleName) { + return rbac.applicationAccessPrivilegesByRoles().getOrDefault(roleName, ApplicationAccessPrivileges.NO_PRIVILEGE); + } } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java b/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java deleted file mode 100644 index 120dede3d..000000000 --- a/src/main/java/fr/insee/rmes/external/services/rbac/RBACServiceImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package fr.insee.rmes.external.services.rbac; - -import fr.insee.rmes.config.auth.RBACConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Map; - -@Service -public record RBACServiceImpl(Map applicationAccessPrivilegesByRoles) implements RBACService { - - private static final Logger log = LoggerFactory.getLogger(RBACServiceImpl.class); - - @Autowired - public RBACServiceImpl(RBACConfiguration configuration) { - this(configuration.toMapOfApplicationAccessPrivilegesByRoles()); - } - - @Override - public ApplicationAccessPrivileges computeRbac(List roles) { - ApplicationAccessPrivileges applicationAccessPrivileges = roles.stream() - .map(this::getApplicationAccessPrivileges) - .reduce(ApplicationAccessPrivileges::merge) - .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); - log.atTrace().log(()->"Privileges computed for roles "+roles+" : "+applicationAccessPrivileges); - return applicationAccessPrivileges; - } - - private ApplicationAccessPrivileges getApplicationAccessPrivileges(RBACConfiguration.RoleName roleName) { - return applicationAccessPrivilegesByRoles.getOrDefault(roleName, ApplicationAccessPrivileges.NO_PRIVILEGE); - } -} diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java b/src/main/java/fr/insee/rmes/model/rbac/ApplicationAccessPrivileges.java similarity index 58% rename from src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java rename to src/main/java/fr/insee/rmes/model/rbac/ApplicationAccessPrivileges.java index d09f53a9d..177568689 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/ApplicationAccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/model/rbac/ApplicationAccessPrivileges.java @@ -1,7 +1,4 @@ -package fr.insee.rmes.external.services.rbac; - -import fr.insee.rmes.model.rbac.ModuleAccessPrivileges; -import fr.insee.rmes.model.rbac.RBAC; +package fr.insee.rmes.model.rbac; import java.util.EnumMap; import java.util.Map; @@ -12,13 +9,18 @@ * An instance of AccessPrivileges gathers strategysByPrivileges for all modules of the application. * */ -public record ApplicationAccessPrivileges(EnumMap privilegesByModules) { - public static final ApplicationAccessPrivileges NO_PRIVILEGE = new ApplicationAccessPrivileges(new EnumMap<>(RBAC.Module.class)); +public record ApplicationAccessPrivileges(EnumMap privilegesByModules) { + + public ApplicationAccessPrivileges { + Objects.requireNonNull(privilegesByModules); + } + + public static final ApplicationAccessPrivileges NO_PRIVILEGE = new ApplicationAccessPrivileges(new EnumMap<>(Module.class)); - public static ApplicationAccessPrivileges of(Map> privilegesByModules) { + public static ApplicationAccessPrivileges of(Map> privilegesByModules) { return new ApplicationAccessPrivileges(new EnumMap<>(privilegesByModules.entrySet().stream() .collect( - Collectors.toMap(Map.Entry>::getKey, + Collectors.toMap(Map.Entry>::getKey, entry -> new ModuleAccessPrivileges(entry.getValue()), ModuleAccessPrivileges::merge ) @@ -27,14 +29,14 @@ public static ApplicationAccessPrivileges of(Map(RBAC.Module.class); - for (RBAC.Module module : RBAC.Module.values()) { + var mergedMap = new EnumMap(Module.class); + for (Module module : Module.values()) { mergedMap.put(module, ModuleAccessPrivileges.merge(privilegesByModules.get(module), other.privilegesByModules.get(module))); } return new ApplicationAccessPrivileges(mergedMap); } - public ModuleAccessPrivileges privilegesForModule(RBAC.Module module) { + public ModuleAccessPrivileges privilegesForModule(Module module) { var privileges = privilegesByModules.get(module); return privileges==null?ModuleAccessPrivileges.NO_PRIVILEGE:privileges; } diff --git a/src/main/java/fr/insee/rmes/model/rbac/Module.java b/src/main/java/fr/insee/rmes/model/rbac/Module.java new file mode 100644 index 000000000..61dd182cc --- /dev/null +++ b/src/main/java/fr/insee/rmes/model/rbac/Module.java @@ -0,0 +1,13 @@ +package fr.insee.rmes.model.rbac; + +public enum Module { + CONCEPT, + COLLECTION, + FAMILY, + SERIE, + OPERATION, + INDICATOR, + SIMS, + CLASSIFICATION, + DATASET +} diff --git a/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java b/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java index dd9ace7a3..730e747e0 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java +++ b/src/main/java/fr/insee/rmes/model/rbac/ModuleAccessPrivileges.java @@ -2,13 +2,18 @@ import java.util.EnumMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; -public record ModuleAccessPrivileges(EnumMap strategysByPrivileges) { +public record ModuleAccessPrivileges(EnumMap strategysByPrivileges) { - public static final ModuleAccessPrivileges NO_PRIVILEGE = new ModuleAccessPrivileges(new EnumMap<>(RBAC.Privilege.class)); + public ModuleAccessPrivileges{ + Objects.requireNonNull(strategysByPrivileges); + } + + public static final ModuleAccessPrivileges NO_PRIVILEGE = new ModuleAccessPrivileges(new EnumMap<>(Privilege.class)); - public ModuleAccessPrivileges(Map strategysByPrivileges){ + public ModuleAccessPrivileges(Map strategysByPrivileges){ this(new EnumMap<>(strategysByPrivileges)); } @@ -23,14 +28,14 @@ private ModuleAccessPrivileges merge(ModuleAccessPrivileges other) { if (other == null){ return this; } - EnumMap mergedPrivileges = new EnumMap<>(RBAC.Privilege.class); - for (RBAC.Privilege privilege : RBAC.Privilege.values()) { - mergedPrivileges.put(privilege, RBAC.Strategy.merge(strategysByPrivileges.get(privilege), other.strategysByPrivileges.get(privilege))); + EnumMap mergedPrivileges = new EnumMap<>(Privilege.class); + for (Privilege privilege : Privilege.values()) { + mergedPrivileges.put(privilege, Strategy.merge(strategysByPrivileges.get(privilege), other.strategysByPrivileges.get(privilege))); } return new ModuleAccessPrivileges(mergedPrivileges); } - public Optional strategyFor(RBAC.Privilege privilege) { + public Optional strategyFor(Privilege privilege) { return Optional.ofNullable(strategysByPrivileges.get(privilege)); } } diff --git a/src/main/java/fr/insee/rmes/model/rbac/Privilege.java b/src/main/java/fr/insee/rmes/model/rbac/Privilege.java new file mode 100644 index 000000000..8e45d92a3 --- /dev/null +++ b/src/main/java/fr/insee/rmes/model/rbac/Privilege.java @@ -0,0 +1,10 @@ +package fr.insee.rmes.model.rbac; + +public enum Privilege { + CREATE, + READ, + UPDATE, + DELETE, + PUBLISH, + VALIDATE +} diff --git a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java index 0317e5c02..b73a4192a 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/RBAC.java +++ b/src/main/java/fr/insee/rmes/model/rbac/RBAC.java @@ -1,47 +1,14 @@ package fr.insee.rmes.model.rbac; -public class RBAC { - public enum Module { - CONCEPT, - COLLECTION, - FAMILY, - SERIE, - OPERATION, - INDICATOR, - SIMS, - CLASSIFICATION, - DATASET - } - - public enum Privilege { - CREATE, - READ, - UPDATE, - DELETE, - PUBLISH, - VALIDATE - } +import fr.insee.rmes.config.auth.RBACConfiguration; - public enum Strategy { - ALL, STAMP; +import java.util.Map; +import java.util.Objects; - public static Strategy merge(Strategy strategy1, Strategy strategy2) { - if (strategy1 != null){ - return strategy1.merge(strategy2); - } - return strategy2; - } +public record RBAC(Map applicationAccessPrivilegesByRoles) { - private Strategy merge(Strategy other) { - return switch (other){ - case ALL -> ALL; - case STAMP -> this; - case null -> this; - }; - } - - public boolean isAllStampAuthorized() { - return this == ALL; - } + public RBAC{ + Objects.requireNonNull(applicationAccessPrivilegesByRoles); } + } diff --git a/src/main/java/fr/insee/rmes/model/rbac/Strategy.java b/src/main/java/fr/insee/rmes/model/rbac/Strategy.java new file mode 100644 index 000000000..897026bd2 --- /dev/null +++ b/src/main/java/fr/insee/rmes/model/rbac/Strategy.java @@ -0,0 +1,24 @@ +package fr.insee.rmes.model.rbac; + +public enum Strategy { + ALL, STAMP; + + public static Strategy merge(Strategy strategy1, Strategy strategy2) { + if (strategy1 != null) { + return strategy1.merge(strategy2); + } + return strategy2; + } + + private Strategy merge(Strategy other) { + return switch (other) { + case ALL -> ALL; + case STAMP -> this; + case null -> this; + }; + } + + public boolean isAllStampAuthorized() { + return this == ALL; + } +} diff --git a/src/main/java/fr/insee/rmes/webservice/UserResources.java b/src/main/java/fr/insee/rmes/webservice/UserResources.java index 970b5a1cf..3ad70bf7b 100644 --- a/src/main/java/fr/insee/rmes/webservice/UserResources.java +++ b/src/main/java/fr/insee/rmes/webservice/UserResources.java @@ -5,8 +5,8 @@ import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.authentication.stamps.StampsService; -import fr.insee.rmes.external.services.rbac.ApplicationAccessPrivileges; import fr.insee.rmes.external.services.rbac.RBACService; +import fr.insee.rmes.model.rbac.ApplicationAccessPrivileges; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index 1bbf2ca30..e164b457b 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -86,6 +86,7 @@ public String publishDataset(@PathVariable(Constants.ID) String datasetId) throw return this.datasetService.publishDataset(datasetId); } + @PreAuthorize("canReadAllDataset()") @GetMapping(value = "/archivageUnits", consumes = APPLICATION_JSON_VALUE) @Operation(operationId = "getArchivageUnits", summary = "Get all archivage units") public String getArchivageUnits() throws RmesException { diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java index 4c6b38800..dfc86b03e 100644 --- a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java +++ b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java @@ -1,6 +1,7 @@ package fr.insee.rmes.bauhaus_services; -import fr.insee.rmes.model.rbac.RBAC; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; +import fr.insee.rmes.model.rbac.Module; import fr.insee.rmes.stubs.StampRestritionVerifierStub; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -10,12 +11,12 @@ class StampAuthorizationCheckerTest { - private final StampAuthorizationChecker stampAuthorizationChecker=new StampAuthorizationChecker(new StampRestritionVerifierStub()); + private final AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker=new AuthorizationCheckerWithResourceOwnershipByStamp(new StampRestritionVerifierStub()); @ParameterizedTest @ValueSource(strings = {SERIES_STUB_ID, DATASET_STUB_ID, DISTRIBUTION_STUB_ID, COMPONENT_STUB_ID, STRUCTURE_STUB_ID, CODES_LISTES_STUB_ID}) void userStampIsAuthorizedForResource(String id) { //Given - RBAC.Module module = RBAC.Module.DATASET; + Module module = Module.DATASET; //When then assertThat(this.stampAuthorizationChecker.userStampIsAuthorizedForResource(module, id, null)).isTrue(); } diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java index f1b5737ef..bfb0a10a7 100644 --- a/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java +++ b/src/test/java/fr/insee/rmes/bauhaus_services/stamps/StampsRestrictionServiceImplTest.java @@ -1,6 +1,6 @@ package fr.insee.rmes.bauhaus_services.stamps; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifierImpl; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifierImpl; import fr.insee.rmes.bauhaus_services.rdf_utils.RepositoryGestion; import fr.insee.rmes.config.ConfigStub; import fr.insee.rmes.config.auth.UserProvider; @@ -34,7 +34,7 @@ class StampsRestrictionServiceImplTest { @Mock private RepositoryGestion repoGestion; - private StampsRestrictionsVerifierImpl stampsRestrictionsVerifier; + private ResourceOwnershipByStampVerifierImpl stampsRestrictionsVerifier; @Mock private UserProvider userProvider; private final IRI iriToCheck = SimpleValueFactory.getInstance().createIRI("http://bauhaus/operations/serie/s2132"); @@ -57,7 +57,7 @@ static void configureOpSeriesQueries() { @BeforeEach void injectRepositoryGestion() { - stampsRestrictionsVerifier = new StampsRestrictionsVerifierImpl(null, repoGestion, null); + stampsRestrictionsVerifier = new ResourceOwnershipByStampVerifierImpl(null, repoGestion, null); } @Test diff --git a/src/test/java/fr/insee/rmes/config/RBACTest.java b/src/test/java/fr/insee/rmes/config/RBACTest.java new file mode 100644 index 000000000..576646006 --- /dev/null +++ b/src/test/java/fr/insee/rmes/config/RBACTest.java @@ -0,0 +1,19 @@ +package fr.insee.rmes.config; + +import fr.insee.rmes.config.auth.AccessControlConfiguration; +import fr.insee.rmes.config.auth.RBACConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = "spring.config.additional-location=classpath:rbac-test.yml") +@ContextConfiguration(classes = {AccessControlConfiguration.class}) +@EnableConfigurationProperties(RBACConfiguration.class) +public @interface RBACTest { +} diff --git a/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java b/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java index 5b3c83d30..34f7e5549 100644 --- a/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java +++ b/src/test/java/fr/insee/rmes/config/auth/security/RBACConfigurationTest.java @@ -1,16 +1,14 @@ package fr.insee.rmes.config.auth.security; +import fr.insee.rmes.config.RBACTest; import fr.insee.rmes.config.auth.RBACConfiguration; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.SpringBootTest; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest(properties = "spring.config.additional-location=classpath:rbac.yml") -@EnableConfigurationProperties(RBACConfiguration.class) +@RBACTest class RBACConfigurationTest { @Autowired @@ -18,6 +16,6 @@ class RBACConfigurationTest { @Test void testReadRbacConfig() { - assertThat(rbacConfiguration.toMapOfApplicationAccessPrivilegesByRoles()).isNotNull(); + assertThat(rbacConfiguration.toRBAC()).isNotNull(); } } \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java deleted file mode 100644 index 58a6417be..000000000 --- a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceImplTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package fr.insee.rmes.external.services.rbac; - -import fr.insee.rmes.config.auth.RBACConfiguration; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest -class RBACServiceImplTest { - - @Autowired - RBACServiceImpl rbacService; - - @Test - void shouldReturnAnEmptyMapIfMissingRoles() { - ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of()); - assertThat(applicationAccessPrivileges).isEqualTo(ApplicationAccessPrivileges.NO_PRIVILEGE); - } - - @Test - void shouldReturnAnEmptyMapIfUnknownRole() { - ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("UNKNOWN"))); - assertThat(applicationAccessPrivileges).isEqualTo(ApplicationAccessPrivileges.NO_PRIVILEGE); - } -} \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java new file mode 100644 index 000000000..324776e77 --- /dev/null +++ b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java @@ -0,0 +1,112 @@ +package fr.insee.rmes.external.services.rbac; + +import fr.insee.rmes.config.RBACTest; +import fr.insee.rmes.config.auth.RBACConfiguration; +import fr.insee.rmes.model.rbac.Module; +import fr.insee.rmes.model.rbac.*; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static fr.insee.rmes.model.rbac.Module.CONCEPT; +import static fr.insee.rmes.model.rbac.Privilege.*; +import static fr.insee.rmes.model.rbac.Strategy.ALL; +import static fr.insee.rmes.model.rbac.Strategy.STAMP; +import static java.util.function.Predicate.not; +import static org.assertj.core.api.Assertions.assertThat; + +@RBACTest +class RBACServiceTest { + + @Autowired + RBACService rbacService; + + @Test + void shouldReturnAnEmptyMapIfMissingRoles() { + ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of()); + assertThat(applicationAccessPrivileges).isEqualTo(ApplicationAccessPrivileges.NO_PRIVILEGE); + } + + @Test + void shouldReturnAnEmptyMapIfUnknownRole() { + ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("UNKNOWN"))); + assertThat(applicationAccessPrivileges).isEqualTo(ApplicationAccessPrivileges.NO_PRIVILEGE); + } + + @Test + void shouldReturnAllReadWithUserRole() { + checkCrossModulesWithPrivilegeForStrategyRegardingRole("Utilisateur_RMESGNCS", Module.values(), new Privilege[]{READ}, ALL); + } + + private void checkCrossModulesWithPrivilegeForStrategyRegardingRole(String role, Module[] modules, Privilege[] privileges, Strategy strategy) { + ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName(role))); + var expected = new ApplicationAccessPrivileges(new EnumMap<>(Arrays.stream(modules).collect(Collectors.toMap( + Function.identity(), + ignored-> new ModuleAccessPrivileges(Arrays.stream(privileges).collect(Collectors.toMap( + Function.identity(), + ignoredAgain -> strategy, + Strategy::merge + ))), + ModuleAccessPrivileges::merge + )))); + assertThat(applicationAccessPrivileges).isEqualTo(expected); + } + + @Test + void shouldReturnNeverWriteWithUserRole() { + ApplicationAccessPrivileges applicationAccessPrivileges = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("Utilisateur_RMESGNCS"))); + assertThat(allPrivileges(applicationAccessPrivileges)).isNotEmpty().allMatch(READ::equals); + } + + private static Stream allPrivileges(ApplicationAccessPrivileges applicationAccessPrivileges) { + return applicationAccessPrivileges.privilegesByModules().values().stream().flatMap(map -> map.strategysByPrivileges().keySet().stream()); + } + + @Test + void shouldReturnAllPrivilegeWithAdminRole() { + checkCrossModulesWithPrivilegeForStrategyRegardingRole("Administrateur_RMESGNCS", Module.values(), Privilege.values(), ALL); + } + + @Test + void shouldReturnStampPrivilegeForConceptsWithGestionnaireConcept() { + checkCrossModulesWithPrivilegeForStrategyRegardingRole("Gestionnaire_concept_RMESGNCS", new Module[]{CONCEPT}, new Privilege[]{CREATE, READ, PUBLISH, UPDATE, VALIDATE}, STAMP); + } + + @Test + void testMergeAdminAndSimpleUserShouldReturnAdmin(){ + var adminPrivilege = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("Administrateur_RMESGNCS"))); + var mergedPrivilege = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("Utilisateur_RMESGNCS"), new RBACConfiguration.RoleName("Administrateur_RMESGNCS"))); + var mergedPrivilegeReversed = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("Administrateur_RMESGNCS"), new RBACConfiguration.RoleName("Utilisateur_RMESGNCS"))); + assertThat(mergedPrivilege).isEqualTo(adminPrivilege); + assertThat(mergedPrivilegeReversed).isEqualTo(adminPrivilege); + } + + @Test + void testMergeGestionnaireAndSimpleUserShouldReturnMix(){ + var mergedPrivilege = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("Utilisateur_RMESGNCS"), new RBACConfiguration.RoleName("Gestionnaire_concept_RMESGNCS"))); + var mergedPrivilegeReversed = rbacService.computeRbac(List.of(new RBACConfiguration.RoleName("Gestionnaire_concept_RMESGNCS"), new RBACConfiguration.RoleName("Utilisateur_RMESGNCS"))); + var allModulesExceptConcept = Arrays.stream(Module.values()).filter(not(CONCEPT::equals)).toList(); + assertThat(allModulesExceptConcept.stream().map(mergedPrivilege::privilegesForModule).flatMap(m->m.strategysByPrivileges().keySet().stream())).isNotEmpty().allMatch(READ::equals); + assertThat(allModulesExceptConcept.stream().map(mergedPrivilegeReversed::privilegesForModule).flatMap(m->m.strategysByPrivileges().keySet().stream())).isNotEmpty().allMatch(READ::equals); + List writePrivilege=List.of(PUBLISH, UPDATE, VALIDATE); + ModuleAccessPrivileges moduleAccessPrivilegesReversed = mergedPrivilegeReversed.privilegesForModule(CONCEPT); + assertThat(writePrivilege.stream().map(moduleAccessPrivilegesReversed::strategyFor)).isNotEmpty().allMatch(Optional.of(STAMP)::equals); + //TODO : fix fail when comment at https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issuecomment-2284329692 will be answered + assertThat(moduleAccessPrivilegesReversed.strategyFor(CREATE)).isPresent().isEqualTo(ALL); + assertThat(moduleAccessPrivilegesReversed.strategyFor(READ)).isEqualTo(Optional.of(ALL)); + + ModuleAccessPrivileges moduleAccessPrivileges = mergedPrivilege.privilegesForModule(CONCEPT); + assertThat(writePrivilege.stream().map(moduleAccessPrivileges::strategyFor)).isNotEmpty().allMatch(Optional.of(STAMP)::equals); + //TODO : fix fail when comment at https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issuecomment-2284329692 will be answered + assertThat(moduleAccessPrivileges.strategyFor(CREATE)).isPresent().isEqualTo(ALL); + assertThat(moduleAccessPrivileges.strategyFor(READ)).isPresent().isEqualTo(Optional.of(ALL)); + } + +} \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java b/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java index fc45c21ab..0c761fff2 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/ConfigurationForTestWithAuth.java @@ -1,14 +1,14 @@ package fr.insee.rmes.integration.authorizations; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; +import fr.insee.rmes.config.auth.AccessControlConfiguration; import fr.insee.rmes.config.auth.RBACConfiguration; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.BauhausMethodSecurityExpressionHandler; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; import fr.insee.rmes.config.auth.security.DefaultSecurityContext; import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; -import fr.insee.rmes.external.services.rbac.RBACServiceImpl; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Import; @@ -21,7 +21,7 @@ CommonSecurityConfiguration.class, UserProviderFromSecurityContext.class, BauhausMethodSecurityExpressionHandler.class, - RBACServiceImpl.class, - StampAuthorizationChecker.class}) + AccessControlConfiguration.class, + AuthorizationCheckerWithResourceOwnershipByStamp.class}) public class ConfigurationForTestWithAuth { } diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/PublicResourcesAuthorizationsTest.java b/src/test/java/fr/insee/rmes/integration/authorizations/PublicResourcesAuthorizationsTest.java index abdde6499..ee084e282 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/PublicResourcesAuthorizationsTest.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/PublicResourcesAuthorizationsTest.java @@ -1,6 +1,6 @@ package fr.insee.rmes.integration.authorizations; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; @@ -46,7 +46,7 @@ class PublicResourcesAuthorizationsTest { @Autowired private MockMvc mvc; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean private StampsService stampsService; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java index fa1a87dcb..a7b59c9de 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestCodeListsResourcesEnvProd.java @@ -2,8 +2,8 @@ import fr.insee.rmes.bauhaus_services.CodeListService; import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.config.auth.roles.Roles; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.model.ValidationStatus; @@ -51,9 +51,9 @@ class TestCodeListsResourcesEnvProd { @MockBean protected OperationsDocumentationsService documentationsService; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean - StampsRestrictionsVerifier stampsRestrictionsVerifier; + ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; private final String idep = "xxxxxx"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java index 48f331ebd..3ef7f283d 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestDatasetsResourcesEnvProd.java @@ -1,20 +1,12 @@ package fr.insee.rmes.integration.authorizations; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; -import fr.insee.rmes.config.Config; -import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; -import fr.insee.rmes.config.auth.roles.Roles; -import fr.insee.rmes.config.auth.security.BauhausMethodSecurityExpressionHandler; -import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; -import fr.insee.rmes.config.auth.security.DefaultSecurityContext; -import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; import fr.insee.rmes.config.auth.user.Stamp; -import fr.insee.rmes.external.services.rbac.RBACServiceImpl; import fr.insee.rmes.model.dataset.Dataset; import fr.insee.rmes.webservice.dataset.DatasetResources; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -22,12 +14,15 @@ import org.springframework.http.MediaType; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.util.Arrays; import java.util.List; +import java.util.function.Function; import static fr.insee.rmes.integration.authorizations.TokenForTestsConfiguration.*; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(controllers = DatasetResources.class, @@ -36,6 +31,7 @@ "jwt.role-claim=" + ROLE_CLAIM, "jwt.id-claim=" + ID_CLAIM, "jwt.role-claim.roles=" + KEY_FOR_ROLES_IN_ROLE_CLAIM, + "spring.config.additional-location=classpath:rbac-test.yml", "logging.level.org.springframework.security=DEBUG", "logging.level.org.springframework.security.web.access=TRACE", "logging.level.fr.insee.rmes.config.auth=TRACE", @@ -53,7 +49,7 @@ class TestDatasetsResourcesEnvProd { @MockBean DatasetService datasetService; @MockBean - StampsRestrictionsVerifier stampsRestrictionsVerifier; + ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; private static Dataset dataset; @@ -64,282 +60,63 @@ class TestDatasetsResourcesEnvProd { int datasetId=10; - - @Test - void shouldGetDatasetsWithAnyRole() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - - mvc.perform(get("/datasets").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDatasetsWhenAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - - mvc.perform(get("/datasets").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDatasetsWhenDatasetContributor() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - - mvc.perform(get("/datasets").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDatasetWithAnyRole() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - - mvc.perform(get("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); + @CsvSource(textBlock = """ + #Method, resource, droits, Optional, HttpStatusCode, hasRightStamp + GET, '/datasets', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}',, 200, + GET, '/datasets', '{Utilisateur_RMESGNCS}',, 200, + GET, '/datasets', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 200, + GET, '/datasets/10', '{Utilisateur_RMESGNCS}',, 200, + GET, '/datasets/10', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}',, 200, + GET, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 200, + GET, '/datasets/10/distributions', '{Utilisateur_RMESGNCS}',, 200, + GET, '/datasets/10/distributions', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}',, 200, + GET, '/datasets/10/distributions', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 200, + POST, '/datasets', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}','{"id": "1"}', 201, + POST, '/datasets', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}','{"id": "1"}', 201, + POST, '/datasets', '{Utilisateur_RMESGNCS}', '{"id": "1"}', 403, + PUT, '/datasets/10', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}','{"id": "1"}', 200, + PUT, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}','{"id": "1"}', 200, true + PUT, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}','{"id": "1"}', 403, false + PUT, '/datasets/10', '{Utilisateur_RMESGNCS}', '{"id": "1"}', 403, + GET, '/datasets/archivageUnits', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}',, 200, + GET, '/datasets/archivageUnits', '{Utilisateur_RMESGNCS}',, 200, + GET, '/datasets/archivageUnits', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 200, + PATCH, '/datasets/10', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}','{"numObservations": 1}', 200, + PATCH, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}','{"numObservations": 1}', 200, true + PATCH, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}','{"numObservations": 1}', 403, false + PATCH, '/datasets/10', '{Utilisateur_RMESGNCS}', '{"numObservations": 1}', 403, + DELETE, '/datasets/10', '{Utilisateur_RMESGNCS}',, 403, + DELETE, '/datasets/10', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}',, 200, + DELETE, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 200, true + DELETE, '/datasets/10', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 403, false + PUT, '/datasets/10/validate', '{Utilisateur_RMESGNCS, Administrateur_RMESGNCS}',, 200, + PUT, '/datasets/10/validate', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 200, true + PUT, '/datasets/10/validate', '{Utilisateur_RMESGNCS, Gestionnaire_jeu_donnees_RMESGNCS}',, 403, false + PUT, '/datasets/10/validate', '{Utilisateur_RMESGNCS}', , 403, + """) + @ParameterizedTest(name = "{0} {1} for {2} => {4}") + void shouldGetDatasets(String method, String getResource, String roles, String content, int expectedStatus, Boolean hasRightStamp) throws Exception { + if (hasRightStamp!=null){ + when(resourceOwnershipByStampVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(hasRightStamp); + } + configureJwtDecoderMock(jwtDecoder, idep, timbre, toList(roles)); + Function httpCall = switch(method){ + case "GET" -> MockMvcRequestBuilders::get; + case "POST" -> MockMvcRequestBuilders::post; + case "PUT" -> MockMvcRequestBuilders::put; + case "DELETE" -> MockMvcRequestBuilders::delete; + case "PATCH" -> MockMvcRequestBuilders::patch; + default -> throw new IllegalArgumentException("Unknown method: " + method); + }; + mvc.perform(httpCall.apply(getResource).header("Authorization", "Bearer toto") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content==null?new byte[0]:content.getBytes())) + .andExpect(status().is(expectedStatus)); + } + + private List toList(String roles) { + return Arrays.stream(roles.substring(1,roles.length()-1).split(",")).map(String::trim).toList(); } - @Test - void shouldGetDatasetWhenAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - - mvc.perform(get("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDatasetWhenDatasetContributor() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - - mvc.perform(get("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDistributionsWithAnyRole() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - - mvc.perform(get("/datasets/" + datasetId + "/distributions").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDistributionsWhenAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - - mvc.perform(get("/datasets/" + datasetId + "/distributions").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldGetDistributionsWhenDatasetContributor() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - - mvc.perform(get("/datasets/" + datasetId + "/distributions").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldCreateADatasetIfAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - mvc.perform(post("/datasets/").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isCreated()); - } - - @Test - void shouldCreateADatasetIfDatasetContributor() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(post("/datasets/").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isCreated()); - } - - @Test - void shouldNotCreateADataset() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - mvc.perform(post("/datasets/").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isForbidden()); - } - - @Test - void shouldUpdateADatasetIfAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - mvc.perform(put("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isOk()); - } - - @Test - void shouldUpdateADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(put("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isOk()); - } - - @Test - void shouldNotUpdateADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(put("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isForbidden()); - } - - @Test - void shouldNotUpdateADataset() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - mvc.perform(put("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"id\": \"1\"}")) - .andExpect(status().isForbidden()); - } - - @Test - void shouldPublishADatasetIfAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - mvc.perform(put("/datasets/" + datasetId + "/validate").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldPublishADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(put("/datasets/" + datasetId + "/validate").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldNotPublishADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(put("/datasets/" + datasetId + "/validate").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); - } - - @Test - void shouldNotPublisheADataset() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - mvc.perform(put("/datasets/" + datasetId + "/validate").header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); - } - - @Test - void shouldDeleteADatasetIfAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - dataset=new Dataset(); - dataset.setValidationState("Unpublished"); - mvc.perform(delete("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - void shouldDeleteADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - dataset=new Dataset(); - dataset.setValidationState("Unpublished"); - mvc.perform(delete("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - - @Test - void shouldNotDeleteADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(delete("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); - } - - @Test - void shouldNotDeleteADataset() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - mvc.perform(delete("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()); - } - - @Test - void shouldPatchADatasetIfAdmin() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.ADMIN)); - mvc.perform(patch("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"numObservations\": 1}")) - .andExpect(status().isOk()); - } - - @Test - void shouldPatchADatasetIfDatasetContributorBasedOnStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(true); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(patch("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"numObservations\": 1}")) - .andExpect(status().isOk()); - } - - @Test - void shouldNotPatchADatasetIfDatasetContributorWithoutStamp() throws Exception { - when(stampsRestrictionsVerifier.isDatasetManagerWithStamp(String.valueOf(datasetId),new Stamp(timbre))).thenReturn(false); - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of(Roles.DATASET_CONTRIBUTOR)); - mvc.perform(patch("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"numObservations\": 1}")) - .andExpect(status().isForbidden()); - } - - @Test - void shouldNotPatchADataset() throws Exception { - configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of()); - mvc.perform(patch("/datasets/" + datasetId).header("Authorization", "Bearer toto") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content("{\"numObservations\": 1}")) - .andExpect(status().isForbidden()); - } - - } diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java index 70253b9b1..15d6d2a37 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestDistributionsResourcesEnvProd.java @@ -1,17 +1,11 @@ package fr.insee.rmes.integration.authorizations; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; import fr.insee.rmes.bauhaus_services.distribution.DistributionService; -import fr.insee.rmes.config.Config; -import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.roles.Roles; -import fr.insee.rmes.config.auth.security.BauhausMethodSecurityExpressionHandler; -import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; -import fr.insee.rmes.config.auth.security.DefaultSecurityContext; -import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.webservice.distribution.DistributionResources; import org.junit.jupiter.api.Test; @@ -53,9 +47,9 @@ class TestDistributionsResourcesEnvProd { @MockBean private DistributionService distributionService; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean - StampsRestrictionsVerifier stampsRestrictionsVerifier; + ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; private final String idep = "xxxxxx"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestGeographyResourcesAuthorizationsEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestGeographyResourcesAuthorizationsEnvProd.java index cff770453..c552ff004 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestGeographyResourcesAuthorizationsEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestGeographyResourcesAuthorizationsEnvProd.java @@ -1,7 +1,7 @@ package fr.insee.rmes.integration.authorizations; import fr.insee.rmes.bauhaus_services.GeographyService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; @@ -47,7 +47,7 @@ class TestGeographyResourcesAuthorizationsEnvProd { private GeographyService geographyService; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestIndicatorsResourcesAuthorizationsEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestIndicatorsResourcesAuthorizationsEnvProd.java index 76768ef59..8adcac9fa 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestIndicatorsResourcesAuthorizationsEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestIndicatorsResourcesAuthorizationsEnvProd.java @@ -2,14 +2,13 @@ import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.OperationsService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.roles.Roles; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; import fr.insee.rmes.config.auth.security.DefaultSecurityContext; import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; -import fr.insee.rmes.config.auth.security.SecurityExpressionRootForBauhaus; import fr.insee.rmes.webservice.operations.IndicatorsResources; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -57,7 +56,7 @@ class TestIndicatorsResourcesAuthorizationsEnvProd { @MockBean private JwtDecoder jwtDecoder; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; private final String idep = "xxxxux"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestMetadataReportResourcesAuthorizationsEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestMetadataReportResourcesAuthorizationsEnvProd.java index 9401fe892..1212a70b7 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestMetadataReportResourcesAuthorizationsEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestMetadataReportResourcesAuthorizationsEnvProd.java @@ -2,9 +2,7 @@ import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.OperationsService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.bauhaus_services.datasets.DatasetQueries; -import fr.insee.rmes.bauhaus_services.rdf_utils.FreeMarkerUtils; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; @@ -13,8 +11,8 @@ import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.model.operations.documentations.Documentation; import fr.insee.rmes.model.operations.documentations.MSD; +import fr.insee.rmes.utils.XMLUtils; import fr.insee.rmes.webservice.operations.MetadataReportResources; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -30,19 +28,15 @@ import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.test.web.servlet.MockMvc; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static fr.insee.rmes.integration.authorizations.TokenForTestsConfiguration.*; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import fr.insee.rmes.utils.XMLUtils; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(controllers = MetadataReportResources.class, properties = {"fr.insee.rmes.bauhaus.env=PROD", @@ -72,7 +66,7 @@ class TestMetadataReportResourcesAuthorizationsEnvProd { private OperationsService operationsService; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean private JwtDecoder jwtDecoder; @@ -81,7 +75,7 @@ class TestMetadataReportResourcesAuthorizationsEnvProd { private final String timbre = "XX59-YYY"; @Test - public void testGetMSDJson() throws Exception { + void testGetMSDJson() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String jsonResponse = "{\"key\":\"value\"}"; @@ -97,7 +91,7 @@ public void testGetMSDJson() throws Exception { } @Test - public void testGetMSDXml() throws Exception { + void testGetMSDXml() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); MSD msd = new MSD(); @@ -117,7 +111,7 @@ public void testGetMSDXml() throws Exception { } @Test - public void testGetMSDJsonRmesException() throws Exception { + void testGetMSDJsonRmesException() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); when(documentationsService.getMSDJson()).thenThrow(new RmesException(HttpStatus.INTERNAL_SERVER_ERROR, "Error", "Detailed error message")); @@ -129,7 +123,7 @@ public void testGetMSDJsonRmesException() throws Exception { } @Test - public void testGetMSDXmlRmesException() throws Exception { + void testGetMSDXmlRmesException() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); when(documentationsService.getMSD()).thenThrow(new RmesException(HttpStatus.INTERNAL_SERVER_ERROR, "Error", "Detailed error message")); @@ -142,7 +136,7 @@ public void testGetMSDXmlRmesException() throws Exception { @Test - public void testGetMetadataAttribute() throws Exception { + void testGetMetadataAttribute() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String id = "testId"; @@ -159,7 +153,7 @@ public void testGetMetadataAttribute() throws Exception { } @Test - public void testGetMetadataAttributeRmesException() throws Exception { + void testGetMetadataAttributeRmesException() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String id = "testId"; @@ -173,7 +167,7 @@ public void testGetMetadataAttributeRmesException() throws Exception { } @Test - public void testGetMetadataAttributes() throws Exception { + void testGetMetadataAttributes() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String jsonResponse = "{\"key\":\"value\"}"; @@ -189,7 +183,7 @@ public void testGetMetadataAttributes() throws Exception { } @Test - public void testGetMetadataAttributesRmesException() throws Exception { + void testGetMetadataAttributesRmesException() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); when(documentationsService.getMetadataAttributes()).thenThrow(new RmesException(HttpStatus.INTERNAL_SERVER_ERROR, "Error", "Detailed error message")); @@ -201,7 +195,7 @@ public void testGetMetadataAttributesRmesException() throws Exception { } @Test - public void testGetMetadataReport() throws Exception { + void testGetMetadataReport() throws Exception { String id = "1234"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -218,7 +212,7 @@ public void testGetMetadataReport() throws Exception { } @Test - public void testGetMetadataReportRmesException() throws Exception { + void testGetMetadataReportRmesException() throws Exception { String id = "1234"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -230,7 +224,7 @@ public void testGetMetadataReportRmesException() throws Exception { .andExpect(status().isInternalServerError()); } @Test - public void testGetMetadataReportDefaultValue() throws Exception { + void testGetMetadataReportDefaultValue() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String jsonResponse = "{\"key\":\"value\"}"; @@ -246,7 +240,7 @@ public void testGetMetadataReportDefaultValue() throws Exception { } @Test - public void testGetFullSimsJson() throws Exception { + void testGetFullSimsJson() throws Exception { String id = "1234"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -263,7 +257,7 @@ public void testGetFullSimsJson() throws Exception { } @Test - public void testGetFullSimsXml() throws Exception { + void testGetFullSimsXml() throws Exception { String id = "1234"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -284,7 +278,7 @@ public void testGetFullSimsXml() throws Exception { } @Test - public void testGetFullSimsJsonRmesException() throws Exception { + void testGetFullSimsJsonRmesException() throws Exception { String id = "1234"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -297,7 +291,7 @@ public void testGetFullSimsJsonRmesException() throws Exception { } @Test - public void testGetFullSimsXmlRmesException() throws Exception { + void testGetFullSimsXmlRmesException() throws Exception { String id = "1234"; configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); @@ -310,7 +304,7 @@ public void testGetFullSimsXmlRmesException() throws Exception { } @Test - public void testGetSimsExport() throws Exception { + void testGetSimsExport() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String id = "1234"; @@ -336,7 +330,7 @@ public void testGetSimsExport() throws Exception { } @Test - public void testGetSimsExport_DefaultValues() throws Exception { + void testGetSimsExport_DefaultValues() throws Exception { configureJwtDecoderMock(jwtDecoder, idep, timbre, List.of("Administrateur_RMESGNCS")); String id = "1234"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesAuthorizationsEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesAuthorizationsEnvProd.java index e3e03aeff..e1ff274b7 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesAuthorizationsEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesAuthorizationsEnvProd.java @@ -2,7 +2,7 @@ import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.OperationsService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.roles.Roles; @@ -55,7 +55,7 @@ class TestSeriesResourcesAuthorizationsEnvProd { @MockBean private JwtDecoder jwtDecoder; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; private final String idep = "xxxxux"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java index 7a4d42140..a693156f5 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestSeriesResourcesEnvProd.java @@ -2,15 +2,9 @@ import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; import fr.insee.rmes.bauhaus_services.OperationsService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; -import fr.insee.rmes.config.Config; -import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.config.auth.roles.Roles; -import fr.insee.rmes.config.auth.security.BauhausMethodSecurityExpressionHandler; -import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; -import fr.insee.rmes.config.auth.security.DefaultSecurityContext; -import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.webservice.operations.SeriesResources; import org.junit.jupiter.api.Test; @@ -54,10 +48,10 @@ class TestSeriesResourcesEnvProd { protected OperationsDocumentationsService documentationsService; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean - StampsRestrictionsVerifier stampsRestrictionsVerifier; + ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; @MockBean private JwtDecoder jwtDecoder; diff --git a/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java b/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java index b66cf80b3..be9e98be3 100644 --- a/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java +++ b/src/test/java/fr/insee/rmes/integration/authorizations/TestStructuresResourcesEnvProd.java @@ -1,17 +1,11 @@ package fr.insee.rmes.integration.authorizations; import fr.insee.rmes.bauhaus_services.OperationsDocumentationsService; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.bauhaus_services.structures.StructureComponent; import fr.insee.rmes.bauhaus_services.structures.StructureService; -import fr.insee.rmes.config.Config; -import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.roles.Roles; -import fr.insee.rmes.config.auth.security.BauhausMethodSecurityExpressionHandler; -import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; -import fr.insee.rmes.config.auth.security.DefaultSecurityContext; -import fr.insee.rmes.config.auth.security.OpenIDConnectSecurityContext; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.model.ValidationStatus; import fr.insee.rmes.webservice.StructureResources; @@ -58,9 +52,9 @@ class TestStructuresResourcesEnvProd { @MockBean protected OperationsDocumentationsService documentationsService; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean - StampsRestrictionsVerifier stampsRestrictionsVerifier; + ResourceOwnershipByStampVerifier resourceOwnershipByStampVerifier; private final String idep = "xxxxxx"; private final String timbre = "XX59-YYY"; diff --git a/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java b/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java index 1f63c3a5f..594d374a3 100644 --- a/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java +++ b/src/test/java/fr/insee/rmes/stubs/StampRestritionVerifierStub.java @@ -1,6 +1,6 @@ package fr.insee.rmes.stubs; -import fr.insee.rmes.bauhaus_services.accesscontrol.StampsRestrictionsVerifier; +import fr.insee.rmes.bauhaus_services.accesscontrol.ResourceOwnershipByStampVerifier; import fr.insee.rmes.config.auth.security.restrictions.StampsRestrictionsService; import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.RmesException; @@ -11,7 +11,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; -public class StampRestritionVerifierStub implements StampsRestrictionsVerifier { +public class StampRestritionVerifierStub implements ResourceOwnershipByStampVerifier { public static final String DATASET_STUB_ID = "datasetID"; public static final String SERIES_STUB_ID = "seriesID"; diff --git a/src/test/java/fr/insee/rmes/webservice/PublicResourcesTest.java b/src/test/java/fr/insee/rmes/webservice/PublicResourcesTest.java index 3868053e5..3b9087d6f 100644 --- a/src/test/java/fr/insee/rmes/webservice/PublicResourcesTest.java +++ b/src/test/java/fr/insee/rmes/webservice/PublicResourcesTest.java @@ -1,6 +1,6 @@ package fr.insee.rmes.webservice; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; @@ -46,7 +46,7 @@ class PublicResourcesTest { @Autowired private MockMvc mvc; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean StampsService stampsService; diff --git a/src/test/java/fr/insee/rmes/webservice/UnactiveStructureResourcesTest.java b/src/test/java/fr/insee/rmes/webservice/UnactiveStructureResourcesTest.java index d75e2150e..5fe385a48 100644 --- a/src/test/java/fr/insee/rmes/webservice/UnactiveStructureResourcesTest.java +++ b/src/test/java/fr/insee/rmes/webservice/UnactiveStructureResourcesTest.java @@ -1,6 +1,6 @@ package fr.insee.rmes.webservice; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.webservice.operations.*; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ class UnactiveModulesTest { @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @Autowired(required = false) ClassificationsResources classificationsResources; diff --git a/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java b/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java index f2b492cd1..e0b56303b 100644 --- a/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java +++ b/src/test/java/fr/insee/rmes/webservice/UserResourcesEnvHorsProdTest.java @@ -1,6 +1,6 @@ package fr.insee.rmes.webservice; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; @@ -48,7 +48,7 @@ class UserResourcesEnvHorsProdTest { private MockMvc mvc; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean private RBACService rbacService; @MockBean diff --git a/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java b/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java index 2a8fef754..edc2ed32f 100644 --- a/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java +++ b/src/test/java/fr/insee/rmes/webservice/UserResourcesTestEnvProd.java @@ -1,6 +1,6 @@ package fr.insee.rmes.webservice; -import fr.insee.rmes.bauhaus_services.StampAuthorizationChecker; +import fr.insee.rmes.bauhaus_services.accesscontrol.AuthorizationCheckerWithResourceOwnershipByStamp; import fr.insee.rmes.config.Config; import fr.insee.rmes.config.auth.UserProviderFromSecurityContext; import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; @@ -46,7 +46,7 @@ class UserResourcesEnvProdTest { private MockMvc mvc; @MockBean - StampAuthorizationChecker stampAuthorizationChecker; + AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker; @MockBean private RBACService rbacService; @MockBean diff --git a/src/test/resources/rbac-test.yml b/src/test/resources/rbac-test.yml new file mode 100644 index 000000000..25754efaf --- /dev/null +++ b/src/test/resources/rbac-test.yml @@ -0,0 +1,143 @@ +rbac: + config: + Administrateur_RMESGNCS: + concept: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + collection: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + family: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + serie: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + operation: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + indicator: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + sims: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + classification: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + dataset: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL + Utilisateur_RMESGNCS: + concept: + read: ALL + collection: + read: ALL + family: + read: ALL + serie: + read: ALL + operation: + read: ALL + indicator: + read: ALL + sims: + read: ALL + classification: + read: ALL + dataset: + read: ALL + Gestionnaire_concept_RMESGNCS: + concept: + create: STAMP + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + Gestionnaire_ensemble_concepts_RMESGNCS: + concept: + create: STAMP + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + Gestionnaire_serie_RMESGNCS: + serie: + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + operation: + create: STAMP + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + sims: + create: STAMP + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + Gestionnaire_indicateur_RMESGNCS: + serie: + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + indicator: + create: STAMP + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + sims: + create: STAMP + read: STAMP + update: STAMP + publish: STAMP + validate: STAMP + Gestionnaire_jeu_donnees_RMESGNCS: + dataset: + delete: STAMP + update: STAMP + validate: STAMP + publish: STAMP + create: ALL + read: STAMP From 28d975dded9d807801c5c1696ab8bacec7b85fae Mon Sep 17 00:00:00 2001 From: Fabrice B Date: Tue, 13 Aug 2024 17:54:51 +0200 Subject: [PATCH 14/19] fix (RBAC : Gestionnaire_ensemble_concepts_RMESGNCS has ALL strategy for concepts) : https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issue-2325216176 --- src/main/resources/rbac.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/rbac.yml b/src/main/resources/rbac.yml index 25754efaf..2768b5ef2 100644 --- a/src/main/resources/rbac.yml +++ b/src/main/resources/rbac.yml @@ -92,11 +92,11 @@ rbac: validate: STAMP Gestionnaire_ensemble_concepts_RMESGNCS: concept: - create: STAMP - read: STAMP - update: STAMP - publish: STAMP - validate: STAMP + create: ALL + read: ALL + update: ALL + publish: ALL + validate: ALL Gestionnaire_serie_RMESGNCS: serie: read: STAMP From eda452616f65c12dad6c33694542b30a000077f3 Mon Sep 17 00:00:00 2001 From: Fabrice B Date: Wed, 14 Aug 2024 19:07:31 +0200 Subject: [PATCH 15/19] =?UTF-8?q?feat=20(implement=20create=20rights=20str?= =?UTF-8?q?atey)=20:=20-=20Un=20utilisateur=20avec=20la=20strat=C3=A9gie?= =?UTF-8?q?=20STAMP=20ne=20peut=20cr=C3=A9er=20de=20ressource=20que=20pour?= =?UTF-8?q?=20son=20timbre=20-=20Un=20utilisateur=20avec=20la=20strat?= =?UTF-8?q?=C3=A9gie=20ALL=20peut=20cr=C3=A9er=20des=20ressources=20pour?= =?UTF-8?q?=20n'importe=20quel=20timbre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datasets/DatasetService.java | 5 +- .../datasets/DatasetServiceImpl.java | 9 +++- .../rmes/config/auth/RBACConfiguration.java | 9 +++- .../SecurityExpressionRootForBauhaus.java | 4 ++ .../rbac/CheckAccessPrivilegeForUser.java | 19 +++++-- .../insee/rmes/webservice/UserResources.java | 7 ++- .../webservice/dataset/DatasetResources.java | 52 +++++++++++++++---- .../rmes/webservice/DatasetResourcesTest.java | 32 ++++++++++-- 8 files changed, 113 insertions(+), 24 deletions(-) diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetService.java b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetService.java index 77b4f9888..de96441f0 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetService.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetService.java @@ -1,5 +1,6 @@ package fr.insee.rmes.bauhaus_services.datasets; +import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.model.dataset.Dataset; import fr.insee.rmes.model.dataset.PatchDataset; @@ -12,7 +13,7 @@ public interface DatasetService { String update(String datasetId, String body) throws RmesException; - String create(String body) throws RmesException; + String create(Dataset dataset) throws RmesException; String getDistributions(String id) throws RmesException; @@ -25,4 +26,6 @@ public interface DatasetService { String publishDataset(String id) throws RmesException; void deleteDatasetId(String datasetId) throws RmesException; + + String createWithStamp(Dataset dataset, Stamp stamp) throws RmesException; } \ No newline at end of file diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java index c6de957ed..e485c1a02 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImpl.java @@ -4,6 +4,7 @@ import fr.insee.rmes.bauhaus_services.operations.series.SeriesUtils; import fr.insee.rmes.bauhaus_services.rdf_utils.RdfService; import fr.insee.rmes.bauhaus_services.rdf_utils.RdfUtils; +import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.exceptions.ErrorCodes; import fr.insee.rmes.exceptions.RmesBadRequestException; import fr.insee.rmes.exceptions.RmesException; @@ -210,8 +211,7 @@ public String update(String datasetId, String body) throws RmesException { } @Override - public String create(String body) throws RmesException { - Dataset dataset = Deserializer.deserializeJsonString(body, Dataset.class); + public String create(Dataset dataset) throws RmesException { dataset.setId(idGenerator.generateNextId()); dataset.setValidationState(ValidationStatus.UNPUBLISHED.toString()); @@ -305,6 +305,11 @@ public void deleteDatasetId(String datasetId) throws RmesException{ repoGestion.deleteTripletByPredicate(datasetIRI, DCAT.DATASET, graph); } + @Override + public String createWithStamp(Dataset dataset, Stamp stamp) throws RmesException { + throw new RmesException(500, "Not implemented", "this method is still under development"); + } + private boolean isPublished(Dataset dataset) { return !"Unpublished".equalsIgnoreCase(dataset.getValidationState()); diff --git a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java index 4bea82fc3..c68fb7c8d 100644 --- a/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/RBACConfiguration.java @@ -4,7 +4,9 @@ import fr.insee.rmes.model.rbac.Module; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @@ -19,5 +21,10 @@ public RBAC toRBAC() { ))); } - public record RoleName(String role) {} + public static List toRolesNames(List roles) { + return roles == null ? List.of() : roles.stream().filter(Objects::nonNull).map(RoleName::new).toList(); + } + + public record RoleName(String role) { + } } diff --git a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java index 74aed14b8..d5dfd4c81 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/SecurityExpressionRootForBauhaus.java @@ -269,6 +269,10 @@ public boolean canCreateDataset(String datasetId) { return getAccessPrivilegesForUser().isGranted(Privilege.CREATE).on(Module.DATASET).withId(datasetId); } + public boolean canCreateDataset() { + return getAccessPrivilegesForUser().isGranted(Privilege.CREATE).on(Module.DATASET).whateverIdIs(); + } + public boolean canPublishDataset(String datasetId) { return getAccessPrivilegesForUser().isGranted(Privilege.PUBLISH).on(Module.DATASET).withId(datasetId); } diff --git a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java index 29f14fe2e..6ac48ac3e 100644 --- a/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java +++ b/src/main/java/fr/insee/rmes/external/services/rbac/CheckAccessPrivilegeForUser.java @@ -4,11 +4,12 @@ import fr.insee.rmes.model.rbac.ApplicationAccessPrivileges; import fr.insee.rmes.model.rbac.Module; import fr.insee.rmes.model.rbac.Privilege; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; public record CheckAccessPrivilegeForUser(ApplicationAccessPrivileges applicationAccessPrivileges, User user, AuthorizationChecker authorizationChecker, AtomicReference privilege, AtomicReference module) { @@ -37,6 +38,10 @@ private void withModule(Module module) { } public boolean withId(String id) { + return check(Optional.ofNullable(id), this::checkStampFor); + } + + private boolean check(Optional id, Predicate idChecker) { var strategy = applicationAccessPrivileges.privilegesForModule(module.get()).strategyFor(privilege.get()); if (strategy.isEmpty()){ log.atDebug().log(()->debugAccess()+" : no privilege found for "+ user.roles()); @@ -46,16 +51,22 @@ public boolean withId(String id) { log.atDebug().log(()->debugAccess()+" : ALL privilege found"); return true; } - boolean authorized = checkStampFor(id); - log.atDebug().log(()->debugAccess()+" : STAMP privilege found : "+user.stamp()+" "+(authorized?"":"un")+"authorized for id"+id); + boolean authorized = id.isPresent() && idChecker.test(id.get()); + log.atDebug().log(()->debugAccess()+" : STAMP privilege found : "+user.stamp()+" "+(authorized?"":"un")+"authorized for id"+ id); return authorized; } - private @NotNull String debugAccess() { + public boolean whateverIdIs() { + Predicate noIdCheck = ignored -> false; + return check(Optional.empty(), noIdCheck); + } + + private String debugAccess() { return "Check access for " + user + " for " + privilege + " in " + module; } private boolean checkStampFor(String id) { return id != null && authorizationChecker.userStampIsAuthorizedForResource(module.get(), id, user.stamp()); } + } diff --git a/src/main/java/fr/insee/rmes/webservice/UserResources.java b/src/main/java/fr/insee/rmes/webservice/UserResources.java index 3ad70bf7b..b815bae21 100644 --- a/src/main/java/fr/insee/rmes/webservice/UserResources.java +++ b/src/main/java/fr/insee/rmes/webservice/UserResources.java @@ -3,6 +3,7 @@ import fr.insee.rmes.config.auth.RBACConfiguration; import fr.insee.rmes.config.auth.security.UserDecoder; import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.authentication.stamps.StampsService; import fr.insee.rmes.external.services.rbac.RBACService; @@ -15,6 +16,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.apache.http.HttpStatus; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; @@ -24,8 +26,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; import java.util.Objects; +import static fr.insee.rmes.config.auth.RBACConfiguration.toRolesNames; + /** * WebService class for resources of Concepts * @@ -78,7 +83,7 @@ public UserResources(StampsService stampsService, RBACService rbacService, UserD ) public ApplicationAccessPrivileges getUserInformation(@AuthenticationPrincipal Object principal) throws RmesException { return this.userDecoder.fromPrincipal(principal) - .map(user-> rbacService.computeRbac(user.roles().stream().filter(Objects::nonNull).map(RBACConfiguration.RoleName::new).toList())) + .map(user-> rbacService.computeRbac(toRolesNames(user.roles()))) .orElse(ApplicationAccessPrivileges.NO_PRIVILEGE); } diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index e164b457b..62db558dc 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -2,10 +2,17 @@ import fr.insee.rmes.bauhaus_services.Constants; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; +import fr.insee.rmes.config.auth.security.UserDecoder; +import fr.insee.rmes.config.auth.user.Stamp; +import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; +import fr.insee.rmes.external.services.rbac.RBACService; import fr.insee.rmes.model.dataset.Dataset; import fr.insee.rmes.model.dataset.Distribution; import fr.insee.rmes.model.dataset.PatchDataset; +import fr.insee.rmes.model.rbac.Module; +import fr.insee.rmes.model.rbac.Privilege; +import fr.insee.rmes.model.rbac.Strategy; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -19,8 +26,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.Optional; + +import static fr.insee.rmes.config.auth.RBACConfiguration.toRolesNames; +import static fr.insee.rmes.model.rbac.Strategy.STAMP; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RestController @@ -31,12 +43,16 @@ public class DatasetResources { final DatasetService datasetService; + private final RBACService rbacService; + private final UserDecoder userDecoder; - public DatasetResources(DatasetService datasetService) { + public DatasetResources(DatasetService datasetService, RBACService rbacService, UserDecoder userDecoder) { this.datasetService = datasetService; + this.rbacService = rbacService; + this.userDecoder = userDecoder; } -// @PreAuthorize("canReadDataset(#datasetId)") + // @PreAuthorize("canReadDataset(#datasetId)") @GetMapping(produces = "application/json") @Operation(operationId = "getDatasets", summary = "List of datasets", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -44,7 +60,7 @@ public String getDatasets() throws RmesException { return this.datasetService.getDatasets(); } -// @PreAuthorize("canReadDataset(#datasetId)") + // @PreAuthorize("canReadDataset(#datasetId)") @GetMapping(value = "/{id}", produces = "application/json") @Operation(operationId = "getDataset", summary = "Get a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -52,7 +68,7 @@ public Dataset getDataset(@PathVariable(Constants.ID) String id) throws RmesExce return this.datasetService.getDatasetByID(id); } -// @PreAuthorize("canReadDataset(#datasetId)") + // @PreAuthorize("canReadDataset(#datasetId)") @GetMapping("/{id}/distributions") @Operation(operationId = "getDistributionsByDataset", summary = "List of distributions for a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -60,21 +76,37 @@ public String getDistributionsByDataset(@PathVariable(Constants.ID) String id) t return this.datasetService.getDistributions(id); } - @PreAuthorize("canCreateDataset(#datasetId)") + @PreAuthorize("canCreateDataset()") @PostMapping(value = "", consumes = APPLICATION_JSON_VALUE) @Operation(operationId = "createDataset", summary = "Create a dataset") @ResponseStatus(HttpStatus.CREATED) - public String setDataset( - @Parameter(description = "Dataset", required = true) @RequestBody String body) throws RmesException { - return this.datasetService.create(body); + public String createDataset( + @Parameter(description = "Dataset", required = true) @RequestBody Dataset body, + @AuthenticationPrincipal Object principal + ) throws RmesException { + User user = this.userDecoder.fromPrincipal(principal).orElseThrow(()->new RmesException(500, "User informations mandatories for this endpoint", "Unable to retrieve user from "+principal)); + if (hasStrategyAllForCreation(user)) { + return this.datasetService.create(body); + } + return this.datasetService.createWithStamp(body, user.stamp()); + } + + private boolean hasStrategyAllForCreation(User user) { + return creationStrategy(user).map(STAMP::equals).orElse(false); + } + + private Optional creationStrategy(User user) { + return this.rbacService.computeRbac(toRolesNames(user.roles())) + .privilegesForModule(Module.DATASET) + .strategyFor(Privilege.CREATE); } @PreAuthorize("canUpdateDataset(#datasetId)") @PutMapping(value = "/{id}", consumes = APPLICATION_JSON_VALUE) @Operation(operationId = "updateDataset", summary = "Update a dataset") - public String setDataset( + public String updateDataset( @PathVariable("id") String datasetId, - @Parameter(description = "Dataset", required = true) @RequestBody String body ) throws RmesException { + @Parameter(description = "Dataset", required = true) @RequestBody String body) throws RmesException { return this.datasetService.update(datasetId, body); } diff --git a/src/test/java/fr/insee/rmes/webservice/DatasetResourcesTest.java b/src/test/java/fr/insee/rmes/webservice/DatasetResourcesTest.java index 13ecf4598..649773d19 100644 --- a/src/test/java/fr/insee/rmes/webservice/DatasetResourcesTest.java +++ b/src/test/java/fr/insee/rmes/webservice/DatasetResourcesTest.java @@ -1,8 +1,15 @@ package fr.insee.rmes.webservice; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; +import fr.insee.rmes.config.auth.security.UserDecoder; +import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; +import fr.insee.rmes.external.services.rbac.RBACService; import fr.insee.rmes.model.dataset.Dataset; +import fr.insee.rmes.model.rbac.ApplicationAccessPrivileges; +import fr.insee.rmes.model.rbac.ModuleAccessPrivileges; +import fr.insee.rmes.model.rbac.Privilege; +import fr.insee.rmes.model.rbac.Strategy; import fr.insee.rmes.webservice.dataset.DatasetResources; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -11,18 +18,30 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static fr.insee.rmes.model.rbac.Module.DATASET; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -public class DatasetResourcesTest { +class DatasetResourcesTest { @InjectMocks private DatasetResources datasetResources; @Mock DatasetService datasetService; + @Mock + UserDecoder userDecoder; + + @Mock + RBACService rbacService; + @Test void shouldReturn200IfRmesExceptionWhenFetchingDatasets() throws RmesException { when(datasetService.getDatasets()).thenReturn("result"); @@ -46,14 +65,17 @@ void shouldReturn200IfRmesExceptionWhenFetchingDistributions() throws RmesExcept @Test void shouldReturn201IfRmesExceptionWhenPostingADataset() throws RmesException { - when(datasetService.create(anyString())).thenReturn("result"); - Assertions.assertEquals("result", datasetResources.setDataset("")); + when(datasetService.create(any())).thenReturn("result"); + String user = "User"; + when(userDecoder.fromPrincipal(user)).thenReturn(Optional.of(User.EMPTY_USER)); + when(rbacService.computeRbac(List.of())).thenReturn(new ApplicationAccessPrivileges(new EnumMap<>(Map.of(DATASET, new ModuleAccessPrivileges(Map.of(Privilege.CREATE, Strategy.ALL)))))); + Assertions.assertEquals("result", datasetResources.createDataset(new Dataset(), user)); } @Test void shouldReturn200IfRmesExceptionWhenUpdatingADataset() throws RmesException { when(datasetService.update(anyString(), anyString())).thenReturn("result"); - Assertions.assertEquals("result", datasetResources.setDataset("", "")); + Assertions.assertEquals("result", datasetResources.updateDataset("", "")); } @Test From cab1235df86c9c3a18595a97a02118a669eccf03 Mon Sep 17 00:00:00 2001 From: Fabrice B Date: Mon, 19 Aug 2024 17:07:34 +0200 Subject: [PATCH 16/19] test (fix tests) --- ...onCheckerWithResourceOwnershipByStamp.java | 3 +- .../java/fr/insee/rmes/model/rbac/Module.java | 3 +- .../java/fr/insee/rmes/utils/JSONUtils.java | 114 ++++++++++-------- .../webservice/dataset/DatasetResources.java | 5 +- src/main/resources/rbac.yml | 9 ++ .../StampAuthorizationCheckerTest.java | 16 ++- .../datasets/DatasetServiceImplTest.java | 61 ++++++---- .../services/rbac/RBACServiceTest.java | 8 +- src/test/resources/rbac-test.yml | 19 ++- 9 files changed, 142 insertions(+), 96 deletions(-) diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java index b1a6a8c72..15bfae071 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java @@ -46,12 +46,13 @@ public boolean userStampIsAuthorizedForResource(Module module, String id, Stamp case CONCEPT -> false; case COLLECTION -> false; case FAMILY -> false; - case SERIE -> false; + case SERIE -> isSeriesManagerWithStamp(id, stamp); case OPERATION -> false; case INDICATOR -> false; case SIMS -> false; case CLASSIFICATION -> false; case DATASET -> isDatasetManagerWithStamp(id, stamp); + case CODE_LIST -> isCodesListManagerWithStamp(id, stamp); case null -> false; }; } diff --git a/src/main/java/fr/insee/rmes/model/rbac/Module.java b/src/main/java/fr/insee/rmes/model/rbac/Module.java index 61dd182cc..785856748 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/Module.java +++ b/src/main/java/fr/insee/rmes/model/rbac/Module.java @@ -9,5 +9,6 @@ public enum Module { INDICATOR, SIMS, CLASSIFICATION, - DATASET + DATASET, + CODE_LIST } diff --git a/src/main/java/fr/insee/rmes/utils/JSONUtils.java b/src/main/java/fr/insee/rmes/utils/JSONUtils.java index 9af847590..8fa0643bd 100644 --- a/src/main/java/fr/insee/rmes/utils/JSONUtils.java +++ b/src/main/java/fr/insee/rmes/utils/JSONUtils.java @@ -1,65 +1,77 @@ package fr.insee.rmes.utils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.json.JSONArray; import org.json.JSONObject; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; public class JSONUtils { - - public static JSONArray extractFieldToArray(JSONArray jsonA, String field) { - JSONArray res = new JSONArray(); - for(Object o: jsonA){ - res.put(((JSONObject) o).getString(field)); - } - return res; - } - - /** - * Transform an array to a string with " - " separator - * @param jsonArray - * @return - */ - public static String jsonArrayOfStringToString(JSONArray jsonArray) { - if (jsonArray.length() == 1) { - return jsonArray.getString(0); - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < jsonArray.length()-1; i++) { - sb.append(jsonArray.getString(i) + " - "); - } - sb.append(jsonArray.getString(jsonArray.length()-1)); - return sb.toString(); - } - - public static boolean isEmpty(JSONObject obj) { - Iterator keys = obj.keys(); - while(keys.hasNext()) { - if(!obj.getString((keys.next())).isEmpty()) { - return false; - } - } - return true; - } - /** - * Transform an array to a list of strings - * @param jsonArray - * @return - */ - public static List jsonArrayToList(JSONArray jsonArray) { - return IntStream.range(0, jsonArray.length()) - .mapToObj(jsonArray::get) - .map(Object::toString) - .collect(Collectors.toList()); - } - - private JSONUtils() { - throw new IllegalStateException("Utility class"); - } + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public static JSONArray extractFieldToArray(JSONArray jsonA, String field) { + JSONArray res = new JSONArray(); + for (Object o : jsonA) { + res.put(((JSONObject) o).getString(field)); + } + return res; + } + + /** + * Transform an array to a string with " - " separator + * + * @param jsonArray + * @return + */ + public static String jsonArrayOfStringToString(JSONArray jsonArray) { + if (jsonArray.length() == 1) { + return jsonArray.getString(0); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < jsonArray.length() - 1; i++) { + sb.append(jsonArray.getString(i) + " - "); + } + sb.append(jsonArray.getString(jsonArray.length() - 1)); + return sb.toString(); + } + + public static boolean isEmpty(JSONObject obj) { + Iterator keys = obj.keys(); + while (keys.hasNext()) { + if (!obj.getString((keys.next())).isEmpty()) { + return false; + } + } + return true; + } + + /** + * Transform an array to a list of strings + * + * @param jsonArray + * @return + */ + public static List jsonArrayToList(JSONArray jsonArray) { + return IntStream.range(0, jsonArray.length()) + .mapToObj(jsonArray::get) + .map(Object::toString) + .collect(Collectors.toList()); + } + + public static T deserialize(JSONObject jsonObject, Class clazz) throws JsonProcessingException { + return objectMapper.readValue(jsonObject.toString(), clazz); + } + + + private JSONUtils() { + throw new IllegalStateException("Utility class"); + } + - } diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index 62db558dc..453fc02c8 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -3,7 +3,6 @@ import fr.insee.rmes.bauhaus_services.Constants; import fr.insee.rmes.bauhaus_services.datasets.DatasetService; import fr.insee.rmes.config.auth.security.UserDecoder; -import fr.insee.rmes.config.auth.user.Stamp; import fr.insee.rmes.config.auth.user.User; import fr.insee.rmes.exceptions.RmesException; import fr.insee.rmes.external.services.rbac.RBACService; @@ -32,7 +31,7 @@ import java.util.Optional; import static fr.insee.rmes.config.auth.RBACConfiguration.toRolesNames; -import static fr.insee.rmes.model.rbac.Strategy.STAMP; +import static fr.insee.rmes.model.rbac.Strategy.ALL; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RestController @@ -92,7 +91,7 @@ public String createDataset( } private boolean hasStrategyAllForCreation(User user) { - return creationStrategy(user).map(STAMP::equals).orElse(false); + return creationStrategy(user).map(ALL::equals).orElse(false); } private Optional creationStrategy(User user) { diff --git a/src/main/resources/rbac.yml b/src/main/resources/rbac.yml index 2768b5ef2..2d1b5b8ae 100644 --- a/src/main/resources/rbac.yml +++ b/src/main/resources/rbac.yml @@ -64,6 +64,13 @@ rbac: delete: ALL publish: ALL validate: ALL + code_list: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL Utilisateur_RMESGNCS: concept: read: ALL @@ -83,6 +90,8 @@ rbac: read: ALL dataset: read: ALL + code_list: + read: ALL Gestionnaire_concept_RMESGNCS: concept: create: STAMP diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java index dfc86b03e..cff3dbe05 100644 --- a/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java +++ b/src/test/java/fr/insee/rmes/bauhaus_services/StampAuthorizationCheckerTest.java @@ -4,7 +4,9 @@ import fr.insee.rmes.model.rbac.Module; import fr.insee.rmes.stubs.StampRestritionVerifierStub; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.*; + +import java.util.stream.Stream; import static fr.insee.rmes.stubs.StampRestritionVerifierStub.*; import static org.assertj.core.api.Assertions.assertThat; @@ -12,11 +14,15 @@ class StampAuthorizationCheckerTest { private final AuthorizationCheckerWithResourceOwnershipByStamp stampAuthorizationChecker=new AuthorizationCheckerWithResourceOwnershipByStamp(new StampRestritionVerifierStub()); + @ParameterizedTest - @ValueSource(strings = {SERIES_STUB_ID, DATASET_STUB_ID, DISTRIBUTION_STUB_ID, COMPONENT_STUB_ID, STRUCTURE_STUB_ID, CODES_LISTES_STUB_ID}) - void userStampIsAuthorizedForResource(String id) { - //Given - Module module = Module.DATASET; + @CsvSource({ + "'"+SERIES_STUB_ID+"', SERIE", + "'"+DATASET_STUB_ID+"', DATASET", + "'"+CODES_LISTES_STUB_ID+"', CODE_LIST" + } + ) + void userStampIsAuthorizedForResource(String id, Module module) { //When then assertThat(this.stampAuthorizationChecker.userStampIsAuthorizedForResource(module, id, null)).isTrue(); } diff --git a/src/test/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImplTest.java b/src/test/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImplTest.java index 6b760d57a..05ddd174e 100644 --- a/src/test/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImplTest.java +++ b/src/test/java/fr/insee/rmes/bauhaus_services/datasets/DatasetServiceImplTest.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Set; +import static fr.insee.rmes.utils.JSONUtils.deserialize; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @@ -152,28 +153,29 @@ void shouldReturnDistributions() throws RmesException, JSONException { } @Test - void shouldReturnAnErrorIfLabelLg1NotDefinedWhenCreating() throws RmesException, JSONException { + void shouldReturnAnErrorIfLabelLg1NotDefinedWhenCreating() throws RmesException, JSONException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); - JSONObject body = new JSONObject(); - + JSONObject jsonObject = new JSONObject(); + Dataset body = deserialize(jsonObject, Dataset.class); when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { JSONObject lastId = new JSONObject(); lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body)); Assertions.assertEquals("{\"message\":\"The property labelLg1 is required\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorIfLabelLg2NotDefinedWhenCreating() throws RmesException { + void shouldReturnAnErrorIfLabelLg2NotDefinedWhenCreating() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); body.put("labelLg1", "labelLg1"); + Dataset bodyDataset = deserialize(body, Dataset.class); when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { @@ -181,18 +183,19 @@ void shouldReturnAnErrorIfLabelLg2NotDefinedWhenCreating() throws RmesException lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"The property labelLg2 is required\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorIfCreatorNotDefinedWhenCreating() throws RmesException { + void shouldReturnAnErrorIfCreatorNotDefinedWhenCreating() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); body.put("labelLg1", "labelLg1"); body.put("labelLg2", "labelLg2R"); + Dataset bodyDataset = deserialize(body, Dataset.class); when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { @@ -200,13 +203,13 @@ void shouldReturnAnErrorIfCreatorNotDefinedWhenCreating() throws RmesException { lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"The property creator is required\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorIfContributorNotDefinedWhenCreating() throws RmesException { + void shouldReturnAnErrorIfContributorNotDefinedWhenCreating() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); @@ -216,6 +219,7 @@ void shouldReturnAnErrorIfContributorNotDefinedWhenCreating() throws RmesExcepti JSONObject record = new JSONObject(); record.put("creator", "creator"); body.put("catalogRecord", record); + Dataset bodyDataset = deserialize(body, Dataset.class); when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { @@ -223,13 +227,13 @@ void shouldReturnAnErrorIfContributorNotDefinedWhenCreating() throws RmesExcepti lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"The property contributor is required\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorIfDisseminationStatusNotDefinedWhenCreating() throws RmesException { + void shouldReturnAnErrorIfDisseminationStatusNotDefinedWhenCreating() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); @@ -238,18 +242,20 @@ void shouldReturnAnErrorIfDisseminationStatusNotDefinedWhenCreating() throws Rme body.put("catalogRecord", this.generateCatalogRecord()); + Dataset bodyDataset = deserialize(body, Dataset.class); + when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { JSONObject lastId = new JSONObject(); lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"The property disseminationStatus is required\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorBadFormattedAltIdentifierWhenCreating() throws RmesException { + void shouldReturnAnErrorBadFormattedAltIdentifierWhenCreating() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); @@ -259,18 +265,20 @@ void shouldReturnAnErrorBadFormattedAltIdentifierWhenCreating() throws RmesExcep body.put("altIdentifier", "%abc"); body.put("catalogRecord", this.generateCatalogRecord()); + Dataset bodyDataset = deserialize(body, Dataset.class); + when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { JSONObject lastId = new JSONObject(); lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"The property altIdentifier contains forbidden characters\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreating() throws RmesException { + void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreating() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); @@ -280,6 +288,8 @@ void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreating() throws RmesExcep body.put("altIdentifier", "abc"); body.put("catalogRecord", this.generateCatalogRecord()); + Dataset bodyDataset = deserialize(body, Dataset.class); + when(seriesUtils.isSeriesAndOperationsExist(any())).thenReturn(false); when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { @@ -287,13 +297,13 @@ void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreating() throws RmesExcep lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"Some series or operations do not exist\"}", exception.getDetails()); } } @Test - void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreatingEvenIfAltIdentifierMissing() throws RmesException { + void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreatingEvenIfAltIdentifierMissing() throws RmesException, JsonProcessingException { try (MockedStatic mockedFactory = mockStatic(DatasetQueries.class)) { mockedFactory.when(() -> DatasetQueries.lastDatasetId(any())).thenReturn("query"); JSONObject body = new JSONObject(); @@ -302,6 +312,8 @@ void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreatingEvenIfAltIdentifier body.put("disseminationStatus", "disseminationStatus"); body.put("catalogRecord", this.generateCatalogRecord()); + Dataset bodyDataset = deserialize(body, Dataset.class); + when(seriesUtils.isSeriesAndOperationsExist(anyList())).thenReturn(false); when(repositoryGestion.getResponseAsObject(anyString())).then(invocationOnMock -> { @@ -309,13 +321,13 @@ void shouldReturnAnErrorIfUnknownSeriesNotDefinedWhenCreatingEvenIfAltIdentifier lastId.put("id", "1000"); return lastId; }); - RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(body.toString())); + RmesException exception = assertThrows(RmesBadRequestException.class, () -> datasetService.create(bodyDataset)); Assertions.assertEquals("{\"message\":\"Some series or operations do not exist\"}", exception.getDetails()); } } @Test - void shouldPersistNewDatasetWithAndIncrementedId() throws RmesException { + void shouldPersistNewDatasetWithAndIncrementedId() throws RmesException, JsonProcessingException { createANewDataset("jd1001"); } @@ -402,7 +414,7 @@ private JSONObject generateCatalogRecord() { return record; } - private void createANewDataset(String nextId) throws RmesException { + private void createANewDataset(String nextId) throws RmesException, JsonProcessingException { try ( MockedStatic datasetQueriesMock = mockStatic(DatasetQueries.class); MockedStatic rdfUtilsMock = mockStatic(RdfUtils.class); @@ -454,7 +466,9 @@ private void createANewDataset(String nextId) throws RmesException { when(seriesUtils.isSeriesAndOperationsExist(anyList())).thenReturn(true); - String id = datasetService.create(body.toString()); + Dataset bodyDataset = deserialize(body, Dataset.class); + + String id = datasetService.create(bodyDataset); ArgumentCaptor model = ArgumentCaptor.forClass(Model.class); verify(repositoryGestion, times(1)).loadSimpleObject(eq(iri), model.capture(), any()); @@ -562,11 +576,6 @@ void shouldPublishADataset() throws RmesException { Assertions.assertEquals("1", id); } - @Test - void shouldThrowAnExceptionIfTheBodyIsNotAJSONDuringCreation() { - assertThrows(RmesException.class, () -> datasetService.create("")); - } - @Test void shouldThrowAnExceptionIfTheBodyIsNotAJSONDuringUpdate() { assertThrows(RmesException.class, () -> datasetService.update("d1000", "")); diff --git a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java index 324776e77..0697af77d 100644 --- a/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java +++ b/src/test/java/fr/insee/rmes/external/services/rbac/RBACServiceTest.java @@ -98,14 +98,14 @@ void testMergeGestionnaireAndSimpleUserShouldReturnMix(){ List writePrivilege=List.of(PUBLISH, UPDATE, VALIDATE); ModuleAccessPrivileges moduleAccessPrivilegesReversed = mergedPrivilegeReversed.privilegesForModule(CONCEPT); assertThat(writePrivilege.stream().map(moduleAccessPrivilegesReversed::strategyFor)).isNotEmpty().allMatch(Optional.of(STAMP)::equals); - //TODO : fix fail when comment at https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issuecomment-2284329692 will be answered - assertThat(moduleAccessPrivilegesReversed.strategyFor(CREATE)).isPresent().isEqualTo(ALL); + //according to https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issuecomment-2286303989 + assertThat(moduleAccessPrivilegesReversed.strategyFor(CREATE)).isPresent().isEqualTo(Optional.of(STAMP)); assertThat(moduleAccessPrivilegesReversed.strategyFor(READ)).isEqualTo(Optional.of(ALL)); ModuleAccessPrivileges moduleAccessPrivileges = mergedPrivilege.privilegesForModule(CONCEPT); assertThat(writePrivilege.stream().map(moduleAccessPrivileges::strategyFor)).isNotEmpty().allMatch(Optional.of(STAMP)::equals); - //TODO : fix fail when comment at https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issuecomment-2284329692 will be answered - assertThat(moduleAccessPrivileges.strategyFor(CREATE)).isPresent().isEqualTo(ALL); + //according to https://github.com/InseeFr/Bauhaus-Back-Office/issues/634#issuecomment-2286303989 + assertThat(moduleAccessPrivileges.strategyFor(CREATE)).isPresent().isEqualTo(Optional.of(STAMP)); assertThat(moduleAccessPrivileges.strategyFor(READ)).isPresent().isEqualTo(Optional.of(ALL)); } diff --git a/src/test/resources/rbac-test.yml b/src/test/resources/rbac-test.yml index 25754efaf..269894a9f 100644 --- a/src/test/resources/rbac-test.yml +++ b/src/test/resources/rbac-test.yml @@ -64,6 +64,13 @@ rbac: delete: ALL publish: ALL validate: ALL + code_list: + create: ALL + read: ALL + update: ALL + delete: ALL + publish: ALL + validate: ALL Utilisateur_RMESGNCS: concept: read: ALL @@ -83,6 +90,8 @@ rbac: read: ALL dataset: read: ALL + code_list: + read: ALL Gestionnaire_concept_RMESGNCS: concept: create: STAMP @@ -92,11 +101,11 @@ rbac: validate: STAMP Gestionnaire_ensemble_concepts_RMESGNCS: concept: - create: STAMP - read: STAMP - update: STAMP - publish: STAMP - validate: STAMP + create: ALL + read: ALL + update: ALL + publish: ALL + validate: ALL Gestionnaire_serie_RMESGNCS: serie: read: STAMP From eceae2455883027ee875249d95f9dc244647a9b4 Mon Sep 17 00:00:00 2001 From: Fabrice B Date: Mon, 19 Aug 2024 17:13:16 +0200 Subject: [PATCH 17/19] reformat --- .../config/auth/security/OpenIDConnectSecurityContext.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContext.java b/src/main/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContext.java index aeda23e78..fa31ea1f3 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContext.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/OpenIDConnectSecurityContext.java @@ -44,11 +44,8 @@ public class OpenIDConnectSecurityContext { public static final String[] PUBLIC_RESOURCES_ANT_PATTERNS = {"/init", "/stamps", "/disseminationStatus"}; private final String stampClaim; - private final String roleClaimKey; - private final String idClaim; - private final boolean requiresSsl; private final String keyForRolesInRoleClaim; From 4a70b3d2fc22dfc228b8ffb1a2bacfc4336fd1c2 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Mon, 9 Sep 2024 10:28:56 +0200 Subject: [PATCH 18/19] fix : remove comments from DatasetResources --- .../insee/rmes/webservice/dataset/DatasetResources.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java index 453fc02c8..c6d35dde5 100644 --- a/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java +++ b/src/main/java/fr/insee/rmes/webservice/dataset/DatasetResources.java @@ -25,7 +25,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.Optional; @@ -51,7 +50,7 @@ public DatasetResources(DatasetService datasetService, RBACService rbacService, this.userDecoder = userDecoder; } - // @PreAuthorize("canReadDataset(#datasetId)") + @PreAuthorize("canReadDataset(#datasetId)") @GetMapping(produces = "application/json") @Operation(operationId = "getDatasets", summary = "List of datasets", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -59,7 +58,7 @@ public String getDatasets() throws RmesException { return this.datasetService.getDatasets(); } - // @PreAuthorize("canReadDataset(#datasetId)") + @PreAuthorize("canReadDataset(#datasetId)") @GetMapping(value = "/{id}", produces = "application/json") @Operation(operationId = "getDataset", summary = "Get a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -67,7 +66,7 @@ public Dataset getDataset(@PathVariable(Constants.ID) String id) throws RmesExce return this.datasetService.getDatasetByID(id); } - // @PreAuthorize("canReadDataset(#datasetId)") + @PreAuthorize("canReadDataset(#datasetId)") @GetMapping("/{id}/distributions") @Operation(operationId = "getDistributionsByDataset", summary = "List of distributions for a dataset", responses = {@ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Dataset.class))))}) @@ -81,7 +80,7 @@ public String getDistributionsByDataset(@PathVariable(Constants.ID) String id) t @ResponseStatus(HttpStatus.CREATED) public String createDataset( @Parameter(description = "Dataset", required = true) @RequestBody Dataset body, - @AuthenticationPrincipal Object principal + Object principal ) throws RmesException { User user = this.userDecoder.fromPrincipal(principal).orElseThrow(()->new RmesException(500, "User informations mandatories for this endpoint", "Unable to retrieve user from "+principal)); if (hasStrategyAllForCreation(user)) { From 6b5408caf26cdfc786030f981c00d9e9d2d58ee7 Mon Sep 17 00:00:00 2001 From: Hugo Bouttes Date: Mon, 9 Sep 2024 15:18:27 +0200 Subject: [PATCH 19/19] fix : Module ENUM and AuthorizationChecker --- .../AuthorizationCheckerWithResourceOwnershipByStamp.java | 3 +++ src/main/java/fr/insee/rmes/model/rbac/Module.java | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java index 15bfae071..27ab59c34 100644 --- a/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java +++ b/src/main/java/fr/insee/rmes/bauhaus_services/accesscontrol/AuthorizationCheckerWithResourceOwnershipByStamp.java @@ -52,6 +52,9 @@ public boolean userStampIsAuthorizedForResource(Module module, String id, Stamp case SIMS -> false; case CLASSIFICATION -> false; case DATASET -> isDatasetManagerWithStamp(id, stamp); + case DISTRIBUTION -> isDistributionManagerWithStamp(id, stamp); + case COMPONENT -> isComponentManagerWithStamp(id, stamp); + case STRUCTURE -> isStructureManagerWithStamp(id, stamp); case CODE_LIST -> isCodesListManagerWithStamp(id, stamp); case null -> false; }; diff --git a/src/main/java/fr/insee/rmes/model/rbac/Module.java b/src/main/java/fr/insee/rmes/model/rbac/Module.java index 785856748..3d1d60836 100644 --- a/src/main/java/fr/insee/rmes/model/rbac/Module.java +++ b/src/main/java/fr/insee/rmes/model/rbac/Module.java @@ -10,5 +10,8 @@ public enum Module { SIMS, CLASSIFICATION, DATASET, - CODE_LIST + CODE_LIST, + COMPONENT, + STRUCTURE, + DISTRIBUTION }