diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AffiliatedAuthorizationRequirement.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AffiliatedAuthorizationRequirement.java new file mode 100644 index 000000000..dddcdde28 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AffiliatedAuthorizationRequirement.java @@ -0,0 +1,40 @@ +package eu.eudat.authorization; + +import gr.cite.commons.web.authz.policy.AuthorizationRequirement; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class AffiliatedAuthorizationRequirement implements AuthorizationRequirement { + private final Set requiredPermissions; + private final boolean matchAll; + + public AffiliatedAuthorizationRequirement(Set requiredPermissions) { + this(false, requiredPermissions); + } + + public AffiliatedAuthorizationRequirement(String... requiredPermissions) { + this(false, requiredPermissions); + + } + + public AffiliatedAuthorizationRequirement(boolean matchAll, Set requiredPermissions) { + this.matchAll = matchAll; + this.requiredPermissions = requiredPermissions; + } + + public AffiliatedAuthorizationRequirement(boolean matchAll, String... requiredPermissions) { + this.requiredPermissions = new HashSet<>(); + this.matchAll = matchAll; + this.requiredPermissions.addAll(Arrays.stream(requiredPermissions).distinct().toList()); + } + + public Set getRequiredPermissions() { + return requiredPermissions; + } + + public boolean getMatchAll() { + return matchAll; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AffiliatedResource.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AffiliatedResource.java new file mode 100644 index 000000000..1cd45fd45 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AffiliatedResource.java @@ -0,0 +1,31 @@ +package eu.eudat.authorization; + +import eu.eudat.commons.enums.DmpUserRole; +import gr.cite.commons.web.authz.policy.AuthorizationResource; + +import java.util.HashSet; +import java.util.List; + +public class AffiliatedResource extends AuthorizationResource { + private HashSet dmpUserRoles; + + public AffiliatedResource() { + dmpUserRoles = new HashSet<>(); + } + + public AffiliatedResource(DmpUserRole dmpUserRole) { + this(List.of(dmpUserRole)); + } + + public AffiliatedResource(List dmpUserRoles) { + this.dmpUserRoles = new HashSet<>(dmpUserRoles); + } + + public HashSet getDmpUserRoles() { + return dmpUserRoles; + } + + public void setDmpUserRoles(HashSet dmpUserRoles) { + this.dmpUserRoles = dmpUserRoles; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java new file mode 100644 index 000000000..cb982f855 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/PermissionNameProvider.java @@ -0,0 +1,40 @@ +package eu.eudat.authorization; + +import eu.eudat.convention.ConventionService; +import eu.eudat.service.deposit.DepositServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +@Service +@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) +public class PermissionNameProvider { + private static final Logger logger = LoggerFactory.getLogger(DepositServiceImpl.class); + private final List permissions; + + public PermissionNameProvider(ConventionService conventionService) { + this.permissions = new ArrayList<>(); + Class clazz = Permission.class; + for (Field f : clazz.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + try { + Object value = f.get(null); + if (value != null && !conventionService.isNullOrEmpty((String)value)) this.permissions.add((String)value); + } catch (Exception e) { + logger.error("Can not load permission " + f.getName() + " " + e.getMessage()); + } + } + } + } + + public List getPermissions() { + return permissions; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AffiliationCacheOptions.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AffiliationCacheOptions.java new file mode 100644 index 000000000..eaf62ca40 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AffiliationCacheOptions.java @@ -0,0 +1,10 @@ +package eu.eudat.authorization.authorizationcontentresolver; + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.affiliation") +public class AffiliationCacheOptions extends CacheOptions { +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AffiliationCacheService.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AffiliationCacheService.java new file mode 100644 index 000000000..71f583cb5 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AffiliationCacheService.java @@ -0,0 +1,97 @@ +package eu.eudat.authorization.authorizationcontentresolver; + +import eu.eudat.authorization.AffiliatedResource; +import eu.eudat.convention.ConventionService; +import gr.cite.tools.cache.CacheService; +import gr.cite.tools.exception.MyApplicationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +@Service +public class AffiliationCacheService extends CacheService { + + public static class AffiliationCacheValue { + + public AffiliationCacheValue() { + } + + public AffiliationCacheValue(UUID userId, UUID entityId, String entityType, AffiliatedResource affiliatedResource) { + this.userId = userId; + this.entityId = entityId; + this.entityType = entityType; + this.affiliatedResource = affiliatedResource; + } + + private UUID userId; + private UUID entityId; + private String entityType; + private AffiliatedResource affiliatedResource; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public AffiliatedResource getAffiliatedResource() { + return affiliatedResource; + } + + public void setAffiliatedResource(AffiliatedResource affiliatedResource) { + this.affiliatedResource = affiliatedResource; + } + } + + private final ConventionService conventionService; + @Autowired + public AffiliationCacheService(AffiliationCacheOptions options, ConventionService conventionService) { + super(options); + this.conventionService = conventionService; + } + + @Override + protected Class valueClass() { + return AffiliationCacheValue.class; + } + + @Override + public String keyOf(AffiliationCacheValue value) { + return this.buildKey(value.getUserId(), value.getEntityId(), value.getEntityType()); + } + + + public String buildKey(UUID userId, UUID entityId, String entityType) { + if (userId == null) throw new IllegalArgumentException("userId id is required"); + if (entityId == null) throw new IllegalArgumentException("entityId id is required"); + if (this.conventionService.isNullOrEmpty(entityType)) throw new IllegalArgumentException("entityType id is required"); + + HashMap keyParts = new HashMap<>(); + keyParts.put("$user$", userId.toString().replace("-", "").toLowerCase(Locale.ROOT)); + keyParts.put("$entity$", entityId.toString().replace("-", "").toLowerCase(Locale.ROOT)); + keyParts.put("$type$", entityType); + return this.generateKey(keyParts); + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java new file mode 100644 index 000000000..f44af1597 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolver.java @@ -0,0 +1,13 @@ +package eu.eudat.authorization.authorizationcontentresolver; + +import eu.eudat.authorization.AffiliatedResource; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface AuthorizationContentResolver { + List getPermissionNames(); + + Map dmpAffiliation(List ids); +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java new file mode 100644 index 000000000..72c8bdbc3 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/authorizationcontentresolver/AuthorizationContentResolverImpl.java @@ -0,0 +1,77 @@ +package eu.eudat.authorization.authorizationcontentresolver; + +import eu.eudat.authorization.AffiliatedResource; +import eu.eudat.authorization.PermissionNameProvider; +import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.scope.user.UserScope; +import eu.eudat.data.DmpEntity; +import eu.eudat.data.DmpUserEntity; +import eu.eudat.model.DmpUser; +import eu.eudat.query.DmpUserQuery; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; +import org.springframework.stereotype.Service; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.*; + +@Service +@RequestScope +public class AuthorizationContentResolverImpl implements AuthorizationContentResolver { + private final QueryFactory queryFactory; + private final UserScope userScope; + private final AffiliationCacheService affiliationCacheService; + private final PermissionNameProvider permissionNameProvider; + public AuthorizationContentResolverImpl(QueryFactory queryFactory, UserScope userScope, AffiliationCacheService affiliationCacheService, PermissionNameProvider permissionNameProvider) { + this.queryFactory = queryFactory; + this.userScope = userScope; + this.affiliationCacheService = affiliationCacheService; + this.permissionNameProvider = permissionNameProvider; + } + + @Override + public List getPermissionNames() { + return permissionNameProvider.getPermissions(); + } + @Override + public Map dmpAffiliation(List ids){ + UUID userId = this.userScope.getUserIdSafe(); + Map affiliatedResources = new HashMap<>(); + for (UUID id : ids){ + affiliatedResources.put(id, new AffiliatedResource()); + } + if (userId == null || !userScope.isSet()) return affiliatedResources; + + List idsToResolve = this.getAffiliatedFromCache(ids, userId, affiliatedResources, DmpEntity.class.getSimpleName()); + if (idsToResolve.isEmpty()) return affiliatedResources; + + List dmpUsers = this.queryFactory.query(DmpUserQuery.class).dmpIds(ids).userIds(userId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(DmpUser._role).ensure(DmpUser._dmp)); + + for (DmpUserEntity dmpUser : dmpUsers){ + affiliatedResources.get(dmpUser.getDmpId()).getDmpUserRoles().add(dmpUser.getRole()); + } + + this.ensureAffiliatedInCache(idsToResolve, userId, affiliatedResources, DmpEntity.class.getSimpleName()); + return affiliatedResources; + } + + private List getAffiliatedFromCache(List ids, UUID userId, Map affiliatedResources, String entityType){ + List idsToResolve = new ArrayList<>(); + for (UUID id : ids){ + AffiliationCacheService.AffiliationCacheValue cacheValue = this.affiliationCacheService.lookup(this.affiliationCacheService.buildKey(userId, id, entityType)); + if (cacheValue != null) affiliatedResources.put(id, cacheValue.getAffiliatedResource()); + else idsToResolve.add(id); + } + return idsToResolve; + } + + private void ensureAffiliatedInCache(List idsToResolve, UUID userId, Map affiliatedResources, String entityType){ + for (UUID id : idsToResolve){ + AffiliatedResource affiliatedResource = affiliatedResources.getOrDefault(id, null); + if (affiliatedResource != null) { + AffiliationCacheService.AffiliationCacheValue cacheValue = new AffiliationCacheService.AffiliationCacheValue(userId, id, entityType, affiliatedResource); + this.affiliationCacheService.put(cacheValue); + } + } + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/Dmp.java b/dmp-backend/core/src/main/java/eu/eudat/model/Dmp.java index 1807bcc3a..9e351b515 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/Dmp.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/Dmp.java @@ -84,6 +84,9 @@ public class Dmp { private List entityDois; public static final String _entityDois = "entityDois"; + private List authorizationFlags; + public static final String _authorizationFlags = "authorizationFlags"; + public UUID getId() { return id; } @@ -275,4 +278,12 @@ public class Dmp { public void setEntityDois(List entityDois) { this.entityDois = entityDois; } + + public List getAuthorizationFlags() { + return authorizationFlags; + } + + public void setAuthorizationFlags(List authorizationFlags) { + this.authorizationFlags = authorizationFlags; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/builder/BaseBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/model/builder/BaseBuilder.java index 445413f89..e3f74e602 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/builder/BaseBuilder.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/builder/BaseBuilder.java @@ -1,12 +1,18 @@ package eu.eudat.model.builder; +import eu.eudat.authorization.AffiliatedResource; +import eu.eudat.authorization.Permission; +import eu.eudat.authorization.PermissionNameProvider; import eu.eudat.convention.ConventionService; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.Builder; import gr.cite.tools.data.query.QueryBase; import gr.cite.tools.exception.MyApplicationException; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.time.Instant; import java.util.*; import java.util.function.Function; @@ -92,5 +98,28 @@ public abstract class BaseBuilder implements Builder { return this.conventionService.asIndexer(names); } + + protected Set extractAuthorizationFlags(FieldSet fields, String propertyName, List permissionNames){ + if (fields == null) return new HashSet<>(); + if (permissionNames == null) return new HashSet<>(); + + FieldSet authorizationFlags = fields.extractPrefixed(this.asPrefix(propertyName)); + List permissions = new ArrayList<>(); + for (String fieldValue : authorizationFlags.getFields()) permissions.addAll(permissionNames.stream().filter(x-> x.equalsIgnoreCase(fieldValue)).toList()); + return new HashSet<>(permissions); + } + + protected List evaluateAuthorizationFlags(AuthorizationService authorizationService, Set authorizationFlags, AffiliatedResource affiliatedResource) { + List allowed = new ArrayList<>(); + if (authorizationFlags == null) return allowed; + if (authorizationService == null) return allowed; + + for (String permission : authorizationFlags) { + Boolean isAllowed = affiliatedResource == null ? authorizationService.authorize(permission) : authorizationService.authorizeAtLeastOne(List.of(affiliatedResource), permission); + if (isAllowed) allowed.add(permission); + } + return allowed; + } + } diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/builder/DmpBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/model/builder/DmpBuilder.java index b1c713de4..9ab100b9e 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/builder/DmpBuilder.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/builder/DmpBuilder.java @@ -1,6 +1,9 @@ package eu.eudat.model.builder; +import eu.eudat.authorization.AffiliatedResource; import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.authorization.Permission; +import eu.eudat.authorization.authorizationcontentresolver.AuthorizationContentResolver; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.enums.EntityType; import eu.eudat.commons.types.description.PropertyDefinitionEntity; @@ -12,6 +15,7 @@ import eu.eudat.model.*; import eu.eudat.model.builder.descriptionpropertiesdefinition.PropertyDefinitionBuilder; import eu.eudat.model.builder.dmpproperties.DmpPropertiesBuilder; import eu.eudat.query.*; +import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; @@ -36,17 +40,21 @@ public class DmpBuilder extends BaseBuilder { private final BuilderFactory builderFactory; private final JsonHandlingService jsonHandlingService; + private final AuthorizationService authorizationService; + private final AuthorizationContentResolver authorizationContentResolver; private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); @Autowired public DmpBuilder(ConventionService conventionService, QueryFactory queryFactory, - BuilderFactory builderFactory, JsonHandlingService jsonHandlingService) { + BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver) { super(conventionService, new LoggerService(LoggerFactory.getLogger(DmpBuilder.class))); this.queryFactory = queryFactory; this.builderFactory = builderFactory; this.jsonHandlingService = jsonHandlingService; + this.authorizationService = authorizationService; + this.authorizationContentResolver = authorizationContentResolver; } public DmpBuilder authorize(EnumSet values) { @@ -84,6 +92,10 @@ public class DmpBuilder extends BaseBuilder { FieldSet dmpDescriptionTemplatesFields = fields.extractPrefixed(this.asPrefix(Dmp._dmpDescriptionTemplates)); Map> dmpDescriptionTemplatesMap = this.collectDmpDescriptionTemplates(dmpDescriptionTemplatesFields, data); + Set authorizationFlags = this.extractAuthorizationFlags(fields, Dmp._authorizationFlags, this.authorizationContentResolver.getPermissionNames()); + + Map affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.dmpAffiliation(data.stream().map(DmpEntity::getId).collect(Collectors.toList())); + FieldSet propertiesFields = fields.extractPrefixed(this.asPrefix(Dmp._properties)); for (DmpEntity d : data) { Dmp m = new Dmp(); @@ -113,6 +125,7 @@ public class DmpBuilder extends BaseBuilder { DmpPropertiesEntity propertyDefinition = this.jsonHandlingService.fromJsonSafe(DmpPropertiesEntity.class, d.getProperties()); m.setProperties(this.builderFactory.builder(DmpPropertiesBuilder.class).authorize(this.authorize).build(propertiesFields, propertyDefinition)); } + if (authorizationFlags != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null))); models.add(m); } this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/censorship/ReferenceCensor.java b/dmp-backend/core/src/main/java/eu/eudat/model/censorship/ReferenceCensor.java index 859d38b2f..3aea1777b 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/censorship/ReferenceCensor.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/censorship/ReferenceCensor.java @@ -38,7 +38,7 @@ public class ReferenceCensor extends BaseCensor { if (fields == null || fields.isEmpty()) return; - this.authService.authorizeForce(Permission.BrowseReference); + this.authService.authorizeForce(Permission.BrowseReference, Permission.DeferredAffiliation); FieldSet definitionFields = fields.extractPrefixed(this.asIndexerPrefix(Reference._definition)); this.censorFactory.censor(DefinitionCensor.class).censor(definitionFields, userId); FieldSet dmpReferencesFields = fields.extractPrefixed(this.asIndexerPrefix(Reference._dmpReferences)); diff --git a/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java b/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java index 3c90783ad..777855b1f 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java +++ b/dmp-backend/core/src/main/java/eu/eudat/query/DmpUserQuery.java @@ -259,6 +259,8 @@ public class DmpUserQuery extends QueryBase { else if (item.match(DmpUser._updatedAt)) return DmpUserEntity._updatedAt; else if (item.match(DmpUser._isActive)) return DmpUserEntity._isActive; else if (item.match(DmpUser._hash)) return DmpUserEntity._updatedAt; + else if (item.match(DmpUser._dmp)) return DmpUserEntity._dmpId; + else if (item.match(DmpUser._user)) return DmpUserEntity._userId; else return null; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/authorization/AffiliatedAuthorizationHandler.java b/dmp-backend/web/src/main/java/eu/eudat/authorization/AffiliatedAuthorizationHandler.java new file mode 100644 index 000000000..2307ab5f8 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/authorization/AffiliatedAuthorizationHandler.java @@ -0,0 +1,69 @@ +package eu.eudat.authorization; + +import eu.eudat.commons.enums.DmpUserRole; +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; + +import java.util.HashSet; + +@Component("affiliatedAuthorizationHandler") +public class AffiliatedAuthorizationHandler extends AuthorizationHandler { + + private final CustomPermissionAttributesConfiguration myConfiguration; + + @Autowired + public AffiliatedAuthorizationHandler(CustomPermissionAttributesConfiguration myConfiguration) { + this.myConfiguration = myConfiguration; + } + + @Override + public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) { + AffiliatedAuthorizationRequirement req = (AffiliatedAuthorizationRequirement) requirement; + if (req.getRequiredPermissions() == null) + return ACCESS_NOT_DETERMINED; + + AffiliatedResource rs = (AffiliatedResource) resource; + + boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated(); + if (!isAuthenticated) + return ACCESS_NOT_DETERMINED; + + if (myConfiguration.getMyPolicies() == null) + return ACCESS_NOT_DETERMINED; + + int hits = 0; + HashSet roles = rs != null && rs.getDmpUserRoles() != null ? rs.getDmpUserRoles() : null; + + for (String permission : req.getRequiredPermissions()) { + CustomPermissionAttributesProperties.MyPermission policy = myConfiguration.getMyPolicies().get(permission); + boolean hasPermission = policy != null && hasPermission(policy.getDmp(), roles); + if (hasPermission) hits += 1; + } + if ((req.getMatchAll() && req.getRequiredPermissions().size() == hits) || (!req.getMatchAll() && hits > 0)) + return ACCESS_GRANTED; + + return ACCESS_NOT_DETERMINED; + } + + private Boolean hasPermission(DmpRole dmpRole, HashSet roles) { + if (roles == null) + return Boolean.FALSE; + if (dmpRole == null || dmpRole.getRoles() == null) + return Boolean.FALSE; + for (DmpUserRole role : dmpRole.getRoles()) { + if (roles.contains(role)) + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + @Override + public Class supporting() { + return AffiliatedAuthorizationRequirement.class; + } + +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/authorization/CustomPermissionAttributesConfiguration.java b/dmp-backend/web/src/main/java/eu/eudat/authorization/CustomPermissionAttributesConfiguration.java new file mode 100644 index 000000000..8190c54d4 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/authorization/CustomPermissionAttributesConfiguration.java @@ -0,0 +1,24 @@ +package eu.eudat.authorization; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; + +@Configuration +@EnableConfigurationProperties(CustomPermissionAttributesProperties.class) +public class CustomPermissionAttributesConfiguration { + + private final CustomPermissionAttributesProperties properties; + + @Autowired + public CustomPermissionAttributesConfiguration(CustomPermissionAttributesProperties properties) { + this.properties = properties; + } + + public HashMap getMyPolicies() { + return properties.getPolicies(); + } + +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/authorization/CustomPermissionAttributesProperties.java b/dmp-backend/web/src/main/java/eu/eudat/authorization/CustomPermissionAttributesProperties.java new file mode 100644 index 000000000..d73bccd79 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/authorization/CustomPermissionAttributesProperties.java @@ -0,0 +1,39 @@ +package eu.eudat.authorization; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +import java.util.HashMap; + +@ConfigurationProperties(prefix = "permissions") +@ConditionalOnProperty(prefix = "permissions", name = "enabled", havingValue = "true") +public class CustomPermissionAttributesProperties { + + private final HashMap policies; + + @ConstructorBinding + public CustomPermissionAttributesProperties(HashMap policies) { + this.policies = policies; + } + + public HashMap getPolicies() { + return policies; + } + + public static class MyPermission { + + private final DmpRole dmp; + + @ConstructorBinding + public MyPermission(DmpRole dmp) { + this.dmp = dmp; + } + + + public DmpRole getDmp() { + return dmp; + } + } + +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/authorization/DmpRole.java b/dmp-backend/web/src/main/java/eu/eudat/authorization/DmpRole.java new file mode 100644 index 000000000..7389398ce --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/authorization/DmpRole.java @@ -0,0 +1,21 @@ +package eu.eudat.authorization; + + +import eu.eudat.commons.enums.DmpUserRole; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +import java.util.Set; + +public class DmpRole { + private final Set roles; + + @ConstructorBinding + public DmpRole(Set roles) { + this.roles = roles; + } + + public Set getRoles() { + return roles; + } + +} 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 index f6ef8f821..5eac83967 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/authorization/OwnedAuthorizationHandler.java +++ b/dmp-backend/web/src/main/java/eu/eudat/authorization/OwnedAuthorizationHandler.java @@ -1,5 +1,6 @@ package eu.eudat.authorization; +import eu.eudat.commons.enums.DmpUserRole; import eu.eudat.commons.scope.user.UserScope; import gr.cite.commons.web.authz.handler.AuthorizationHandler; import gr.cite.commons.web.authz.handler.AuthorizationHandlerContext; @@ -8,6 +9,9 @@ import gr.cite.commons.web.oidc.principal.MyPrincipal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.HashSet; +import java.util.List; + @Component("ownedAuthorizationHandler") public class OwnedAuthorizationHandler extends AuthorizationHandler { @@ -40,3 +44,4 @@ public class OwnedAuthorizationHandler extends AuthorizationHandler authenticationManagerResolver; private final Filter apiKeyFilter; private final OwnedAuthorizationHandler ownedAuthorizationHandler; + private final AffiliatedAuthorizationHandler affiliatedAuthorizationHandler; @Autowired public SecurityConfiguration(WebSecurityProperties webSecurityProperties, @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, @Qualifier("apiKeyFilter") Filter apiKeyFilter, - @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler) { + @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler, + @Qualifier("affiliatedAuthorizationHandler") AffiliatedAuthorizationHandler affiliatedAuthorizationHandler) { this.webSecurityProperties = webSecurityProperties; this.authenticationManagerResolver = authenticationManagerResolver; this.apiKeyFilter = apiKeyFilter; this.ownedAuthorizationHandler = ownedAuthorizationHandler; + this.affiliatedAuthorizationHandler = affiliatedAuthorizationHandler; } @Bean @@ -80,7 +81,7 @@ public class SecurityConfiguration { //If not set / set to null, only the default authorization handlers will be used @Override public List> addCustomHandlers() { - return List.of( ownedAuthorizationHandler); + return List.of(affiliatedAuthorizationHandler, ownedAuthorizationHandler); } //Here you can register your custom authorization requirements (if any) @@ -116,6 +117,9 @@ public class SecurityConfiguration { if (OwnedResource.class.equals(type)) { return new OwnedAuthorizationRequirement(); } + if (AffiliatedResource.class.equals(type)) { + return new AffiliatedAuthorizationRequirement(matchAll, permissions); + } throw new IllegalArgumentException("resource"); } }; diff --git a/dmp-backend/web/src/main/resources/config/cache.yml b/dmp-backend/web/src/main/resources/config/cache.yml index c717f36b0..8fbdce800 100644 --- a/dmp-backend/web/src/main/resources/config/cache.yml +++ b/dmp-backend/web/src/main/resources/config/cache.yml @@ -34,6 +34,14 @@ cache: expireAfterWriteMinutes: 10 expireAfterAccessMinutes: 10 refreshAfterWriteMinutes: 10 + - names: [ "affiliation" ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 1 + expireAfterAccessMinutes: 1 + refreshAfterWriteMinutes: 1 - names: [ "dashboardStatisticsByUserId" ] allowNullValues: true initialCapacity: 100 @@ -79,4 +87,7 @@ cache: keyPattern: base:v0 token-exchange-key: name: tokenExchangeKey - keyPattern: resolve_$keyhash$:v0 \ No newline at end of file + keyPattern: resolve_$keyhash$:v0 + affiliation: + name: affiliation + keyPattern: affiliation_$entity$_$user$_$type$:v0 \ No newline at end of file diff --git a/dmp-backend/web/src/main/resources/config/permissions.yml b/dmp-backend/web/src/main/resources/config/permissions.yml index 1e3f21f34..db2d3d94a 100644 --- a/dmp-backend/web/src/main/resources/config/permissions.yml +++ b/dmp-backend/web/src/main/resources/config/permissions.yml @@ -206,9 +206,6 @@ permissions: BrowseDmpAssociatedUser: roles: - Admin - - DescriptionTemplateEditor - - Manager - - User claims: [ ] clients: [ ] allowAnonymous: false @@ -217,6 +214,9 @@ permissions: BrowseDescriptionTemplateType: roles: - Admin + - User + - Manager + - DescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -321,6 +321,12 @@ permissions: EditDmp: roles: - Admin + dmp: + roles: + - Owner + - User + - DescriptionContributor + - Reviewer clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -463,7 +469,6 @@ permissions: BrowseReference: roles: - Admin - - User clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -527,6 +532,9 @@ permissions: BrowseSupportiveMaterial: roles: - Admin + - User + - Manager + - DescriptionTemplateEditor clients: [ ] allowAnonymous: yes allowAuthenticated: yes @@ -548,6 +556,9 @@ permissions: BrowseReferenceType: roles: - Admin + - User + - Manager + - DescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -691,25 +702,24 @@ permissions: # Lock Permissions BrowseLock: roles: - - User - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false EditLock: roles: - - User + - Admin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteLock: roles: - - User + - Admin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false - # Lock Permissions + # Contact Permissions SendContactSupport: roles: [] clients: [ ] @@ -740,6 +750,9 @@ permissions: BrowsePrefillingSource: roles: - Admin + - DescriptionTemplateEditor + - Manager + - User clients: [ ] allowAnonymous: false allowAuthenticated: false