From 83b5ec5a80bdae28be7750b90839daf93b4f891b Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Tue, 2 Apr 2024 17:18:07 +0300 Subject: [PATCH 1/2] multitenant changes --- .../eu/eudat/authorization/Permission.java | 25 +- .../eu/eudat/data/TenantEntityManager.java | 37 +- .../eu/eudat/data/tenant/TenantListener.java | 42 +- .../service/language/LanguageServiceImpl.java | 10 +- .../interceptors/user/UserInterceptor.java | 10 +- .../src/main/resources/config/permissions.yml | 397 ++++++++++++------ dmp-frontend/src/app/app-routing.module.ts | 21 +- .../app/core/common/enum/permission.enum.ts | 36 +- .../app/core/services/auth/auth.service.ts | 2 +- .../tenant/editor/tenant-editor.model.ts | 12 +- .../src/app/ui/navbar/navbar.component.ts | 35 +- .../src/app/ui/sidebar/sidebar.component.ts | 158 +++---- 12 files changed, 437 insertions(+), 348 deletions(-) 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 index ad6e48715..f031e632a 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/Permission.java @@ -123,9 +123,6 @@ public final class Permission { public static String DeleteUserSettings = "DeleteUserSettings"; - // UI Pages - public static String ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage"; - public static String ViewDmpBlueprintPage = "ViewDmpBlueprintPage"; //Reference public static String BrowseReference = "BrowseReference"; @@ -191,4 +188,26 @@ public final class Permission { public static String DeletePrefillingSource = "DeletePrefillingSource"; + // UI Pages + public static String ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage"; + public static String ViewMaintenancePage = "ViewMaintenancePage"; + public static String ViewNotificationPage = "ViewNotificationPage"; + public static String ViewNotificationTemplatePage = "ViewNotificationTemplatePage"; + public static String ViewSupportiveMaterialPage = "ViewSupportiveMaterialPage"; + public static String ViewLanguagePage = "ViewLanguagePage"; + public static String ViewUserPage = "ViewUserPage"; + public static String ViewTenantPage = "ViewTenantPage"; + public static String ViewPrefillingSourcePage = "ViewPrefillingSourcePage"; + public static String ViewReferenceTypePage = "ViewReferenceTypePage"; + public static String ViewReferencePage = "ViewReferencePage"; + public static String ViewEntityLockPage = "ViewEntityLockPage"; + public static String ViewDescriptionTemplatePage = "ViewDescriptionTemplatePage"; + public static String ViewDmpBlueprintPage = "ViewDmpBlueprintPage"; + public static String ViewPublicDescriptionPage = "ViewPublicDescriptionPage"; + public static String ViewPublicDmpPage = "ViewPublicDmpPage"; + public static String ViewMyDescriptionPage = "ViewMyDescriptionPage"; + public static String ViewMyDmpPage = "ViewMyDmpPage"; + public static String ViewHomePage = "ViewHomePage"; + public static String ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage"; + } diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/TenantEntityManager.java b/dmp-backend/core/src/main/java/eu/eudat/data/TenantEntityManager.java index 9d715901d..efbaccdb2 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/data/TenantEntityManager.java +++ b/dmp-backend/core/src/main/java/eu/eudat/data/TenantEntityManager.java @@ -21,9 +21,6 @@ import java.util.UUID; public class TenantEntityManager { @PersistenceContext private EntityManager entityManager; -// private final CurrentPrincipalResolver currentPrincipalResolver; -// private final ClaimExtractor claimExtractor; -// private final AuthorizationService authorizationService; private final TenantScope tenantScope; public TenantEntityManager(TenantScope tenantScope) { @@ -36,23 +33,23 @@ public class TenantEntityManager { } public T merge(T entity) throws InvalidApplicationException { - if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) { -// this.currentPrincipalResolver.currentPrincipal().isAuthenticated(); -// this.claimExtractor.subjectUUID(this.currentPrincipalResolver.currentPrincipal()); -// boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant); - - boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant() || this.tenantScope.isDefaultTenant(); - final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null; - if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering"); + if (tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) { + if (!tenantScope.isDefaultTenant()) { + if (tenantScopedEntity.getTenantId() == null || !tenantScopedEntity.getTenantId().equals(tenantScope.getTenant())) throw new MyForbiddenException("tenant tampering"); + } else if (tenantScopedEntity.getTenantId() != null) { + throw new MyForbiddenException("tenant tampering"); + } } return this.entityManager.merge(entity); } public void remove(Object entity) throws InvalidApplicationException { - if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) { - boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant() || this.tenantScope.isDefaultTenant(); - final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null; - if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering"); + if (tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) { + if (!tenantScope.isDefaultTenant()) { + if (tenantScopedEntity.getTenantId() == null || !tenantScopedEntity.getTenantId().equals(tenantScope.getTenant())) throw new MyForbiddenException("tenant tampering"); + } else if (tenantScopedEntity.getTenantId() != null) { + throw new MyForbiddenException("tenant tampering"); + } } this.entityManager.remove(entity); } @@ -60,14 +57,8 @@ public class TenantEntityManager { public T find(Class entityClass, Object primaryKey) throws InvalidApplicationException { T entity = this.entityManager.find(entityClass, primaryKey); - if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) { -// this.currentPrincipalResolver.currentPrincipal().isAuthenticated(); -// this.claimExtractor.subjectUUID(this.currentPrincipalResolver.currentPrincipal()); -// boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant); - - boolean isAllowedNoTenant = ((TenantScoped) entity).allowNullTenant() || this.tenantScope.isDefaultTenant(); - final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null; - if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) return null; + if (tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) { + if (tenantScopedEntity.getTenantId() != null && !tenantScopedEntity.getTenantId().equals(tenantScope.getTenant())) return null; } return entity; } diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/tenant/TenantListener.java b/dmp-backend/core/src/main/java/eu/eudat/data/tenant/TenantListener.java index 965658728..e2d00d29f 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/data/tenant/TenantListener.java +++ b/dmp-backend/core/src/main/java/eu/eudat/data/tenant/TenantListener.java @@ -33,7 +33,7 @@ public class TenantListener { logger.error("somebody tried to set not login tenant"); throw new MyForbiddenException("tenant tampering"); } - if (!entity.allowNullTenant() && !tenantScope.isDefaultTenant()) { + if (!tenantScope.isDefaultTenant()) { final UUID tenantId = tenantScope.getTenant(); entity.setTenantId(tenantId); } @@ -46,38 +46,30 @@ public class TenantListener { @PreRemove public void setTenantOnUpdate(TenantScoped entity) throws InvalidApplicationException { if (tenantScope.isMultitenant()) { - if (!entity.allowNullTenant()){ - if (!tenantScope.isDefaultTenant()) { - if (entity.getTenantId() == null) { - logger.error("somebody tried to set null tenant"); - throw new MyForbiddenException("tenant tampering"); - } - if (entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) { - logger.error("somebody tried to change an entries tenant"); - throw new MyForbiddenException("tenant tampering"); - } - - final UUID tenantId = tenantScope.getTenant(); - entity.setTenantId(tenantId); - } else { - if (entity.getTenantId() != null) { - logger.error("somebody tried to set null tenant"); - throw new MyForbiddenException("tenant tampering"); - } + if (!tenantScope.isDefaultTenant()) { + if (entity.getTenantId() == null) { + logger.error("somebody tried to set null tenant"); + throw new MyForbiddenException("tenant tampering"); } - } else { - if (entity.getTenantId() != null && (!this.tenantScope.isDefaultTenant() ||entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) { + if (entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) { logger.error("somebody tried to change an entries tenant"); throw new MyForbiddenException("tenant tampering"); } + + final UUID tenantId = tenantScope.getTenant(); + entity.setTenantId(tenantId); + } else { + if (entity.getTenantId() != null) { + logger.error("somebody tried to set null tenant"); + throw new MyForbiddenException("tenant tampering"); + } } - - } else { - if (entity.getTenantId() != null) { - logger.error("somebody tried to set non null tenant"); + if (entity.getTenantId() != null && (!this.tenantScope.isDefaultTenant() ||entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) { + logger.error("somebody tried to change an entries tenant"); throw new MyForbiddenException("tenant tampering"); } } + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/language/LanguageServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/language/LanguageServiceImpl.java index 7081e44b8..1fd02d446 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/language/LanguageServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/language/LanguageServiceImpl.java @@ -74,7 +74,6 @@ public class LanguageServiceImpl implements LanguageService { LanguageEntity data; if (isUpdate) { - ((org.hibernate.Session) entityManager).setHibernateFlushMode(FlushMode.MANUAL); data = this.entityManager.find(LanguageEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Language.class.getSimpleName()}, LocaleContextHolder.getLocale())); @@ -90,11 +89,10 @@ public class LanguageServiceImpl implements LanguageService { data.setPayload(model.getPayload() != null && !model.getPayload().isEmpty() ? model.getPayload() : null); data.setOrdinal(model.getOrdinal()); data.setUpdatedAt(Instant.now()); - data.setIsActive(IsActive.Inactive); -// if (isUpdate) this.entityManager.merge(data); -// else this.entityManager.persist(data); -// -// this.entityManager.flush(); + if (isUpdate) this.entityManager.merge(data); + else this.entityManager.persist(data); + + this.entityManager.flush(); return this.builderFactory.builder(LanguageBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Language._id), data); } diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java index 9445584d9..543278f51 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java @@ -173,11 +173,11 @@ public class UserInterceptor implements WebRequestInterceptor { } } - List existingUserRoles = this.collectUserRoles(userId); - if (!this.userRolesSynced(existingUserRoles)) { - this.syncRoles(userId); - hasChanges = true; - } +// List existingUserRoles = this.collectUserRoles(userId); +// if (!this.userRolesSynced(existingUserRoles)) { +// this.syncRoles(userId); +// hasChanges = true; +// } UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).first(); if (userCredential == null) { diff --git a/dmp-backend/web/src/main/resources/config/permissions.yml b/dmp-backend/web/src/main/resources/config/permissions.yml index 6b925144b..c7369a0ac 100644 --- a/dmp-backend/web/src/main/resources/config/permissions.yml +++ b/dmp-backend/web/src/main/resources/config/permissions.yml @@ -15,13 +15,13 @@ permissions: allowAuthenticated: true ###### - # public + # Affiliation DeferredAffiliation: roles: - - Admin - - User - - Manager - - DescriptionTemplateEditor + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -76,6 +76,11 @@ permissions: clients: [ ] allowAnonymous: true allowAuthenticated: true + BrowsePublicStatistics: + roles: [ ] + clients: [ ] + allowAnonymous: true + allowAuthenticated: true # Elastic ManageElastic: roles: @@ -87,13 +92,13 @@ permissions: # Deposit BrowseDeposit: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDeposit: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -106,13 +111,13 @@ permissions: allowAuthenticated: true EditLanguage: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteLanguage: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -123,15 +128,10 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: true - BrowsePublicStatistics: - roles: [ ] - clients: [ ] - allowAnonymous: true - allowAuthenticated: true # Description BrowseDescription: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -143,7 +143,7 @@ permissions: allowAuthenticated: false EditDescription: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -153,7 +153,7 @@ permissions: allowAuthenticated: false FinalizeDescription: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -163,7 +163,7 @@ permissions: allowAuthenticated: false DeleteDescription: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -174,7 +174,7 @@ permissions: allowAuthenticated: false CloneDescription: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -186,19 +186,19 @@ permissions: # Tag BrowseTag: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditTag: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteTag: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -206,33 +206,33 @@ permissions: # User BrowseUser: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditUser: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteUser: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false ExportUsers: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false BrowseDmpAssociatedUser: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -246,22 +246,22 @@ permissions: # DescriptionTemplateType BrowseDescriptionTemplateType: roles: - - Admin - - User - - Manager - - DescriptionTemplateEditor + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false EditDescriptionTemplateType: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDescriptionTemplateType: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -275,14 +275,14 @@ permissions: allowAuthenticated: true EditStorageFile: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteStorageFile: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -290,56 +290,56 @@ permissions: # DescriptionTemplate BrowseDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor - - Manager - - User + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false EditDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor + - TenantAdmin + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor + - TenantAdmin + - TenantDescriptionTemplateEditor claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false CloneDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor + - TenantAdmin + - TenantDescriptionTemplateEditor claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false CreateNewVersionDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor + - TenantAdmin + - TenantDescriptionTemplateEditor claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false ImportDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor + - TenantAdmin + - TenantDescriptionTemplateEditor claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false ExportDescriptionTemplate: roles: - - Admin - - DescriptionTemplateEditor + - TenantAdmin + - TenantDescriptionTemplateEditor claims: [ ] clients: [ ] allowAnonymous: false @@ -347,13 +347,13 @@ permissions: # Dmp BrowseDmp: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -365,16 +365,16 @@ permissions: allowAuthenticated: false NewDmp: roles: - - Admin - - User - - Manager - - DescriptionTemplateEditor + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -384,7 +384,7 @@ permissions: allowAuthenticated: false DepositDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -394,7 +394,7 @@ permissions: allowAuthenticated: false CloneDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -404,7 +404,7 @@ permissions: allowAuthenticated: false ExportDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -414,7 +414,7 @@ permissions: allowAuthenticated: false CreateNewVersionDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -424,7 +424,7 @@ permissions: allowAuthenticated: false FinalizeDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -434,7 +434,7 @@ permissions: allowAuthenticated: false UndoFinalizeDmp: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -444,7 +444,7 @@ permissions: allowAuthenticated: false AssignDmpUsers: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -454,7 +454,7 @@ permissions: allowAuthenticated: false InviteDmpUsers: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -465,47 +465,47 @@ permissions: # DmpBlueprint BrowseDmpBlueprint: roles: - - Admin - - User - - Manager - - DescriptionTemplateEditor + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false EditDmpBlueprint: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false CloneDmpBlueprint: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false CreateNewVersionDmpBlueprint: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false ExportDmpBlueprint: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false ImportDmpBlueprint: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDmpBlueprint: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -513,48 +513,41 @@ permissions: # EntityDoi BrowseEntityDoi: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditEntityDoi: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteEntityDoi: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false - # ViewPage Permissions - ViewDescriptionTemplateTypePage: - roles: - - Admin - clients: [ ] - allowAnonymous: false - allowAuthenticated: false # Reference Permissions BrowseReference: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditReference: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteReference: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -563,19 +556,19 @@ permissions: # DmpReference Permissions BrowseDmpReference: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDmpReference: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDmpReference: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -584,19 +577,19 @@ permissions: # DmpUser Permissions BrowseDmpUser: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDmpUser: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDmpUser: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -607,20 +600,22 @@ permissions: roles: - Admin - User - - Manager - - DescriptionTemplateEditor + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: yes allowAuthenticated: yes EditSupportiveMaterial: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteSupportiveMaterial: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -629,22 +624,22 @@ permissions: # ReferenceType Permissions BrowseReferenceType: roles: - - Admin - - User - - Manager - - DescriptionTemplateEditor + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false EditReferenceType: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteReferenceType: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -670,30 +665,26 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false - AllowNoTenant: - roles: - - TenantManager - claims: [ ] - clients: [ ] - allowAnonymous: false - allowAuthenticated: false # TenantUser Permissions BrowseTenantUser: roles: - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditTenantUser: roles: - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteTenantUser: roles: - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -702,19 +693,19 @@ permissions: # DmpDescriptionTemplate Permissions BrowseDmpDescriptionTemplate: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDmpDescriptionTemplate: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDmpDescriptionTemplate: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -723,19 +714,19 @@ permissions: # DescriptionReference Permissions BrowseDescriptionReference: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDescriptionReference: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDescriptionReference: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -744,19 +735,19 @@ permissions: # DescriptionReference Permissions BrowseDescriptionTag: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditDescriptionTag: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteDescriptionTag: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -764,10 +755,10 @@ permissions: # Prefilling BrowsePrefilling: roles: - - Admin - - DescriptionTemplateEditor - - Manager - - User + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor claims: [ ] clients: [ ] allowAnonymous: false @@ -776,16 +767,16 @@ permissions: # Lock Permissions BrowseLock: roles: - - Admin - - DescriptionTemplateEditor - - Manager - - User + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false EditLock: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -797,7 +788,7 @@ permissions: allowAuthenticated: false DeleteLock: roles: - - Admin + - TenantAdmin dmp: roles: - Owner @@ -808,6 +799,7 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false + # Contact Permissions SendContactSupport: roles: [] @@ -817,19 +809,19 @@ permissions: # ActionConfirmation Permissions BrowseActionConfirmation: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false EditActionConfirmation: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeleteActionConfirmation: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false @@ -838,23 +830,148 @@ permissions: # PrefillingSource Permissions BrowsePrefillingSource: roles: - - Admin - - DescriptionTemplateEditor - - Manager - - User + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor clients: [ ] allowAnonymous: false allowAuthenticated: false EditPrefillingSource: roles: - - Admin + - TenantAdmin clients: [ ] allowAnonymous: false allowAuthenticated: false DeletePrefillingSource: roles: - - Admin + - TenantAdmin claims: [ ] clients: [ ] allowAnonymous: false allowAuthenticated: false + + # ViewPage Permissions + ViewDescriptionTemplateTypePage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewMaintenancePage: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewNotificationPage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewNotificationTemplatePage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewSupportiveMaterialPage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewLanguagePage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewUserPage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewTenantPage: + roles: + - Admin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewPrefillingSourcePage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewReferenceTypePage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewReferencePage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewEntityLockPage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewDescriptionTemplatePage: + roles: + - TenantAdmin + - TenantDescriptionTemplateEditor + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewDmpBlueprintPage: + roles: + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewPublicDescriptionPage: + roles: [ ] + clients: [ ] + allowAnonymous: true + allowAuthenticated: true + ViewPublicDmpPage: + roles: [ ] + clients: [ ] + allowAnonymous: true + allowAuthenticated: true + ViewMyDescriptionPage: + roles: + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewMyDmpPage: + roles: + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewHomePage: + roles: [ ] + clients: [ ] + allowAnonymous: true + allowAuthenticated: true + ViewMineInAppNotificationPage: + roles: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: true \ No newline at end of file diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index d10e6ad88..63463d9bd 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -30,6 +30,9 @@ const appRoutes: Routes = [ path: 'descriptions', loadChildren: () => import('./ui/description/description.module').then(m => m.DescriptionModule), data: { + authContext: { + permissions: [AppPermission.ViewMyDescriptionPage] + }, breadcrumb: true, title: 'GENERAL.TITLES.DESCRIPTIONS' } @@ -46,6 +49,9 @@ const appRoutes: Routes = [ path: 'plans', loadChildren: () => import('./ui/dmp/dmp.module').then(m => m.DmpModule), data: { + authContext: { + permissions: [AppPermission.ViewMyDmpPage] + }, breadcrumb: true, title: 'GENERAL.TITLES.PLANS' } @@ -71,7 +77,7 @@ const appRoutes: Routes = [ } }, - + { path: 'about', loadChildren: () => import('./ui/about/about.module').then(m => m.AboutModule), @@ -80,7 +86,7 @@ const appRoutes: Routes = [ title: 'GENERAL.TITLES.ABOUT' } }, - + { path: 'description-templates', loadChildren: () => import('./ui/admin/description-template/description-template.module').then(m => m.DescriptionTemplateModule), @@ -169,7 +175,7 @@ const appRoutes: Routes = [ title: 'GENERAL.TITLES.COOKIES-POLICY' } }, - + // { // path: 'splash', // loadChildren: () => import('./ui/splash/splash.module').then(m => m.SplashModule), @@ -188,6 +194,9 @@ const appRoutes: Routes = [ path: 'users', loadChildren: () => import('./ui/admin/user/user.module').then(m => m.UsersModule), data: { + authContext: { + permissions: [AppPermission.ViewUserPage] + }, breadcrumb: true, title: 'GENERAL.TITLES.USERS' }, @@ -324,6 +333,9 @@ const appRoutes: Routes = [ path: 'index-managment', loadChildren: () => import('./ui/admin/index-managment/index-managment.module').then(m => m.IndexManagmentModule), data: { + authContext: { + permissions: [AppPermission.ViewMaintenancePage] + }, breadcrumb: true, title: 'GENERAL.TITLES.INDEX-MANAGMENT' }, @@ -332,6 +344,9 @@ const appRoutes: Routes = [ path: 'maintenance-tasks', loadChildren: () => import('./ui/admin/maintenance-tasks/maintenance-tasks.module').then(m => m.MaintenanceTasksModule), data: { + authContext: { + permissions: [AppPermission.ViewMaintenancePage] + }, breadcrumb: true }, }, diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts index 3f7ad81fa..cf591a0b5 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -33,20 +33,6 @@ export enum AppPermission { EditDescriptionTemplate = "EditDescriptionTemplate", DeleteDescriptionTemplate = "DeleteDescriptionTemplate", - // UI Pages - ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage", - ViewDmpBlueprintPage = "ViewDmpBlueprintPage", - ViewDescriptionTemplatePage = "ViewDescriptionTemplatePage", - ViewSupportiveMaterialPage = 'ViewSupportiveMaterialPage', - ViewReferenceTypePage = 'ViewReferenceTypePage', - ViewReferencePage = 'ViewReferencePage', - ViewTenantPage = 'ViewTenantPage', - ViewLanguagePage = "ViewLanguagePage", - ViewNotificationTemplatePage = "ViewNotificationTemplatePage", - ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage", - ViewNotificationPage = "ViewNotificationPage", - ViewPrefillingSourcePage = "ViewPrefillingSourcePage", - ViewEntityLockPage = "ViewEntityLockPage", //ReferenceType BrowseReferenceType = "BrowseReferenceType", @@ -83,5 +69,27 @@ export enum AppPermission { BrowsePrefillingSource= "BrowsePrefillingSource", EditPrefillingSource = "EditPrefillingSource", DeletePrefillingSource = "DeletePrefillingSource", + + // UI Pages + ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage", + ViewMaintenancePage = "ViewMaintenancePage", + ViewNotificationPage = "ViewNotificationPage", + ViewNotificationTemplatePage = "ViewNotificationTemplatePage", + ViewSupportiveMaterialPage = "ViewSupportiveMaterialPage", + ViewLanguagePage = "ViewLanguagePage", + ViewUserPage = "ViewUserPage", + ViewTenantPage = "ViewTenantPage", + ViewPrefillingSourcePage = "ViewPrefillingSourcePage", + ViewReferenceTypePage = "ViewReferenceTypePage", + ViewReferencePage = "ViewReferencePage", + ViewEntityLockPage = "ViewEntityLockPage", + ViewDescriptionTemplatePage = "ViewDescriptionTemplatePage", + ViewDmpBlueprintPage = "ViewDmpBlueprintPage", + ViewPublicDescriptionPage = "ViewPublicDescriptionPage", + ViewPublicDmpPage = "ViewPublicDmpPage", + ViewMyDescriptionPage = "ViewMyDescriptionPage", + ViewMyDmpPage = "ViewMyDmpPage", + ViewHomePage = "ViewHomePage", + ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage", } 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 1b6f6d5c4..f42ae3d6c 100644 --- a/dmp-frontend/src/app/core/services/auth/auth.service.ts +++ b/dmp-frontend/src/app/core/services/auth/auth.service.ts @@ -331,7 +331,7 @@ export class AuthService extends BaseService { } private evaluatePermission(availablePermissions: string[], permissionToCheck: string): boolean { if (!permissionToCheck) { return false; } - if (this.hasRole(AppRole.Admin)) { return true; } + // if (this.hasRole(AppRole.Admin)) { return true; } return availablePermissions.map(x => x.toLowerCase()).includes(permissionToCheck.toLowerCase()); } public hasAnyPermission(permissions: AppPermission[]): boolean { diff --git a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts index 2d4a0cf05..16b9a4cbd 100644 --- a/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts +++ b/dmp-frontend/src/app/ui/admin/tenant/editor/tenant-editor.model.ts @@ -23,7 +23,7 @@ export class TenantEditorModel extends BaseEditorModel implements TenantPersist this.name = item.name; this.code = item.code; this.description = item.description; - if (item.config) this.config = new TenantConfigEditorModel(this.validationErrorModel).fromModel(item.config); + if (item.config) this.config = new TenantConfigEditorModel(this.validationErrorModel).fromModel(item.config); } return this; } @@ -38,7 +38,7 @@ export class TenantEditorModel extends BaseEditorModel implements TenantPersist description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], config: this.config.buildForm({ rootPath: `config.`, - }), + }), hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators] }); } @@ -97,8 +97,8 @@ export class TenantConfigEditorModel implements TenantConfigPersist { public fromModel(item: TenantConfig): TenantConfigEditorModel { if (item) { - if (item.deposit) this.deposit = new TenantDepositConfigEditorModel(this.validationErrorModel).fromModel(item.deposit); - if (item.fileTransformers) this.fileTransformers = new TenantFileTransformersConfigEditorModel(this.validationErrorModel).fromModel(item.fileTransformers); + if (item.deposit) this.deposit = new TenantDepositConfigEditorModel(this.validationErrorModel).fromModel(item.deposit); + if (item.fileTransformers) this.fileTransformers = new TenantFileTransformersConfigEditorModel(this.validationErrorModel).fromModel(item.fileTransformers); } return this; } @@ -188,7 +188,7 @@ export class TenantDepositConfigEditorModel implements TenantDepositConfigPersis const baseContext: ValidationContext = new ValidationContext(); const baseValidationArray: Validation[] = new Array(); - baseValidationArray.push({ key: 'sources', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}sources`)] }); + baseValidationArray.push({ key: 'sources', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sources`)] }); baseContext.validation = baseValidationArray; return baseContext; @@ -258,7 +258,7 @@ export class TenantFileTransformersConfigEditorModel implements TenantFileTransf const baseContext: ValidationContext = new ValidationContext(); const baseValidationArray: Validation[] = new Array(); - baseValidationArray.push({ key: 'sources', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}sources`)] }); + baseValidationArray.push({ key: 'sources', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sources`)] }); baseContext.validation = baseValidationArray; return baseContext; diff --git a/dmp-frontend/src/app/ui/navbar/navbar.component.ts b/dmp-frontend/src/app/ui/navbar/navbar.component.ts index 061bf1b4c..0e91df8fe 100644 --- a/dmp-frontend/src/app/ui/navbar/navbar.component.ts +++ b/dmp-frontend/src/app/ui/navbar/navbar.component.ts @@ -15,7 +15,6 @@ import { takeUntil } from 'rxjs/operators'; import { StartNewDmpDialogComponent } from '../dmp/new/start-new-dmp-dialogue/start-new-dmp-dialog.component'; import { FaqDialogComponent } from '../faq/dialog/faq-dialog.component'; import { UserDialogComponent } from './user-dialog/user-dialog.component'; -import { DATASETS_ROUTES, DMP_ROUTES, GENERAL_ROUTES } from '../sidebar/sidebar.component'; import { MineInAppNotificationListingDialogComponent } from '../inapp-notification/listing-dialog/mine-inapp-notification-listing-dialog.component'; import { InAppNotificationService } from '@app/core/services/inapp-notification/inapp-notification.service'; import { timer } from 'rxjs'; @@ -28,7 +27,7 @@ import { ConfigurationService } from '@app/core/services/configuration/configura }) export class NavbarComponent extends BaseComponent implements OnInit { progressIndication = false; - private listTitles: any[]; + //private listTitles: any[]; location: Location; mobile_menu_visible: any = 0; private toggleButton: any; @@ -64,10 +63,10 @@ export class NavbarComponent extends BaseComponent implements OnInit { ngOnInit() { this.matomoService.trackPageView('Navbar'); this.currentRoute = this.router.url; - this.listTitles = GENERAL_ROUTES.filter(listTitle => listTitle); - this.listTitles.push(DMP_ROUTES.filter(listTitle => listTitle)); + // this.listTitles = GENERAL_ROUTES.filter(listTitle => listTitle); + // this.listTitles.push(DMP_ROUTES.filter(listTitle => listTitle)); // this.listTitles.push(HISTORY_ROUTES.filter(listTitle => listTitle)); - this.listTitles.push(DATASETS_ROUTES.filter(listTitle => listTitle)); + // this.listTitles.push(DATASETS_ROUTES.filter(listTitle => listTitle)); // const navbar: HTMLElement = this.element.nativeElement; // this.toggleButton = navbar.getElementsByClassName('navbar-toggler')[0]; // this.router.events.subscribe((event) => { @@ -193,20 +192,20 @@ export class NavbarComponent extends BaseComponent implements OnInit { } }; - getTitle() { - var titlee = this.location.prepareExternalUrl(this.location.path()); - if (titlee.charAt(0) === '#') { - titlee = titlee.slice(2); - } - titlee = titlee.split('/').pop(); + // getTitle() { + // var titlee = this.location.prepareExternalUrl(this.location.path()); + // if (titlee.charAt(0) === '#') { + // titlee = titlee.slice(2); + // } + // titlee = titlee.split('/').pop(); - for (var item = 0; item < this.listTitles.length; item++) { - if (this.listTitles[item].path === titlee) { - return this.listTitles[item].title; - } - } - return 'Dashboard'; - } + // for (var item = 0; item < this.listTitles.length; item++) { + // if (this.listTitles[item].path === titlee) { + // return this.listTitles[item].title; + // } + // } + // return 'Dashboard'; + // } public getCurrentLanguage(): any { const lang = this.languages.find(lang => lang.value === this.languageService.getCurrentLanguage()); diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts index 16e6b36b5..f71882124 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts @@ -6,9 +6,11 @@ import { Router } from '@angular/router'; import { MatomoService } from '@app/core/services/matomo/matomo-service'; import { TranslateService } from '@ngx-translate/core'; import { AppRole } from '../../core/common/enum/app-role'; -import { AuthService } from '../../core/services/auth/auth.service'; +import { AuthService, LoginStatus } from '../../core/services/auth/auth.service'; import { LanguageDialogComponent } from '../language/dialog/language-dialog.component'; import { UserDialogComponent } from '../navbar/user-dialog/user-dialog.component'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { takeUntil } from 'rxjs/operators'; declare interface RouteInfo { path: string; @@ -19,56 +21,8 @@ declare interface RouteInfo { declare interface GroupMenuItem { title: string; routes: RouteInfo[]; - requiresAuthentication: boolean; - requiresSpecialPermission?: AppRole; - requiresAdmin: boolean; - isGeneral: boolean; } -export const GENERAL_ROUTES: RouteInfo[] = [ - { path: '/home', title: 'SIDE-BAR.DASHBOARD', icon: 'home' } -]; -export const DMP_ROUTES: RouteInfo[] = [ - { path: '/plans', title: 'SIDE-BAR.MY-DMPS', icon: 'library_books' }, - { path: '/descriptions', title: 'SIDE-BAR.MY-DESCRIPTIONS', icon: 'dns' }, -]; -export const DATASETS_ROUTES: RouteInfo[] = [ - { path: '/explore-plans', title: 'SIDE-BAR.PUBLIC-DMPS', icon: 'library_books' }, - { path: '/explore-descriptions', title: 'SIDE-BAR.PUBLIC-DESC', icon: 'dns' }, -]; - -export const PUBLIC_ROUTES: RouteInfo[] = [ - { path: '/explore-plans', title: 'SIDE-BAR.PUBLIC-DMPS', icon: 'library_books' }, - { path: '/explore-descriptions', title: 'SIDE-BAR.PUBLIC-DESC', icon: 'dns' } -]; - -export const ADMIN_ROUTES: RouteInfo[] = [ - { path: '/dmp-blueprints', title: 'SIDE-BAR.DMP-BLUEPRINTS', icon: 'library_books' }, - { path: '/description-templates', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'description' }, - { path: '/description-template-type', title: 'SIDE-BAR.DESCRIPTION-TEMPLATE-TYPES', icon: 'stack' }, - { path: '/entity-locks', title: 'SIDE-BAR.ENTITY-LOCKS', icon: 'build' }, - { path: '/references', title: 'SIDE-BAR.REFERENCES', icon: 'dataset_linked' }, - { path: '/reference-type', title: 'SIDE-BAR.REFERENCE-TYPES', icon: 'add_link' }, - { path: '/prefilling-sources', title: 'SIDE-BAR.PREFILLING-SOURCES', icon: 'add_link' }, - { path: '/tenants', title: 'SIDE-BAR.TENANTS', icon: 'tenancy' }, - { path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' }, - { path: '/languages', title: 'SIDE-BAR.LANGUAGES', icon: 'language' }, - { path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'dataset_linked' }, - { path: '/notification-templates', title: 'SIDE-BAR.NOTIFICATION-TEMPLATES', icon: 'build' }, - { path: '/notifications', title: 'SIDE-BAR.NOTIFICATIONS', icon: 'build' }, - { path: '/index-managment', title: 'SIDE-BAR.MAINTENANCE', icon: 'build' } -]; - -export const DATASET_TEMPLATE_ROUTES: RouteInfo[] = [ - { path: '/description-templates', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'description' } -]; - -export const INFO_ROUTES: RouteInfo[] = [ - { path: '/co-branding', title: 'SIDE-BAR.CO-BRANDING', icon: 'toll' }, - { path: '/contact-support', title: 'SIDE-BAR.SUPPORT', icon: 'help' }, - { path: '/feedback', title: 'SIDE-BAR.FEEDBACK', icon: 'feedback', url: 'https://docs.google.com/forms/d/12RSCrUjdSDp2LZLpjDKOi44cN1fLDD2q1-F66SqZIis/viewform?edit_requested=true' } -]; - @Component({ selector: 'app-sidebar', templateUrl: './sidebar.component.html', @@ -102,71 +56,80 @@ export class SidebarComponent implements OnInit { ngOnInit() { this.matomoService.trackPageView('Sidebar'); this.currentRoute = this.router.url; + + this.authentication.getAuthenticationStateObservable().pipe().subscribe(authenticationState => { + this.reCalculateMenu() + }); + + this.reCalculateMenu(); + + this.router.events.subscribe((event) => this.currentRoute = this.router.url); + } + + private reCalculateMenu() { + this.groupMenuItems = [] this.generalItems = { title: 'SIDE-BAR.GENERAL', - routes: GENERAL_ROUTES, - requiresAuthentication: false, - requiresAdmin: false, - isGeneral: true + routes: [], } + this.generalItems.routes.push({ path: '/home', title: 'SIDE-BAR.DASHBOARD', icon: 'home' }); + this.groupMenuItems.push(this.generalItems); this.dmpItems = { title: 'SIDE-BAR.DMP', - routes: DMP_ROUTES, - requiresAuthentication: true, - requiresAdmin: false, - isGeneral: false + routes: [], } + + if (this.authentication.hasPermission(AppPermission.ViewMyDmpPage)) this.dmpItems.routes.push({ path: '/plans', title: 'SIDE-BAR.MY-DMPS', icon: 'library_books' }); + if (this.authentication.hasPermission(AppPermission.ViewMyDescriptionPage)) this.dmpItems.routes.push({ path: '/descriptions', title: 'SIDE-BAR.MY-DESCRIPTIONS', icon: 'dns' }); this.groupMenuItems.push(this.dmpItems); this.datasetItems = { title: 'SIDE-BAR.DATASETS', - routes: DATASETS_ROUTES, - requiresAuthentication: true, - requiresAdmin: false, - isGeneral: false + routes: [], } + + if (this.authentication.hasPermission(AppPermission.ViewPublicDmpPage)) this.datasetItems.routes.push({ path: '/explore-plans', title: 'SIDE-BAR.PUBLIC-DMPS', icon: 'library_books' }); + if (this.authentication.hasPermission(AppPermission.ViewPublicDescriptionPage)) this.datasetItems.routes.push({ path: '/explore-descriptions', title: 'SIDE-BAR.PUBLIC-DESC', icon: 'dns' }); this.groupMenuItems.push(this.datasetItems); - this.adminItems = { - title: 'SIDE-BAR.ADMIN', - routes: ADMIN_ROUTES, - requiresAuthentication: true, - requiresAdmin: true, - isGeneral: false - } - this.groupMenuItems.push(this.adminItems); - - this.datasetTemplateItems = { - title: 'SIDE-BAR.ADMIN', - routes: DATASET_TEMPLATE_ROUTES, - requiresAuthentication: true, - requiresSpecialPermission: AppRole.DescriptionTemplateEditor, - requiresAdmin: false, - isGeneral: false - } - this.groupMenuItems.push(this.datasetTemplateItems); - this.publicItems = { title: 'SIDE-BAR.PUBLIC', - routes: PUBLIC_ROUTES, - requiresAuthentication: false, - requiresAdmin: false, - isGeneral: false + routes: [], } + this.publicItems.routes.push({ path: '/explore-plans', title: 'SIDE-BAR.PUBLIC-DMPS', icon: 'library_books' }); + this.publicItems.routes.push({ path: '/explore-descriptions', title: 'SIDE-BAR.PUBLIC-DESC', icon: 'dns' }); this.groupMenuItems.push(this.publicItems); + this.adminItems = { + title: 'SIDE-BAR.ADMIN', + routes: [], + } + if (this.authentication.hasPermission(AppPermission.ViewDmpBlueprintPage)) this.adminItems.routes.push({ path: '/dmp-blueprints', title: 'SIDE-BAR.DMP-BLUEPRINTS', icon: 'library_books' }); + if (this.authentication.hasPermission(AppPermission.ViewDescriptionTemplatePage)) this.adminItems.routes.push({ path: '/description-templates', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'description' }); + if (this.authentication.hasPermission(AppPermission.ViewDescriptionTemplateTypePage)) this.adminItems.routes.push({ path: '/description-template-type', title: 'SIDE-BAR.DESCRIPTION-TEMPLATE-TYPES', icon: 'stack' }); + if (this.authentication.hasPermission(AppPermission.ViewEntityLockPage)) this.adminItems.routes.push({ path: '/entity-locks', title: 'SIDE-BAR.ENTITY-LOCKS', icon: 'build' }); + if (this.authentication.hasPermission(AppPermission.ViewReferencePage)) this.adminItems.routes.push({ path: '/references', title: 'SIDE-BAR.REFERENCES', icon: 'dataset_linked' }); + if (this.authentication.hasPermission(AppPermission.ViewReferenceTypePage)) this.adminItems.routes.push({ path: '/reference-type', title: 'SIDE-BAR.REFERENCE-TYPES', icon: 'add_link' }); + if (this.authentication.hasPermission(AppPermission.ViewPrefillingSourcePage)) this.adminItems.routes.push({ path: '/prefilling-sources', title: 'SIDE-BAR.PREFILLING-SOURCES', icon: 'add_link' }); + if (this.authentication.hasPermission(AppPermission.ViewTenantPage)) this.adminItems.routes.push({ path: '/tenants', title: 'SIDE-BAR.TENANTS', icon: 'tenancy' }); + if (this.authentication.hasPermission(AppPermission.ViewUserPage)) this.adminItems.routes.push({ path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' }); + if (this.authentication.hasPermission(AppPermission.ViewLanguagePage)) this.adminItems.routes.push({ path: '/languages', title: 'SIDE-BAR.LANGUAGES', icon: 'language' }); + if (this.authentication.hasPermission(AppPermission.ViewSupportiveMaterialPage)) this.adminItems.routes.push({ path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'dataset_linked' }); + if (this.authentication.hasPermission(AppPermission.ViewNotificationTemplatePage)) this.adminItems.routes.push({ path: '/notification-templates', title: 'SIDE-BAR.NOTIFICATION-TEMPLATES', icon: 'build' }); + if (this.authentication.hasPermission(AppPermission.ViewNotificationPage)) this.adminItems.routes.push({ path: '/notifications', title: 'SIDE-BAR.NOTIFICATIONS', icon: 'build' }); + if (this.authentication.hasPermission(AppPermission.ViewMaintenancePage)) this.adminItems.routes.push({ path: '/index-managment', title: 'SIDE-BAR.MAINTENANCE', icon: 'build' }); + this.groupMenuItems.push(this.adminItems); + this.infoItems = { title: "", - routes: INFO_ROUTES, - requiresAuthentication: false, - requiresAdmin: false, - isGeneral: false + routes: [], } + this.infoItems.routes.push({ path: '/co-branding', title: 'SIDE-BAR.CO-BRANDING', icon: 'toll' }); + this.infoItems.routes.push({ path: '/contact-support', title: 'SIDE-BAR.SUPPORT', icon: 'help' }); + this.infoItems.routes.push({ path: '/feedback', title: 'SIDE-BAR.FEEDBACK', icon: 'feedback', url: 'https://docs.google.com/forms/d/12RSCrUjdSDp2LZLpjDKOi44cN1fLDD2q1-F66SqZIis/viewform?edit_requested=true' }); this.groupMenuItems.push(this.infoItems); - - this.router.events.subscribe((event) => this.currentRoute = this.router.url); } public principalHasAvatar(): boolean { @@ -206,20 +169,7 @@ export class SidebarComponent implements OnInit { } showItem(value: GroupMenuItem) { - if (this.isAuthenticated()) { - if (value.requiresAdmin) { - return this.isAdmin(); - } - else if (value.requiresSpecialPermission !== undefined) { - return this.hasPermission(value.requiresSpecialPermission); - } - else { - return value.isGeneral || value.requiresAuthentication; - } - } - else { - return !value.requiresAuthentication; - } + return value.routes && value.routes.length > 0; } openProfile() { From 3e37c91035af414ef906b96574e58d7a7a7db960 Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Tue, 2 Apr 2024 18:39:46 +0300 Subject: [PATCH 2/2] multi tenant changes --- .../AuthorizationConfiguration.java | 9 ++ .../AuthorizationProperties.java | 31 ++++ .../eu/eudat/authorization/ClaimNames.java | 4 + .../commons/scope/tenant/TenantScope.java | 3 - .../controllers/PrincipalController.java | 3 +- .../tenant/TenantInterceptor.java | 137 +++++++++++++++++- .../tenant/TenantScopeClaimInterceptor.java | 38 ++--- .../tenant/TenantScopeHeaderInterceptor.java | 37 +---- .../interceptors/user/UserInterceptor.java | 63 ++++++-- .../src/main/resources/config/application.yml | 1 + .../main/resources/config/authorization.yml | 9 ++ .../src/main/resources/config/idpclaims.yml | 8 + 12 files changed, 259 insertions(+), 84 deletions(-) create mode 100644 dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java create mode 100644 dmp-backend/web/src/main/resources/config/authorization.yml diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java new file mode 100644 index 000000000..005bd9604 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationConfiguration.java @@ -0,0 +1,9 @@ +package eu.eudat.authorization; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration("AppAuthorizationConfiguration") +@EnableConfigurationProperties(AuthorizationProperties.class) +public class AuthorizationConfiguration { +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java new file mode 100644 index 000000000..acb8fe32c --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java @@ -0,0 +1,31 @@ +package eu.eudat.authorization; + + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.HashSet; +import java.util.List; + +@ConfigurationProperties(prefix = "authorization") +public class AuthorizationProperties { + + private List allowedTenantRoles; + + public List getAllowedTenantRoles() { + return allowedTenantRoles; + } + + public void setAllowedTenantRoles(List allowedTenantRoles) { + this.allowedTenantRoles = allowedTenantRoles; + } + + private List allowedGlobalRoles; + + public List getAllowedGlobalRoles() { + return allowedGlobalRoles; + } + + public void setAllowedGlobalRoles(List allowedGlobalRoles) { + this.allowedGlobalRoles = allowedGlobalRoles; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java index 4e6edfdbd..e29f99c84 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/ClaimNames.java @@ -2,4 +2,8 @@ package eu.eudat.authorization; public class ClaimNames { public static final String ExternalProviderName = "ExternalProviderName"; + public static final String TenantCodesClaimName = "TenantCodes"; + public static final String TenantClaimName = "x-tenant"; + public static final String GlobalRolesClaimName = "GlobalRoles"; + public static final String TenantRolesClaimName = "TenantRoles"; } diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java b/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java index 3c88a8115..52a884d0d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/scope/tenant/TenantScope.java @@ -16,9 +16,6 @@ import java.util.concurrent.atomic.AtomicReference; @RequestScope public class TenantScope { public static final String TenantReplaceParameter = "::TenantCode::"; - public static final String TenantCodesClaimName = "TenantCodes"; - public static final String TenantClaimName = "x-tenant"; - private final MultitenancyProperties multitenancy; private final AtomicReference tenant = new AtomicReference<>(); private final AtomicReference tenantCode = new AtomicReference<>(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java index 2ad57ad54..ee1cce6c6 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/PrincipalController.java @@ -1,6 +1,7 @@ package eu.eudat.controllers; import eu.eudat.audit.AuditableAction; +import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.models.Account; import eu.eudat.models.AccountBuilder; @@ -83,7 +84,7 @@ public class PrincipalController { logger.debug("my-tenants"); MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); - List tenants = this.claimExtractor.asStrings(principal, TenantScope.TenantCodesClaimName); + List tenants = this.claimExtractor.asStrings(principal, ClaimNames.TenantCodesClaimName); this.auditService.track(AuditableAction.Principal_MyTenants); //auditService.trackIdentity(AuditableAction.IdentityTracking_Action); diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java index 904bc2b67..62e2d0d0a 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantInterceptor.java @@ -1,15 +1,20 @@ package eu.eudat.interceptors.tenant; +import eu.eudat.authorization.AuthorizationProperties; +import eu.eudat.authorization.ClaimNames; import eu.eudat.authorization.Permission; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.lock.LockByKeyManager; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.scope.user.UserScope; +import eu.eudat.convention.ConventionService; import eu.eudat.data.TenantUserEntity; import eu.eudat.data.UserEntity; +import eu.eudat.data.UserRoleEntity; import eu.eudat.data.tenant.TenantScopedBaseEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; +import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; import eu.eudat.query.utils.BuildSubQueryInput; import eu.eudat.query.utils.QueryUtilsService; import gr.cite.commons.web.authz.service.AuthorizationService; @@ -19,7 +24,6 @@ import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.logging.LoggerService; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import jakarta.persistence.Tuple; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Root; @@ -41,6 +45,7 @@ import org.springframework.web.context.request.WebRequestInterceptor; import javax.management.InvalidApplicationException; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -61,6 +66,9 @@ public class TenantInterceptor implements WebRequestInterceptor { private final ErrorThesaurusProperties errors; private final QueryUtilsService queryUtilsService; private final LockByKeyManager lockByKeyManager; + private final ConventionService conventionService; + private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler; + private final AuthorizationProperties authorizationProperties; @PersistenceContext public EntityManager entityManager; @@ -74,7 +82,7 @@ public class TenantInterceptor implements WebRequestInterceptor { TenantScopeProperties tenantScopeProperties, UserAllowedTenantCacheService userAllowedTenantCacheService, PlatformTransactionManager transactionManager, - ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager) { + ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, LockByKeyManager lockByKeyManager, ConventionService conventionService, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, AuthorizationProperties authorizationProperties) { this.tenantScope = tenantScope; this.userScope = userScope; this.currentPrincipalResolver = currentPrincipalResolver; @@ -86,6 +94,9 @@ public class TenantInterceptor implements WebRequestInterceptor { this.errors = errors; this.queryUtilsService = queryUtilsService; this.lockByKeyManager = lockByKeyManager; + this.conventionService = conventionService; + this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler; + this.authorizationProperties = authorizationProperties; } @Override @@ -95,7 +106,7 @@ public class TenantInterceptor implements WebRequestInterceptor { boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant); if (tenantScope.isSet() && this.entityManager != null) { - List currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), TenantScope.TenantCodesClaimName); + List currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantCodesClaimName); if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) { logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); @@ -133,6 +144,8 @@ public class TenantInterceptor implements WebRequestInterceptor { throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); } } + + this.syncUserWithClaims(); } else { if (!isAllowedNoTenant) { if (!this.isWhiteListedEndpoint(request)) { @@ -163,7 +176,7 @@ public class TenantInterceptor implements WebRequestInterceptor { if (this.tenantScopeProperties.getAutoCreateTenantUser()) usedResource = this.lockByKeyManager.tryLock(lockId, 5000, TimeUnit.MILLISECONDS); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(UserEntity.class); Root root = query.from(UserEntity.class); query.where(criteriaBuilder.and( criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active), @@ -188,7 +201,7 @@ public class TenantInterceptor implements WebRequestInterceptor { ) )); query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.isEmpty() && this.tenantScopeProperties.getAutoCreateTenantUser()) { return this.createTenantUser(); } else { @@ -210,7 +223,6 @@ public class TenantInterceptor implements WebRequestInterceptor { user.setIsActive(IsActive.Active); user.setTenantId(this.tenantScope.getTenant()); user.setUserId(userScope.getUserId()); - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); @@ -227,9 +239,122 @@ public class TenantInterceptor implements WebRequestInterceptor { if (status != null) transactionManager.rollback(status); throw ex; } + this.userTouchedIntegrationEventHandler.handle(this.userScope.getUserId()); return true; } + private void syncUserWithClaims() throws InvalidApplicationException, InterruptedException { + boolean usedResource = false; + String lockId = userScope.getUserId().toString().toLowerCase(Locale.ROOT); + boolean hasChanges = false; + try { + usedResource = this.lockByKeyManager.tryLock(lockId, 5000, TimeUnit.MILLISECONDS); + + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); + definition.setName(UUID.randomUUID().toString()); + definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); + definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + TransactionStatus status = null; + try { + status = transactionManager.getTransaction(definition); + + List existingUserRoles = this.collectUserRoles(); + if (!this.userRolesSynced(existingUserRoles)) { + this.syncRoles(); + hasChanges = true; + } + + this.entityManager.flush(); + transactionManager.commit(status); + } catch (Exception ex) { + if (status != null) transactionManager.rollback(status); + throw ex; + } + } finally { + if (usedResource) this.lockByKeyManager.unlock(lockId); + } + if (hasChanges){ + this.userTouchedIntegrationEventHandler.handle(this.userScope.getUserId()); + } + } + + private List getRolesFromClaims() { + List claimsRoles = this.claimExtractor.asStrings(currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantRolesClaimName); + if (claimsRoles == null) claimsRoles = new ArrayList<>(); + claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank() && (this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedTenantRoles()) || this.authorizationProperties.getAllowedTenantRoles().contains(x))).distinct().toList(); + return claimsRoles; + } + + private List collectUserRoles() throws InvalidApplicationException { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedTenantRoles()) inRolesClause.value(item); + + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userScope.getUserId()), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedTenantRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + this.tenantScope.isDefaultTenant() ? criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) : criteriaBuilder.equal(root.get(UserRoleEntity._tenantId), this.tenantScope.getTenant()) + )).multiselect(root.get(UserRoleEntity._role).alias(UserRoleEntity._role)); + List results = this.entityManager.createQuery(query).getResultList(); + + return results.stream().map(UserRoleEntity::getRole).toList(); + } + + private boolean userRolesSynced(List existingUserRoles) { + List claimsRoles = this.getRolesFromClaims(); + if (existingUserRoles == null) existingUserRoles = new ArrayList<>(); + existingUserRoles = existingUserRoles.stream().filter(x -> x != null && !x.isBlank()).distinct().toList(); + if (claimsRoles.size() != existingUserRoles.size()) return false; + + for (String claim : claimsRoles) { + if (existingUserRoles.stream().noneMatch(claim::equalsIgnoreCase)) return false; + } + return true; + } + + private void syncRoles() throws InvalidApplicationException { + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedTenantRoles()) inRolesClause.value(item); + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userScope.getUserId()), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedTenantRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + this.tenantScope.isDefaultTenant() ? criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) : criteriaBuilder.equal(root.get(UserRoleEntity._tenantId), this.tenantScope.getTenant()) + )); + List existingUserRoles = this.entityManager.createQuery(query).getResultList(); + + List foundRoles = new ArrayList<>(); + for (String claimRole : this.getRolesFromClaims()) { + UserRoleEntity roleEntity = existingUserRoles.stream().filter(x -> x.getRole().equals(claimRole)).findFirst().orElse(null); + if (roleEntity == null) { + roleEntity = this.buildRole(claimRole); + this.entityManager.persist(roleEntity); + } + foundRoles.add(roleEntity.getId()); + } + for (UserRoleEntity existing : existingUserRoles) { + if (!foundRoles.contains(existing.getId())) { + this.entityManager.remove(existing); + } + } + } + + private UserRoleEntity buildRole(String role) throws InvalidApplicationException { + UserRoleEntity data = new UserRoleEntity(); + data.setId(UUID.randomUUID()); + data.setUserId( userScope.getUserId()); + data.setRole(role); + if (this.tenantScope.isDefaultTenant()) data.setTenantId(this.tenantScope.getTenant()); + data.setCreatedAt(Instant.now()); + return data; + } + @Override public void postHandle(@NonNull WebRequest request, ModelMap model) { this.tenantScope.setTenant(null, null); diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java index 38236eba7..a1661c9d7 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeClaimInterceptor.java @@ -1,10 +1,12 @@ package eu.eudat.interceptors.tenant; +import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.convention.ConventionService; import eu.eudat.data.TenantEntity; +import eu.eudat.data.UserEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.MyPrincipal; @@ -68,7 +70,7 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { this.claimExtractorContext = claimExtractorContext; this.tenantByCodeCacheService = tenantByCodeCacheService; this.tenantByIdCacheService = tenantByIdCacheService; - this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName; + this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + ClaimNames.TenantClaimName; } @Override @@ -140,7 +142,7 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { private UUID getTenantIdFromDatabase(String tenantCode) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(UserEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -148,27 +150,16 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._id); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (UUID) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getId(); } return null; } private String getTenantCodeFromDatabase(UUID tenantId) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(TenantEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -176,20 +167,9 @@ public class TenantScopeClaimInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._code); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (String) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getCode(); } return null; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java index b301abea7..9f5c98919 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/tenant/TenantScopeHeaderInterceptor.java @@ -1,6 +1,7 @@ package eu.eudat.interceptors.tenant; +import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.convention.ConventionService; @@ -60,7 +61,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; if (!this.tenantScope.isMultitenant()) return; - String tenantCode = request.getHeader(TenantScope.TenantClaimName); + String tenantCode = request.getHeader(ClaimNames.TenantClaimName); logger.debug("retrieved request tenant header is: {}", tenantCode); if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return; @@ -101,7 +102,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { private UUID getTenantIdFromDatabase(String tenantCode) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(TenantEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -109,27 +110,16 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._id); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (UUID) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getId(); } return null; } private String getTenantCodeFromDatabase(UUID tenantId) { CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); - CriteriaQuery query = criteriaBuilder.createQuery(Tuple.class); + CriteriaQuery query = criteriaBuilder.createQuery(TenantEntity.class); Root root = query.from(TenantEntity.class); query = query.where( criteriaBuilder.and( @@ -137,20 +127,9 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor { criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) ) ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); - List results = this.entityManager.createQuery(query).getResultList(); + List results = this.entityManager.createQuery(query).getResultList(); if (results.size() == 1) { - Object o; - try { - o = results.getFirst().get(TenantEntity._code); - } catch (IllegalArgumentException e) { - return null; - } - if (o == null) return null; - try { - return (String) o; - } catch (ClassCastException e) { - return null; - } + return results.getFirst().getCode(); } return null; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java b/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java index 543278f51..282413e60 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java +++ b/dmp-backend/web/src/main/java/eu/eudat/interceptors/user/UserInterceptor.java @@ -1,6 +1,7 @@ package eu.eudat.interceptors.user; +import eu.eudat.authorization.AuthorizationProperties; import eu.eudat.authorization.ClaimNames; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.enums.ContactInfoType; @@ -10,17 +11,13 @@ import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.types.user.AdditionalInfoEntity; import eu.eudat.commons.types.usercredential.UserCredentialDataEntity; import eu.eudat.commons.locale.LocaleProperties; -import eu.eudat.data.UserContactInfoEntity; -import eu.eudat.data.UserCredentialEntity; -import eu.eudat.data.UserEntity; -import eu.eudat.data.UserRoleEntity; +import eu.eudat.convention.ConventionService; +import eu.eudat.data.*; import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; import eu.eudat.model.UserContactInfo; import eu.eudat.model.UserCredential; -import eu.eudat.model.UserRole; import eu.eudat.query.UserContactInfoQuery; import eu.eudat.query.UserCredentialQuery; -import eu.eudat.query.UserRoleQuery; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.tools.data.query.QueryFactory; @@ -29,6 +26,10 @@ import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.logging.LoggerService; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.apache.commons.validator.routines.EmailValidator; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -61,6 +62,8 @@ public class UserInterceptor implements WebRequestInterceptor { private final LockByKeyManager lockByKeyManager; private final LocaleProperties localeProperties; private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler; + private final AuthorizationProperties authorizationProperties; + private final ConventionService conventionService; @PersistenceContext public EntityManager entityManager; @@ -74,7 +77,7 @@ public class UserInterceptor implements WebRequestInterceptor { JsonHandlingService jsonHandlingService, QueryFactory queryFactory, LockByKeyManager lockByKeyManager, - LocaleProperties localeProperties, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler) { + LocaleProperties localeProperties, UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler, AuthorizationProperties authorizationProperties, ConventionService conventionService) { this.userScope = userScope; this.currentPrincipalResolver = currentPrincipalResolver; this.claimExtractor = claimExtractor; @@ -85,6 +88,8 @@ public class UserInterceptor implements WebRequestInterceptor { this.lockByKeyManager = lockByKeyManager; this.localeProperties = localeProperties; this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler; + this.authorizationProperties = authorizationProperties; + this.conventionService = conventionService; } @Override @@ -173,11 +178,11 @@ public class UserInterceptor implements WebRequestInterceptor { } } -// List existingUserRoles = this.collectUserRoles(userId); -// if (!this.userRolesSynced(existingUserRoles)) { -// this.syncRoles(userId); -// hasChanges = true; -// } + List existingUserRoles = this.collectUserRoles(userId); + if (!this.userRolesSynced(existingUserRoles)) { + this.syncRoles(userId); + hasChanges = true; + } UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).first(); if (userCredential == null) { @@ -230,14 +235,27 @@ public class UserInterceptor implements WebRequestInterceptor { } private List getRolesFromClaims() { - List claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal()); + List claimsRoles = this.claimExtractor.asStrings(currentPrincipalResolver.currentPrincipal(), ClaimNames.GlobalRolesClaimName); if (claimsRoles == null) claimsRoles = new ArrayList<>(); + claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank() && (this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedGlobalRoles()) || this.authorizationProperties.getAllowedGlobalRoles().contains(x))).distinct().toList(); claimsRoles = claimsRoles.stream().filter(x -> x != null && !x.isBlank()).distinct().toList(); return claimsRoles; } private void syncRoles(UUID userId) { - List existingUserRoles = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collect(); + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedGlobalRoles()) inRolesClause.value(item); + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userId), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedGlobalRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) + )); + List existingUserRoles = this.entityManager.createQuery(query).getResultList(); + List foundRoles = new ArrayList<>(); for (String claimRole : this.getRolesFromClaims()) { UserRoleEntity roleEntity = existingUserRoles.stream().filter(x -> x.getRole().equals(claimRole)).findFirst().orElse(null); @@ -255,8 +273,21 @@ public class UserInterceptor implements WebRequestInterceptor { } private List collectUserRoles(UUID userId) { - List items = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collectAs(new BaseFieldSet().ensure(UserRole._role)); - return items == null ? new ArrayList<>() : items.stream().map(UserRoleEntity::getRole).toList(); + CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(UserRoleEntity.class); + Root root = query.from(UserRoleEntity.class); + + CriteriaBuilder.In inRolesClause = criteriaBuilder.in(root.get(UserRoleEntity._role)); + for (String item : this.authorizationProperties.getAllowedGlobalRoles()) inRolesClause.value(item); + + query.where(criteriaBuilder.and( + criteriaBuilder.equal(root.get(UserRoleEntity._userId), userId), + this.conventionService.isListNullOrEmpty(this.authorizationProperties.getAllowedGlobalRoles()) ? criteriaBuilder.isNotNull(root.get(UserRoleEntity._role)) : inRolesClause, + criteriaBuilder.isNull(root.get(UserRoleEntity._tenantId)) + )).multiselect(root.get(UserRoleEntity._role).alias(UserRoleEntity._role)); + List results = this.entityManager.createQuery(query).getResultList(); + + return results.stream().map(UserRoleEntity::getRole).toList(); } private List collectUserEmails(UUID userId) { diff --git a/dmp-backend/web/src/main/resources/config/application.yml b/dmp-backend/web/src/main/resources/config/application.yml index a4b175e10..686f12bc6 100644 --- a/dmp-backend/web/src/main/resources/config/application.yml +++ b/dmp-backend/web/src/main/resources/config/application.yml @@ -31,6 +31,7 @@ spring: optional:classpath:config/public-api.yml[.yml], optional:classpath:config/public-api-${spring.profiles.active}.yml[.yml], optional:file:../config/public-api-${spring.profiles.active}.yml[.yml], optional:classpath:config/dashboard.yml[.yml], optional:classpath:config/dashboard-${spring.profiles.active}.yml[.yml], optional:file:../config/dashboard-${spring.profiles.active}.yml[.yml], optional:classpath:config/transformer.yml[.yml], optional:classpath:config/transformer-${spring.profiles.active}.yml[.yml], optional:file:../config/transformer-${spring.profiles.active}.yml[.yml], + optional:classpath:config/authorization.yml[.yml], optional:classpath:config/authorization-${spring.profiles.active}.yml[.yml], optional:file:../config/authorization-${spring.profiles.active}.yml[.yml], optional:classpath:config/lock.yml[.yml], optional:classpath:config/lock-${spring.profiles.active}.yml[.yml], optional:file:../config/lock-${spring.profiles.active}.yml[.yml] diff --git a/dmp-backend/web/src/main/resources/config/authorization.yml b/dmp-backend/web/src/main/resources/config/authorization.yml new file mode 100644 index 000000000..8fde55603 --- /dev/null +++ b/dmp-backend/web/src/main/resources/config/authorization.yml @@ -0,0 +1,9 @@ +authorization: + allowedTenantRoles: + - TenantAdmin + - TenantUser + - TenantManager + - TenantDescriptionTemplateEditor + allowedGlobalRoles: + - Admin + - User \ No newline at end of file diff --git a/dmp-backend/web/src/main/resources/config/idpclaims.yml b/dmp-backend/web/src/main/resources/config/idpclaims.yml index 94e843a48..26e00f2a1 100644 --- a/dmp-backend/web/src/main/resources/config/idpclaims.yml +++ b/dmp-backend/web/src/main/resources/config/idpclaims.yml @@ -24,6 +24,14 @@ idpclient: filterBy: "(.*):::TenantCode::" extractByExpression: "(.*):(.*)" extractExpressionValue: "[[g1]]" + GlobalRoles: + - type: resource_access + path: dmp_web.roles + TenantRoles: + - type: tenant_roles + filterBy: "(.*):::TenantCode::" + extractByExpression: "(.*):(.*)" + extractExpressionValue: "[[g1]]" Scope: - type: scope AccessToken: