Skip to content

Commit bb92147

Browse files
author
Tom Kirkpatrick
committed
Allow pass through access if no group context is found
1 parent 12250df commit bb92147

File tree

1 file changed

+57
-60
lines changed

1 file changed

+57
-60
lines changed

lib/utils.js

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ module.exports = class AccessUtils {
4848
* Add operation hooks to limit access.
4949
*/
5050
setupModels() {
51-
this.getGroupContentModels().forEach(modelName => {
51+
const models = [ this.options.groupModel ].concat(this.getGroupContentModels());
52+
53+
models.forEach(modelName => {
5254
const Model = this.app.models[modelName];
5355

5456
if (typeof Model.observe === 'function') {
@@ -81,15 +83,15 @@ module.exports = class AccessUtils {
8183
debug('%s observe access: query=%s, options=%o, hookState=%o',
8284
Model.modelName, JSON.stringify(ctx.query, null, 4), ctx.options, ctx.hookState);
8385

84-
return this.buildFilter(currentUser.getId())
86+
return this.buildFilter(currentUser.getId(), ctx.Model)
8587
.then(filter => {
86-
debug('filter: %o', filter);
88+
debug('original query: %o', JSON.stringify(ctx.query, null, 4));
8789
const where = ctx.query.where ? {
8890
and: [ ctx.query.where, filter ]
8991
} : filter;
9092

9193
ctx.query.where = where;
92-
debug('where query modified to: %s', JSON.stringify(ctx.query, null, 4));
94+
debug('modified query: %s', JSON.stringify(ctx.query, null, 4));
9395
});
9496
}
9597
return next();
@@ -101,16 +103,19 @@ module.exports = class AccessUtils {
101103
/**
102104
* Build a where filter to restrict search results to a users group
103105
*
104-
* @param {String} userId UserId to build filter for,
106+
* @param {String} userId UserId to build filter for.
107+
* @param {Object} Model Model to build filter for,
105108
* @returns {Object} A where filter.
106109
*/
107-
buildFilter(userId) {
110+
buildFilter(userId, Model) {
108111
const filter = { };
112+
const key = this.isGroupModel(Model)? Model.getIdName() : this.options.foreignKey;
113+
// TODO: Support key determination based on the belongsTo relationship.
109114

110115
return this.getUserGroups(userId)
111116
.then(userGroups => {
112117
userGroups = Array.from(userGroups, group => group[this.options.foreignKey]);
113-
filter[this.options.foreignKey] = { inq: userGroups };
118+
filter[key] = { inq: userGroups };
114119
return filter;
115120
});
116121
}
@@ -132,6 +137,23 @@ module.exports = class AccessUtils {
132137
return false;
133138
}
134139

140+
/**
141+
* Check if a model class is the configured group access model.
142+
*
143+
* @param {String|Object} modelClass Model class to check.
144+
* @returns {Boolean} Returns true if the principalId is on the expected format.
145+
*/
146+
isGroupAccessModel(modelClass) {
147+
if (modelClass) {
148+
const groupAccessModel = this.app.models[this.options.groupAccessModel];
149+
150+
return modelClass === groupAccessModel ||
151+
modelClass.prototype instanceof groupAccessModel ||
152+
modelClass === this.options.groupAccessModel;
153+
}
154+
return false;
155+
}
156+
135157
/**
136158
* Get a list of group content models (models that have a belongs to relationship to the group model)
137159
*
@@ -143,8 +165,8 @@ module.exports = class AccessUtils {
143165
Object.keys(this.app.models).forEach(modelName => {
144166
const modelClass = this.app.models[modelName];
145167

146-
// TODO: Should we allow the access group model to be treated as a group content model too?
147-
if (modelName === this.options.groupAccessModel) {
168+
// Mark the group itself as a group or the group access model.
169+
if (this.isGroupModel(modelClass) || this.isGroupAccessModel(modelClass)) {
148170
return;
149171
}
150172

@@ -153,7 +175,7 @@ module.exports = class AccessUtils {
153175
rel = modelClass.relations[rel];
154176
// debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel);
155177
if (rel.type === 'belongsTo' && this.isGroupModel(rel.modelTo)) {
156-
models.push(modelName);
178+
return models.push(modelName);
157179
}
158180
}
159181
});
@@ -206,15 +228,6 @@ module.exports = class AccessUtils {
206228
getCurrentUser() {
207229
const ctx = this.app.loopback.getCurrentContext();
208230
const currentUser = ctx && ctx.get('currentUser') || null;
209-
210-
if (ctx) {
211-
debug('getCurrentUser() - currentUser: %o', currentUser);
212-
}
213-
else {
214-
// this means its a server-side logic call w/o any HTTP req/resp aspect to it.
215-
debug('getCurrentUser() - no loopback context');
216-
}
217-
218231
return currentUser;
219232
}
220233

@@ -226,15 +239,6 @@ module.exports = class AccessUtils {
226239
getCurrentUserGroups() {
227240
const ctx = this.app.loopback.getCurrentContext();
228241
const currentUserGroups = ctx && ctx.get('currentUserGroups') || [];
229-
230-
if (ctx) {
231-
debug('currentUserGroups(): %o', currentUserGroups);
232-
}
233-
else {
234-
// this means its a server-side logic call w/o any HTTP req/resp aspect to it.
235-
debug('currentUserGroups(): no loopback context');
236-
}
237-
238242
return currentUserGroups;
239243
}
240244

@@ -269,7 +273,16 @@ module.exports = class AccessUtils {
269273

270274
Role.registerResolver(accessGroup, (role, context, cb) => {
271275
if (!context || !context.model || !context.modelId) {
272-
process.nextTick(function() {
276+
process.nextTick(() => {
277+
debug('Allow passthrough access (context: %s, context.model: %s, context.modelId: %s)',
278+
!!context, !!context.model, !!context.modelId);
279+
280+
const currentUser = this.getCurrentUser();
281+
282+
if (currentUser) {
283+
this.app.loopback.getCurrentContext().set('groupAccessApplied', true);
284+
}
285+
273286
if (cb) cb(null, false);
274287
});
275288
return;
@@ -284,56 +297,41 @@ module.exports = class AccessUtils {
284297

285298
debug(`Role resolver for ${role}: evaluate ${modelClass.modelName} with id: ${modelId} for user: ${userId}`);
286299

287-
this.isGroupMemberWithRole(modelClass, modelId, userId, roleName)
300+
return this.isGroupMemberWithRole(modelClass, modelId, userId, roleName)
288301
.then(res => {
289302
debug('Resolved to', res);
290303
cb(null, res);
291304
})
292305
.catch(cb);
293306

294-
// return Promise.join(this.getCurrentGroupId(context), this.getTargetGroupId(context),
295-
// (currentGroupId, targetGroupId) => {
296-
// if (!currentGroupId) {
297-
// // TODO: Use promise cancellation to abort the chain early.
298-
// // Causes the access check to be bypassed (see below).
299-
// return [ false ];
300-
// }
301-
//
302-
// scope.currentGroupId = currentGroupId;
303-
// scope.targetGroupId = targetGroupId;
307+
// return this.getTargetGroupId(context)
308+
// .then(targetGroupId => {
304309
// const actions = [ ];
305310
//
306-
// actions.push(this.hasRoleInGroup(userId, roleName, currentGroupId, context));
311+
// actions.push(this.isGroupMemberWithRole(modelClass, modelId, userId, roleName));
307312
//
308313
// // If this is an attempt to save the item into a new group, check the user has access to the target group.
309-
// if (targetGroupId && targetGroupId !== currentGroupId) {
310-
// actions.push(this.hasRoleInGroup(userId, roleName, targetGroupId, context));
314+
// if (targetGroupId && targetGroupId !== modelId) {
315+
// scope.targetGroupId = targetGroupId;
316+
// actions.push(this.isGroupMemberWithRole(modelClass, targetGroupId, userId, roleName));
311317
// }
312318
//
313319
// return actions;
314320
// })
315321
// .spread((currentGroupCount, targetGroupCount) => {
316322
// let res = false;
317323
//
318-
// if (currentGroupCount === false) {
319-
// // No group context was determined, so allow passthrough access.
320-
// res = true;
321-
// }
322-
// else {
323-
// // Determine grant based on the current/target group context.
324-
// res = currentGroupCount > 0;
324+
// // Determine grant based on the current/target group context.
325+
// res = currentGroupCount > 0;
325326
//
326-
// debug(`user ${userId} ${res ? 'is a' : 'is not a'}` +
327-
// ` ${roleName} of group ${scope.currentGroupId}`);
327+
// debug(`user ${userId} ${res ? 'is a' : 'is not a'} ${roleName} of group ${modelId}`);
328328
//
329-
// // If it's an attempt to save into a new group, also ensure the user has access to the target group.
330-
// if (scope.targetGroupId && scope.targetGroupId !== scope.currentGroupId) {
331-
// const tMember = targetGroupCount > 0;
329+
// // If it's an attempt to save into a new group, also ensure the user has access to the target group.
330+
// if (scope.targetGroupId && scope.targetGroupId !== modelId) {
331+
// const tMember = targetGroupCount > 0;
332332
//
333-
// debug(`user ${userId} ${tMember ? 'is a' : 'is not a'}` +
334-
// ` ${roleName} of group ${scope.targetGroupId}`);
335-
// res = res && tMember;
336-
// }
333+
// debug(`user ${userId} ${tMember ? 'is a' : 'is not a'} ${roleName} of group ${scope.targetGroupId}`);
334+
// res = res && tMember;
337335
// }
338336
//
339337
// // Note the fact that we are allowing access due to passing an ACL.
@@ -358,7 +356,6 @@ module.exports = class AccessUtils {
358356
*/
359357
isGroupMemberWithRole(modelClass, modelId, userId, roleId, cb) {
360358
cb = cb || createPromiseCallback();
361-
assert(modelClass, 'Model class is required');
362359
debug('isGroupMemberWithRole: modelClass: %o, modelId: %o, userId: %o, roleId: %o',
363360
modelClass && modelClass.modelName, modelId, userId, roleId);
364361

0 commit comments

Comments
 (0)