fix tenant select

This commit is contained in:
Efstratios Giannopoulos 2024-05-17 11:27:33 +03:00
parent a9468abe61
commit 30e5ed504a
11 changed files with 96 additions and 52 deletions

View File

@ -1,18 +1,16 @@
package org.opencdmp.service.tenant;
import org.opencdmp.model.Tenant;
import org.opencdmp.model.persist.TenantPersist;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.FieldSet;
import org.opencdmp.model.Tenant;
import org.opencdmp.model.persist.TenantPersist;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@ -26,4 +24,6 @@ public interface TenantService {
InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException;
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException;
List<Tenant> myTenants(FieldSet fieldSet) throws MyForbiddenException;
}

View File

@ -1,8 +1,12 @@
package org.opencdmp.service.tenant;
import gr.cite.commons.web.authz.service.AuthorizationService;
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;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.data.query.Ordering;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
@ -14,6 +18,7 @@ import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.AuthorizationProperties;
import org.opencdmp.authorization.ClaimNames;
import org.opencdmp.authorization.Permission;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.scope.tenant.TenantScope;
@ -31,6 +36,7 @@ import org.opencdmp.model.Tenant;
import org.opencdmp.model.builder.TenantBuilder;
import org.opencdmp.model.deleter.TenantDeleter;
import org.opencdmp.model.persist.TenantPersist;
import org.opencdmp.query.TenantQuery;
import org.opencdmp.query.UserCredentialQuery;
import org.opencdmp.query.UserRoleQuery;
import org.opencdmp.service.keycloak.KeycloakService;
@ -48,6 +54,7 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ -73,6 +80,8 @@ public class TenantServiceImpl implements TenantService {
private final AuthorizationProperties authorizationProperties;
private final TenantScope tenantScope;
private final QueryFactory queryFactory;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractor claimExtractor;
@Autowired
@ -83,7 +92,7 @@ public class TenantServiceImpl implements TenantService {
BuilderFactory builderFactory,
ConventionService conventionService,
MessageSource messageSource,
ErrorThesaurusProperties errors, TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler, TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler, KeycloakService keycloakService, AuthorizationProperties authorizationProperties, TenantScope tenantScope, QueryFactory queryFactory) {
ErrorThesaurusProperties errors, TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler, TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler, KeycloakService keycloakService, AuthorizationProperties authorizationProperties, TenantScope tenantScope, QueryFactory queryFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractor claimExtractor) {
this.entityManager = entityManager;
this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory;
@ -97,6 +106,8 @@ public class TenantServiceImpl implements TenantService {
this.authorizationProperties = authorizationProperties;
this.tenantScope = tenantScope;
this.queryFactory = queryFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
}
@Override
@ -177,8 +188,6 @@ public class TenantServiceImpl implements TenantService {
}
@Override
public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug("deleting : {}", id);
@ -192,5 +201,38 @@ public class TenantServiceImpl implements TenantService {
this.tenantRemovalIntegrationEventHandler.handle(tenantRemovalIntegrationEvent);
}
@Override
public List<Tenant> myTenants(FieldSet fieldSet) throws MyForbiddenException {
logger.debug("my tenants");
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
List<String> tenants = this.claimExtractor.asStrings(principal, ClaimNames.TenantCodesClaimName);
List<Tenant> models = new ArrayList<>();
if (tenants != null && !tenants.isEmpty()) {
if (fieldSet == null || fieldSet.isEmpty()) {
fieldSet = new BaseFieldSet(
Tenant._id,
Tenant._code,
Tenant._name);
}
TenantQuery query = this.queryFactory.query(TenantQuery.class).codes(tenants).isActive(IsActive.Active);
query.setOrder(new Ordering().addAscending(Tenant._name));
List<TenantEntity> data = query.collectAs(fieldSet);
models.addAll(this.builderFactory.builder(TenantBuilder.class).build(fieldSet, data));
if (tenants.contains(this.tenantScope.getDefaultTenantCode())){
Tenant tenant = new Tenant();
tenant.setCode(this.tenantScope.getDefaultTenantCode());
tenant.setName(this.messageSource.getMessage("DefaultTenant_Name", new Object[]{}, LocaleContextHolder.getLocale()));
models.addFirst(tenant);
}
}
return models;
}
}

View File

@ -1,17 +1,16 @@
package org.opencdmp.controllers;
import org.opencdmp.audit.AuditableAction;
import org.opencdmp.authorization.ClaimNames;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.models.Account;
import org.opencdmp.models.AccountBuilder;
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;
import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.audit.AuditableAction;
import org.opencdmp.model.Tenant;
import org.opencdmp.models.Account;
import org.opencdmp.models.AccountBuilder;
import org.opencdmp.service.tenant.TenantService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@ -21,27 +20,26 @@ import org.springframework.web.bind.annotation.RestController;
import javax.management.InvalidApplicationException;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping(value = { "/api/principal/" })
@RequestMapping("/api/principal/")
public class PrincipalController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class));
private final AuditService auditService;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final AccountBuilder accountBuilder;
private final ClaimExtractor claimExtractor;
private final TenantService tenantService;
@Autowired
public PrincipalController(
CurrentPrincipalResolver currentPrincipalResolver,
AccountBuilder accountBuilder,
AuditService auditService, ClaimExtractor claimExtractor) {
AuditService auditService, TenantService tenantService) {
this.currentPrincipalResolver = currentPrincipalResolver;
this.accountBuilder = accountBuilder;
this.auditService = auditService;
this.claimExtractor = claimExtractor;
this.tenantService = tenantService;
}
@RequestMapping(path = "me", method = RequestMethod.GET )
@ -80,16 +78,15 @@ public class PrincipalController {
}
@GetMapping("my-tenants")
public List<String> myTenants() {
public List<Tenant> myTenants(FieldSet fieldSet) {
logger.debug("my-tenants");
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
List<String> tenants = this.claimExtractor.asStrings(principal, ClaimNames.TenantCodesClaimName);
List<Tenant> models = this.tenantService.myTenants(fieldSet);
this.auditService.track(AuditableAction.Principal_MyTenants);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return tenants == null ? null : tenants.stream().distinct().collect(Collectors.toList());
return models;
}
}

