models: move ACL LDL def into a json file
This commit is contained in:
parent
ef890d5f26
commit
7c01d59d80
|
@ -5,9 +5,7 @@
|
|||
var loopback = require('../../lib/loopback')
|
||||
, assert = require('assert')
|
||||
, uid = require('uid2')
|
||||
, DEFAULT_TOKEN_LEN = 64
|
||||
, Role = require('./role').Role
|
||||
, ACL = require('./acl').ACL;
|
||||
, DEFAULT_TOKEN_LEN = 64;
|
||||
|
||||
/**
|
||||
* Token based authentication and access control.
|
||||
|
|
|
@ -45,6 +45,8 @@ var role = require('./role');
|
|||
var Role = role.Role;
|
||||
|
||||
/**
|
||||
* A Model for access control meta data.
|
||||
*
|
||||
* System grants permissions to principals (users/applications, can be grouped
|
||||
* into roles).
|
||||
*
|
||||
|
@ -54,18 +56,6 @@ var Role = role.Role;
|
|||
* For a given principal, such as client application and/or user, is it allowed
|
||||
* to access (read/write/execute)
|
||||
* the protected resource?
|
||||
*/
|
||||
var ACLSchema = {
|
||||
model: String, // The name of the model
|
||||
property: String, // The name of the property, method, scope, or relation
|
||||
accessType: String,
|
||||
permission: String,
|
||||
principalType: String,
|
||||
principalId: String
|
||||
};
|
||||
|
||||
/**
|
||||
* A Model for access control meta data.
|
||||
*
|
||||
* @header ACL
|
||||
* @property {String} model Name of the model.
|
||||
|
@ -78,394 +68,395 @@ var ACLSchema = {
|
|||
* - DENY: Explicitly denies access to the resource.
|
||||
* @property {String} principalType Type of the principal; one of: Application, Use, Role.
|
||||
* @property {String} principalId ID of the principal - such as appId, userId or roleId
|
||||
* @class
|
||||
* @inherits Model
|
||||
*
|
||||
* @class ACL
|
||||
* @inherits PersistedModel
|
||||
*/
|
||||
|
||||
var ACL = loopback.PersistedModel.extend('ACL', ACLSchema);
|
||||
module.exports = function(ACL) {
|
||||
|
||||
ACL.ALL = AccessContext.ALL;
|
||||
ACL.ALL = AccessContext.ALL;
|
||||
|
||||
ACL.DEFAULT = AccessContext.DEFAULT; // Not specified
|
||||
ACL.ALLOW = AccessContext.ALLOW; // Allow
|
||||
ACL.ALARM = AccessContext.ALARM; // Warn - send an alarm
|
||||
ACL.AUDIT = AccessContext.AUDIT; // Audit - record the access
|
||||
ACL.DENY = AccessContext.DENY; // Deny
|
||||
ACL.DEFAULT = AccessContext.DEFAULT; // Not specified
|
||||
ACL.ALLOW = AccessContext.ALLOW; // Allow
|
||||
ACL.ALARM = AccessContext.ALARM; // Warn - send an alarm
|
||||
ACL.AUDIT = AccessContext.AUDIT; // Audit - record the access
|
||||
ACL.DENY = AccessContext.DENY; // Deny
|
||||
|
||||
ACL.READ = AccessContext.READ; // Read operation
|
||||
ACL.WRITE = AccessContext.WRITE; // Write operation
|
||||
ACL.EXECUTE = AccessContext.EXECUTE; // Execute operation
|
||||
ACL.READ = AccessContext.READ; // Read operation
|
||||
ACL.WRITE = AccessContext.WRITE; // Write operation
|
||||
ACL.EXECUTE = AccessContext.EXECUTE; // Execute operation
|
||||
|
||||
ACL.USER = Principal.USER;
|
||||
ACL.APP = ACL.APPLICATION = Principal.APPLICATION;
|
||||
ACL.ROLE = Principal.ROLE;
|
||||
ACL.SCOPE = Principal.SCOPE;
|
||||
ACL.USER = Principal.USER;
|
||||
ACL.APP = ACL.APPLICATION = Principal.APPLICATION;
|
||||
ACL.ROLE = Principal.ROLE;
|
||||
ACL.SCOPE = Principal.SCOPE;
|
||||
|
||||
/**
|
||||
* Calculate the matching score for the given rule and request
|
||||
* @param {ACL} rule The ACL entry
|
||||
* @param {AccessRequest} req The request
|
||||
* @returns {Number}
|
||||
*/
|
||||
ACL.getMatchingScore = function getMatchingScore(rule, req) {
|
||||
var props = ['model', 'property', 'accessType'];
|
||||
var score = 0;
|
||||
/**
|
||||
* Calculate the matching score for the given rule and request
|
||||
* @param {ACL} rule The ACL entry
|
||||
* @param {AccessRequest} req The request
|
||||
* @returns {Number}
|
||||
*/
|
||||
ACL.getMatchingScore = function getMatchingScore(rule, req) {
|
||||
var props = ['model', 'property', 'accessType'];
|
||||
var score = 0;
|
||||
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
// Shift the score by 4 for each of the properties as the weight
|
||||
score = score * 4;
|
||||
var val1 = rule[props[i]] || ACL.ALL;
|
||||
var val2 = req[props[i]] || ACL.ALL;
|
||||
var isMatchingMethodName = props[i] === 'property' && req.methodNames.indexOf(val1) !== -1;
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
// Shift the score by 4 for each of the properties as the weight
|
||||
score = score * 4;
|
||||
var val1 = rule[props[i]] || ACL.ALL;
|
||||
var val2 = req[props[i]] || ACL.ALL;
|
||||
var isMatchingMethodName = props[i] === 'property' && req.methodNames.indexOf(val1) !== -1;
|
||||
|
||||
if (val1 === val2 || isMatchingMethodName) {
|
||||
// Exact match
|
||||
score += 3;
|
||||
} else if (val1 === ACL.ALL) {
|
||||
// Wildcard match
|
||||
score += 2;
|
||||
} else if (val2 === ACL.ALL) {
|
||||
// Doesn't match at all
|
||||
score += 1;
|
||||
} else {
|
||||
return -1;
|
||||
if (val1 === val2 || isMatchingMethodName) {
|
||||
// Exact match
|
||||
score += 3;
|
||||
} else if (val1 === ACL.ALL) {
|
||||
// Wildcard match
|
||||
score += 2;
|
||||
} else if (val2 === ACL.ALL) {
|
||||
// Doesn't match at all
|
||||
score += 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Weigh against the principal type into 4 levels
|
||||
// - user level (explicitly allow/deny a given user)
|
||||
// - app level (explicitly allow/deny a given app)
|
||||
// - role level (role based authorization)
|
||||
// - other
|
||||
// user > app > role > ...
|
||||
score = score * 4;
|
||||
switch(rule.principalType) {
|
||||
case ACL.USER:
|
||||
score += 4;
|
||||
break;
|
||||
case ACL.APP:
|
||||
score += 3;
|
||||
break;
|
||||
case ACL.ROLE:
|
||||
score += 2;
|
||||
break;
|
||||
default:
|
||||
score +=1;
|
||||
}
|
||||
|
||||
// Weigh against the roles
|
||||
// everyone < authenticated/unauthenticated < related < owner < ...
|
||||
score = score * 8;
|
||||
if(rule.principalType === ACL.ROLE) {
|
||||
switch(rule.principalId) {
|
||||
case Role.OWNER:
|
||||
// Weigh against the principal type into 4 levels
|
||||
// - user level (explicitly allow/deny a given user)
|
||||
// - app level (explicitly allow/deny a given app)
|
||||
// - role level (role based authorization)
|
||||
// - other
|
||||
// user > app > role > ...
|
||||
score = score * 4;
|
||||
switch (rule.principalType) {
|
||||
case ACL.USER:
|
||||
score += 4;
|
||||
break;
|
||||
case Role.RELATED:
|
||||
case ACL.APP:
|
||||
score += 3;
|
||||
break;
|
||||
case Role.AUTHENTICATED:
|
||||
case Role.UNAUTHENTICATED:
|
||||
case ACL.ROLE:
|
||||
score += 2;
|
||||
break;
|
||||
case Role.EVERYONE:
|
||||
score += 1;
|
||||
break;
|
||||
default:
|
||||
score += 5;
|
||||
score += 1;
|
||||
}
|
||||
}
|
||||
score = score * 4;
|
||||
score += AccessContext.permissionOrder[rule.permission || ACL.ALLOW] - 1;
|
||||
return score;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get matching score for the given `AccessRequest`.
|
||||
* @param {AccessRequest} req The request
|
||||
* @returns {Number} score
|
||||
*/
|
||||
|
||||
ACL.prototype.score = function(req) {
|
||||
return this.constructor.getMatchingScore(this, req);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Resolve permission from the ACLs
|
||||
* @param {Object[]) acls The list of ACLs
|
||||
* @param {Object} req The request
|
||||
* @returns {AccessRequest} result The effective ACL
|
||||
*/
|
||||
ACL.resolvePermission = function resolvePermission(acls, req) {
|
||||
if(!(req instanceof AccessRequest)) {
|
||||
req = new AccessRequest(req);
|
||||
}
|
||||
// Sort by the matching score in descending order
|
||||
acls = acls.sort(function (rule1, rule2) {
|
||||
return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req);
|
||||
});
|
||||
var permission = ACL.DEFAULT;
|
||||
var score = 0;
|
||||
|
||||
for (var i = 0; i < acls.length; i++) {
|
||||
score = ACL.getMatchingScore(acls[i], req);
|
||||
if (score < 0) {
|
||||
// the highest scored ACL did not match
|
||||
break;
|
||||
// Weigh against the roles
|
||||
// everyone < authenticated/unauthenticated < related < owner < ...
|
||||
score = score * 8;
|
||||
if (rule.principalType === ACL.ROLE) {
|
||||
switch (rule.principalId) {
|
||||
case Role.OWNER:
|
||||
score += 4;
|
||||
break;
|
||||
case Role.RELATED:
|
||||
score += 3;
|
||||
break;
|
||||
case Role.AUTHENTICATED:
|
||||
case Role.UNAUTHENTICATED:
|
||||
score += 2;
|
||||
break;
|
||||
case Role.EVERYONE:
|
||||
score += 1;
|
||||
break;
|
||||
default:
|
||||
score += 5;
|
||||
}
|
||||
}
|
||||
if (!req.isWildcard()) {
|
||||
// We should stop from the first match for non-wildcard
|
||||
permission = acls[i].permission;
|
||||
break;
|
||||
} else {
|
||||
if(req.exactlyMatches(acls[i])) {
|
||||
permission = acls[i].permission;
|
||||
score = score * 4;
|
||||
score += AccessContext.permissionOrder[rule.permission || ACL.ALLOW] - 1;
|
||||
return score;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get matching score for the given `AccessRequest`.
|
||||
* @param {AccessRequest} req The request
|
||||
* @returns {Number} score
|
||||
*/
|
||||
|
||||
ACL.prototype.score = function(req) {
|
||||
return this.constructor.getMatchingScore(this, req);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Resolve permission from the ACLs
|
||||
* @param {Object[]) acls The list of ACLs
|
||||
* @param {Object} req The request
|
||||
* @returns {AccessRequest} result The effective ACL
|
||||
*/
|
||||
ACL.resolvePermission = function resolvePermission(acls, req) {
|
||||
if (!(req instanceof AccessRequest)) {
|
||||
req = new AccessRequest(req);
|
||||
}
|
||||
// Sort by the matching score in descending order
|
||||
acls = acls.sort(function(rule1, rule2) {
|
||||
return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req);
|
||||
});
|
||||
var permission = ACL.DEFAULT;
|
||||
var score = 0;
|
||||
|
||||
for (var i = 0; i < acls.length; i++) {
|
||||
score = ACL.getMatchingScore(acls[i], req);
|
||||
if (score < 0) {
|
||||
// the highest scored ACL did not match
|
||||
break;
|
||||
}
|
||||
// For wildcard match, find the strongest permission
|
||||
if(AccessContext.permissionOrder[acls[i].permission]
|
||||
> AccessContext.permissionOrder[permission]) {
|
||||
if (!req.isWildcard()) {
|
||||
// We should stop from the first match for non-wildcard
|
||||
permission = acls[i].permission;
|
||||
break;
|
||||
} else {
|
||||
if (req.exactlyMatches(acls[i])) {
|
||||
permission = acls[i].permission;
|
||||
break;
|
||||
}
|
||||
// For wildcard match, find the strongest permission
|
||||
if (AccessContext.permissionOrder[acls[i].permission]
|
||||
> AccessContext.permissionOrder[permission]) {
|
||||
permission = acls[i].permission;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(debug.enabled) {
|
||||
debug('The following ACLs were searched: ');
|
||||
acls.forEach(function(acl) {
|
||||
acl.debug();
|
||||
debug('with score:', acl.score(req));
|
||||
});
|
||||
}
|
||||
if (debug.enabled) {
|
||||
debug('The following ACLs were searched: ');
|
||||
acls.forEach(function(acl) {
|
||||
acl.debug();
|
||||
debug('with score:', acl.score(req));
|
||||
});
|
||||
}
|
||||
|
||||
var res = new AccessRequest(req.model, req.property, req.accessType,
|
||||
permission || ACL.DEFAULT);
|
||||
return res;
|
||||
};
|
||||
var res = new AccessRequest(req.model, req.property, req.accessType,
|
||||
permission || ACL.DEFAULT);
|
||||
return res;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the static ACLs from the model definition
|
||||
* @param {String} model The model name
|
||||
* @param {String} property The property/method/relation name
|
||||
*
|
||||
* @return {Object[]} An array of ACLs
|
||||
*/
|
||||
ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||
var modelClass = loopback.findModel(model);
|
||||
var staticACLs = [];
|
||||
if (modelClass && modelClass.settings.acls) {
|
||||
modelClass.settings.acls.forEach(function (acl) {
|
||||
if (!acl.property || acl.property === ACL.ALL
|
||||
|| property === acl.property) {
|
||||
/*!
|
||||
* Get the static ACLs from the model definition
|
||||
* @param {String} model The model name
|
||||
* @param {String} property The property/method/relation name
|
||||
*
|
||||
* @return {Object[]} An array of ACLs
|
||||
*/
|
||||
ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||
var modelClass = loopback.findModel(model);
|
||||
var staticACLs = [];
|
||||
if (modelClass && modelClass.settings.acls) {
|
||||
modelClass.settings.acls.forEach(function(acl) {
|
||||
if (!acl.property || acl.property === ACL.ALL
|
||||
|| property === acl.property) {
|
||||
staticACLs.push(new ACL({
|
||||
model: model,
|
||||
property: acl.property || ACL.ALL,
|
||||
principalType: acl.principalType,
|
||||
principalId: acl.principalId, // TODO: Should it be a name?
|
||||
accessType: acl.accessType || ACL.ALL,
|
||||
permission: acl.permission
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
var prop = modelClass &&
|
||||
(modelClass.definition.properties[property] // regular property
|
||||
|| (modelClass._scopeMeta && modelClass._scopeMeta[property]) // relation/scope
|
||||
|| modelClass[property] // static method
|
||||
|| modelClass.prototype[property]); // prototype method
|
||||
if (prop && prop.acls) {
|
||||
prop.acls.forEach(function(acl) {
|
||||
staticACLs.push(new ACL({
|
||||
model: model,
|
||||
property: acl.property || ACL.ALL,
|
||||
model: modelClass.modelName,
|
||||
property: property,
|
||||
principalType: acl.principalType,
|
||||
principalId: acl.principalId, // TODO: Should it be a name?
|
||||
accessType: acl.accessType || ACL.ALL,
|
||||
principalId: acl.principalId,
|
||||
accessType: acl.accessType,
|
||||
permission: acl.permission
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
var prop = modelClass &&
|
||||
(modelClass.definition.properties[property] // regular property
|
||||
|| (modelClass._scopeMeta && modelClass._scopeMeta[property]) // relation/scope
|
||||
|| modelClass[property] // static method
|
||||
|| modelClass.prototype[property]); // prototype method
|
||||
if (prop && prop.acls) {
|
||||
prop.acls.forEach(function (acl) {
|
||||
staticACLs.push(new ACL({
|
||||
model: modelClass.modelName,
|
||||
property: property,
|
||||
principalType: acl.principalType,
|
||||
principalId: acl.principalId,
|
||||
accessType: acl.accessType,
|
||||
permission: acl.permission
|
||||
}));
|
||||
});
|
||||
}
|
||||
return staticACLs;
|
||||
};
|
||||
});
|
||||
}
|
||||
return staticACLs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given principal is allowed to access the model/property
|
||||
* @param {String} principalType The principal type.
|
||||
* @param {String} principalId The principal ID.
|
||||
* @param {String} model The model name.
|
||||
* @param {String} property The property/method/relation name.
|
||||
* @param {String} accessType The access type.
|
||||
* @callback {Function} callback Callback function.
|
||||
* @param {String|Error} err The error object
|
||||
* @param {AccessRequest} result The access permission
|
||||
*/
|
||||
ACL.checkPermission = function checkPermission(principalType, principalId,
|
||||
model, property, accessType,
|
||||
callback) {
|
||||
if(principalId !== null && principalId !== undefined && (typeof principalId !== 'string') ) {
|
||||
principalId = principalId.toString();
|
||||
}
|
||||
property = property || ACL.ALL;
|
||||
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
|
||||
accessType = accessType || ACL.ALL;
|
||||
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
||||
/**
|
||||
* Check if the given principal is allowed to access the model/property
|
||||
* @param {String} principalType The principal type.
|
||||
* @param {String} principalId The principal ID.
|
||||
* @param {String} model The model name.
|
||||
* @param {String} property The property/method/relation name.
|
||||
* @param {String} accessType The access type.
|
||||
* @callback {Function} callback Callback function.
|
||||
* @param {String|Error} err The error object
|
||||
* @param {AccessRequest} result The access permission
|
||||
*/
|
||||
ACL.checkPermission = function checkPermission(principalType, principalId,
|
||||
model, property, accessType,
|
||||
callback) {
|
||||
if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {
|
||||
principalId = principalId.toString();
|
||||
}
|
||||
property = property || ACL.ALL;
|
||||
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
|
||||
accessType = accessType || ACL.ALL;
|
||||
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
||||
|
||||
var req = new AccessRequest(model, property, accessType);
|
||||
var req = new AccessRequest(model, property, accessType);
|
||||
|
||||
var acls = this.getStaticACLs(model, property);
|
||||
var acls = this.getStaticACLs(model, property);
|
||||
|
||||
var resolved = this.resolvePermission(acls, req);
|
||||
var resolved = this.resolvePermission(acls, req);
|
||||
|
||||
if(resolved && resolved.permission === ACL.DENY) {
|
||||
debug('Permission denied by statically resolved permission');
|
||||
debug(' Resolved Permission: %j', resolved);
|
||||
process.nextTick(function() {
|
||||
callback && callback(null, resolved);
|
||||
});
|
||||
return;
|
||||
if (resolved && resolved.permission === ACL.DENY) {
|
||||
debug('Permission denied by statically resolved permission');
|
||||
debug(' Resolved Permission: %j', resolved);
|
||||
process.nextTick(function() {
|
||||
callback && callback(null, resolved);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.find({where: {principalType: principalType, principalId: principalId,
|
||||
model: model, property: propertyQuery, accessType: accessTypeQuery}},
|
||||
function(err, dynACLs) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
acls = acls.concat(dynACLs);
|
||||
resolved = self.resolvePermission(acls, req);
|
||||
if (resolved && resolved.permission === ACL.DEFAULT) {
|
||||
var modelClass = loopback.findModel(model);
|
||||
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
||||
}
|
||||
callback && callback(null, resolved);
|
||||
});
|
||||
};
|
||||
|
||||
ACL.prototype.debug = function() {
|
||||
if (debug.enabled) {
|
||||
debug('---ACL---');
|
||||
debug('model %s', this.model);
|
||||
debug('property %s', this.property);
|
||||
debug('principalType %s', this.principalType);
|
||||
debug('principalId %s', this.principalId);
|
||||
debug('accessType %s', this.accessType);
|
||||
debug('permission %s', this.permission);
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.find({where: {principalType: principalType, principalId: principalId,
|
||||
model: model, property: propertyQuery, accessType: accessTypeQuery}},
|
||||
function (err, dynACLs) {
|
||||
/**
|
||||
* Check if the request has the permission to access.
|
||||
* @options {Object} context See below.
|
||||
* @property {Object[]} principals An array of principals.
|
||||
* @property {String|Model} model The model name or model class.
|
||||
* @property {*} id The model instance ID.
|
||||
* @property {String} property The property/method/relation name.
|
||||
* @property {String} accessType The access type: READE, WRITE, or EXECUTE.
|
||||
* @param {Function} callback Callback function
|
||||
*/
|
||||
|
||||
ACL.checkAccessForContext = function(context, callback) {
|
||||
if (!(context instanceof AccessContext)) {
|
||||
context = new AccessContext(context);
|
||||
}
|
||||
|
||||
var model = context.model;
|
||||
var property = context.property;
|
||||
var accessType = context.accessType;
|
||||
var modelName = context.modelName;
|
||||
|
||||
var methodNames = context.methodNames;
|
||||
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
|
||||
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
||||
|
||||
var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames);
|
||||
|
||||
var effectiveACLs = [];
|
||||
var staticACLs = this.getStaticACLs(model.modelName, property);
|
||||
|
||||
var self = this;
|
||||
var roleModel = loopback.getModelByType(Role);
|
||||
this.find({where: {model: model.modelName, property: propertyQuery,
|
||||
accessType: accessTypeQuery}}, function(err, acls) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
acls = acls.concat(dynACLs);
|
||||
resolved = self.resolvePermission(acls, req);
|
||||
if(resolved && resolved.permission === ACL.DEFAULT) {
|
||||
var modelClass = loopback.findModel(model);
|
||||
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
|
||||
}
|
||||
callback && callback(null, resolved);
|
||||
});
|
||||
};
|
||||
var inRoleTasks = [];
|
||||
|
||||
ACL.prototype.debug = function() {
|
||||
if(debug.enabled) {
|
||||
debug('---ACL---');
|
||||
debug('model %s', this.model);
|
||||
debug('property %s', this.property);
|
||||
debug('principalType %s', this.principalType);
|
||||
debug('principalId %s', this.principalId);
|
||||
debug('accessType %s', this.accessType);
|
||||
debug('permission %s', this.permission);
|
||||
}
|
||||
}
|
||||
acls = acls.concat(staticACLs);
|
||||
|
||||
/**
|
||||
* Check if the request has the permission to access.
|
||||
* @options {Object} context See below.
|
||||
* @property {Object[]} principals An array of principals.
|
||||
* @property {String|Model} model The model name or model class.
|
||||
* @property {*} id The model instance ID.
|
||||
* @property {String} property The property/method/relation name.
|
||||
* @property {String} accessType The access type: READE, WRITE, or EXECUTE.
|
||||
* @param {Function} callback Callback function
|
||||
*/
|
||||
acls.forEach(function(acl) {
|
||||
// Check exact matches
|
||||
for (var i = 0; i < context.principals.length; i++) {
|
||||
var p = context.principals[i];
|
||||
if (p.type === acl.principalType
|
||||
&& String(p.id) === String(acl.principalId)) {
|
||||
effectiveACLs.push(acl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ACL.checkAccessForContext = function (context, callback) {
|
||||
if(!(context instanceof AccessContext)) {
|
||||
context = new AccessContext(context);
|
||||
}
|
||||
// Check role matches
|
||||
if (acl.principalType === ACL.ROLE) {
|
||||
inRoleTasks.push(function(done) {
|
||||
roleModel.isInRole(acl.principalId, context,
|
||||
function(err, inRole) {
|
||||
if (!err && inRole) {
|
||||
effectiveACLs.push(acl);
|
||||
}
|
||||
done(err, acl);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var model = context.model;
|
||||
var property = context.property;
|
||||
var accessType = context.accessType;
|
||||
var modelName = context.modelName;
|
||||
|
||||
var methodNames = context.methodNames;
|
||||
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
|
||||
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL]};
|
||||
|
||||
var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames);
|
||||
|
||||
var effectiveACLs = [];
|
||||
var staticACLs = this.getStaticACLs(model.modelName, property);
|
||||
|
||||
var self = this;
|
||||
var roleModel = loopback.getModelByType(Role);
|
||||
this.find({where: {model: model.modelName, property: propertyQuery,
|
||||
accessType: accessTypeQuery}}, function (err, acls) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
var inRoleTasks = [];
|
||||
|
||||
acls = acls.concat(staticACLs);
|
||||
|
||||
acls.forEach(function (acl) {
|
||||
// Check exact matches
|
||||
for (var i = 0; i < context.principals.length; i++) {
|
||||
var p = context.principals[i];
|
||||
if (p.type === acl.principalType
|
||||
&& String(p.id) === String(acl.principalId)) {
|
||||
effectiveACLs.push(acl);
|
||||
async.parallel(inRoleTasks, function(err, results) {
|
||||
if (err) {
|
||||
callback && callback(err, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var resolved = self.resolvePermission(effectiveACLs, req);
|
||||
if (resolved && resolved.permission === ACL.DEFAULT) {
|
||||
resolved.permission = (model && model.settings.defaultPermission) || ACL.ALLOW;
|
||||
}
|
||||
debug('---Resolved---');
|
||||
resolved.debug();
|
||||
callback && callback(null, resolved);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Check role matches
|
||||
if (acl.principalType === ACL.ROLE) {
|
||||
inRoleTasks.push(function (done) {
|
||||
roleModel.isInRole(acl.principalId, context,
|
||||
function (err, inRole) {
|
||||
if (!err && inRole) {
|
||||
effectiveACLs.push(acl);
|
||||
}
|
||||
done(err, acl);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Check if the given access token can invoke the method
|
||||
* @param {AccessToken} token The access token
|
||||
* @param {String} model The model name
|
||||
* @param {*} modelId The model id
|
||||
* @param {String} method The method name
|
||||
* @callback {Function} callback Callback function
|
||||
* @param {String|Error} err The error object
|
||||
* @param {Boolean} allowed is the request allowed
|
||||
*/
|
||||
ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
|
||||
assert(token, 'Access token is required');
|
||||
|
||||
var context = new AccessContext({
|
||||
accessToken: token,
|
||||
model: model,
|
||||
property: method,
|
||||
method: method,
|
||||
modelId: modelId
|
||||
});
|
||||
|
||||
async.parallel(inRoleTasks, function (err, results) {
|
||||
if(err) {
|
||||
callback && callback(err, null);
|
||||
this.checkAccessForContext(context, function(err, access) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
var resolved = self.resolvePermission(effectiveACLs, req);
|
||||
if(resolved && resolved.permission === ACL.DEFAULT) {
|
||||
resolved.permission = (model && model.settings.defaultPermission) || ACL.ALLOW;
|
||||
}
|
||||
debug('---Resolved---');
|
||||
resolved.debug();
|
||||
callback && callback(null, resolved);
|
||||
callback && callback(null, access.permission !== ACL.DENY);
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given access token can invoke the method
|
||||
* @param {AccessToken} token The access token
|
||||
* @param {String} model The model name
|
||||
* @param {*} modelId The model id
|
||||
* @param {String} method The method name
|
||||
* @callback {Function} callback Callback function
|
||||
* @param {String|Error} err The error object
|
||||
* @param {Boolean} allowed is the request allowed
|
||||
*/
|
||||
ACL.checkAccessForToken = function (token, model, modelId, method, callback) {
|
||||
assert(token, 'Access token is required');
|
||||
|
||||
var context = new AccessContext({
|
||||
accessToken: token,
|
||||
model: model,
|
||||
property: method,
|
||||
method: method,
|
||||
modelId: modelId
|
||||
});
|
||||
|
||||
this.checkAccessForContext(context, function (err, access) {
|
||||
if (err) {
|
||||
callback && callback(err);
|
||||
return;
|
||||
}
|
||||
callback && callback(null, access.permission !== ACL.DENY);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.ACL = ACL;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "ACL",
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "The name of the model"
|
||||
},
|
||||
"property": {
|
||||
"type": "string",
|
||||
"description": "The name of the property, method, scope, or relation"
|
||||
},
|
||||
"accessType": "string",
|
||||
"permission": "string",
|
||||
"principalType": "string",
|
||||
"principalId": "string"
|
||||
}
|
||||
}
|
|
@ -281,7 +281,7 @@ AccessRequest.prototype.exactlyMatches = function(acl) {
|
|||
*/
|
||||
|
||||
AccessRequest.prototype.isAllowed = function() {
|
||||
return this.permission !== require('../common/models/acl').ACL.DENY;
|
||||
return this.permission !== loopback.ACL.DENY;
|
||||
}
|
||||
|
||||
AccessRequest.prototype.debug = function() {
|
||||
|
|
|
@ -15,7 +15,10 @@ module.exports = function(loopback) {
|
|||
|
||||
loopback.Role = require('../common/models/role').Role;
|
||||
loopback.RoleMapping = require('../common/models/role').RoleMapping;
|
||||
loopback.ACL = require('../common/models/acl').ACL;
|
||||
|
||||
loopback.ACL = createModel(
|
||||
require('../common/models/acl.json'),
|
||||
require('../common/models/acl.js'));
|
||||
|
||||
loopback.Scope = createModel(
|
||||
require('../common/models/scope.json'),
|
||||
|
|
Loading…
Reference in New Issue