Initial relation auth implementation
This commit is contained in:
parent
28a0537e81
commit
b99e110b3a
|
@ -301,18 +301,16 @@ app.enableAuth = function() {
|
|||
var Model = method.ctor;
|
||||
var modelInstance = ctx.instance;
|
||||
|
||||
var modelId = modelInstance && modelInstance.id ||
|
||||
// replacement for deprecated req.param()
|
||||
(req.params && req.params.id !== undefined ? req.params.id :
|
||||
req.body && req.body.id !== undefined ? req.body.id :
|
||||
req.query && req.query.id !== undefined ? req.query.id :
|
||||
undefined);
|
||||
var modelId = modelInstance && modelInstance.id || ctx.args.id;
|
||||
ctx.modelId = modelId;
|
||||
|
||||
var modelName = Model.modelName;
|
||||
|
||||
var modelSettings = Model.settings || {};
|
||||
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
|
||||
if (!req.accessToken) {
|
||||
if (req.accessToken) {
|
||||
ctx.accessToken = req.accessToken;
|
||||
} else {
|
||||
errStatusCode = 401;
|
||||
}
|
||||
|
||||
|
|
123
lib/model.js
123
lib/model.js
|
@ -5,6 +5,8 @@ var assert = require('assert');
|
|||
var RemoteObjects = require('strong-remoting');
|
||||
var SharedClass = require('strong-remoting').SharedClass;
|
||||
var extend = require('util')._extend;
|
||||
var normalizeInclude = require('loopback-datasource-juggler/lib/include').normalizeInclude;
|
||||
var async = require('async');
|
||||
|
||||
module.exports = function(registry) {
|
||||
|
||||
|
@ -289,9 +291,10 @@ module.exports = function(registry) {
|
|||
*/
|
||||
|
||||
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
|
||||
var Model = this;
|
||||
var aclModel = Model._ACL();
|
||||
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
|
||||
token = token || ANONYMOUS;
|
||||
var aclModel = Model._ACL();
|
||||
|
||||
ctx = ctx || {};
|
||||
if (typeof ctx === 'function' && callback === undefined) {
|
||||
|
@ -310,7 +313,11 @@ module.exports = function(registry) {
|
|||
remotingContext: ctx
|
||||
}, function(err, accessRequest) {
|
||||
if (err) return callback(err);
|
||||
callback(null, accessRequest.isAllowed());
|
||||
if(typeof sharedMethod.authorization === 'function') {
|
||||
sharedMethod.authorization(ctx, accessRequest, callback);
|
||||
} else {
|
||||
callback(null, accessRequest.isAllowed());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -436,6 +443,7 @@ module.exports = function(registry) {
|
|||
var modelName = relation.modelTo && relation.modelTo.modelName;
|
||||
modelName = modelName || 'PersistedModel';
|
||||
var fn = this.prototype[relationName];
|
||||
var registry = this.registry;
|
||||
var pathName = (relation.options.http && relation.options.http.path) || relationName;
|
||||
define('__get__' + relationName, {
|
||||
isStatic: false,
|
||||
|
@ -443,7 +451,11 @@ module.exports = function(registry) {
|
|||
accepts: {arg: 'refresh', type: 'boolean', http: {source: 'query'}},
|
||||
accessType: 'READ',
|
||||
description: 'Fetches belongsTo relation ' + relationName + '.',
|
||||
returns: {arg: relationName, type: modelName, root: true}
|
||||
returns: {arg: relationName, type: modelName, root: true},
|
||||
authorization: function(ctx, done) {
|
||||
var targetSharedMethod = relation.modelTo.sharedClass.find('findOne', true);
|
||||
relation.modelTo.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, done);
|
||||
}
|
||||
}, fn);
|
||||
};
|
||||
|
||||
|
@ -461,6 +473,9 @@ module.exports = function(registry) {
|
|||
Model.hasOneRemoting = function(relationName, relation, define) {
|
||||
var pathName = (relation.options.http && relation.options.http.path) || relationName;
|
||||
var toModelName = relation.modelTo.modelName;
|
||||
var Model = this;
|
||||
var registry = Model.registry;
|
||||
var TargetModel = registry.getModel(toModelName);
|
||||
|
||||
define('__get__' + relationName, {
|
||||
isStatic: false,
|
||||
|
@ -469,7 +484,11 @@ module.exports = function(registry) {
|
|||
description: 'Fetches hasOne relation ' + relationName + '.',
|
||||
accessType: 'READ',
|
||||
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
|
||||
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
|
||||
rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('findById', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
|
||||
define('__create__' + relationName, {
|
||||
|
@ -478,7 +497,11 @@ module.exports = function(registry) {
|
|||
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
|
||||
description: 'Creates a new instance in ' + relationName + ' of this model.',
|
||||
accessType: 'WRITE',
|
||||
returns: {arg: 'data', type: toModelName, root: true}
|
||||
returns: {arg: 'data', type: toModelName, root: true},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('create', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
|
||||
define('__update__' + relationName, {
|
||||
|
@ -487,20 +510,29 @@ module.exports = function(registry) {
|
|||
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
|
||||
description: 'Update ' + relationName + ' of this model.',
|
||||
accessType: 'WRITE',
|
||||
returns: {arg: 'data', type: toModelName, root: true}
|
||||
returns: {arg: 'data', type: toModelName, root: true},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('update', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
|
||||
define('__destroy__' + relationName, {
|
||||
isStatic: false,
|
||||
http: {verb: 'delete', path: '/' + pathName},
|
||||
description: 'Deletes ' + relationName + ' of this model.',
|
||||
accessType: 'WRITE'
|
||||
accessType: 'WRITE',
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('delete', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Model.hasManyRemoting = function(relationName, relation, define) {
|
||||
var pathName = (relation.options.http && relation.options.http.path) || relationName;
|
||||
var toModelName = relation.modelTo.modelName;
|
||||
var TargetModel = relation.modelTo;
|
||||
|
||||
var findByIdFunc = this.prototype['__findById__' + relationName];
|
||||
define('__findById__' + relationName, {
|
||||
|
@ -512,7 +544,11 @@ module.exports = function(registry) {
|
|||
description: 'Find a related item by id for ' + relationName + '.',
|
||||
accessType: 'READ',
|
||||
returns: {arg: 'result', type: toModelName, root: true},
|
||||
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
|
||||
rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('findById', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
}, findByIdFunc);
|
||||
|
||||
var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
|
||||
|
@ -524,7 +560,11 @@ module.exports = function(registry) {
|
|||
http: {source: 'path'}},
|
||||
description: 'Delete a related item by id for ' + relationName + '.',
|
||||
accessType: 'WRITE',
|
||||
returns: []
|
||||
returns: [],
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('destroyById', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
}, destroyByIdFunc);
|
||||
|
||||
var updateByIdFunc = this.prototype['__updateById__' + relationName];
|
||||
|
@ -539,7 +579,11 @@ module.exports = function(registry) {
|
|||
],
|
||||
description: 'Update a related item by id for ' + relationName + '.',
|
||||
accessType: 'WRITE',
|
||||
returns: {arg: 'result', type: toModelName, root: true}
|
||||
returns: {arg: 'result', type: toModelName, root: true},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('updateById', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
}, updateByIdFunc);
|
||||
|
||||
if (relation.modelThrough || relation.type === 'referencesMany') {
|
||||
|
@ -560,7 +604,11 @@ module.exports = function(registry) {
|
|||
http: {source: 'path'}}].concat(accepts),
|
||||
description: 'Add a related item by id for ' + relationName + '.',
|
||||
accessType: 'WRITE',
|
||||
returns: {arg: relationName, type: modelThrough.modelName, root: true}
|
||||
returns: {arg: relationName, type: modelThrough.modelName, root: true},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = relation.modelThrough.sharedClass.find('create', true);
|
||||
relation.modelThrough.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
}, addFunc);
|
||||
|
||||
var removeFunc = this.prototype['__unlink__' + relationName];
|
||||
|
@ -572,7 +620,11 @@ module.exports = function(registry) {
|
|||
http: {source: 'path'}},
|
||||
description: 'Remove the ' + relationName + ' relation to an item by id.',
|
||||
accessType: 'WRITE',
|
||||
returns: []
|
||||
returns: [],
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('updateById', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
}, removeFunc);
|
||||
|
||||
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
|
||||
|
@ -602,6 +654,10 @@ module.exports = function(registry) {
|
|||
cb();
|
||||
}
|
||||
}
|
||||
},
|
||||
authorization: function(ctx, done) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('exists', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
}, existsFunc);
|
||||
}
|
||||
|
@ -613,6 +669,8 @@ module.exports = function(registry) {
|
|||
|
||||
var isStatic = scope.isStatic;
|
||||
var toModelName = scope.modelTo.modelName;
|
||||
var registry = this.registry;
|
||||
var TargetModel = registry.getModel(toModelName);
|
||||
|
||||
// https://github.com/strongloop/loopback/issues/811
|
||||
// Check if the scope is for a hasMany relation
|
||||
|
@ -629,7 +687,24 @@ module.exports = function(registry) {
|
|||
accepts: {arg: 'filter', type: 'object'},
|
||||
description: 'Queries ' + scopeName + ' of ' + this.modelName + '.',
|
||||
accessType: 'READ',
|
||||
returns: {arg: scopeName, type: [toModelName], root: true}
|
||||
returns: {arg: scopeName, type: [toModelName], root: true},
|
||||
authorization: function(ctx, done) {
|
||||
var modelsToCheck = [toModelName];
|
||||
var include = ctx.args.filter && ctx.args.filter.include;
|
||||
if (include) {
|
||||
modelsToCheck = modelsToCheck.concat(normalizeInclude(include));
|
||||
}
|
||||
|
||||
async.map(modelsToCheck, function(modelName, cb) {
|
||||
var TargetModel = registry.get(modelName);
|
||||
var targetSharedMethod = ModelToCheck.sharedClass.find('find', true);
|
||||
ModelToCheck.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}, function(err, results) {
|
||||
if (err) return done(err);
|
||||
// if false is in the results, the result is false
|
||||
done(null, results.indexOf(false) === -1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
define('__create__' + scopeName, {
|
||||
|
@ -638,14 +713,22 @@ module.exports = function(registry) {
|
|||
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
|
||||
description: 'Creates a new instance in ' + scopeName + ' of this model.',
|
||||
accessType: 'WRITE',
|
||||
returns: {arg: 'data', type: toModelName, root: true}
|
||||
returns: {arg: 'data', type: toModelName, root: true},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('create', true);
|
||||
ModelToCheck.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
|
||||
define('__delete__' + scopeName, {
|
||||
isStatic: isStatic,
|
||||
http: {verb: 'delete', path: '/' + pathName},
|
||||
description: 'Deletes all ' + scopeName + ' of this model.',
|
||||
accessType: 'WRITE'
|
||||
accessType: 'WRITE',
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('delete', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
|
||||
define('__count__' + scopeName, {
|
||||
|
@ -654,7 +737,11 @@ module.exports = function(registry) {
|
|||
accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'},
|
||||
description: 'Counts ' + scopeName + ' of ' + this.modelName + '.',
|
||||
accessType: 'READ',
|
||||
returns: {arg: 'count', type: 'number'}
|
||||
returns: {arg: 'count', type: 'number'},
|
||||
authorization: function(ctx, next) {
|
||||
var targetSharedMethod = TargetModel.sharedClass.find('count', true);
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, targetSharedMethod, ctx, cb);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
@ -680,6 +767,7 @@ module.exports = function(registry) {
|
|||
var http = [].concat(sharedToClass.http || [])[0];
|
||||
var httpPath;
|
||||
var acceptArgs;
|
||||
var TargetModel = relation.modelTo;
|
||||
|
||||
if (relation.multiple) {
|
||||
httpPath = pathName + '/:' + paramName;
|
||||
|
@ -732,6 +820,9 @@ module.exports = function(registry) {
|
|||
opts.accessType = method.accessType;
|
||||
opts.rest = extend({}, method.rest || {});
|
||||
opts.rest.delegateTo = method;
|
||||
options.authorization = function(ctx, next) {
|
||||
TargetModel.checkAccess(ctx.accessToken, ctx.modelId, method, ctx, cb);
|
||||
}
|
||||
|
||||
opts.http = [];
|
||||
var routes = [].concat(method.http || []);
|
||||
|
|
Loading…
Reference in New Issue