View File

@ -1,10 +1,5 @@
package org.opencdmp.models;
import org.opencdmp.commons.JsonHandlingService;
import org.opencdmp.commons.scope.user.UserScope;
import org.opencdmp.commons.types.user.AdditionalInfoEntity;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.data.UserEntity;
import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration;
import gr.cite.commons.web.authz.configuration.Permission;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
@ -13,6 +8,11 @@ import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorKeys;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import org.opencdmp.commons.JsonHandlingService;
import org.opencdmp.commons.scope.user.UserScope;
import org.opencdmp.commons.types.user.AdditionalInfoEntity;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.data.UserEntity;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@ -21,7 +21,7 @@ import javax.management.InvalidApplicationException;
import java.util.*;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AccountBuilder {
private final ClaimExtractor claimExtractor;
@ -91,13 +91,13 @@ public class AccountBuilder {
}
}
if (fields.hasField(Account._roles)) {
List<String> roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
List<String> roles = this.claimExtractor.roles(this.currentPrincipalResolver.currentPrincipal());
model.setRoles(roles);
}
if (fields.hasField(Account._permissions)) {
List<String> roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
Set<String> permissions = authorizationConfiguration.permissionsOfRoles(roles);
for (Map.Entry<String, Permission> permissionEntry : authorizationConfiguration.getRawPolicies().entrySet()){
List<String> roles = this.claimExtractor.roles(this.currentPrincipalResolver.currentPrincipal());
Set<String> permissions = this.authorizationConfiguration.permissionsOfRoles(roles);
for (Map.Entry<String, Permission> permissionEntry : this.authorizationConfiguration.getRawPolicies().entrySet()){
if (permissionEntry.getValue().getAllowAuthenticated()){
permissions.add(permissionEntry.getKey());
}

View File

@ -27,4 +27,6 @@ Validation.LargerThenEqual= value {0} must be equal or larger than {1}
Validation.MissingFields= missing fields: {0}
Validation.InvalidDescriptionTemplateMultiplicity= {0} can not be used
Validation.InvalidDescriptionTemplateMultiplicityOnDMP= Description Templates has multiplicity errors
Validation_UrlRequired={0} is not valid url
Validation_UrlRequired={0} is not valid url
DefaultTenant_Name=Default

View File

@ -4,6 +4,7 @@ import { Observable } from 'rxjs';
import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service';
import { map } from 'rxjs/operators';
import { Tenant } from '@app/core/model/tenant/tenant';
@Injectable()
export class PrincipalService {
@ -20,8 +21,8 @@ export class PrincipalService {
return this.http.get<AppAccount>(url, options);
}
public myTenants(options?: Object): Observable<Array<string>> {
public myTenants(options?: Object): Observable<Array<Tenant>> {
const url = `${this.apiBase}/my-tenants`;
return this.http.get<Array<string>>(url, options);
return this.http.get<Array<Tenant>>(url, options);
}
}

View File

@ -64,9 +64,9 @@ export class PostLoginComponent extends BaseComponent implements OnInit {
this.principalService.myTenants({ params: params }).subscribe(myTenants => {
if (myTenants) {
if (myTenants.length > 1) {
this.tenants = myTenants.map(function (code) { return { 'code': code }; });
this.tenants = myTenants;
} else if (myTenants.length === 1) {
this.authService.selectedTenant(myTenants[0]);
this.authService.selectedTenant(myTenants[0]?.code);
this.loadUser();
} else {
this.authService.selectedTenant(null);

View File

@ -1,5 +1,5 @@
<mat-button-toggle-group class="tenant-menu" vertical (change)="onTenantSelected($event)" [value]="this.currentTenant">
<div *ngFor="let tenant of tenants | async">
<mat-button-toggle class="tenant-button" [value]="tenant">{{ tenant }}</mat-button-toggle>
<mat-button-toggle class="tenant-button" [value]="tenant.code">{{ tenant.name }}</mat-button-toggle>
</div>
</mat-button-toggle-group>

View File

@ -1,6 +1,7 @@
import { Component, EventEmitter, OnInit, Output } from "@angular/core";
import { MatButtonToggleChange } from "@angular/material/button-toggle";
import { Router } from "@angular/router";
import { Tenant } from "@app/core/model/tenant/tenant";
import { AuthService } from "@app/core/services/auth/auth.service";
import { PrincipalService } from "@app/core/services/http/principal.service";
import { BaseComponent } from "@common/base/base.component";
@ -16,7 +17,7 @@ import { takeUntil } from "rxjs/operators";
styleUrls: ['tenant-switch.component.scss']
})
export class TenantSwitchComponent extends BaseComponent implements OnInit {
tenants: Observable<Array<string>>;
tenants: Observable<Array<Tenant>>;
constructor(
private router: Router,
@ -26,7 +27,7 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
) {
super();
}
get currentTenant(): string {
return this.authService.selectedTenant();
}
@ -36,7 +37,7 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
//this.tenantChange.emit(this.getCurrentLanguage())
}
loadUserTenants(): Observable<Array<string>> {
loadUserTenants(): Observable<Array<Tenant>> {
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]
@ -61,8 +62,8 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
.subscribe(
() => {
this.authService.onAuthenticateSuccessReload();
},
},
(error) => this.authService.onAuthenticateError(error)
);
}
}
}

View File

@ -15,7 +15,7 @@
<mat-label>Tenant</mat-label>
<mat-select placeholder="Tenant" [formControl]="this.tenantFormGroup.get('tenantCode')">
<ng-container *ngFor="let tenant of tenants | async">
<mat-option [value]="tenant">{{ tenant }}</mat-option>
<mat-option [value]="tenant.code">{{ tenant.name }}</mat-option>
</ng-container>
</mat-select>
</mat-form-field>
@ -83,9 +83,9 @@
<span *ngIf="hasProvider(userCredential.data.provider, authProviderEnum.Configurable)" class="configurableIcon"></span>
<span *ngIf="hasProvider(userCredential.data.provider, authProviderEnum.Zenodo)" class="zenodoIcon"></span> -->
<div *ngIf="i != 0" class="col-auto d-flex align-items-center unlink-mail" (click)="userCredential.data.email !== firstEmail && removeAccount(userCredential)">
<mat-icon
[matTooltip]="(userCredential.data?.email !== firstEmail ) ? ('USER-PROFILE.ACTIONS.UNLINK-ACCOUNT' | translate) : ('USER-PROFILE.ACTIONS.UNLINK-ACCOUNT-DISABLED' | translate)"
matTooltipPosition="right"
<mat-icon
[matTooltip]="(userCredential.data?.email !== firstEmail ) ? ('USER-PROFILE.ACTIONS.UNLINK-ACCOUNT' | translate) : ('USER-PROFILE.ACTIONS.UNLINK-ACCOUNT-DISABLED' | translate)"
matTooltipPosition="right"
[ngClass]="{'disable-unlink':userCredential.data.email === firstEmail}"
>link_off</mat-icon>
</div>
@ -226,7 +226,7 @@
</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</div>
</div>
</div>

View File

@ -38,6 +38,7 @@ import { ReferenceTypeService } from '@app/core/services/reference-type/referenc
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { ReferenceSourceType } from '@app/core/common/enum/reference-source-type';
import { ReferenceType } from '@app/core/model/reference-type/reference-type';
import { Tenant } from '@app/core/model/tenant/tenant';
@Component({
selector: 'app-user-profile',
@ -61,7 +62,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
errorMessages = [];
nestedCount = [];
nestedIndex = 0;
tenants: Observable<Array<string>>;
tenants: Observable<Array<Tenant>>;
expandedPreferences: boolean = false;
organisationsSingleAutoCompleteConfiguration: SingleAutoCompleteConfiguration;
@ -154,7 +155,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
[nameof<User>(x => x.additionalInfo), nameof<UserAdditionalInfo>(x => x.organization), nameof<Reference>(x => x.reference)].join('.'),
[nameof<User>(x => x.additionalInfo), nameof<UserAdditionalInfo>(x => x.organization), nameof<Reference>(x => x.source)].join('.'),
[nameof<User>(x => x.additionalInfo), nameof<UserAdditionalInfo>(x => x.organization), nameof<Reference>(x => x.sourceType)].join('.'),
[nameof<User>(x => x.additionalInfo), nameof<UserAdditionalInfo>(x => x.organization), nameof<Reference>(x => x.isActive)].join('.'),
[nameof<User>(x => x.additionalInfo), nameof<UserAdditionalInfo>(x => x.organization), nameof<Reference>(x => x.isActive)].join('.'),
nameof<User>(x => x.additionalInfo.roleOrganization),
nameof<User>(x => x.createdAt),
nameof<User>(x => x.updatedAt),
@ -438,7 +439,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
}
// Switch Tenant
loadUserTenants(): Observable<Array<string>> {
loadUserTenants(): Observable<Array<Tenant>> {
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]