@@ -48,7 +48,9 @@ module.exports = class AccessUtils {
48
48
* Add operation hooks to limit access.
49
49
*/
50
50
setupModels ( ) {
51
- this . getGroupContentModels ( ) . forEach ( modelName => {
51
+ const models = [ this . options . groupModel ] . concat ( this . getGroupContentModels ( ) ) ;
52
+
53
+ models . forEach ( modelName => {
52
54
const Model = this . app . models [ modelName ] ;
53
55
54
56
if ( typeof Model . observe === 'function' ) {
@@ -81,15 +83,15 @@ module.exports = class AccessUtils {
81
83
debug ( '%s observe access: query=%s, options=%o, hookState=%o' ,
82
84
Model . modelName , JSON . stringify ( ctx . query , null , 4 ) , ctx . options , ctx . hookState ) ;
83
85
84
- return this . buildFilter ( currentUser . getId ( ) )
86
+ return this . buildFilter ( currentUser . getId ( ) , ctx . Model )
85
87
. then ( filter => {
86
- debug ( 'filter : %o' , filter ) ;
88
+ debug ( 'original query : %o' , JSON . stringify ( ctx . query , null , 4 ) ) ;
87
89
const where = ctx . query . where ? {
88
90
and : [ ctx . query . where , filter ]
89
91
} : filter ;
90
92
91
93
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 ) ) ;
93
95
} ) ;
94
96
}
95
97
return next ( ) ;
@@ -101,16 +103,19 @@ module.exports = class AccessUtils {
101
103
/**
102
104
* Build a where filter to restrict search results to a users group
103
105
*
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,
105
108
* @returns {Object } A where filter.
106
109
*/
107
- buildFilter ( userId ) {
110
+ buildFilter ( userId , Model ) {
108
111
const filter = { } ;
112
+ const key = this . isGroupModel ( Model ) ? Model . getIdName ( ) : this . options . foreignKey ;
113
+ // TODO: Support key determination based on the belongsTo relationship.
109
114
110
115
return this . getUserGroups ( userId )
111
116
. then ( userGroups => {
112
117
userGroups = Array . from ( userGroups , group => group [ this . options . foreignKey ] ) ;
113
- filter [ this . options . foreignKey ] = { inq : userGroups } ;
118
+ filter [ key ] = { inq : userGroups } ;
114
119
return filter ;
115
120
} ) ;
116
121
}
@@ -132,6 +137,23 @@ module.exports = class AccessUtils {
132
137
return false ;
133
138
}
134
139
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
+
135
157
/**
136
158
* Get a list of group content models (models that have a belongs to relationship to the group model)
137
159
*
@@ -143,8 +165,8 @@ module.exports = class AccessUtils {
143
165
Object . keys ( this . app . models ) . forEach ( modelName => {
144
166
const modelClass = this . app . models [ modelName ] ;
145
167
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 ) ) {
148
170
return ;
149
171
}
150
172
@@ -153,7 +175,7 @@ module.exports = class AccessUtils {
153
175
rel = modelClass . relations [ rel ] ;
154
176
// debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel);
155
177
if ( rel . type === 'belongsTo' && this . isGroupModel ( rel . modelTo ) ) {
156
- models . push ( modelName ) ;
178
+ return models . push ( modelName ) ;
157
179
}
158
180
}
159
181
} ) ;
@@ -206,15 +228,6 @@ module.exports = class AccessUtils {
206
228
getCurrentUser ( ) {
207
229
const ctx = this . app . loopback . getCurrentContext ( ) ;
208
230
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
-
218
231
return currentUser ;
219
232
}
220
233
@@ -226,15 +239,6 @@ module.exports = class AccessUtils {
226
239
getCurrentUserGroups ( ) {
227
240
const ctx = this . app . loopback . getCurrentContext ( ) ;
228
241
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
-
238
242
return currentUserGroups ;
239
243
}
240
244
@@ -269,7 +273,16 @@ module.exports = class AccessUtils {
269
273
270
274
Role . registerResolver ( accessGroup , ( role , context , cb ) => {
271
275
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
+
273
286
if ( cb ) cb ( null , false ) ;
274
287
} ) ;
275
288
return ;
@@ -284,56 +297,41 @@ module.exports = class AccessUtils {
284
297
285
298
debug ( `Role resolver for ${ role } : evaluate ${ modelClass . modelName } with id: ${ modelId } for user: ${ userId } ` ) ;
286
299
287
- this . isGroupMemberWithRole ( modelClass , modelId , userId , roleName )
300
+ return this . isGroupMemberWithRole ( modelClass , modelId , userId , roleName )
288
301
. then ( res => {
289
302
debug ( 'Resolved to' , res ) ;
290
303
cb ( null , res ) ;
291
304
} )
292
305
. catch ( cb ) ;
293
306
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 => {
304
309
// const actions = [ ];
305
310
//
306
- // actions.push(this.hasRoleInGroup(userId, roleName, currentGroupId, context ));
311
+ // actions.push(this.isGroupMemberWithRole(modelClass, modelId, userId, roleName ));
307
312
//
308
313
// // 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));
311
317
// }
312
318
//
313
319
// return actions;
314
320
// })
315
321
// .spread((currentGroupCount, targetGroupCount) => {
316
322
// let res = false;
317
323
//
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;
325
326
//
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}`);
328
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;
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;
332
332
//
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;
337
335
// }
338
336
//
339
337
// // Note the fact that we are allowing access due to passing an ACL.
@@ -358,7 +356,6 @@ module.exports = class AccessUtils {
358
356
*/
359
357
isGroupMemberWithRole ( modelClass , modelId , userId , roleId , cb ) {
360
358
cb = cb || createPromiseCallback ( ) ;
361
- assert ( modelClass , 'Model class is required' ) ;
362
359
debug ( 'isGroupMemberWithRole: modelClass: %o, modelId: %o, userId: %o, roleId: %o' ,
363
360
modelClass && modelClass . modelName , modelId , userId , roleId ) ;
364
361
0 commit comments