Code cleanup
Make it easier to add method/app-level scopes in the future
This commit is contained in:
parent
5c04712efc
commit
fab857dd5f
|
@ -256,7 +256,6 @@ module.exports = function(ACL) {
|
||||||
model: req.model,
|
model: req.model,
|
||||||
property: req.property,
|
property: req.property,
|
||||||
accessType: req.accessType,
|
accessType: req.accessType,
|
||||||
accessScope: req.accessScope,
|
|
||||||
permission: permission || ACL.DEFAULT,
|
permission: permission || ACL.DEFAULT,
|
||||||
registry: this.registry});
|
registry: this.registry});
|
||||||
|
|
||||||
|
@ -444,7 +443,6 @@ module.exports = function(ACL) {
|
||||||
var modelDefaultPermission = model && model.settings.defaultPermission;
|
var modelDefaultPermission = model && model.settings.defaultPermission;
|
||||||
var property = context.property;
|
var property = context.property;
|
||||||
var accessType = context.accessType;
|
var accessType = context.accessType;
|
||||||
var accessScope = context.accessScope;
|
|
||||||
var modelName = context.modelName;
|
var modelName = context.modelName;
|
||||||
|
|
||||||
var methodNames = context.methodNames;
|
var methodNames = context.methodNames;
|
||||||
|
@ -460,7 +458,6 @@ module.exports = function(ACL) {
|
||||||
model: modelName,
|
model: modelName,
|
||||||
property,
|
property,
|
||||||
accessType,
|
accessType,
|
||||||
accessScope,
|
|
||||||
permission: ACL.DEFAULT,
|
permission: ACL.DEFAULT,
|
||||||
methodNames,
|
methodNames,
|
||||||
registry: this.registry});
|
registry: this.registry});
|
||||||
|
@ -468,10 +465,11 @@ module.exports = function(ACL) {
|
||||||
if (!context.isScopeAllowed()) {
|
if (!context.isScopeAllowed()) {
|
||||||
req.permission = ACL.DENY;
|
req.permission = ACL.DENY;
|
||||||
debug('--Denied by scope config--');
|
debug('--Denied by scope config--');
|
||||||
debug('Scopes allowed:', context.accessToken.scopes || '<default>');
|
debug('Scopes allowed:', context.accessToken.scopes || 'DEFAULT');
|
||||||
debug('Scope required:', accessScope || '<default>');
|
debug('Scope required:', context.getScopes() || 'DEFAULT');
|
||||||
context.debug();
|
context.debug();
|
||||||
return callback(null, req);
|
callback(null, req);
|
||||||
|
return callback.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
var effectiveACLs = [];
|
var effectiveACLs = [];
|
||||||
|
|
|
@ -8,6 +8,8 @@ var assert = require('assert');
|
||||||
var loopback = require('./loopback');
|
var loopback = require('./loopback');
|
||||||
var debug = require('debug')('loopback:security:access-context');
|
var debug = require('debug')('loopback:security:access-context');
|
||||||
|
|
||||||
|
const DEFAULT_SCOPES = ['DEFAULT'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access context represents the context for a request to access protected
|
* Access context represents the context for a request to access protected
|
||||||
* resources
|
* resources
|
||||||
|
@ -32,8 +34,6 @@ var debug = require('debug')('loopback:security:access-context');
|
||||||
* @property {String} property The model property/method/relation name
|
* @property {String} property The model property/method/relation name
|
||||||
* @property {String} method The model method to be invoked
|
* @property {String} method The model method to be invoked
|
||||||
* @property {String} accessType The access type: READ, REPLICATE, WRITE, or EXECUTE.
|
* @property {String} accessType The access type: READ, REPLICATE, WRITE, or EXECUTE.
|
||||||
* @property {String[]} accessScopes The access scopes that can authorize
|
|
||||||
* access to the invoked resource.
|
|
||||||
* @property {AccessToken} accessToken The access token resolved for the request
|
* @property {AccessToken} accessToken The access token resolved for the request
|
||||||
* @property {RemotingContext} remotingContext The request's remoting context
|
* @property {RemotingContext} remotingContext The request's remoting context
|
||||||
* @property {Registry} registry The application or global registry
|
* @property {Registry} registry The application or global registry
|
||||||
|
@ -72,7 +72,6 @@ function AccessContext(context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accessType = context.accessType || AccessContext.ALL;
|
this.accessType = context.accessType || AccessContext.ALL;
|
||||||
this.accessScopes = context.accessScopes;
|
|
||||||
|
|
||||||
assert(loopback.AccessToken,
|
assert(loopback.AccessToken,
|
||||||
'AccessToken model must be defined before AccessContext model');
|
'AccessToken model must be defined before AccessContext model');
|
||||||
|
@ -197,6 +196,25 @@ AccessContext.prototype.isAuthenticated = function() {
|
||||||
return !!(this.getUserId() || this.getAppId());
|
return !!(this.getUserId() || this.getAppId());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of scopes required by the current access context.
|
||||||
|
*/
|
||||||
|
AccessContext.prototype.getScopes = function() {
|
||||||
|
if (!this.sharedMethod)
|
||||||
|
return DEFAULT_SCOPES;
|
||||||
|
|
||||||
|
// For backwards compatibility, methods with no scopes defined
|
||||||
|
// are assigned a single "DEFAULT" scope
|
||||||
|
const methodLevel = this.sharedMethod.accessScopes || DEFAULT_SCOPES;
|
||||||
|
|
||||||
|
// TODO add model-level and app-level scopes
|
||||||
|
|
||||||
|
debug('--Context scopes of %s()--', this.sharedMethod.stringName);
|
||||||
|
debug(' method-level: %j', methodLevel);
|
||||||
|
|
||||||
|
return methodLevel;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the scope required by the remote method is allowed
|
* Check if the scope required by the remote method is allowed
|
||||||
* by the scopes granted to the requesting access token.
|
* by the scopes granted to the requesting access token.
|
||||||
|
@ -207,11 +225,9 @@ AccessContext.prototype.isScopeAllowed = function() {
|
||||||
|
|
||||||
// For backwards compatibility, tokens with no scopes are treated
|
// For backwards compatibility, tokens with no scopes are treated
|
||||||
// as if they have "DEFAULT" scope granted
|
// as if they have "DEFAULT" scope granted
|
||||||
const tokenScopes = this.accessToken.scopes || ['DEFAULT'];
|
const tokenScopes = this.accessToken.scopes || DEFAULT_SCOPES;
|
||||||
|
|
||||||
// For backwards compatibility, methods with no scopes defined
|
const resourceScopes = this.getScopes();
|
||||||
// are assigned a single "DEFAULT" scope
|
|
||||||
const resourceScopes = this.accessScopes || ['DEFAULT'];
|
|
||||||
|
|
||||||
// Scope is allowed when at least one of token's scopes
|
// Scope is allowed when at least one of token's scopes
|
||||||
// is found in method's (resource's) scopes.
|
// is found in method's (resource's) scopes.
|
||||||
|
@ -239,12 +255,12 @@ AccessContext.prototype.debug = function() {
|
||||||
debug('property %s', this.property);
|
debug('property %s', this.property);
|
||||||
debug('method %s', this.method);
|
debug('method %s', this.method);
|
||||||
debug('accessType %s', this.accessType);
|
debug('accessType %s', this.accessType);
|
||||||
debug('accessScopes %s', this.accessScopes || ['DEFAULT']);
|
debug('accessScopes %j', this.getScopes());
|
||||||
if (this.accessToken) {
|
if (this.accessToken) {
|
||||||
debug('accessToken:');
|
debug('accessToken:');
|
||||||
debug(' id %j', this.accessToken.id);
|
debug(' id %j', this.accessToken.id);
|
||||||
debug(' ttl %j', this.accessToken.ttl);
|
debug(' ttl %j', this.accessToken.ttl);
|
||||||
debug(' scopes', this.accessToken.scopes || ['DEFAULT']);
|
debug(' scopes %j', this.accessToken.scopes || DEFAULT_SCOPES);
|
||||||
}
|
}
|
||||||
debug('getUserId() %s', this.getUserId());
|
debug('getUserId() %s', this.getUserId());
|
||||||
debug('isAuthenticated() %s', this.isAuthenticated());
|
debug('isAuthenticated() %s', this.isAuthenticated());
|
||||||
|
@ -299,7 +315,6 @@ Principal.prototype.equals = function(p) {
|
||||||
* or an AccessRequest instance/object.
|
* or an AccessRequest instance/object.
|
||||||
* @param {String} property The property/method/relation name
|
* @param {String} property The property/method/relation name
|
||||||
* @param {String} accessType The access type
|
* @param {String} accessType The access type
|
||||||
* @property {String} accessScope The access scope required by the invoked method.
|
|
||||||
* @param {String} permission The requested permission
|
* @param {String} permission The requested permission
|
||||||
* @param {String[]} methodNames The names of involved methods
|
* @param {String[]} methodNames The names of involved methods
|
||||||
* @param {Registry} registry The application or global registry
|
* @param {Registry} registry The application or global registry
|
||||||
|
@ -315,7 +330,6 @@ function AccessRequest(model, property, accessType, permission, methodNames, reg
|
||||||
this.model = obj.model || AccessContext.ALL;
|
this.model = obj.model || AccessContext.ALL;
|
||||||
this.property = obj.property || AccessContext.ALL;
|
this.property = obj.property || AccessContext.ALL;
|
||||||
this.accessType = obj.accessType || AccessContext.ALL;
|
this.accessType = obj.accessType || AccessContext.ALL;
|
||||||
this.accessScope = obj.accessScope;
|
|
||||||
this.permission = obj.permission || AccessContext.DEFAULT;
|
this.permission = obj.permission || AccessContext.DEFAULT;
|
||||||
this.methodNames = obj.methodNames || [];
|
this.methodNames = obj.methodNames || [];
|
||||||
this.registry = obj.registry;
|
this.registry = obj.registry;
|
||||||
|
@ -400,7 +414,6 @@ AccessRequest.prototype.debug = function() {
|
||||||
debug(' model %s', this.model);
|
debug(' model %s', this.model);
|
||||||
debug(' property %s', this.property);
|
debug(' property %s', this.property);
|
||||||
debug(' accessType %s', this.accessType);
|
debug(' accessType %s', this.accessType);
|
||||||
debug(' accessScopes %s', this.accessScopes);
|
|
||||||
debug(' permission %s', this.permission);
|
debug(' permission %s', this.permission);
|
||||||
debug(' isWildcard() %s', this.isWildcard());
|
debug(' isWildcard() %s', this.isWildcard());
|
||||||
debug(' isAllowed() %s', this.isAllowed());
|
debug(' isAllowed() %s', this.isAllowed());
|
||||||
|
|
|
@ -347,7 +347,6 @@ module.exports = function(registry) {
|
||||||
sharedMethod: sharedMethod,
|
sharedMethod: sharedMethod,
|
||||||
modelId: modelId,
|
modelId: modelId,
|
||||||
accessType: this._getAccessTypeForMethod(sharedMethod),
|
accessType: this._getAccessTypeForMethod(sharedMethod),
|
||||||
accessScopes: sharedMethod.accessScopes,
|
|
||||||
remotingContext: ctx,
|
remotingContext: ctx,
|
||||||
}, function(err, accessRequest) {
|
}, function(err, accessRequest) {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
|
|
|
@ -165,7 +165,6 @@ describe('security ACLs', function() {
|
||||||
assert.deepEqual(perm, {model: 'account',
|
assert.deepEqual(perm, {model: 'account',
|
||||||
property: 'find',
|
property: 'find',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accessScope: undefined,
|
|
||||||
permission: 'ALLOW',
|
permission: 'ALLOW',
|
||||||
methodNames: []});
|
methodNames: []});
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ describe('Authorization scopes', () => {
|
||||||
|
|
||||||
it('allows invocation when at least one method scope is matched', () => {
|
it('allows invocation when at least one method scope is matched', () => {
|
||||||
givenRemoteMethodWithCustomScope(['read', 'write']);
|
givenRemoteMethodWithCustomScope(['read', 'write']);
|
||||||
givenScopedToken(['read', 'execute']).then(() => {
|
return givenScopedToken(['read', 'execute']).then(() => {
|
||||||
return request.get('/users/scoped')
|
return request.get('/users/scoped')
|
||||||
.set('Authorization', scopedToken.id)
|
.set('Authorization', scopedToken.id)
|
||||||
.expect(204);
|
.expect(204);
|
||||||
|
|
Loading…
Reference in New Issue