Browse Source

Merge pull request #20988 from abpframework/issue-20342

Angular - Checking `Property Policy` for the Extensions
pull/21005/head
Masum ULU 2 years ago
committed by GitHub
parent
commit
8129daee18
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts
  2. 7
      npm/ng-packs/packages/account/src/lib/resolvers/extensions.resolver.ts
  3. 17
      npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts
  4. 9
      npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts
  5. 48
      npm/ng-packs/packages/components/extensible/src/lib/utils/props.util.ts
  6. 35
      npm/ng-packs/packages/components/extensible/src/lib/utils/state.util.ts
  7. 35
      npm/ng-packs/packages/components/extensible/src/tests/state.util.spec.ts
  8. 13
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  9. 14
      npm/ng-packs/packages/identity/src/lib/guards/extensions.guard.ts
  10. 11
      npm/ng-packs/packages/identity/src/lib/resolvers/extensions.resolver.ts
  11. 16
      npm/ng-packs/packages/tenant-management/src/lib/guards/extensions.guard.ts
  12. 11
      npm/ng-packs/packages/tenant-management/src/lib/resolvers/extensions.resolver.ts

7
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<boolean> {
@ -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,

7
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<any> = () => {
const configState = inject(ConfigStateService);
const permission = inject(PermissionService);
const extensions = inject(ExtensionsService);
const config = { optional: true };
@ -23,7 +24,7 @@ export const accountExtensionsResolver: ResolveFn<any> = () => {
map(entities => ({
[eAccountComponents.PersonalSettings]: entities.User,
})),
mapEntitiesToContributors(configState, 'AbpIdentity'),
mapEntitiesToContributors(configState, permission, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultProps(
extensions.editFormProps,

17
npm/ng-packs/packages/components/extensible/src/lib/models/internal/object-extensions.ts

@ -61,6 +61,23 @@ export interface ExtensionPropertyDto {
configuration: Record<string, any>;
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 {

9
npm/ng-packs/packages/components/extensible/src/lib/utils/form-props.util.ts

@ -24,7 +24,14 @@ export function generateFormFromProps<R = any>(data: PropData<R>) {
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;

48
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<T>(
name: string,
@ -47,6 +50,51 @@ export function mergeWithDefaultProps<F extends PropsFactory<any>>(
);
});
}
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> =
F extends EntityPropsFactory<infer RE>
? EntityPropDefaults<RE>

35
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<Record<string, ExtensionEnumDto>> {
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<string, ObjectExtensions.ExtensionEnumDto>),
Object.keys(extensions.enums).reduce(
(acc, key) => {
const { fields, localizationResource } = extensions.enums[key];
acc[key] = {
fields,
localizationResource,
transformed: createEnum(fields),
};
return acc;
},
{} as Record<string, ObjectExtensions.ExtensionEnumDto>,
),
),
);
}
@ -71,6 +75,7 @@ export function getObjectExtensionEntitiesFromStore(
export function mapEntitiesToContributors<T = any>(
configState: ConfigStateService,
permissionService: PermissionService,
resource: string,
) {
return pipe(
@ -86,10 +91,16 @@ export function mapEntitiesToContributors<T = any>(
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<T>(
generateDisplayName,

35
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: [],
},

13
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];
}

14
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<boolean> {
@ -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,

11
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<any> = () => {
const configState = inject(ConfigStateService);
const permission = inject(PermissionService);
const extensions = inject(ExtensionsService);
const config = { optional: true };
@ -40,7 +41,7 @@ export const identityExtensionsResolver: ResolveFn<any> = () => {
[eIdentityComponents.Roles]: entities.Role,
[eIdentityComponents.Users]: entities.User,
})),
mapEntitiesToContributors(configState, 'AbpIdentity'),
mapEntitiesToContributors(configState, permission, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultActions(
extensions.entityActions,

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

11
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<any> = () => {
const configState = inject(ConfigStateService);
const permission = inject(PermissionService);
const extensions = inject(ExtensionsService);
const config = { optional: true };
@ -40,7 +41,7 @@ export const tenantManagementExtensionsResolver: ResolveFn<any> = () => {
map(entities => ({
[eTenantManagementComponents.Tenants]: entities.Tenant,
})),
mapEntitiesToContributors(configState, 'TenantManagement'),
mapEntitiesToContributors(configState, permission, 'TenantManagement'),
tap(objectExtensionContributors => {
mergeWithDefaultActions(
extensions.entityActions,

Loading…
Cancel
Save