-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Enable multiple user models inheriting from base class User #2971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,6 +146,16 @@ module.exports = function(AccessToken) { | |
| var userRelation = AccessToken.relations.user; // may not be set up | ||
| var User = userRelation && userRelation.modelTo; | ||
|
|
||
| // redefine user model if accessToken's principalType is available | ||
| if (this.principalType) { | ||
| User = AccessToken.registry.findModel(this.principalType); | ||
| if (!User) { | ||
| process.nextTick(function() { | ||
| return cb(null, false); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. callback |
||
| var now = Date.now(); | ||
| var created = this.created.getTime(); | ||
| var elapsedSeconds = (now - created) / 1000; | ||
|
|
@@ -157,14 +167,18 @@ module.exports = function(AccessToken) { | |
| elapsedSeconds < secondsToLive; | ||
|
|
||
| if (isValid) { | ||
| cb(null, isValid); | ||
| process.nextTick(function() { | ||
| cb(null, isValid); | ||
| }); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the original implem did not respect the async api |
||
| } else { | ||
| this.destroy(function(err) { | ||
| cb(err, isValid); | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| cb(e); | ||
| process.nextTick(function() { | ||
| cb(e); | ||
| }); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| } | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,9 @@ var debug = require('debug')('loopback:security:role'); | |
| var assert = require('assert'); | ||
| var async = require('async'); | ||
| var utils = require('../../lib/utils'); | ||
|
|
||
| var AccessContext = require('../../lib/access-context').AccessContext; | ||
|
|
||
| var ctx = require('../../lib/access-context'); | ||
| var AccessContext = ctx.AccessContext; | ||
| var Principal = ctx.Principal; | ||
| var RoleMapping = loopback.RoleMapping; | ||
|
|
||
| assert(RoleMapping, 'RoleMapping model must be defined before Role model'); | ||
|
|
@@ -70,7 +70,8 @@ module.exports = function(Role) { | |
| callback = utils.createPromiseCallback(); | ||
| } | ||
| } | ||
| if (!query) query = {}; | ||
| query = query || {}; | ||
| query.where = query.where || {}; | ||
|
|
||
| roleModel.resolveRelatedModels(); | ||
| var relsToModels = { | ||
|
|
@@ -86,8 +87,29 @@ module.exports = function(Role) { | |
| roles: ACL.ROLE, | ||
| }; | ||
|
|
||
| var model = relsToModels[rel]; | ||
| listByPrincipalType(this, model, relsToTypes[rel], query, callback); | ||
| var principalModel = relsToModels[rel]; | ||
| var principalType = relsToTypes[rel]; | ||
|
|
||
| // redefine user model and user type if user principalType is custom (available and not "USER") | ||
| var isCustomUserPrincipalType = rel === 'users' && | ||
| query.where.principalType && | ||
| query.where.principalType !== RoleMapping.USER; | ||
|
|
||
| if (isCustomUserPrincipalType) { | ||
| var registry = this.constructor.registry; | ||
| principalModel = registry.findModel(query.where.principalType); | ||
| principalType = query.where.principalType; | ||
| } | ||
| // make sure we don't keep principalType in userModel query | ||
| delete query.where.principalType; | ||
|
|
||
| if (principalModel) { | ||
| listByPrincipalType(this, principalModel, principalType, query, callback); | ||
| } else { | ||
| process.nextTick(function() { | ||
| callback(null, []); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| }); | ||
| } | ||
| return callback.promise; | ||
| }; | ||
| }); | ||
|
|
@@ -102,10 +124,11 @@ module.exports = function(Role) { | |
| * @param {Function} [callback] callback function called with `(err, models)` arguments. | ||
| */ | ||
| function listByPrincipalType(context, model, principalType, query, callback) { | ||
| if (callback === undefined) { | ||
| if (callback === undefined && typeof query === 'function') { | ||
| callback = query; | ||
| query = {}; | ||
| } | ||
| query = query || {}; | ||
|
|
||
| roleModel.roleMappingModel.find({ | ||
| where: {roleId: context.id, principalType: principalType}, | ||
|
|
@@ -303,6 +326,7 @@ module.exports = function(Role) { | |
| * @promise | ||
| */ | ||
| Role.isInRole = function(role, context, callback) { | ||
| context.registry = this.registry; | ||
| if (!(context instanceof AccessContext)) { | ||
| context = new AccessContext(context); | ||
| } | ||
|
|
@@ -421,9 +445,9 @@ module.exports = function(Role) { | |
| callback = utils.createPromiseCallback(); | ||
| } | ||
| } | ||
|
|
||
| if (!options) options = {}; | ||
|
|
||
| context.registry = this.registry; | ||
| if (!(context instanceof AccessContext)) { | ||
| context = new AccessContext(context); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,9 +32,12 @@ function AccessContext(context) { | |
| } | ||
| context = context || {}; | ||
|
|
||
| assert(context.registry, | ||
| 'Application registry is mandatory in AccessContext but missing in provided context'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we fall back to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO we should only fallback to loopback.registry in case localRegistry is set to false, or not set and running a loopback version where it defaults to false. Thoughts?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sure, that sounds good too. It may be best to leave this change out of scope of this pull request, so that this patch can be finally landed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure !!! |
||
| this.registry = context.registry; | ||
| this.principals = context.principals || []; | ||
| var model = context.model; | ||
| model = ('string' === typeof model) ? loopback.getModel(model) : model; | ||
| model = ('string' === typeof model) ? this.registry.getModel(model) : model; | ||
| this.model = model; | ||
| this.modelName = model && model.modelName; | ||
|
|
||
|
|
@@ -62,6 +65,7 @@ function AccessContext(context) { | |
| var principalType = context.principalType || Principal.USER; | ||
| var principalId = context.principalId || undefined; | ||
| var principalName = context.principalName || undefined; | ||
|
|
||
| if (principalId) { | ||
| this.addPrincipal(principalType, principalId, principalName); | ||
| } | ||
|
|
@@ -124,11 +128,25 @@ AccessContext.prototype.addPrincipal = function(principalType, principalId, prin | |
| * @returns {*} | ||
| */ | ||
| AccessContext.prototype.getUserId = function() { | ||
| var BaseUser = this.registry.getModel('User'); | ||
| for (var i = 0; i < this.principals.length; i++) { | ||
| var p = this.principals[i]; | ||
| var isBuiltinPrincipal = p.type === Principal.APP || | ||
| p.type === Principal.ROLE || | ||
| p.type == Principal.SCOPE; | ||
| if (isBuiltinPrincipal) continue; | ||
|
|
||
| // the principalType must either be 'USER' | ||
| if (p.type === Principal.USER) { | ||
| return p.id; | ||
| } | ||
|
|
||
| // or permit to resolve a valid user model | ||
| var userModel = this.registry.findModel(p.type); | ||
| if (!userModel) continue; | ||
| if (userModel.prototype instanceof BaseUser) { | ||
| return p.id; | ||
| } | ||
| } | ||
| return null; | ||
| }; | ||
|
|
@@ -189,8 +207,9 @@ AccessContext.prototype.debug = function() { | |
| * This class represents the abstract notion of a principal, which can be used | ||
| * to represent any entity, such as an individual, a corporation, and a login id | ||
| * @param {String} type The principal type | ||
| * @param {*} id The princiapl id | ||
| * @param {*} id The principal id | ||
| * @param {String} [name] The principal name | ||
| * @param {String} modelName The principal model name | ||
| * @returns {Principal} | ||
| * @class | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i moved back to the original implementation following your comment.
although: if I correctly get the docs for
Registry.getModelByType(...)we could usegetModelByType('User')instead ofAccessToken.relations.user.modelToas the method allows toThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may work in most cases, but I would be concerned about changes in edge cases, e.g. when the app has a custom User-based model that's actually not used for authentication, i.e. AccessToken is attached to the base User model. (That's just an example.)
Let's play it safe and keep the current implementation.