task #9088 Authorization model should be changed to be Permission based

This commit is contained in:
Efstratios Giannopoulos 2023-10-17 15:22:05 +03:00
parent 419c4d64f8
commit 7ed111e936
21 changed files with 302 additions and 72 deletions

View File

@ -21,6 +21,11 @@
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>oidc-authz</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,10 @@
package eu.eudat.authorization;
import java.util.EnumSet;
public enum AuthorizationFlags {
None, Permission, Owner;
public static final EnumSet<AuthorizationFlags> OwnerOrPermission = EnumSet.of(Owner, Permission);
public static final EnumSet<AuthorizationFlags> OwnerOrPermissionOrIndicator = EnumSet.of(Owner, Permission);
public static final EnumSet<AuthorizationFlags> OwnerOrPermissionOrIndicatorOrIndicatorAccess = EnumSet.of(Owner, Permission);
}

View File

@ -0,0 +1,6 @@
package eu.eudat.authorization;
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
public class OwnedAuthorizationRequirement implements AuthorizationRequirement {
}

View File

@ -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<UUID> userIds;
public OwnedResource(UUID userId) {
this(List.of(userId));
}
public OwnedResource(List<UUID> userIds) {
this.userIds = userIds;
}
public List<UUID> getUserIds() {
return userIds;
}
public void setUserIds(List<UUID> userIds) {
this.userIds = userIds;
}
}

View File

@ -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";
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -329,6 +329,12 @@
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>oidc-authz</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
<profiles>

View File

@ -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<OwnedAuthorizationRequirement> {
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<? extends AuthorizationRequirement> supporting() {
return OwnedAuthorizationRequirement.class;
}
}

View File

@ -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<HttpServletRequest> authenticationManagerResolver;
private final Filter apiKeyFilter;
private final OwnedAuthorizationHandler ownedAuthorizationHandler;
@Autowired
public SecurityConfiguration(WebSecurityProperties webSecurityProperties,
@Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver,
@Qualifier("apiKeyFilter") Filter apiKeyFilter) {
@Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver<HttpServletRequest> 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<AuthorizationHandler<? extends AuthorizationRequirement>> 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<? extends AuthorizationRequirement> 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<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> 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<AuthorizationHandler<? extends AuthorizationRequirement>> 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<? extends AuthorizationRequirement> 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<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> 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<String> endpoints) {
if (endpoints == null) {

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -139,6 +139,9 @@ public class Account {
public final static String _principal = "principal";
private PrincipalInfo principal;
public final static String _permissions = "permissions";
private List<String> permissions;
public PrincipalInfo getPrincipal() {
return principal;
}
@ -162,4 +165,16 @@ public class Account {
public void setRoles(List<Integer> roles) {
this.roles = roles;
}
public void setAuthenticated(Boolean authenticated) {
isAuthenticated = authenticated;
}
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
}

View File

@ -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<String> 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<String> roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
Set<String> permissions = authorizationConfiguration.permissionsOfRoles(roles);
model.setPermissions(new ArrayList<>(permissions));
}
return model;
}
}

View File

@ -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],

View File

@ -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

View File

@ -0,0 +1,10 @@
export enum AppPermission {
//DescriptionTemplateType
BrowseDescriptionTemplateType = "BrowseDescriptionTemplateType",
EditDescriptionTemplateType = "EditDescriptionTemplateType",
DeleteDescriptionTemplateType = "DeleteDescriptionTemplateType",
// UI Pages
ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage"
}

View File

@ -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;
}

View File

@ -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;
}
}