From 0ad4bd811b0b4f891ba63463d037bca3648d272b Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 2 May 2018 10:59:43 -0500 Subject: [PATCH] Release 1.0.0-alpha.23 :rocket: - Enabled Dependency Injection for ACL Groups --- package.json | 2 +- src/core/acl.group.match.ts | 35 +++++++++++++++++++++++----- src/core/call.responser.ts | 46 ++++++++++++++++++++++++++++--------- src/core/call.streamer.ts | 44 +++++++++++++++++++++++++---------- src/index.ts | 2 +- test/onixjs.core.unit.ts | 13 ++++++++++- 6 files changed, 110 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index d533817..4e86026 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onixjs/core", - "version": "1.0.0-alpha.22.2", + "version": "1.0.0-alpha.23", "description": "An Enterprise Grade NodeJS Platform that implements Industry Standards and Patterns in order to provide Connectivity, Stability, High-Availability and High-Performance.", "main": "dist/src/index.js", "scripts": { diff --git a/src/core/acl.group.match.ts b/src/core/acl.group.match.ts index 556d7d0..073b28c 100644 --- a/src/core/acl.group.match.ts +++ b/src/core/acl.group.match.ts @@ -4,6 +4,8 @@ import { promiseSeries, IACLRule, IGroup, + Injector, + IModuleConfig, } from '..'; /** * @class GroupMatch @@ -22,32 +24,53 @@ export class GroupMatch { * component ACL Configuration and the operation request. */ static async verify( + // Name of method to be executed name: string, + // Application operation reference operation: IAppOperation, - config: IComponentConfig, + // Module level configurations + moduleConfig: IModuleConfig, + // Component level configurations + componentConfig: IComponentConfig, + // Scoped injector, it will inject module level dependencies + injector: Injector, ) { // Verify the parent component provides an ACL Configuration - if (Array.isArray(config.acl)) { + if (Array.isArray(componentConfig.acl)) { // Need to await the result to allow finding a truthy result return (await promiseSeries( // Iterate over given ACL Rules - config.acl.map((Rule: new () => IACLRule) => async () => { + componentConfig.acl.map((Rule: new () => IACLRule) => async () => { // Need an instance from this rule class. - const rule: IACLRule = new Rule(); + let rule: IACLRule | null = new Rule(); // Make sure this method has any group associated // Or there is a whildcard for any component method if (rule.methods.find(value => value === name || value === '*')) { // Await for group async operations, so we can find thruthy results const results = await promiseSeries( rule.groups.map(Group => async () => { - const group: IGroup = new Group(); + // Create group instance + let group: IGroup | null = new Group(); + // Inject whatever has been requested into this group + await injector.inject(Group, group, moduleConfig); // Execute group access method to verify operation request access - return group.access(operation.message.request, rule.access); + const groupResult: boolean = await group.access( + operation.message.request, + rule!.access, + ); + // Wipe group instance + group = null; + // return group results + return groupResult; }), ); + // Wipe ACL Rule instance + rule = null; // Find if any of the group results gave access for this method. return results.find(value => value === true) ? true : false; } else { + // Wipe ACL Rule instance + rule = null; // Don't give access if there is no group associated for this method return false; } diff --git a/src/core/call.responser.ts b/src/core/call.responser.ts index ad71cfe..a85ea01 100644 --- a/src/core/call.responser.ts +++ b/src/core/call.responser.ts @@ -1,4 +1,9 @@ -import {ReflectionKeys, IAppOperation, IComponentConfig} from '../interfaces'; +import { + ReflectionKeys, + IAppOperation, + IComponentConfig, + IModuleConfig, +} from '../interfaces'; import {AppFactory, LifeCycle} from '../core'; import {GroupMatch} from './acl.group.match'; //import { RoleMatch } from './roles'; @@ -43,12 +48,25 @@ export class CallResponser { return; } // Declare executable endpoint method and hooks references - let scope, + let // Declare Operation Scope + scope, + // Declare Current Module Default Config + moduleConfig: IModuleConfig = { + components: [], + models: [], + services: [], + renderers: [], + }, + // Define a flag for systemcalls, to override ACL access systemcall: boolean = false, + // Define reference for the method to be executed method: Function | null = null, + // Define a main hook, it refers to a module level hook mainHook: Function = () => null, + // Define a slave hook, it refers to a component level hook slaveHook: Function | null = null, - config: IComponentConfig = {}; + // Declare a reference for the executing component config + componentConfig: IComponentConfig = {}; // If segments are exactly 2, then it is an application level call // Only god can remotly execute this type of calls. if (segments.length === 2) { @@ -61,10 +79,7 @@ export class CallResponser { if (segments.length > 2) { scope = this.factory.app.modules[segments[1]]; method = this.factory.app.modules[segments[1]][segments[2]]; - const moduleConfig = Reflect.getMetadata( - ReflectionKeys.MODULE_CONFIG, - scope, - ); + moduleConfig = Reflect.getMetadata(ReflectionKeys.MODULE_CONFIG, scope); mainHook = moduleConfig.lifecycle ? moduleConfig.lifecycle : this.lifecycle.onModuleMethodCall; @@ -76,9 +91,12 @@ export class CallResponser { segments[3] ]; if (scope && method) { - config = Reflect.getMetadata(ReflectionKeys.COMPONENT_CONFIG, scope); - slaveHook = config.lifecycle - ? config.lifecycle + componentConfig = Reflect.getMetadata( + ReflectionKeys.COMPONENT_CONFIG, + scope, + ); + slaveHook = componentConfig.lifecycle + ? componentConfig.lifecycle : this.lifecycle.onComponentMethodCall; } } @@ -93,7 +111,13 @@ export class CallResponser { } // Verify the call request matches the ACL Rules if ( - (await GroupMatch.verify(method.name, operation, config)) || + (await GroupMatch.verify( + method.name, + operation, + moduleConfig, + componentConfig, + this.factory.scopes[segments[1]], + )) || systemcall ) { // Execute main hook, might be app/system or module level. diff --git a/src/core/call.streamer.ts b/src/core/call.streamer.ts index 0945359..e241781 100644 --- a/src/core/call.streamer.ts +++ b/src/core/call.streamer.ts @@ -1,5 +1,5 @@ import {AppFactory} from './app.factory'; -import {IAppOperation, IComponentConfig} from '../interfaces'; +import {IAppOperation, IComponentConfig, IModuleConfig} from '../interfaces'; import {LifeCycle} from '.'; import {ReflectionKeys} from '..'; import {GroupMatch} from './acl.group.match'; @@ -24,12 +24,24 @@ export class CallStreamer { * to send back an answer. */ async register(operation: IAppOperation, handler) { - // Get segments from rpc endpoint - let scope, + // Declare executable endpoint method and hooks references + let // Declare Operation Scope + scope, + // Declare Current Module Default Config + moduleConfig: IModuleConfig = { + components: [], + models: [], + services: [], + renderers: [], + }, + // Define reference for the method to be executed method: Function | null = null, + // Define a main hook, it refers to a module level hook mainHook: Function = () => null, + // Define a slave hook, it refers to a component level hook slaveHook: Function | null = null, - config: IComponentConfig = {}; + // Declare a reference for the executing component config + componentConfig: IComponentConfig = {}; const segments: string[] = operation.message.rpc.split('.'); // Component level method, RPC Exposed @@ -44,10 +56,7 @@ export class CallStreamer { if (segments.length > 2) { scope = this.factory.app.modules[segments[1]]; method = this.factory.app.modules[segments[1]][segments[2]]; - const moduleConfig = Reflect.getMetadata( - ReflectionKeys.MODULE_CONFIG, - scope, - ); + moduleConfig = Reflect.getMetadata(ReflectionKeys.MODULE_CONFIG, scope); mainHook = moduleConfig.lifecycle ? moduleConfig.lifecycle : this.lifecycle.onModuleMethodStream; @@ -57,9 +66,12 @@ export class CallStreamer { scope = this.factory.app.modules[segments[1]][segments[2]]; method = this.factory.app.modules[segments[1]][segments[2]][segments[3]]; if (scope && method) { - config = Reflect.getMetadata(ReflectionKeys.COMPONENT_CONFIG, scope); - slaveHook = config.lifecycle - ? config.lifecycle + componentConfig = Reflect.getMetadata( + ReflectionKeys.COMPONENT_CONFIG, + scope, + ); + slaveHook = componentConfig.lifecycle + ? componentConfig.lifecycle : this.lifecycle.onComponentMethodStream; } } @@ -73,7 +85,15 @@ export class CallStreamer { } // Verify the call request matches the ACL Rules - if (await GroupMatch.verify(method.name, operation, config)) { + if ( + await GroupMatch.verify( + method.name, + operation, + moduleConfig, + componentConfig, + this.factory.scopes[segments[1]], + ) + ) { // Default handler const def = data => data; // Execute main hook, might be app/system or module level. diff --git a/src/index.ts b/src/index.ts index e7ad5d5..c45833c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,7 +40,7 @@ export class OnixJS { * @description Current Onix Version. */ get version(): string { - return '1.0.0-alpha.22.2'; + return '1.0.0-alpha.23'; } /** * @property router diff --git a/test/onixjs.core.unit.ts b/test/onixjs.core.unit.ts index 9d2d542..99a9dbd 100644 --- a/test/onixjs.core.unit.ts +++ b/test/onixjs.core.unit.ts @@ -1388,7 +1388,18 @@ test('CORE: ACL Group Match', async t => { acl: [AllowEveryone], }; // VERIFY ACCESS - const hasAccess: boolean = await GroupMatch.verify(name, operation, config); + const hasAccess: boolean = await GroupMatch.verify( + name, + operation, + { + components: [], + services: [], + models: [], + renderers: [], + }, + config, + new Injector(), + ); // TEST IF HAS ACCESS t.true(hasAccess); });