Skip to content

Commit 7fd0f93

Browse files
feat(provider): add ability to override metadata permissions (#96)
* feat(provider): add ability to override metadata permissions add ability to override metadata permissions with the permission object passed by the user. GH-95 * feat(provider): add ability to override user permissions add ability to provide user permissions GH-95 * docs(provider): add description to overrride the permissions in readme file add description to overrride the permissions in readme file GH-95
1 parent b57177e commit 7fd0f93

File tree

6 files changed

+120
-15
lines changed

6 files changed

+120
-15
lines changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,28 @@ export const enum PermissionKey {
267267
DeleteAudit = 'DeleteAudit',
268268
}
269269
```
270-
- Serving the static files:
270+
- To Override the permissions provided in enum `permissionKey` file:
271+
272+
If the userPermission object is provided,it overrides the default permissions in the authorizationMetadata object.
273+
274+
For providing a userPermission object with custom permissions for specific controller methods, you can bind the following in `application.ts` file in your application.
275+
276+
```ts
277+
278+
this.bind(AuthorizationBindings.PERMISSION).to({
279+
MessageController:{
280+
create:['CreateMessage','ViewMessage'],
281+
updateAll:['UpdateMessage','ViewMessage','ViewMessageNum]
282+
}
283+
AttachmentFileController:{
284+
create:['CreateAttachmentFile','ViewAttachmentFile'],
285+
updateAll:['UpdateAttachmentFile','ViewAttachmentFileNum']
286+
}
287+
})
288+
289+
```
290+
291+
# Serving the static files:
271292
272293
Authorization configuration binding sets up paths that can be accessed without any authorization checks, allowing static files to be served directly from the root URL of the application.The allowAlwaysPaths property is used to define these paths for the files in public directory i.e for a test.html file in public directory ,one can provide its path as follows:
273294
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {expect} from '@loopback/testlab';
2+
import {getAuthorizeMetadata} from '../../providers';
3+
import {AuthorizationMetadata, PermissionObject} from '../../types';
4+
import {authorize} from '../../decorators';
5+
import {get} from '@loopback/rest';
6+
7+
describe('getAuthorizeMetadata()', function () {
8+
it('should return the authorization metadata when userPermission is provided', () => {
9+
class TestController {
10+
@authorize({permissions: ['default1', 'default2']})
11+
@get('/')
12+
async testMethod() {
13+
// This is intentional.
14+
}
15+
}
16+
const methodName = 'testMethod';
17+
const mockUserPermission: PermissionObject = {
18+
TestController: {
19+
testMethod: ['permission1', 'permission2'],
20+
},
21+
};
22+
23+
const authorizationMetadata = getAuthorizeMetadata(
24+
TestController,
25+
methodName,
26+
mockUserPermission,
27+
);
28+
expect(authorizationMetadata?.permissions).which.eql(
29+
mockUserPermission.TestController[methodName],
30+
);
31+
});
32+
33+
it('should return permissions from metadata if userpermission is not provided', () => {
34+
class TestController {
35+
@authorize({permissions: ['default1', 'default2']})
36+
@get('/')
37+
async testMethod() {
38+
// This is intentional.
39+
}
40+
}
41+
const methodName = 'testMethod';
42+
const authorizationMetadata = getAuthorizeMetadata(
43+
TestController,
44+
methodName,
45+
);
46+
const expectedResult: AuthorizationMetadata = {
47+
permissions: ['default1', 'default2'],
48+
};
49+
expect(authorizationMetadata?.permissions).which.eql(
50+
expectedResult.permissions,
51+
);
52+
});
53+
});

src/component.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import {AuthorizationConfig} from './types';
99

1010
export class AuthorizationComponent implements Component {
1111
providers?: ProviderMap;
12-
bindings?: Binding[];
12+
bindings?: Binding[] = [];
1313

1414
constructor(
1515
@inject(AuthorizationBindings.CONFIG)
1616
private readonly config?: AuthorizationConfig,
17+
@inject(AuthorizationBindings.PERMISSION, {optional: true})
18+
private readonly permission?: unknown,
1719
) {
1820
this.providers = {
1921
[AuthorizationBindings.AUTHORIZE_ACTION.key]: AuthorizeActionProvider,
@@ -22,22 +24,26 @@ export class AuthorizationComponent implements Component {
2224
[AuthorizationBindings.METADATA.key]: AuthorizationMetadataProvider,
2325
[AuthorizationBindings.USER_PERMISSIONS.key]: UserPermissionsProvider,
2426
};
25-
27+
if (!this.permission) {
28+
this.bindings?.push(
29+
Binding.bind(AuthorizationBindings.PERMISSION).to(null),
30+
);
31+
}
2632
if (
2733
this.config?.allowAlwaysPaths &&
2834
this.config?.allowAlwaysPaths?.length > 0
2935
) {
30-
this.bindings = [
36+
this.bindings?.push(
3137
Binding.bind(AuthorizationBindings.PATHS_TO_ALLOW_ALWAYS).to(
3238
this.config.allowAlwaysPaths,
3339
),
34-
];
40+
);
3541
} else {
36-
this.bindings = [
42+
this.bindings?.push(
3743
Binding.bind(AuthorizationBindings.PATHS_TO_ALLOW_ALWAYS).to([
3844
'/explorer',
3945
]),
40-
];
46+
);
4147
}
4248
}
4349
}

src/keys.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
CasbinAuthorizeFn,
99
CasbinEnforcerConfigGetterFn,
1010
CasbinResourceModifierFn,
11+
PermissionObject,
1112
} from './types';
1213

1314
/**
@@ -17,6 +18,9 @@ export namespace AuthorizationBindings {
1718
export const AUTHORIZE_ACTION = BindingKey.create<AuthorizeFn>(
1819
'sf.userAuthorization.actions.authorize',
1920
);
21+
export const PERMISSION = BindingKey.create<PermissionObject | null>(
22+
`sf.userAuthorization.authorize.permissions`,
23+
);
2024

2125
export const CASBIN_AUTHORIZE_ACTION = BindingKey.create<CasbinAuthorizeFn>(
2226
'sf.userAuthorization.actions.casbin.authorize',

src/providers/authorization-metadata.provider.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
} from '@loopback/context';
77
import {CoreBindings} from '@loopback/core';
88

9-
import {AUTHORIZATION_METADATA_ACCESSOR} from '../keys';
10-
import {AuthorizationMetadata} from '../types';
9+
import {AUTHORIZATION_METADATA_ACCESSOR, AuthorizationBindings} from '../keys';
10+
import {AuthorizationMetadata, PermissionObject} from '../types';
1111

1212
export class AuthorizationMetadataProvider
1313
implements Provider<AuthorizationMetadata | undefined>
@@ -17,21 +17,37 @@ export class AuthorizationMetadataProvider
1717
private readonly controllerClass: Constructor<{}>,
1818
@inject(CoreBindings.CONTROLLER_METHOD_NAME, {optional: true})
1919
private readonly methodName: string,
20+
@inject(AuthorizationBindings.PERMISSION)
21+
private permissionObject: PermissionObject,
2022
) {}
2123

2224
value(): AuthorizationMetadata | undefined {
2325
if (!this.controllerClass || !this.methodName) return;
24-
return getAuthorizeMetadata(this.controllerClass, this.methodName);
26+
return getAuthorizeMetadata(
27+
this.controllerClass,
28+
this.methodName,
29+
this.permissionObject,
30+
);
2531
}
2632
}
2733

2834
export function getAuthorizeMetadata(
2935
controllerClass: Constructor<{}>,
3036
methodName: string,
37+
userPermission?: PermissionObject,
3138
): AuthorizationMetadata | undefined {
32-
return MetadataInspector.getMethodMetadata<AuthorizationMetadata>(
33-
AUTHORIZATION_METADATA_ACCESSOR,
34-
controllerClass.prototype,
35-
methodName,
36-
);
39+
const authorizationMetadata =
40+
MetadataInspector.getMethodMetadata<AuthorizationMetadata>(
41+
AUTHORIZATION_METADATA_ACCESSOR,
42+
controllerClass.prototype,
43+
methodName,
44+
) ?? {permissions: []};
45+
if (userPermission) {
46+
const methodPermissions =
47+
userPermission?.[controllerClass.name]?.[methodName];
48+
if (methodPermissions) {
49+
authorizationMetadata.permissions = methodPermissions;
50+
}
51+
}
52+
return authorizationMetadata;
3753
}

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export interface CasbinAuthorizeFn {
2424
request: Request,
2525
): Promise<boolean>;
2626
}
27+
export type PermissionObject = {
28+
[controller: string]: {
29+
[method: string]: string[];
30+
};
31+
};
2732
/**
2833
* Authorization metadata interface for the method decorator
2934
*/

0 commit comments

Comments
 (0)