Skip to content

Commit c166348

Browse files
author
vineet-suri
committed
RFIT-188 basic authorisation use case implemented using casbin
1 parent 5d25c41 commit c166348

12 files changed

+999
-43
lines changed

package-lock.json

Lines changed: 739 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@
5151
"@loopback/rest": "^1.16.3"
5252
},
5353
"dependencies": {
54+
"casbin": "^5.1.3",
55+
"casbin-pg-adapter": "^1.4.0",
5456
"lodash": "^4.17.15"
5557
},
5658
"devDependencies": {
5759
"@loopback/boot": "^1.4.4",
5860
"@loopback/build": "^2.0.3",
59-
"@loopback/context": "^1.20.2",
60-
"@loopback/core": "^1.8.5",
61+
"@loopback/context": "^3.8.2",
62+
"@loopback/core": "^2.7.0",
6163
"@loopback/rest": "^1.16.3",
6264
"@loopback/testlab": "^1.6.3",
6365
"@loopback/tslint-config": "^2.1.0",

src/component.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import {Component, ProviderMap, Binding, inject} from '@loopback/core';
1+
import { Component, ProviderMap, Binding, inject } from '@loopback/core';
22

3-
import {AuthorizationBindings} from './keys';
4-
import {AuthorizeActionProvider} from './providers/authorization-action.provider';
5-
import {AuthorizationMetadataProvider} from './providers/authorization-metadata.provider';
6-
import {UserPermissionsProvider} from './providers/user-permissions.provider';
7-
import {AuthorizationConfig} from './types';
3+
import { AuthorizationBindings } from './keys';
4+
import { AuthorizeActionProvider } from './providers/authorization-action.provider';
5+
import { AuthorizationMetadataProvider } from './providers/authorization-metadata.provider';
6+
import { UserPermissionsProvider } from './providers/user-permissions.provider';
7+
import { AuthorizationConfig } from './types';
8+
import { CasbinAuthorizationProvider } from './providers/casbin-authorization-action.provider';
9+
import { CasbinAuthorizationMetadataProvider } from './providers/casbin-authorisation-metadata.provider';
10+
import { CasbinEnforcerProvider } from './providers';
811

912
export class AuthorizationComponent implements Component {
1013
providers?: ProviderMap;
@@ -16,8 +19,11 @@ export class AuthorizationComponent implements Component {
1619
) {
1720
this.providers = {
1821
[AuthorizationBindings.AUTHORIZE_ACTION.key]: AuthorizeActionProvider,
22+
[AuthorizationBindings.CASBIN_AUTHORIZE_ACTION.key]: CasbinAuthorizationProvider,
1923
[AuthorizationBindings.METADATA.key]: AuthorizationMetadataProvider,
24+
[AuthorizationBindings.CASBIN_METADATA.key]: CasbinAuthorizationMetadataProvider,
2025
[AuthorizationBindings.USER_PERMISSIONS.key]: UserPermissionsProvider,
26+
[AuthorizationBindings.CASBIN_ENFORCER.key]: CasbinEnforcerProvider,
2127
};
2228

2329
if (
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { MethodDecoratorFactory } from '@loopback/core';
2+
import { CasbinAuthorizationMetadata } from '../types';
3+
import { CASBIN_AUTHORIZATION_METADATA_ACCESSOR } from '../keys';
4+
5+
export function casbinAuthorize(metadata: CasbinAuthorizationMetadata) {
6+
return MethodDecoratorFactory.createDecorator<CasbinAuthorizationMetadata>(
7+
CASBIN_AUTHORIZATION_METADATA_ACCESSOR,
8+
{
9+
allowedRoles: metadata.allowedRoles || [],
10+
deniedRoles: metadata.deniedRoles || [],
11+
resource: metadata.resource || '',
12+
scopes: metadata.scopes || [],
13+
skip: metadata.skip || false
14+
},
15+
);
16+
}

src/decorators/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './authorize.decorator';
2+
export * from './casbin-authorise.decorator';

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ export * from './component';
22
export * from './types';
33
export * from './keys';
44
export * from './error-keys';
5-
export * from './decorators/authorize.decorator';
5+
export * from './decorators';
66
export * from './providers';

src/keys.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import {BindingKey} from '@loopback/context';
2-
import {MetadataAccessor} from '@loopback/metadata';
1+
import { BindingKey } from '@loopback/context';
2+
import { MetadataAccessor } from '@loopback/metadata';
33
import {
44
AuthorizeFn,
55
AuthorizationMetadata,
66
UserPermissionsFn,
77
AuthorizationConfig,
8+
CasbinAuthorizationMetadata,
9+
CasbinEnforcerFn,
10+
CasbinAuthorizeFn,
811
} from './types';
912

1013
/**
@@ -15,22 +18,42 @@ export namespace AuthorizationBindings {
1518
'sf.userAuthorization.actions.authorize',
1619
);
1720

21+
export const CASBIN_AUTHORIZE_ACTION = BindingKey.create<CasbinAuthorizeFn>(
22+
'sf.userCasbinAuthorization.actions.authorize',
23+
);
24+
1825
export const METADATA = BindingKey.create<AuthorizationMetadata | undefined>(
1926
'sf.userAuthorization.operationMetadata',
2027
);
2128

29+
export const CASBIN_METADATA = BindingKey.create<CasbinAuthorizationMetadata | undefined>(
30+
'sf.userCasbinAuthorization.operationMetadata',
31+
);
32+
2233
export const USER_PERMISSIONS = BindingKey.create<UserPermissionsFn<string>>(
2334
'sf.userAuthorization.actions.userPermissions',
2435
);
2536

37+
export const CASBIN_ENFORCER = BindingKey.create<CasbinEnforcerFn<string>>(
38+
'sf.userCasbinAuthorization.casbinenforcer',
39+
);
40+
2641
export const CONFIG = BindingKey.create<AuthorizationConfig>(
2742
'sf.userAuthorization.config',
2843
);
2944

3045
export const PATHS_TO_ALLOW_ALWAYS = 'sf.userAuthorization.allowAlways';
46+
47+
48+
export const RESOURCE_ID = BindingKey.create<string>('sf.resourceId');
3149
}
3250

3351
export const AUTHORIZATION_METADATA_ACCESSOR = MetadataAccessor.create<
3452
AuthorizationMetadata,
3553
MethodDecorator
3654
>('sf.userAuthorization.accessor.operationMetadata');
55+
56+
export const CASBIN_AUTHORIZATION_METADATA_ACCESSOR = MetadataAccessor.create<
57+
CasbinAuthorizationMetadata,
58+
MethodDecorator
59+
>('sf.userCasbinAuthorization.accessor.operationMetadata');
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
Constructor,
3+
inject,
4+
MetadataInspector,
5+
Provider,
6+
} from '@loopback/context';
7+
import { CoreBindings } from '@loopback/core';
8+
9+
import { CASBIN_AUTHORIZATION_METADATA_ACCESSOR } from '../keys';
10+
import { CasbinAuthorizationMetadata } from '../types';
11+
12+
export class CasbinAuthorizationMetadataProvider
13+
implements Provider<CasbinAuthorizationMetadata | undefined> {
14+
constructor(
15+
@inject(CoreBindings.CONTROLLER_CLASS, { optional: true })
16+
private readonly controllerClass: Constructor<{}>,
17+
@inject(CoreBindings.CONTROLLER_METHOD_NAME, { optional: true })
18+
private readonly methodName: string,
19+
) { }
20+
21+
value(): CasbinAuthorizationMetadata | undefined {
22+
if (!this.controllerClass || !this.methodName) return;
23+
return getCasbinAuthorizeMetadata(this.controllerClass, this.methodName);
24+
}
25+
}
26+
27+
export function getCasbinAuthorizeMetadata(
28+
controllerClass: Constructor<{}>,
29+
methodName: string,
30+
): CasbinAuthorizationMetadata | undefined {
31+
return MetadataInspector.getMethodMetadata<CasbinAuthorizationMetadata>(
32+
CASBIN_AUTHORIZATION_METADATA_ACCESSOR,
33+
controllerClass.prototype,
34+
methodName,
35+
);
36+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { inject, Provider, Getter, InvocationContext } from '@loopback/core';
2+
import * as casbin from 'casbin';
3+
import { CasbinAuthorizeFn, CasbinAuthorizationMetadata } from '../types';
4+
import { AuthorizationBindings } from '../keys';
5+
const DEFAULT_SCOPE = 'execute';
6+
7+
export class CasbinAuthorizationProvider implements Provider<CasbinAuthorizeFn> {
8+
constructor(
9+
@inject.getter(AuthorizationBindings.CASBIN_METADATA)
10+
private readonly getCasbinMetadata: Getter<CasbinAuthorizationMetadata>,
11+
private readonly invocationCtx: InvocationContext
12+
) { }
13+
14+
value(): CasbinAuthorizeFn {
15+
return (response, req) => this.action(response, req);
16+
}
17+
18+
async action(enforcer: casbin.Enforcer, userId: string): Promise<boolean> {
19+
20+
// await enforcer.loadPolicy();
21+
22+
const metadata: CasbinAuthorizationMetadata = await this.getCasbinMetadata();
23+
24+
console.log(this.invocationCtx);
25+
26+
const subject = this.getUserName(userId);
27+
28+
const object = metadata.resource;
29+
30+
const action = metadata.scopes && metadata.scopes.length > 0 ? metadata.scopes[0] : DEFAULT_SCOPE;
31+
32+
const request = {
33+
subject,
34+
object,
35+
action,
36+
};
37+
38+
const allowedRoles = metadata.allowedRoles;
39+
40+
if (!allowedRoles) return true;
41+
if (allowedRoles.length < 1) return false;
42+
43+
const allowedByRole = await enforcer.enforce(
44+
request.subject,
45+
request.object,
46+
request.action,
47+
);
48+
49+
return allowedByRole;
50+
}
51+
52+
// Generate the user name according to the naming convention
53+
// in casbin policy
54+
// A user's name would be `u${id}`
55+
getUserName(id: string): string {
56+
return `u${id}`;
57+
}
58+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Provider } from '@loopback/context';
2+
3+
import { CasbinEnforcerFn } from '../types';
4+
import { HttpErrors } from '@loopback/rest';
5+
import PostgresAdapter from 'casbin-pg-adapter';
6+
7+
export class CasbinEnforcerProvider
8+
implements Provider<CasbinEnforcerFn<string>> {
9+
constructor() { }
10+
11+
value(): CasbinEnforcerFn<string> {
12+
return async (model: string, policy: string | PostgresAdapter) => {
13+
throw new HttpErrors.NotImplemented(
14+
`CasinEnforcerFn Provider is not implemented`,
15+
);
16+
};
17+
}
18+
}

0 commit comments

Comments
 (0)