UNPKG

@delon/acl

Version:

A simple role-based access control.

419 lines (409 loc) 14.4 kB
import * as i0 from '@angular/core'; import { Injectable, inject, ViewContainerRef, TemplateRef, Directive, Input, ElementRef, Renderer2, Injector, NgModule } from '@angular/core'; import { BehaviorSubject, filter, Observable, of, map, tap } from 'rxjs'; import * as i1 from '@delon/util/config'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Router } from '@angular/router'; import { CommonModule } from '@angular/common'; const ACL_DEFAULT_CONFIG = { guard_url: `/403` }; /** * ACL 控制服务,[在线文档](https://ng-alain.com/acl) */ class ACLService { options; roles = []; abilities = []; full = false; aclChange = new BehaviorSubject(null); /** ACL变更通知 */ get change() { return this.aclChange.asObservable(); } /** 获取所有数据 */ get data() { return { full: this.full, roles: this.roles, abilities: this.abilities }; } get guard_url() { return this.options.guard_url; } constructor(configSrv) { this.options = configSrv.merge('acl', ACL_DEFAULT_CONFIG); } parseACLType(val) { let t; if (typeof val === 'number') { t = { ability: [val] }; } else if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'number') { t = { ability: val }; } else if (typeof val === 'object' && !Array.isArray(val)) { t = { ...val }; } else if (Array.isArray(val)) { t = { role: val }; } else { t = { role: val == null ? [] : [val] }; } return { except: false, ...t }; } /** * 设置当前用户角色或权限能力(会先清除所有) */ set(value) { this.full = false; this.abilities = []; this.roles = []; this.add(value); this.aclChange.next(value); } /** * 标识当前用户为全量,即不受限 */ setFull(val) { this.full = val; this.aclChange.next(val); } /** * 设置当前用户权限能力(会先清除所有) */ setAbility(abilities) { this.set({ ability: abilities }); } /** * 设置当前用户角色(会先清除所有) */ setRole(roles) { this.set({ role: roles }); } /** * 为当前用户增加角色或权限能力 */ add(value) { if (value.role && value.role.length > 0) { this.roles.push(...value.role); } if (value.ability && value.ability.length > 0) { this.abilities.push(...value.ability); } } /** * 为当前用户附加角色 */ attachRole(roles) { for (const val of roles) { if (!this.roles.includes(val)) { this.roles.push(val); } } this.aclChange.next(this.data); } /** * 为当前用户附加权限 */ attachAbility(abilities) { for (const val of abilities) { if (!this.abilities.includes(val)) { this.abilities.push(val); } } this.aclChange.next(this.data); } /** * 为当前用户移除角色 */ removeRole(roles) { for (const val of roles) { const idx = this.roles.indexOf(val); if (idx !== -1) { this.roles.splice(idx, 1); } } this.aclChange.next(this.data); } /** * 为当前用户移除权限 */ removeAbility(abilities) { for (const val of abilities) { const idx = this.abilities.indexOf(val); if (idx !== -1) { this.abilities.splice(idx, 1); } } this.aclChange.next(this.data); } /** * 当前用户是否有对应角色,其实 `number` 表示Ability * * - 当 `full: true` 或参数 `null` 时返回 `true` * - 若使用 `ACLType` 参数,可以指定 `mode` 校验模式 */ can(roleOrAbility) { const { preCan } = this.options; if (preCan) { roleOrAbility = preCan(roleOrAbility); } const t = this.parseACLType(roleOrAbility); let result = false; if (this.full === true || !roleOrAbility) { result = true; } else { if (t.role && t.role.length > 0) { if (t.mode === 'allOf') { result = t.role.every(v => this.roles.includes(v)); } else { result = t.role.some(v => this.roles.includes(v)); } } if (t.ability && t.ability.length > 0) { if (t.mode === 'allOf') { result = t.ability.every(v => this.abilities.includes(v)); } else { result = t.ability.some(v => this.abilities.includes(v)); } } } return t.except === true ? !result : result; } /** @inner */ parseAbility(value) { if (typeof value === 'number' || typeof value === 'string' || Array.isArray(value)) { value = { ability: Array.isArray(value) ? value : [value] }; } delete value.role; return value; } /** * 当前用户是否有对应权限点 */ canAbility(value) { return this.can(this.parseAbility(value)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLService, deps: [{ token: i1.AlainConfigService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.AlainConfigService }] }); class ACLIfDirective { srv = inject(ACLService); _viewContainer = inject(ViewContainerRef); static ngAcceptInputType_except; _value; _change$; _thenTemplateRef = inject((TemplateRef)); _elseTemplateRef = null; _thenViewRef = null; _elseViewRef = null; _except = false; constructor() { this._change$ = this.srv.change .pipe(takeUntilDestroyed(), filter(r => r != null)) .subscribe(() => this._updateView()); } set aclIf(value) { this._value = value; this._updateView(); } set aclIfThen(templateRef) { this._thenTemplateRef = templateRef; this._thenViewRef = null; this._updateView(); } set aclIfElse(templateRef) { this._elseTemplateRef = templateRef; this._elseViewRef = null; this._updateView(); } set except(value) { this._except = value != null && `${value}` !== 'false'; } get except() { return this._except; } _updateView() { const res = this.srv.can(this._value); if ((res && !this.except) || (!res && this.except)) { if (!this._thenViewRef) { this._viewContainer.clear(); this._elseViewRef = null; if (this._thenTemplateRef) { this._thenViewRef = this._viewContainer.createEmbeddedView(this._thenTemplateRef); } } } else { if (!this._elseViewRef) { this._viewContainer.clear(); this._thenViewRef = null; if (this._elseTemplateRef) { this._elseViewRef = this._viewContainer.createEmbeddedView(this._elseTemplateRef); } } } } ngOnDestroy() { this._change$.unsubscribe(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLIfDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: ACLIfDirective, isStandalone: true, selector: "[aclIf]", inputs: { aclIf: "aclIf", aclIfThen: "aclIfThen", aclIfElse: "aclIfElse", except: "except" }, exportAs: ["aclIf"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLIfDirective, decorators: [{ type: Directive, args: [{ selector: '[aclIf]', exportAs: 'aclIf' }] }], ctorParameters: () => [], propDecorators: { aclIf: [{ type: Input }], aclIfThen: [{ type: Input }], aclIfElse: [{ type: Input }], except: [{ type: Input }] } }); class ACLDirective { el = inject(ElementRef).nativeElement; renderer = inject(Renderer2); srv = inject(ACLService); _value; change$; set acl(value) { this.set(value); } set ability(value) { this.set(this.srv.parseAbility(value)); } set(value) { this._value = value; const CLS = 'acl__hide'; const el = this.el; if (this.srv.can(this._value)) { this.renderer.removeClass(el, CLS); } else { this.renderer.addClass(el, CLS); } } constructor() { this.change$ = this.srv.change .pipe(takeUntilDestroyed(), filter(r => r != null)) .subscribe(() => this.set(this._value)); } ngOnDestroy() { this.change$.unsubscribe(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: ACLDirective, isStandalone: true, selector: "[acl]", inputs: { acl: "acl", ability: ["acl-ability", "ability"] }, exportAs: ["acl"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLDirective, decorators: [{ type: Directive, args: [{ selector: '[acl]', exportAs: 'acl' }] }], ctorParameters: () => [], propDecorators: { acl: [{ type: Input, args: ['acl'] }], ability: [{ type: Input, args: ['acl-ability'] }] } }); /** * NOTE:`ACLType` 类型可能会被其他类库所引用,为了减少类库间彼此的依赖性,其他类库会以复制的形式存在 * 当这里有变化时,请务必同步更新,涉及:`MenuService.acl`、`util.AlainACLType` * TODO: 尝试增加 `@delon/core` 类库用于处理这种通用型 */ class ACLGuardService { srv = inject(ACLService); router = inject(Router); injector = inject(Injector); process(data) { data = { guard: null, guard_url: this.srv.guard_url, ...data }; let guard = data.guard; if (typeof guard === 'function') guard = guard(this.srv, this.injector); return (guard && guard instanceof Observable ? guard : of(guard != null ? guard : null)).pipe(map(v => this.srv.can(v)), tap(v => { if (v) return; this.router.navigateByUrl(data.guard_url); })); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLGuardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLGuardService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ACLGuardService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). * * ```ts * data: { * path: 'home', * canActivate: [ aclCanActivate ], * data: { guard: 'user1' } * } * ``` */ const aclCanActivate = route => inject(ACLGuardService).process(route.data); /** * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). * * ```ts * data: { * path: 'home', * canActivateChild: [ aclCanActivateChild ], * data: { guard: 'user1' } * } * ``` */ const aclCanActivateChild = route => inject(ACLGuardService).process(route.data); /** * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). * * ```ts * data: { * path: 'home', * canMatch: [ aclCanMatch ], * data: { guard: 'user1' } * } * ``` */ const aclCanMatch = route => inject(ACLGuardService).process(route.data); const COMPONENTS = [ACLDirective, ACLIfDirective]; class DelonACLModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DelonACLModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: DelonACLModule, imports: [CommonModule, ACLDirective, ACLIfDirective], exports: [ACLDirective, ACLIfDirective] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DelonACLModule, imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DelonACLModule, decorators: [{ type: NgModule, args: [{ imports: [CommonModule, ...COMPONENTS], exports: COMPONENTS }] }] }); /** * Generated bundle index. Do not edit. */ export { ACLDirective, ACLGuardService, ACLIfDirective, ACLService, ACL_DEFAULT_CONFIG, DelonACLModule, aclCanActivate, aclCanActivateChild, aclCanMatch }; //# sourceMappingURL=acl.mjs.map