From 7ed111e936dbc7929120d1fb82500b630ad2f919 Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Tue, 17 Oct 2023 15:22:05 +0300 Subject: [PATCH] task #9088 Authorization model should be changed to be Permission based --- dmp-backend/core/pom.xml | 5 + .../authorization/AuthorizationFlags.java | 10 ++ .../OwnedAuthorizationRequirement.java | 6 + .../eu/eudat/authorization/OwnedResource.java | 26 ++++ .../eu/eudat/authorization/Permission.java | 13 ++ .../commons/scope/{ => user}/UserScope.java | 2 +- .../DescriptionTemplateTypeCensor.java | 12 +- dmp-backend/pom.xml | 6 + .../OwnedAuthorizationHandler.java | 42 ++++++ .../configurations/SecurityConfiguration.java | 129 ++++++++++-------- .../configurations/WebMVCConfiguration.java | 2 +- .../controllers/v2/PrincipalController.java | 3 +- .../eudat/interceptors/UserInterceptor.java | 5 +- .../handlers/PrincipalArgumentResolver.java | 2 +- .../main/java/eu/eudat/models/v2/Account.java | 15 ++ .../eu/eudat/models/v2/AccountBuilder.java | 13 +- .../src/main/resources/config/application.yml | 1 + .../src/main/resources/config/permissions.yml | 31 +++++ .../app/core/common/enum/permission.enum.ts | 10 ++ .../src/app/core/model/auth/principal.ts | 2 + .../app/core/services/auth/auth.service.ts | 39 +++++- 21 files changed, 302 insertions(+), 72 deletions(-) create mode 100644 dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationFlags.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedAuthorizationRequirement.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedResource.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java rename dmp-backend/core/src/main/java/eu/eudat/commons/scope/{ => user}/UserScope.java (96%) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/authorization/OwnedAuthorizationHandler.java create mode 100644 dmp-backend/web/src/main/resources/config/permissions.yml create mode 100644 dmp-frontend/src/app/core/common/enum/permission.enum.ts diff --git a/dmp-backend/core/pom.xml b/dmp-backend/core/pom.xml index e6c6fac3a..0c81669f0 100644 --- a/dmp-backend/core/pom.xml +++ b/dmp-backend/core/pom.xml @@ -21,6 +21,11 @@ 1.0-SNAPSHOT compile + + gr.cite + oidc-authz + 2.1.0 + diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationFlags.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationFlags.java new file mode 100644 index 000000000..b1868d641 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationFlags.java @@ -0,0 +1,10 @@ +package eu.eudat.authorization; + +import java.util.EnumSet; + +public enum AuthorizationFlags { + None, Permission, Owner; + public static final EnumSet OwnerOrPermission = EnumSet.of(Owner, Permission); + public static final EnumSet OwnerOrPermissionOrIndicator = EnumSet.of(Owner, Permission); + public static final EnumSet OwnerOrPermissionOrIndicatorOrIndicatorAccess = EnumSet.of(Owner, Permission); +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedAuthorizationRequirement.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedAuthorizationRequirement.java new file mode 100644 index 000000000..e43d2cb06 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedAuthorizationRequirement.java @@ -0,0 +1,6 @@ +package eu.eudat.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; + +public class OwnedAuthorizationRequirement implements AuthorizationRequirement { +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedResource.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedResource.java new file mode 100644 index 000000000..58ab094d4 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/OwnedResource.java @@ -0,0 +1,26 @@ +package eu.eudat.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationResource; + +import java.util.List; +import java.util.UUID; + +public class OwnedResource extends AuthorizationResource { + private List userIds; + + public OwnedResource(UUID userId) { + this(List.of(userId)); + } + + public OwnedResource(List userIds) { + this.userIds = userIds; + } + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java new file mode 100644 index 000000000..b68deaf62 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java @@ -0,0 +1,13 @@ +package eu.eudat.authorization; + +public final class Permission { + + //DescriptionTemplateType + public static String BrowseDescriptionTemplateType = "BrowseDescriptionTemplateType"; + public static String EditDescriptionTemplateType = "EditDescriptionTemplateType"; + public static String DeleteDescriptionTemplateType = "DeleteDescriptionTemplateType"; + + // UI Pages + public static String ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage"; + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/scope/UserScope.java b/dmp-backend/core/src/main/java/eu/eudat/commons/scope/user/UserScope.java similarity index 96% rename from dmp-backend/core/src/main/java/eu/eudat/commons/scope/UserScope.java rename to dmp-backend/core/src/main/java/eu/eudat/commons/scope/user/UserScope.java index 99fd79695..717f85e3d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/scope/UserScope.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/scope/user/UserScope.java @@ -1,4 +1,4 @@ -package eu.eudat.commons.scope; +package eu.eudat.commons.scope.user; import gr.cite.tools.logging.LoggerService; import org.slf4j.LoggerFactory; diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/censorship/DescriptionTemplateTypeCensor.java b/dmp-backend/core/src/main/java/eu/eudat/model/censorship/DescriptionTemplateTypeCensor.java index 1b621ae12..e4deef226 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/censorship/DescriptionTemplateTypeCensor.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/censorship/DescriptionTemplateTypeCensor.java @@ -1,6 +1,8 @@ package eu.eudat.model.censorship; +import eu.eudat.authorization.Permission; import eu.eudat.convention.ConventionService; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.DataLogEntry; import gr.cite.tools.logging.LoggerService; @@ -14,15 +16,17 @@ import org.springframework.stereotype.Component; public class DescriptionTemplateTypeCensor extends BaseCensor{ private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DescriptionTemplateTypeCensor.class)); - - public DescriptionTemplateTypeCensor(ConventionService conventionService) { + protected final AuthorizationService authService; + public DescriptionTemplateTypeCensor(ConventionService conventionService, AuthorizationService authService) { super(conventionService); + this.authService = authService; } public void censor(FieldSet fields) { logger.debug(new DataLogEntry("censoring fields", fields)); - if (fields.isEmpty()) - return; + if (fields.isEmpty()) return; + + this.authService.authorizeForce(Permission.BrowseDescriptionTemplateType); } } diff --git a/dmp-backend/pom.xml b/dmp-backend/pom.xml index 719322697..b3444341c 100644 --- a/dmp-backend/pom.xml +++ b/dmp-backend/pom.xml @@ -329,6 +329,12 @@ 2.1.0 + + gr.cite + oidc-authz + 2.1.0 + + diff --git a/dmp-backend/web/src/main/java/eu/eudat/authorization/OwnedAuthorizationHandler.java b/dmp-backend/web/src/main/java/eu/eudat/authorization/OwnedAuthorizationHandler.java new file mode 100644 index 000000000..f6ef8f821 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/authorization/OwnedAuthorizationHandler.java @@ -0,0 +1,42 @@ +package eu.eudat.authorization; + +import eu.eudat.commons.scope.user.UserScope; +import gr.cite.commons.web.authz.handler.AuthorizationHandler; +import gr.cite.commons.web.authz.handler.AuthorizationHandlerContext; +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; +import gr.cite.commons.web.oidc.principal.MyPrincipal; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("ownedAuthorizationHandler") +public class OwnedAuthorizationHandler extends AuthorizationHandler { + + private final UserScope userScope; + + @Autowired + public OwnedAuthorizationHandler(UserScope userScope) { + this.userScope = userScope; + } + + @Override + public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) { + OwnedAuthorizationRequirement req = (OwnedAuthorizationRequirement) requirement; + + OwnedResource rs = (OwnedResource) resource; + + boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated(); + if (!isAuthenticated) return ACCESS_NOT_DETERMINED; + + if (this.userScope.getUserIdSafe() == null) return ACCESS_NOT_DETERMINED; + + if (rs != null && rs.getUserIds() != null && rs.getUserIds().contains(this.userScope.getUserIdSafe())) return ACCESS_GRANTED; + + return ACCESS_NOT_DETERMINED; + } + + @Override + public Class supporting() { + return OwnedAuthorizationRequirement.class; + } + +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/configurations/SecurityConfiguration.java b/dmp-backend/web/src/main/java/eu/eudat/configurations/SecurityConfiguration.java index d5477240b..f734459b6 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/configurations/SecurityConfiguration.java +++ b/dmp-backend/web/src/main/java/eu/eudat/configurations/SecurityConfiguration.java @@ -1,6 +1,16 @@ package eu.eudat.configurations; +import eu.eudat.authorization.OwnedAuthorizationHandler; +import eu.eudat.authorization.OwnedAuthorizationRequirement; +import eu.eudat.authorization.OwnedResource; +import gr.cite.commons.web.authz.handler.AuthorizationHandler; +import gr.cite.commons.web.authz.handler.PermissionClientAuthorizationHandler; +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; +import gr.cite.commons.web.authz.policy.AuthorizationRequirementMapper; +import gr.cite.commons.web.authz.policy.AuthorizationResource; +import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyConfigurer; +import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyResolverStrategy; import gr.cite.commons.web.oidc.configuration.WebSecurityProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -17,24 +27,28 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; + import java.util.List; import java.util.Set; @Configuration @EnableWebSecurity -public class SecurityConfiguration { +public class SecurityConfiguration { private final WebSecurityProperties webSecurityProperties; private final AuthenticationManagerResolver authenticationManagerResolver; private final Filter apiKeyFilter; + private final OwnedAuthorizationHandler ownedAuthorizationHandler; @Autowired public SecurityConfiguration(WebSecurityProperties webSecurityProperties, - @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, - @Qualifier("apiKeyFilter") Filter apiKeyFilter) { + @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, + @Qualifier("apiKeyFilter") Filter apiKeyFilter, + @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler) { this.webSecurityProperties = webSecurityProperties; this.authenticationManagerResolver = authenticationManagerResolver; this.apiKeyFilter = apiKeyFilter; + this.ownedAuthorizationHandler = ownedAuthorizationHandler; } @Bean @@ -52,60 +66,61 @@ public class SecurityConfiguration { return tempHttp.build(); } -// @Bean -// AuthorizationPolicyConfigurer authorizationPolicyConfigurer() { -// return new AuthorizationPolicyConfigurer() { -// -// @Override -// public AuthorizationPolicyResolverStrategy strategy() { -// return AuthorizationPolicyResolverStrategy.STRICT_CONSENSUS_BASED; -// } -// -// //Here you can register your custom authorization handlers, which will get used as well as the existing ones -// //This is optional and can be omitted -// //If not set / set to null, only the default authorization handlers will be used -// @Override -// public List> addCustomHandlers() { -// return null; -// } -// -// //Here you can register your custom authorization requirements (if any) -// //This is optional and can be omitted -// //If not set / set to null, only the default authorization requirements will be used -// @Override -// public List extendRequirements() { -// return List.of( -//// new TimeOfDayAuthorizationRequirement(new TimeOfDay("08:00","16:00"), true) -// ); -// } -// -// //Here you can select handlers you want to disable by providing the classes they are implemented by -// //You can disable any handler (including any custom one) -// //This is optional and can be omitted -// //If not set / set to null, all the handlers will be invoked, based on their requirement support -// //In the example below, the default client handler will be ignored by the resolver -// @Override -// public List>> disableHandlers() { -// return List.of(PermissionClientAuthorizationHandler.class); -// } -// }; -// } -// -// @Bean -// AuthorizationRequirementMapper authorizationRequirementMapper() { -// return new AuthorizationRequirementMapper() { -// @Override -// public AuthorizationRequirement map(AuthorizationResource resource, boolean matchAll, String[] permissions) { -// Class type = resource.getClass(); -// if (!AuthorizationResource.class.isAssignableFrom(type)) throw new IllegalArgumentException("resource"); -// -// if (OwnedResource.class.equals(type)) { -// return new OwnedAuthorizationRequirement(); -// } -// throw new IllegalArgumentException("resource"); -// } -// }; -// } + @Bean + AuthorizationPolicyConfigurer authorizationPolicyConfigurer() { + return new AuthorizationPolicyConfigurer() { + + @Override + public AuthorizationPolicyResolverStrategy strategy() { + return AuthorizationPolicyResolverStrategy.STRICT_CONSENSUS_BASED; + } + + //Here you can register your custom authorization handlers, which will get used as well as the existing ones + //This is optional and can be omitted + //If not set / set to null, only the default authorization handlers will be used + @Override + public List> addCustomHandlers() { + return List.of( ownedAuthorizationHandler); + } + + //Here you can register your custom authorization requirements (if any) + //This is optional and can be omitted + //If not set / set to null, only the default authorization requirements will be used + @Override + public List extendRequirements() { + return List.of( +// new TimeOfDayAuthorizationRequirement(new TimeOfDay("08:00","16:00"), true) + ); + } + + //Here you can select handlers you want to disable by providing the classes they are implemented by + //You can disable any handler (including any custom one) + //This is optional and can be omitted + //If not set / set to null, all the handlers will be invoked, based on their requirement support + //In the example below, the default client handler will be ignored by the resolver + @Override + public List>> disableHandlers() { + return List.of(PermissionClientAuthorizationHandler.class); + } + }; + } + + @Bean + AuthorizationRequirementMapper authorizationRequirementMapper() { + return new AuthorizationRequirementMapper() { + @Override + public AuthorizationRequirement map(AuthorizationResource resource, boolean matchAll, String[] permissions) { + Class type = resource.getClass(); + if (!AuthorizationResource.class.isAssignableFrom(type)) throw new IllegalArgumentException("resource"); + + if (OwnedResource.class.equals(type)) { + return new OwnedAuthorizationRequirement(); + } + throw new IllegalArgumentException("resource"); + } + }; + } + private String[] buildAntPatterns(Set endpoints) { if (endpoints == null) { diff --git a/dmp-backend/web/src/main/java/eu/eudat/configurations/WebMVCConfiguration.java b/dmp-backend/web/src/main/java/eu/eudat/configurations/WebMVCConfiguration.java index a554d13ce..8170b8c72 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/configurations/WebMVCConfiguration.java +++ b/dmp-backend/web/src/main/java/eu/eudat/configurations/WebMVCConfiguration.java @@ -1,6 +1,6 @@ package eu.eudat.configurations; -import eu.eudat.commons.scope.UserScope; +import eu.eudat.commons.scope.user.UserScope; import eu.eudat.interceptors.UserInterceptor; import eu.eudat.logic.handlers.PrincipalArgumentResolver; import eu.eudat.logic.services.ApiContext; diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java index 0aaa176bd..344d5437a 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java @@ -55,7 +55,8 @@ public class PrincipalController { BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt), BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt), BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more), - Account._roles); + Account._roles, + Account._permissions); } MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/UserInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/UserInterceptor.java index 373f9a52c..a488952c0 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/UserInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/UserInterceptor.java @@ -3,16 +3,14 @@ package eu.eudat.interceptors; import eu.eudat.commons.enums.ProviderType; import eu.eudat.commons.enums.Status; -import eu.eudat.commons.scope.UserScope; +import eu.eudat.commons.scope.user.UserScope; import eu.eudat.data.CredentialEntity; import eu.eudat.data.entities.UserInfo; import eu.eudat.data.entities.UserRole; import eu.eudat.exceptions.security.NullEmailException; -import eu.eudat.query.CredentialQuery; import eu.eudat.types.Authorities; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; -import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; import gr.cite.tools.logging.LoggerService; import jakarta.persistence.EntityManager; @@ -72,6 +70,7 @@ public class UserInterceptor implements WebRequestInterceptor { UUID userId = null; if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) { String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal()); + var aa = this.claimExtractor.roles(this.currentPrincipalResolver.currentPrincipal()); UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId)); if (cacheValue != null) { userId = cacheValue.getUserId(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/handlers/PrincipalArgumentResolver.java b/dmp-backend/web/src/main/java/eu/eudat/logic/handlers/PrincipalArgumentResolver.java index 5cdb96839..f185cfe2e 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/handlers/PrincipalArgumentResolver.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/handlers/PrincipalArgumentResolver.java @@ -1,6 +1,6 @@ package eu.eudat.logic.handlers; -import eu.eudat.commons.scope.UserScope; +import eu.eudat.commons.scope.user.UserScope; import eu.eudat.exceptions.security.UnauthorisedException; import eu.eudat.logic.security.claims.ClaimedAuthorities; import eu.eudat.logic.services.operations.authentication.AuthenticationService; diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java b/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java index 3991ef43d..f82e33126 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java @@ -139,6 +139,9 @@ public class Account { public final static String _principal = "principal"; private PrincipalInfo principal; + public final static String _permissions = "permissions"; + private List permissions; + public PrincipalInfo getPrincipal() { return principal; } @@ -162,4 +165,16 @@ public class Account { public void setRoles(List roles) { this.roles = roles; } + + public void setAuthenticated(Boolean authenticated) { + isAuthenticated = authenticated; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List permissions) { + this.permissions = permissions; + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java b/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java index 67bad2fcc..ad1a31b50 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java @@ -1,10 +1,10 @@ package eu.eudat.models.v2; -import eu.eudat.commons.scope.UserScope; +import eu.eudat.commons.scope.user.UserScope; import eu.eudat.data.entities.UserInfo; import eu.eudat.data.entities.UserRole; import eu.eudat.logic.services.ApiContext; -import eu.eudat.types.Authorities; +import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; @@ -24,11 +24,13 @@ public class AccountBuilder { private final ClaimExtractor claimExtractor; private final Set excludeMoreClaim; private final CurrentPrincipalResolver currentPrincipalResolver; + private final AuthorizationConfiguration authorizationConfiguration; private final ApiContext apiContext; private final UserScope userScope; - public AccountBuilder(ClaimExtractor claimExtractor, CurrentPrincipalResolver currentPrincipalResolver, ApiContext apiContext, UserScope userScope) { + public AccountBuilder(ClaimExtractor claimExtractor, CurrentPrincipalResolver currentPrincipalResolver, AuthorizationConfiguration authorizationConfiguration, ApiContext apiContext, UserScope userScope) { this.claimExtractor = claimExtractor; this.currentPrincipalResolver = currentPrincipalResolver; + this.authorizationConfiguration = authorizationConfiguration; this.apiContext = apiContext; this.userScope = userScope; this.excludeMoreClaim = Set.of( @@ -93,6 +95,11 @@ public class AccountBuilder { model.getRoles().add(item.getRole()); } } + if (fields.hasField(Account._permissions)) { + List roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + Set permissions = authorizationConfiguration.permissionsOfRoles(roles); + model.setPermissions(new ArrayList<>(permissions)); + } return model; } } diff --git a/dmp-backend/web/src/main/resources/config/application.yml b/dmp-backend/web/src/main/resources/config/application.yml index e0cf36a95..ced49fdd3 100644 --- a/dmp-backend/web/src/main/resources/config/application.yml +++ b/dmp-backend/web/src/main/resources/config/application.yml @@ -4,6 +4,7 @@ spring: config: import: optional:classpath:config/app.env[.properties], optional:file:../config/app.env[.properties], optional:classpath:config/db.yml[.yml], optional:classpath:config/db-${spring.profiles.active}.yml[.yml], optional:file:../config/db-${spring.profiles.active}.yml[.yml], + optional:classpath:config/permissions.yml[.yml], optional:classpath:config/permissions-${spring.profiles.active}.yml[.yml], optional:file:../config/permissions-${spring.profiles.active}.yml[.yml], optional:classpath:config/security.yml[.yml], optional:classpath:config/security-${spring.profiles.active}.yml[.yml], optional:file:../config/security-${spring.profiles.active}.yml[.yml], optional:classpath:config/server.yml[.yml], optional:classpath:config/server-${spring.profiles.active}.yml[.yml], optional:file:../config/server-${spring.profiles.active}.yml[.yml], optional:classpath:config/logging.yml[.yml], optional:classpath:config/logging-${spring.profiles.active}.yml[.yml], optional:file:../config/logging-${spring.profiles.active}.yml[.yml], diff --git a/dmp-backend/web/src/main/resources/config/permissions.yml b/dmp-backend/web/src/main/resources/config/permissions.yml new file mode 100644 index 000000000..68f99ab75 --- /dev/null +++ b/dmp-backend/web/src/main/resources/config/permissions.yml @@ -0,0 +1,31 @@ +permissions: + extendedClaims: [ ] + policies: + # Users + BrowseDescriptionTemplateType: + roles: + - Admin + clients: [ ] + allowAnonymous: true + allowAuthenticated: false + EditDescriptionTemplateType: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteDescriptionTemplateType: + roles: + - Admin + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + + # ViewPage Permissions + ViewDatasetPage: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts new file mode 100644 index 000000000..063694faf --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -0,0 +1,10 @@ +export enum AppPermission { + //DescriptionTemplateType + BrowseDescriptionTemplateType = "BrowseDescriptionTemplateType", + EditDescriptionTemplateType = "EditDescriptionTemplateType", + DeleteDescriptionTemplateType = "DeleteDescriptionTemplateType", + + // UI Pages + ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage" +} + diff --git a/dmp-frontend/src/app/core/model/auth/principal.ts b/dmp-frontend/src/app/core/model/auth/principal.ts index eb916313c..c0012e7cd 100644 --- a/dmp-frontend/src/app/core/model/auth/principal.ts +++ b/dmp-frontend/src/app/core/model/auth/principal.ts @@ -1,10 +1,12 @@ import { AppRole } from "@app/core/common/enum/app-role"; +import { AppPermission } from "@app/core/common/enum/permission.enum"; import { Guid } from "@common/types/guid"; export interface AppAccount { isAuthenticated: boolean; // permissions: AppPermission[]; roles: AppRole[]; + permissions: AppPermission[]; principal: AppPrincipalInfo; profile: UserProfileInfo; } diff --git a/dmp-frontend/src/app/core/services/auth/auth.service.ts b/dmp-frontend/src/app/core/services/auth/auth.service.ts index ee4ad8e11..66238f10a 100644 --- a/dmp-frontend/src/app/core/services/auth/auth.service.ts +++ b/dmp-frontend/src/app/core/services/auth/auth.service.ts @@ -14,8 +14,12 @@ import { KeycloakEventType, KeycloakService } from 'keycloak-angular'; import { NgZone } from '@angular/core'; import { PrincipalService } from '../http/principal.service'; import { AppRole } from '@app/core/common/enum/app-role'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; - +export interface ResolutionContext { + roles: AppRole[]; + permissions: AppPermission[]; +} export interface AuthenticationState { loginStatus: LoginStatus; } @@ -303,4 +307,37 @@ export class AuthService extends BaseService { this.zone.run(() => this.router.navigate([returnUrl])); } + public hasPermission(permission: AppPermission): boolean { + // if (!this.installationConfiguration.appServiceEnabled) { return true; } //TODO: maybe reconsider + return this.evaluatePermission(this.appAccount?.permissions || [], permission); + + } + private evaluatePermission(availablePermissions: string[], permissionToCheck: string): boolean { + if (!permissionToCheck) { return false; } + if (this.hasRole(AppRole.Admin)) { return true; } + return availablePermissions.map(x => x.toLowerCase()).includes(permissionToCheck.toLowerCase()); + } + public hasAnyPermission(permissions: AppPermission[]): boolean { + if (!permissions) { return false; } + return permissions.filter((p) => this.hasPermission(p)).length > 0; + } + + public authorize(context: ResolutionContext): boolean { + + if (!context || this.hasRole(AppRole.Admin)) { return true; } + + let roleAuthorized = false; + if (context.roles && context.roles.length > 0) { + roleAuthorized = this.hasAnyRole(context.roles); + } + + let permissionAuthorized = false; + if (context.permissions && context.permissions.length > 0) { + permissionAuthorized = this.hasAnyPermission(context.permissions); + } + + if (roleAuthorized || permissionAuthorized) { return true; } + + return false; + } }