diff --git a/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts b/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts index ebeae81a67..a67db6c7b9 100644 --- a/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts +++ b/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts @@ -1,9 +1,7 @@ import { Injectable, inject } from '@angular/core'; - import { Observable } from 'rxjs'; import { tap, map } from 'rxjs/operators'; - -import { ConfigStateService, IAbpGuard } from '@abp/ng.core'; +import { ConfigStateService, IAbpGuard, PermissionService } from '@abp/ng.core'; import { ExtensionsService, getObjectExtensionEntitiesFromStore, @@ -23,6 +21,7 @@ import { eAccountComponents } from '../enums/components'; @Injectable() export class AccountExtensionsGuard implements IAbpGuard { protected readonly configState = inject(ConfigStateService); + protected readonly permmission = inject(PermissionService); protected readonly extensions = inject(ExtensionsService); canActivate(): Observable { @@ -34,7 +33,7 @@ export class AccountExtensionsGuard implements IAbpGuard { map(entities => ({ [eAccountComponents.PersonalSettings]: entities.User, })), - mapEntitiesToContributors(this.configState, 'AbpIdentity'), + mapEntitiesToContributors(this.configState, this.permmission, 'AbpIdentity'), tap(objectExtensionContributors => { mergeWithDefaultProps( this.extensions.editFormProps, diff --git a/npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts b/npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts index b3aec858b8..e744e98442 100644 --- a/npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts +++ b/npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts @@ -1,6 +1,7 @@ import { inject } from '@angular/core'; -import { ConfigStateService } from '@abp/ng.core'; +import { ResolveFn } from '@angular/router'; import { map, tap } from 'rxjs'; +import { ConfigStateService, PermissionService } from '@abp/ng.core'; import { ExtensionsService, getObjectExtensionEntitiesFromStore, @@ -9,10 +10,10 @@ import { } from '@abp/ng.components/extensible'; import { eAccountComponents } from '../enums'; import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, DEFAULT_ACCOUNT_FORM_PROPS } from '../tokens'; -import { ResolveFn } from '@angular/router'; export const accountExtensionsResolver: ResolveFn = () => { const configState = inject(ConfigStateService); + const permission = inject(PermissionService); const extensions = inject(ExtensionsService); const config = { optional: true }; @@ -23,7 +24,7 @@ export const accountExtensionsResolver: ResolveFn = () => { map(entities => ({ [eAccountComponents.PersonalSettings]: entities.User, })), - mapEntitiesToContributors(configState, 'AbpIdentity'), + mapEntitiesToContributors(configState, permission, 'AbpIdentity'), tap(objectExtensionContributors => { mergeWithDefaultProps( extensions.editFormProps, diff --git a/npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts b/npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts index b89cd0216d..2fe8593f8d 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts @@ -61,6 +61,23 @@ export interface ExtensionPropertyDto { configuration: Record; defaultValue: any; formText?: string; + policy?: Policy; +} + +export interface BaseDefinition { + requiresAll: boolean; +} +export interface FeatureDefinition extends BaseDefinition { + features?: string[]; +} +export interface PermissionDefinition extends BaseDefinition { + permissionNames?: string[]; +} + +export interface Policy { + globalFeatures: FeatureDefinition; + features: FeatureDefinition; + permissions: PermissionDefinition; } export interface ExtensionPropertyUiDto { diff --git a/npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts b/npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts index 75b8487b31..1bd25966fc 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts @@ -24,7 +24,14 @@ export function generateFormFromProps(data: PropData) { props.forEach(({ value: prop }) => { const name = prop.name; const isExtraProperty = prop.isExtra || name in extraProperties; - let value = isExtraProperty ? extraProperties[name] : name in record ? record[name] : undefined; + + let value = undefined; + + if (isExtraProperty) { + value = extraProperties[name]; + } else if (name in record) { + value = record[name]; + } if (typeof value === 'undefined') value = prop.defaultValue; diff --git a/npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts b/npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts index 7207858036..7f06a9b1de 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts @@ -1,3 +1,4 @@ +import { ConfigStateService, PermissionService } from '@abp/ng.core'; import { Observable, of } from 'rxjs'; import { EXTRA_PROPERTIES_KEY } from '../constants/extra-properties'; import { @@ -22,6 +23,8 @@ import { PropList, PropsFactory, } from '../models/props'; +import { Policy } from '../models/internal/object-extensions'; +import { ObjectExtensions } from '../models/object-extensions'; export function createExtraPropertyValueResolver( name: string, @@ -47,6 +50,51 @@ export function mergeWithDefaultProps>( ); }); } + +export function checkPolicies( + properties: ObjectExtensions.EntityExtensionProperties, + configState: ConfigStateService, + permissionService: PermissionService, +) { + const props = Object.entries(properties); + + const checkPolicy = (policy: Policy): boolean => { + const { permissions, globalFeatures, features } = policy; + + const checks = [ + { + items: permissions?.permissionNames, + requiresAll: permissions?.requiresAll, + check: (item: string) => permissionService.getGrantedPolicy(item), + }, + { + items: globalFeatures?.features, + requiresAll: globalFeatures?.requiresAll, + check: (item: string) => configState.getGlobalFeatureIsEnabled(item), + }, + { + items: features?.features, + requiresAll: features?.requiresAll, + check: (item: string) => configState.getFeatureIsEnabled(item), + }, + ]; + + return checks.every(({ items, requiresAll, check }) => { + if (!items?.length) { + return true; + } + + return requiresAll ? items.every(check) : items.some(check); + }); + }; + + props.forEach(([name, property]) => { + if (property.policy && !checkPolicy(property.policy)) { + delete properties[name]; + } + }); +} + type InferredPropDefaults = F extends EntityPropsFactory ? EntityPropDefaults diff --git a/npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts b/npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts index 2d545c1162..68a97e844c 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts @@ -5,6 +5,7 @@ import { ExtensionEnumDto, ExtensionPropertyUiLookupDto, ObjectExtensionsDto, + PermissionService, } from '@abp/ng.core'; import { Observable, pipe, zip } from 'rxjs'; import { filter, map, switchMap, take } from 'rxjs/operators'; @@ -15,7 +16,7 @@ import { ObjectExtensions } from '../models/object-extensions'; import { PropCallback } from '../models/props'; import { createEnum, createEnumOptions, createEnumValueResolver } from './enum.util'; import { createDisplayNameLocalizationPipeKeyGenerator } from './localization.util'; -import { createExtraPropertyValueResolver } from './props.util'; +import { checkPolicies, createExtraPropertyValueResolver } from './props.util'; import { createTypeaheadDisplayNameGenerator, createTypeaheadOptions, @@ -39,15 +40,18 @@ function selectEnums( ): Observable> { return selectObjectExtensions(configState).pipe( map((extensions: ObjectExtensionsDto) => - Object.keys(extensions.enums).reduce((acc, key) => { - const { fields, localizationResource } = extensions.enums[key]; - acc[key] = { - fields, - localizationResource, - transformed: createEnum(fields), - }; - return acc; - }, {} as Record), + Object.keys(extensions.enums).reduce( + (acc, key) => { + const { fields, localizationResource } = extensions.enums[key]; + acc[key] = { + fields, + localizationResource, + transformed: createEnum(fields), + }; + return acc; + }, + {} as Record, + ), ), ); } @@ -71,6 +75,7 @@ export function getObjectExtensionEntitiesFromStore( export function mapEntitiesToContributors( configState: ConfigStateService, + permissionService: PermissionService, resource: string, ) { return pipe( @@ -86,10 +91,16 @@ export function mapEntitiesToContributors( acc.editForm[key] = []; const entity: ObjectExtensions.EntityExtensionDto = entities[key]; - if (!entity) return acc; + if (!entity) { + return acc; + } const properties = entity.properties; - if (!properties) return acc; + if (!properties) { + return acc; + } + + checkPolicies(properties, configState, permissionService); const mapPropertiesToContributors = createPropertiesToContributorsMapper( generateDisplayName, diff --git a/npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts b/npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts index 58235e1590..006ea1101e 100644 --- a/npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts +++ b/npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts @@ -1,30 +1,30 @@ -import {ConfigStateService} from '@abp/ng.core'; -import {firstValueFrom, of} from 'rxjs'; -import {take} from 'rxjs/operators'; -import {ePropType} from '../lib/enums/props.enum'; -import {EntityPropList} from '../lib/models/entity-props'; -import {FormPropList} from '../lib/models/form-props'; -import {ObjectExtensions} from '../lib/models/object-extensions'; +import { ConfigStateService, PermissionService } from '@abp/ng.core'; +import { firstValueFrom, lastValueFrom, of } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { ePropType } from '../lib/enums/props.enum'; +import { EntityPropList } from '../lib/models/entity-props'; +import { FormPropList } from '../lib/models/form-props'; +import { ObjectExtensions } from '../lib/models/object-extensions'; import { getObjectExtensionEntitiesFromStore, mapEntitiesToContributors, } from '../lib/utils/state.util'; -const fakeAppConfigService = {get: () => of(createMockState())} as any; -const fakeLocalizationService = {get: () => of(createMockState())} as any; +const fakeAppConfigService = { get: () => of(createMockState()) } as any; +const fakeLocalizationService = { get: () => of(createMockState()) } as any; const configState = new ConfigStateService(fakeAppConfigService, fakeLocalizationService, false); configState.refreshAppState(); +const permissionService = new PermissionService(configState); describe('State Utils', () => { describe('#getObjectExtensionEntitiesFromStore', () => { it('should return observable entities of an existing module', async () => { - const objectExtensionEntitiesFromStore$ = getObjectExtensionEntitiesFromStore( configState, 'Identity', - ) + ); - const entities = await firstValueFrom(objectExtensionEntitiesFromStore$) + const entities = await firstValueFrom(objectExtensionEntitiesFromStore$); expect('Role' in entities).toBe(true); }); @@ -48,9 +48,12 @@ describe('State Utils', () => { describe('#mapEntitiesToContributors', () => { it('should return contributors from given entities', async () => { - const contributors = await of(createMockEntities()) - .pipe(mapEntitiesToContributors(configState, 'AbpIdentity'), take(1)) - .toPromise(); + const contributors = await lastValueFrom( + of(createMockEntities()).pipe( + mapEntitiesToContributors(configState, permissionService, 'AbpIdentity'), + take(1), + ), + ); const propList = new EntityPropList(); contributors.prop.Role.forEach(callback => callback(propList)); @@ -118,7 +121,7 @@ function createMockState() { }, defaultResourceName: 'Default', currentCulture: { - cultureName: 'en' + cultureName: 'en', }, languages: [], }, diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index 66cf601e3b..475f6d76f3 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -5,6 +5,7 @@ import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-co import { AbpApplicationLocalizationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service'; import { ApplicationConfigurationDto, + ApplicationFeatureConfigurationDto, ApplicationGlobalFeatureConfigurationDto, } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models'; import { INCUDE_LOCALIZATION_RESOURCES_TOKEN } from '../tokens/include-localization-resources.token'; @@ -160,6 +161,18 @@ export class ConfigStateService { }); } + private isFeatureEnabled(key: string, features: ApplicationFeatureConfigurationDto) { + return features.values[key] === 'true'; + } + + getFeatureIsEnabled(key: string) { + return this.isFeatureEnabled(key, this.store.state.features); + } + + getFeatureIsEnabled$(key: string) { + return this.store.sliceState(state => this.isFeatureEnabled(key, state.features)); + } + getSetting(key: string) { return this.store.state.setting?.values?.[key]; } diff --git a/npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts b/npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts index c78e03b6af..5d78bba9fb 100644 --- a/npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts +++ b/npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts @@ -1,9 +1,3 @@ -import { Injectable, inject } from '@angular/core'; - -import { Observable } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; - -import { ConfigStateService, IAbpGuard } from '@abp/ng.core'; import { ExtensionsService, getObjectExtensionEntitiesFromStore, @@ -11,6 +5,11 @@ import { mergeWithDefaultActions, mergeWithDefaultProps, } from '@abp/ng.components/extensible'; +import { ConfigStateService, IAbpGuard, PermissionService } from '@abp/ng.core'; +import { Injectable, inject } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; import { eIdentityComponents } from '../enums/components'; import { @@ -32,6 +31,7 @@ import { @Injectable() export class IdentityExtensionsGuard implements IAbpGuard { protected readonly configState = inject(ConfigStateService); + protected readonly permission = inject(PermissionService); protected readonly extensions = inject(ExtensionsService); canActivate(): Observable { @@ -48,7 +48,7 @@ export class IdentityExtensionsGuard implements IAbpGuard { [eIdentityComponents.Roles]: entities.Role, [eIdentityComponents.Users]: entities.User, })), - mapEntitiesToContributors(this.configState, 'AbpIdentity'), + mapEntitiesToContributors(this.configState, this.permission, 'AbpIdentity'), tap(objectExtensionContributors => { mergeWithDefaultActions( this.extensions.entityActions, diff --git a/npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts b/npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts index 45856046d8..812a63d3f7 100644 --- a/npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts +++ b/npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts @@ -1,6 +1,3 @@ -import { inject } from '@angular/core'; -import { map, tap } from 'rxjs'; -import { ConfigStateService } from '@abp/ng.core'; import { ExtensionsService, getObjectExtensionEntitiesFromStore, @@ -8,6 +5,10 @@ import { mergeWithDefaultActions, mergeWithDefaultProps, } from '@abp/ng.components/extensible'; +import { ConfigStateService, PermissionService } from '@abp/ng.core'; +import { inject } from '@angular/core'; +import { ResolveFn } from '@angular/router'; +import { map, tap } from 'rxjs'; import { eIdentityComponents } from '../enums'; import { IDENTITY_ENTITY_ACTION_CONTRIBUTORS, @@ -21,10 +22,10 @@ import { DEFAULT_IDENTITY_CREATE_FORM_PROPS, DEFAULT_IDENTITY_EDIT_FORM_PROPS, } from '../tokens'; -import { ResolveFn } from '@angular/router'; export const identityExtensionsResolver: ResolveFn = () => { const configState = inject(ConfigStateService); + const permission = inject(PermissionService); const extensions = inject(ExtensionsService); const config = { optional: true }; @@ -40,7 +41,7 @@ export const identityExtensionsResolver: ResolveFn = () => { [eIdentityComponents.Roles]: entities.Role, [eIdentityComponents.Users]: entities.User, })), - mapEntitiesToContributors(configState, 'AbpIdentity'), + mapEntitiesToContributors(configState, permission, 'AbpIdentity'), tap(objectExtensionContributors => { mergeWithDefaultActions( extensions.entityActions, diff --git a/npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts b/npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts index d37d735453..0b140f8de8 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts +++ b/npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts @@ -1,9 +1,3 @@ -import { Injectable, inject } from '@angular/core'; - -import { Observable } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; - -import { ConfigStateService, IAbpGuard } from '@abp/ng.core'; import { ExtensionsService, getObjectExtensionEntitiesFromStore, @@ -11,6 +5,11 @@ import { mergeWithDefaultActions, mergeWithDefaultProps, } from '@abp/ng.components/extensible'; +import { ConfigStateService, IAbpGuard, PermissionService } from '@abp/ng.core'; +import { Injectable, inject } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; import { eTenantManagementComponents } from '../enums/components'; import { @@ -32,6 +31,7 @@ import { @Injectable() export class TenantManagementExtensionsGuard implements IAbpGuard { protected readonly configState = inject(ConfigStateService); + protected readonly permission = inject(PermissionService); protected readonly extensions = inject(ExtensionsService); canActivate(): Observable { @@ -49,7 +49,7 @@ export class TenantManagementExtensionsGuard implements IAbpGuard { map(entities => ({ [eTenantManagementComponents.Tenants]: entities.Tenant, })), - mapEntitiesToContributors(this.configState, 'TenantManagement'), + mapEntitiesToContributors(this.configState, this.permission, 'TenantManagement'), tap(objectExtensionContributors => { mergeWithDefaultActions( this.extensions.entityActions, @@ -84,5 +84,3 @@ export class TenantManagementExtensionsGuard implements IAbpGuard { ); } } - - diff --git a/npm/ng-packs/packages/tenant-management/src/lib/resolvers/extensions.resolver.ts b/npm/ng-packs/packages/tenant-management/src/lib/resolvers/extensions.resolver.ts index d3c224aba2..98e3f34aa6 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/resolvers/extensions.resolver.ts +++ b/npm/ng-packs/packages/tenant-management/src/lib/resolvers/extensions.resolver.ts @@ -1,6 +1,3 @@ -import { inject } from '@angular/core'; -import { map, tap } from 'rxjs'; -import { ConfigStateService } from '@abp/ng.core'; import { ExtensionsService, getObjectExtensionEntitiesFromStore, @@ -8,6 +5,10 @@ import { mergeWithDefaultActions, mergeWithDefaultProps, } from '@abp/ng.components/extensible'; +import { ConfigStateService, PermissionService } from '@abp/ng.core'; +import { inject } from '@angular/core'; +import { ResolveFn } from '@angular/router'; +import { map, tap } from 'rxjs'; import { eTenantManagementComponents } from '../enums'; import { TENANT_MANAGEMENT_ENTITY_ACTION_CONTRIBUTORS, @@ -21,10 +22,10 @@ import { DEFAULT_TENANT_MANAGEMENT_CREATE_FORM_PROPS, DEFAULT_TENANT_MANAGEMENT_EDIT_FORM_PROPS, } from '../tokens'; -import { ResolveFn } from '@angular/router'; export const tenantManagementExtensionsResolver: ResolveFn = () => { const configState = inject(ConfigStateService); + const permission = inject(PermissionService); const extensions = inject(ExtensionsService); const config = { optional: true }; @@ -40,7 +41,7 @@ export const tenantManagementExtensionsResolver: ResolveFn = () => { map(entities => ({ [eTenantManagementComponents.Tenants]: entities.Tenant, })), - mapEntitiesToContributors(configState, 'TenantManagement'), + mapEntitiesToContributors(configState, permission, 'TenantManagement'), tap(objectExtensionContributors => { mergeWithDefaultActions( extensions.entityActions,