@loopback/authorization
Advanced tools
Comparing version 0.4.5 to 0.4.6
@@ -6,2 +6,10 @@ # Change Log | ||
## [0.4.6](https://github.com/strongloop/loopback-next/compare/@loopback/authorization@0.4.5...@loopback/authorization@0.4.6) (2019-12-09) | ||
**Note:** Version bump only for package @loopback/authorization | ||
## [0.4.5](https://github.com/strongloop/loopback-next/compare/@loopback/authorization@0.4.4...@loopback/authorization@0.4.5) (2019-11-25) | ||
@@ -8,0 +16,0 @@ |
@@ -18,6 +18,9 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const context_1 = require("@loopback/context"); | ||
const security_1 = require("@loopback/security"); | ||
const debugFactory = require("debug"); | ||
const debug_1 = __importDefault(require("debug")); | ||
const authorize_1 = require("./decorators/authorize"); | ||
@@ -27,3 +30,3 @@ const keys_1 = require("./keys"); | ||
const util_1 = require("./util"); | ||
const debug = debugFactory('loopback:authorization:interceptor'); | ||
const debug = debug_1.default('loopback:authorization:interceptor'); | ||
let AuthorizationInterceptor = class AuthorizationInterceptor { | ||
@@ -39,2 +42,3 @@ constructor(authorizers, options = {}) { | ||
async intercept(invocationCtx, next) { | ||
var _a, _b; | ||
const description = debug.enabled ? invocationCtx.description : ''; | ||
@@ -45,4 +49,4 @@ let metadata = authorize_1.getAuthorizationMetadata(invocationCtx.target, invocationCtx.methodName); | ||
} | ||
metadata = metadata || this.options.defaultMetadata; | ||
if (!metadata || (metadata && metadata.skip)) { | ||
metadata = (metadata !== null && metadata !== void 0 ? metadata : this.options.defaultMetadata); | ||
if (!metadata || ((_a = metadata) === null || _a === void 0 ? void 0 : _a.skip)) { | ||
debug('Authorization is skipped for %s', description); | ||
@@ -66,3 +70,3 @@ const result = await next(); | ||
debug('Security context for %s', description, authorizationCtx); | ||
let authorizers = await loadAuthorizers(invocationCtx, metadata.voters || []); | ||
let authorizers = await loadAuthorizers(invocationCtx, (_b = metadata.voters, (_b !== null && _b !== void 0 ? _b : []))); | ||
let finalDecision = this.options.defaultDecision; | ||
@@ -69,0 +73,0 @@ authorizers = authorizers.concat(this.authorizers); |
@@ -42,3 +42,3 @@ "use strict"; | ||
const list = []; | ||
const set = new Set(src || []); | ||
const set = new Set((src !== null && src !== void 0 ? src : [])); | ||
if (target) { | ||
@@ -45,0 +45,0 @@ for (const i of target) { |
@@ -19,5 +19,6 @@ "use strict"; | ||
function createPrincipalFromUserProfile(user) { | ||
return Object.assign(Object.assign({}, user), { name: user.name || user[security_1.securityId], type: 'USER' }); | ||
var _a; | ||
return Object.assign(Object.assign({}, user), { name: (_a = user.name, (_a !== null && _a !== void 0 ? _a : user[security_1.securityId])), type: 'USER' }); | ||
} | ||
exports.createPrincipalFromUserProfile = createPrincipalFromUserProfile; | ||
//# sourceMappingURL=util.js.map |
{ | ||
"name": "@loopback/authorization", | ||
"version": "0.4.5", | ||
"version": "0.4.6", | ||
"description": "A LoopBack component for authorization support.", | ||
@@ -25,12 +25,12 @@ "engines": { | ||
"dependencies": { | ||
"@loopback/context": "^1.24.0", | ||
"@loopback/core": "^1.11.0", | ||
"@loopback/security": "^0.1.8", | ||
"@loopback/context": "^1.25.0", | ||
"@loopback/core": "^1.12.0", | ||
"@loopback/security": "^0.1.9", | ||
"debug": "^4.1.1" | ||
}, | ||
"devDependencies": { | ||
"@loopback/build": "^2.1.0", | ||
"@loopback/testlab": "^1.9.5", | ||
"@loopback/build": "^3.0.0", | ||
"@loopback/testlab": "^1.10.0", | ||
"@types/debug": "^4.1.4", | ||
"@types/node": "10.17.5", | ||
"@types/node": "10.17.6", | ||
"casbin": "^3.0.7" | ||
@@ -55,3 +55,3 @@ }, | ||
}, | ||
"gitHead": "c111543a6139c5a2999930c593aabbbdf10a838e" | ||
"gitHead": "89eb61bacaed75e6eb61ae6840cea266cb888659" | ||
} |
129
README.md
# @loopback/authorization | ||
A LoopBack 4 component for authorization support. | ||
A LoopBack 4 component for authorization support (Role based, Permission based, | ||
Vote based) | ||
## Overview | ||
To read on key building blocks read through | ||
[loopback authorization docs](https://loopback.io/doc/en/lb4/Loopback-component-authorization.html) | ||
Authorization decides if a **subject** can perform specific **action** on an | ||
**object**. | ||
![Authorization](authorization.png) | ||
### Role based | ||
### Permission based | ||
### Vote based | ||
### Key building blocks | ||
1. Decorate a method to describe: | ||
- Permission (maps the method to an action on the protected resource) | ||
- Type of the protected resource (such as `customer` or `order`) | ||
- What action does the method represent (such as `changeEmail`, `createOrder`, | ||
or `cancelOrder`) | ||
- ACL (provides role based rules) | ||
- allowedRoles | ||
- deniedRoles | ||
- Voters (supplies a list of function to vote on the decision) | ||
2. Intercept a method invocation | ||
- Build authorization context | ||
- Subject (who) - from authentication | ||
- Map principals to roles | ||
- Inspect the target method for metadata - Permission, ACL, voters | ||
- Run through voters/enforcers to make decisions | ||
## Decision matrix | ||
The final decision is controlled by voting results from authorizers and options | ||
for the authorization component. | ||
The following table illustrates the decision matrix with 3 voters and | ||
corresponding options. | ||
| Vote #1 | Vote # 2 | Vote #3 | Options | Final Decision | | ||
| ------- | -------- | ------- | ------------------------ | -------------- | | ||
| Deny | Deny | Deny | **any** | Deny | | ||
| Allow | Allow | Allow | **any** | Allow | | ||
| Abstain | Allow | Abstain | **any** | Allow | | ||
| Abstain | Deny | Abstain | **any** | Deny | | ||
| Deny | Allow | Abstain | {precedence: Deny} | Deny | | ||
| Deny | Allow | Abstain | {precedence: Allow} | Allow | | ||
| Allow | Abstain | Deny | {precedence: Deny} | Deny | | ||
| Allow | Abstain | Deny | {precedence: Allow} | Allow | | ||
| Abstain | Abstain | Abstain | {defaultDecision: Deny} | Deny | | ||
| Abstain | Abstain | Abstain | {defaultDecision: Allow} | Allow | | ||
The `options` is described as follows: | ||
```ts | ||
export interface AuthorizationOptions { | ||
/** | ||
* Default decision if all authorizers vote for ABSTAIN | ||
*/ | ||
defaultDecision?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW; | ||
/** | ||
* Controls if Allow/Deny vote takes precedence and override other votes | ||
*/ | ||
precedence?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW; | ||
} | ||
``` | ||
The authorization component can be configured with options: | ||
@@ -121,50 +51,9 @@ | ||
Please note that `@authorize` can also be applied at class level for all methods | ||
within the class. In the code below, `numOfViews` is protected with | ||
`BasicStrategy` (inherited from the class level) while `hello` does not require | ||
authorization (skipped by `@authorize.skip`). | ||
## Extract common layer | ||
```ts | ||
@authorize({allow: ['ADMIN']}) | ||
export class MyController { | ||
@get('/number-of-views') | ||
numOfViews(): number { | ||
return 100; | ||
} | ||
`@loopback/authentication` and `@loopback/authorization` share the client | ||
information from the request. Therefore we have created another module, | ||
`@loopback/security` with types/interfaces that describe the client, like | ||
`principles`, `userProfile`, etc. | ||
@authorize.skip() | ||
@get('/hello') | ||
hello(): string { | ||
return 'Hello'; | ||
} | ||
} | ||
``` | ||
## Extract common layer(TBD) | ||
`@loopback/authentication` and `@loopback/authorization` shares the client | ||
information from the request. Therefore we need another module with | ||
types/interfaces that describe the client, like `principles`, `userProfile`, | ||
etc... A draft PR is created for this module: see branch | ||
https://github.com/strongloop/loopback-next/tree/security/packages/security | ||
Since the common module is still in progress, as the first release of | ||
`@loopback/authorization`, we have two choices to inject a user in the | ||
interceptor: | ||
- `@loopback/authorization` requires `@loopback/authentication` as a dependency. | ||
The interceptor injects the current user using | ||
`AuthenticationBindings.CURRENT_USER`. Then we remove this dependency in the | ||
common layer PR, two auth modules will depend on `@loopback/security`. | ||
- This is what's been done in my refactor PR, `Principle` and `UserProfile` | ||
are still decoupled, I added a convertor function to turn a user profile | ||
into a principle. | ||
- The interceptor injects the user using another key not related to | ||
`@loopback/authentication`.(_Which means the code that injects the user stays | ||
as it is in https://github.com/strongloop/loopback-next/pull/1205_). Then we | ||
unify the user set and injection in the common layer PR: same as the 1st | ||
choice, two auth modules will depend on `@loopback/security`. | ||
## Related resources | ||
@@ -171,0 +60,0 @@ |
@@ -21,3 +21,3 @@ // Copyright IBM Corp. 2019. All Rights Reserved. | ||
import {SecurityBindings, UserProfile} from '@loopback/security'; | ||
import * as debugFactory from 'debug'; | ||
import debugFactory from 'debug'; | ||
import {getAuthorizationMetadata} from './decorators/authorize'; | ||
@@ -67,4 +67,4 @@ import {AuthorizationBindings, AuthorizationTags} from './keys'; | ||
} | ||
metadata = metadata || this.options.defaultMetadata; | ||
if (!metadata || (metadata && metadata.skip)) { | ||
metadata = metadata ?? this.options.defaultMetadata; | ||
if (!metadata || metadata?.skip) { | ||
debug('Authorization is skipped for %s', description); | ||
@@ -94,3 +94,3 @@ const result = await next(); | ||
invocationCtx, | ||
metadata.voters || [], | ||
metadata.voters ?? [], | ||
); | ||
@@ -97,0 +97,0 @@ |
@@ -81,3 +81,3 @@ // Copyright IBM Corp. 2019. All Rights Reserved. | ||
const list: T[] = []; | ||
const set = new Set<T>(src || []); | ||
const set = new Set<T>(src ?? []); | ||
if (target) { | ||
@@ -84,0 +84,0 @@ for (const i of target) { |
@@ -21,5 +21,5 @@ import {Principal, securityId, UserProfile} from '@loopback/security'; | ||
...user, | ||
name: user.name || user[securityId], | ||
name: user.name ?? user[securityId], | ||
type: 'USER', | ||
}; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1305
63798
77
Updated@loopback/context@^1.25.0
Updated@loopback/core@^1.12.0
Updated@loopback/security@^0.1.9