@@ -268,80 +268,172 @@ module.exports = class AccessUtils {
268
268
const Role = this . app . models [ this . options . roleModel ] ;
269
269
270
270
Role . registerResolver ( accessGroup , ( role , context , cb ) => {
271
- const currentUser = this . getCurrentUser ( ) ;
271
+ if ( ! context || ! context . model || ! context . modelId ) {
272
+ process . nextTick ( function ( ) {
273
+ if ( cb ) cb ( null , false ) ;
274
+ } ) ;
275
+ return ;
276
+ }
277
+
278
+ const modelClass = context . model ;
279
+ const modelId = context . modelId ;
280
+ const userId = context . getUserId ( ) ;
272
281
const roleName = this . extractRoleName ( role ) ;
273
282
const GroupAccess = this . app . models [ this . options . groupAccessModel ] ;
274
283
const scope = { } ;
275
284
276
- // Do not allow anonymous users.
277
- if ( ! currentUser ) {
278
- debug ( 'access denied for anonymous user' ) ;
279
- return process . nextTick ( ( ) => cb ( null , false ) ) ;
280
- }
281
-
282
- debug ( `Role resolver for ${ role } : evaluate ${ context . model . definition . name } with id: ${ context . modelId } ` +
283
- ` for currentUser.getId(): ${ currentUser . getId ( ) } ` ) ;
285
+ debug ( `Role resolver for ${ role } : evaluate ${ modelClass . modelName } with id: ${ modelId } for user: ${ userId } ` ) ;
284
286
285
- return Promise . join ( this . getCurrentGroupId ( context ) , this . getTargetGroupId ( context ) ,
286
- ( currentGroupId , targetGroupId ) => {
287
- if ( ! currentGroupId ) {
288
- // TODO: Use promise cancellation to abort the chain early.
289
- // Causes the access check to be bypassed (see below).
290
- return [ false ] ;
291
- }
292
-
293
- scope . currentGroupId = currentGroupId ;
294
- scope . targetGroupId = targetGroupId ;
295
- const actions = [ ] ;
296
- const conditions = {
297
- userId : currentUser . getId ( ) ,
298
- role : roleName
299
- } ;
300
-
301
- conditions [ this . options . foreignKey ] = currentGroupId ;
302
- actions . push ( GroupAccess . count ( conditions ) ) ;
303
-
304
- // If this is an attempt to save the item into a new group, check the user has access to the target group.
305
- if ( targetGroupId && targetGroupId !== currentGroupId ) {
306
- conditions [ this . options . foreignKey ] = targetGroupId ;
307
- actions . push ( GroupAccess . count ( conditions ) ) ;
308
- }
309
-
310
- return actions ;
287
+ this . isGroupMemberWithRole ( modelClass , modelId , userId , roleName )
288
+ . then ( res => {
289
+ debug ( 'Resolved to' , res ) ;
290
+ cb ( null , res ) ;
311
291
} )
312
- . spread ( ( currentGroupCount , targetGroupCount ) => {
313
- let res = false ;
314
-
315
- if ( currentGroupCount === false ) {
316
- // No group context was determined, so allow passthrough access.
317
- res = true ;
318
- }
319
- else {
320
- // Determine grant based on the current/target group context.
321
- res = currentGroupCount > 0 ;
292
+ . catch ( cb ) ;
322
293
323
- debug ( `user ${ currentUser . getId ( ) } ${ res ? 'is a' : 'is not a' } ` +
324
- `${ roleName } of group ${ scope . currentGroupId } ` ) ;
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;
304
+ // const actions = [ ];
305
+ //
306
+ // actions.push(this.hasRoleInGroup(userId, roleName, currentGroupId, context));
307
+ //
308
+ // // 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));
311
+ // }
312
+ //
313
+ // return actions;
314
+ // })
315
+ // .spread((currentGroupCount, targetGroupCount) => {
316
+ // let res = false;
317
+ //
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;
325
+ //
326
+ // debug(`user ${userId} ${res ? 'is a' : 'is not a'}` +
327
+ // ` ${roleName} of group ${scope.currentGroupId}`);
328
+ //
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;
332
+ //
333
+ // debug(`user ${userId} ${tMember ? 'is a' : 'is not a'}` +
334
+ // ` ${roleName} of group ${scope.targetGroupId}`);
335
+ // res = res && tMember;
336
+ // }
337
+ // }
338
+ //
339
+ // // Note the fact that we are allowing access due to passing an ACL.
340
+ // if (res) {
341
+ // this.app.loopback.getCurrentContext().set('groupAccessApplied', true);
342
+ // }
343
+ //
344
+ // debug(`${accessGroup} role resolver returns ${res} for user ${userId}`);
345
+ // return cb(null, res);
346
+ // })
347
+ // .catch(cb);
348
+ } ) ;
349
+ }
325
350
326
- // If it's an attempt to save into a new group, also ensure the user has access to the target group.
327
- if ( scope . targetGroupId && scope . targetGroupId !== scope . currentGroupId ) {
328
- const tMember = targetGroupCount > 0 ;
351
+ /**
352
+ * Check if a given user ID has a given role in the model instances group.
353
+ * @param {Function } modelClass The model class
354
+ * @param {* } modelId The model ID
355
+ * @param {* } userId The user ID
356
+ * @param {* } roleId The role ID
357
+ * @param {Function } callback Callback function
358
+ */
359
+ isGroupMemberWithRole ( modelClass , modelId , userId , roleId , cb ) {
360
+ cb = cb || createPromiseCallback ( ) ;
361
+ assert ( modelClass , 'Model class is required' ) ;
362
+ debug ( 'isGroupMemberWithRole: modelClass: %o, modelId: %o, userId: %o, roleId: %o' ,
363
+ modelClass && modelClass . modelName , modelId , userId , roleId ) ;
364
+
365
+ // No userId is present
366
+ if ( ! userId ) {
367
+ process . nextTick ( ( ) => {
368
+ cb ( null , false ) ;
369
+ } ) ;
370
+ return cb . promise ;
371
+ }
329
372
330
- debug ( `user ${ currentUser . getId ( ) } ${ tMember ? 'is a' : 'is not a' } ` +
331
- `${ roleName } of group ${ scope . targetGroupId } ` ) ;
332
- res = res && tMember ;
333
- }
334
- }
373
+ // Is the modelClass GroupModel or a subclass of GroupModel?
374
+ if ( this . isGroupModel ( modelClass ) ) {
375
+ this . hasRoleInGroup ( userId , roleId , modelId , context )
376
+ . then ( res => cb ( null , res ) ) ;
377
+ return cb . promise ;
378
+ }
335
379
336
- // Note the fact that we are allowing access due to passing an ACL.
337
- if ( res ) {
338
- this . app . loopback . getCurrentContext ( ) . set ( 'groupAccessApplied' , true ) ;
380
+ modelClass . findById ( modelId , ( err , inst ) => {
381
+ if ( err || ! inst ) {
382
+ debug ( 'Model not found for id %j' , modelId ) ;
383
+ if ( cb ) cb ( err , false ) ;
384
+ return ;
385
+ }
386
+ debug ( 'Model found: %j' , inst ) ;
387
+ var groupId = inst [ this . options . foreignKey ] ;
388
+ // Ensure groupId exists and is not a function/relation
389
+ if ( groupId && 'function' !== typeof groupId ) {
390
+ if ( cb ) {
391
+ return this . hasRoleInGroup ( userId , roleId , groupId , context )
392
+ . then ( res => cb ( null , res ) ) ;
393
+ }
394
+ } else {
395
+ // Try to follow belongsTo
396
+ for ( var r in modelClass . relations ) {
397
+ var rel = modelClass . relations [ r ] ;
398
+ if ( rel . type === 'belongsTo' && isGroupModel ( rel . modelTo ) ) {
399
+ debug ( 'Checking relation %s to %s: %j' , r , rel . modelTo . modelName , rel ) ;
400
+ inst [ r ] ( processRelatedGroup ) ;
401
+ return ;
339
402
}
403
+ }
404
+ debug ( 'No matching belongsTo relation found for model %j and group: %j' , modelId , groupId ) ;
405
+ if ( cb ) cb ( null , false ) ;
406
+ }
340
407
341
- return cb ( null , res ) ;
342
- } )
343
- . catch ( cb ) ;
408
+ function processRelatedGroup ( err , group ) {
409
+ if ( ! err && group ) {
410
+ debug ( 'Group found: %j' , group . getId ( ) ) ;
411
+ if ( cb ) cb ( null , this . hasRoleInGroup ( userId , roleId , group . getId ( ) , context , cb ) ) ;
412
+ } else {
413
+ if ( cb ) cb ( err , false ) ;
414
+ }
415
+ }
344
416
} ) ;
417
+ return cb . promise ;
418
+ } ;
419
+
420
+ hasRoleInGroup ( userId , role , group , context , cb ) {
421
+ debug ( 'hasRoleInGroup: role: %o, group: %o, userId: %o' , role , group , userId ) ;
422
+ cb = cb || createPromiseCallback ( ) ;
423
+ const GroupAccess = this . app . models [ this . options . groupAccessModel ] ;
424
+ const conditions = {
425
+ userId,
426
+ role,
427
+ }
428
+ conditions [ this . options . foreignKey ] = group ;
429
+ GroupAccess . count ( conditions )
430
+ . then ( count => {
431
+ const res = count > 0 ;
432
+
433
+ debug ( `user ${ userId } has role ${ role } in group ${ group } : ${ res } ` ) ;
434
+ cb ( null , res ) ;
435
+ } )
436
+ return cb . promise ;
345
437
}
346
438
347
439
/**
@@ -362,7 +454,7 @@ module.exports = class AccessUtils {
362
454
return cb . promise ;
363
455
}
364
456
365
- // If we are accessing an existing model, get the store id from the existing model instance.
457
+ // If we are accessing an existing model, get the group id from the existing model instance.
366
458
// TODO: Cache this result so that it can be reused across each ACL lookup attempt.
367
459
if ( context . modelId ) {
368
460
debug ( `fetching group id for existing model with id: ${ context . modelId } ` ) ;
@@ -372,7 +464,7 @@ module.exports = class AccessUtils {
372
464
. then ( item => {
373
465
// TODO: Attempt to follow relationships in addition to the foreign key.
374
466
if ( item ) {
375
- debug ( `determined group id ${ item [ this . options . foreignKey ] } from existing model %o` , item ) ;
467
+ debug ( `determined group id ${ item [ this . options . foreignKey ] } from existing model ${ context . modelId } ` ) ;
376
468
groupId = item [ this . options . foreignKey ] ;
377
469
}
378
470
cb ( null , groupId ) ;
0 commit comments