From ed1a3fadd89cb464690427667be5687d1216f505 Mon Sep 17 00:00:00 2001 From: deepakrkris Date: Fri, 9 Jun 2017 14:54:06 -0700 Subject: [PATCH] Authentication metadata provider Implement provider to return controller method metadata --- packages/authentication/src/constants.ts | 8 ++ packages/authentication/src/decorator.ts | 15 ++-- packages/authentication/src/index.ts | 2 + .../authentication/src/metadata-provider.ts | 26 +++++++ .../{authenticate.test.ts => decorator.ts} | 0 .../test/unit/metadata-provider.ts | 73 +++++++++++++++++++ packages/core/src/constants.ts | 10 +++ packages/core/src/index.ts | 1 + 8 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 packages/authentication/src/constants.ts create mode 100644 packages/authentication/src/metadata-provider.ts rename packages/authentication/test/unit/{authenticate.test.ts => decorator.ts} (100%) create mode 100644 packages/authentication/test/unit/metadata-provider.ts create mode 100644 packages/core/src/constants.ts diff --git a/packages/authentication/src/constants.ts b/packages/authentication/src/constants.ts new file mode 100644 index 000000000000..f4df33fa6652 --- /dev/null +++ b/packages/authentication/src/constants.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export class Keys { + static readonly AUTHENTICATION_KEY: string = 'authenticate'; +} diff --git a/packages/authentication/src/decorator.ts b/packages/authentication/src/decorator.ts index 80cb480338b2..6f5aa0b23f14 100644 --- a/packages/authentication/src/decorator.ts +++ b/packages/authentication/src/decorator.ts @@ -4,11 +4,12 @@ // License text available at https://opensource.org/licenses/MIT import { Reflector } from '@loopback/context'; +import { Keys } from './constants'; /* interface to define authentication metadata structure expected in * json objects */ -export interface IMetadata { +export interface Metadata { // name of the passport strategy to use for authentication strategy: string; // options to configure the passport strategy @@ -25,7 +26,7 @@ export class AuthenticationMetadata { this.strategyName = strategy; this.options = optionValues || {}; } - getMetadata(): IMetadata { + getMetadata(): Metadata { return {strategy: this.strategyName, options: this.options}; } } @@ -38,7 +39,7 @@ export class AuthenticationMetadata { export function authenticate(strategyName: string, options? : Object) { return function(controllerClass: Object, methodName: string) { const metadataObj: AuthenticationMetadata = new AuthenticationMetadata(strategyName, options || {}); - Reflector.defineMetadata('loopback:authenticate', metadataObj, controllerClass, methodName); + Reflector.defineMetadata(Keys.AUTHENTICATION_KEY, metadataObj, controllerClass, methodName); }; } @@ -47,7 +48,9 @@ export function authenticate(strategyName: string, options? : Object) { * @param controllerObj * @param methodName */ -export function getAuthenticateMetadata(controllerObj: Object, methodName: string): IMetadata { - const metadataObj: AuthenticationMetadata = Reflector.getMetadata('loopback:authenticate', controllerObj, methodName); - return metadataObj.getMetadata(); +export function getAuthenticateMetadata(controllerObj: Object, methodName: string): Metadata { + const metadataObj: AuthenticationMetadata = Reflector.getMetadata(Keys.AUTHENTICATION_KEY, controllerObj, methodName); + if (metadataObj) + return metadataObj.getMetadata(); + throw new Error('No metadata defined'); } diff --git a/packages/authentication/src/index.ts b/packages/authentication/src/index.ts index 18748b97db91..ed2e612a18f2 100644 --- a/packages/authentication/src/index.ts +++ b/packages/authentication/src/index.ts @@ -5,3 +5,5 @@ export * from './decorator'; export * from './strategy-adapter'; +export * from './metadata-provider'; +export * from './constants'; diff --git a/packages/authentication/src/metadata-provider.ts b/packages/authentication/src/metadata-provider.ts new file mode 100644 index 000000000000..615a40b474eb --- /dev/null +++ b/packages/authentication/src/metadata-provider.ts @@ -0,0 +1,26 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {RequestContextkeys} from '@loopback/core'; +import {Provider, inject} from '@loopback/context'; +import {Metadata, getAuthenticateMetadata} from './decorator'; + +/** + * @description Provides authentication metadata of a controller method + * @example `context.bind('authentication.meta').toProvider(AuthMetadataProvider)` + */ +export class AuthMetadataProvider implements Provider { + constructor( + @inject(RequestContextkeys.CONTROLLER_CLASS) private readonly controllerClass: Object, + @inject(RequestContextkeys.CONTROLLER_METHOD_NAME) private readonly methodName: string, + ) {} + + /** + * @returns IMetadata + */ + value(): Metadata { + return getAuthenticateMetadata(this.controllerClass, this.methodName); + } +} diff --git a/packages/authentication/test/unit/authenticate.test.ts b/packages/authentication/test/unit/decorator.ts similarity index 100% rename from packages/authentication/test/unit/authenticate.test.ts rename to packages/authentication/test/unit/decorator.ts diff --git a/packages/authentication/test/unit/metadata-provider.ts b/packages/authentication/test/unit/metadata-provider.ts new file mode 100644 index 000000000000..58fe11cbbd8d --- /dev/null +++ b/packages/authentication/test/unit/metadata-provider.ts @@ -0,0 +1,73 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import {Context, Provider} from '@loopback/context'; +import {ParsedRequest, RequestContextkeys} from '@loopback/core'; +import {AuthMetadataProvider, Metadata, authenticate} from '../..'; + +describe('AuthMetadataProvider', () => { + let provider: Provider; + + class TestController { + @authenticate('my-strategy', {option1: 'value1', option2: 'value2'}) + async whoAmI() {} + } + + class ControllerWithNoMetadata { + async whoAmI() {} + } + + const testController: TestController = new TestController(); + const controllerNoMetadata: ControllerWithNoMetadata = new ControllerWithNoMetadata(); + + beforeEach(givenAuthMetadataProvider); + + describe('value()', () => { + it('returns the authentication metadata of a controller method', async () => { + const authMetadata: Metadata = await Promise.resolve(provider.value()); + expect(authMetadata).to.be.eql({strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}}); + }); + + describe('context.get(provider_key)', () => { + it('returns the authentication metadata of a controller method', async () => { + const context: Context = new Context(); + context.bind(RequestContextkeys.CONTROLLER_CLASS).to(testController); + context.bind(RequestContextkeys.CONTROLLER_METHOD_NAME).to('whoAmI'); + context.bind(RequestContextkeys.CONTROLLER_METHOD_META).toProvider(AuthMetadataProvider); + const authMetadata = await context.get('controller.method.meta'); + expect(authMetadata).to.be.eql({strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}}); + }); + }); + + describe('context.get(provider_key)', () => { + it('returns the authentication metadata of a controller method', async () => { + const context: Context = new Context(); + context.bind(RequestContextkeys.CONTROLLER_CLASS).to(testController); + context.bind(RequestContextkeys.CONTROLLER_METHOD_NAME).to('whoAmI'); + context.bind(RequestContextkeys.CONTROLLER_METHOD_META).toProvider(AuthMetadataProvider); + const authMetadata = await context.get('controller.method.meta'); + expect(authMetadata).to.be.eql({strategy: 'my-strategy', options: {option1: 'value1', option2: 'value2'}}); + }); + + it('throws error if no authentication metadata is defined', async () => { + const context: Context = new Context(); + context.bind(RequestContextkeys.CONTROLLER_CLASS).to(controllerNoMetadata); + context.bind(RequestContextkeys.CONTROLLER_METHOD_NAME).to('whoAmI'); + context.bind(RequestContextkeys.CONTROLLER_METHOD_META).toProvider(AuthMetadataProvider); + try { + await context.get('controller.method.meta'); + expect.fail('0', '1', 'Metadata error should have been thrown', ''); + } catch (err) { + expect(err).to.have.property('message', 'No metadata defined'); + } + }); + }); + }); + + function givenAuthMetadataProvider() { + provider = new AuthMetadataProvider(testController, 'whoAmI'); + } +}); diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts new file mode 100644 index 000000000000..5ff57c494db8 --- /dev/null +++ b/packages/core/src/constants.ts @@ -0,0 +1,10 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/authentication +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export class RequestContextkeys { + static readonly CONTROLLER_CLASS: string = 'controller.class'; + static readonly CONTROLLER_METHOD_NAME: string = 'controller.method.name'; + static readonly CONTROLLER_METHOD_META: string = 'controller.method.meta'; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c4595c1cefa7..79d792514364 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -37,3 +37,4 @@ export {parseRequestUrl} from './router/routing-table'; export {RoutingTable, ResolvedRoute} from './router/routing-table'; export {HttpHandler} from './http-handler'; export {writeResultToResponse} from './writer'; +export {RequestContextkeys} from './constants';