Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions lib/UsersModule.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AbstractApiModule from 'adapt-authoring-api'
import { hasGroupAccess } from './utils.js'
/**
* Module which handles user management
* Module which handles user management and user groups
* @memberof users
* @extends {AbstractApiModule}
*/
Expand All @@ -10,6 +11,11 @@ class UsersModule extends AbstractApiModule {
await super.setValues()
/** @ignore */ this.schemaName = 'user'
/** @ignore */ this.collectionName = 'users'
/**
* Modules registered for user group support
* @type {Array<AbstractApiModule>}
*/
this.groupModules = []
}

/**
Expand All @@ -29,6 +35,26 @@ class UsersModule extends AbstractApiModule {
this.preInsertHook.tap(this.forceLowerCaseEmail)
this.preUpdateHook.tap((ogDoc, updateData) => this.forceLowerCaseEmail(updateData))
}

await this.registerGroupModule(this)
}

/**
* Registers a module for use with groups. Extends the module's schema
* with the userGroups field.
* @param {AbstractApiModule} mod
*/
async registerGroupModule (mod) {
if (!mod.schemaName) {
return this.log('warn', 'cannot register module, module doesn\'t define a schemaName')
}
const jsonschema = await this.app.waitForModule('jsonschema')
jsonschema.extendSchema(mod.schemaName, 'usergroups')
mod.accessCheckHook.tap((req, resource) => {
return hasGroupAccess(resource.userGroups, req.auth?.user?.userGroups)
})
this.log('debug', `registered ${mod.name} for use with groups`)
this.groupModules.push(mod)
}

forceLowerCaseEmail (data) {
Expand All @@ -38,7 +64,7 @@ class UsersModule extends AbstractApiModule {
/** @override */
async processRequestMiddleware (req, res, next) {
super.processRequestMiddleware(req, res, () => {
req.apiData.schemaName = req.auth.userSchemaName
req.apiData.schemaName = req.routeConfig.schemaName || req.auth.userSchemaName
next()
})
}
Expand Down Expand Up @@ -89,6 +115,32 @@ class UsersModule extends AbstractApiModule {
query.email = this.getConfig('forceLowerCaseEmail') ? query.email?.toLowerCase() : undefined
return super.find(query, options, mongoOptions)
}

/** @override */
async delete (query, options = {}, mongoOptions = {}) {
const doc = await super.delete(query, options, mongoOptions)
if (options.collectionName === 'usergroups') {
await this.removeGroupRefs(doc._id)
}
return doc
}

/**
* Removes references to a deleted group from all registered modules
* @param {String} groupId The _id of the deleted group
*/
async removeGroupRefs (groupId) {
await Promise.all(this.groupModules.map(async m => {
const docs = await m.find({ userGroups: groupId })
return Promise.all(docs.map(async d => {
try {
await m.update({ _id: d._id }, { $pull: { userGroups: groupId } }, { rawUpdate: true })
} catch (e) {
this.log('warn', `Failed to remove group reference, ${e}`)
}
}))
}))
}
}

export default UsersModule
1 change: 1 addition & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { hasGroupAccess } from './utils/hasGroupAccess.js'
14 changes: 14 additions & 0 deletions lib/utils/hasGroupAccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Checks whether a user shares at least one group with a document.
* Returns true if the document has no group restrictions.
* @param {Array} docGroups The userGroups array from the document
* @param {Array} userGroups The userGroups array from the requesting user
* @return {Boolean}
* @memberof users
*/
export function hasGroupAccess (docGroups, userGroups) {
if (!docGroups?.length) return true
if (!userGroups?.length) return false
const userSet = new Set(userGroups.map(g => g.toString()))
return docGroups.some(g => userSet.has(g.toString()))
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"peerDependencies": {
"adapt-authoring-core": "^2.0.0",
"adapt-authoring-jsonschema": "^1.2.0",
"adapt-authoring-mongodb": "^3.0.0",
"adapt-authoring-server": "^2.1.0"
},
Expand Down
30 changes: 30 additions & 0 deletions routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,36 @@
"modifying": false,
"handlers": { "post": "queryHandler" },
"permissions": { "post": ["read:${scope}"] }
},
{
"route": "/groups",
"collectionName": "usergroups",
"schemaName": "usergroup",
"handlers": { "get": "queryHandler", "post": "requestHandler" },
"permissions": { "get": ["read:usergroups"], "post": ["write:usergroups"] }
},
{
"route": "/groups/schema",
"collectionName": "usergroups",
"schemaName": "usergroup",
"handlers": { "get": "serveSchema" },
"permissions": { "get": ["read:schema"] }
},
{
"route": "/groups/:_id",
"collectionName": "usergroups",
"schemaName": "usergroup",
"handlers": { "get": "requestHandler", "put": "requestHandler", "patch": "requestHandler", "delete": "requestHandler" },
"permissions": { "get": ["read:usergroups"], "put": ["write:usergroups"], "patch": ["write:usergroups"], "delete": ["write:usergroups"] }
},
{
"route": "/groups/query",
"collectionName": "usergroups",
"schemaName": "usergroup",
"validate": false,
"modifying": false,
"handlers": { "post": "queryHandler" },
"permissions": { "post": ["read:usergroups"] }
}
]
}
13 changes: 13 additions & 0 deletions schema/usergroup.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$anchor": "usergroup",
"description": "A group of system users",
"type": "object",
"properties": {
"displayName": {
"description": "Display name of the user group",
"type": "string"
}
},
"required": ["displayName"]
}
21 changes: 21 additions & 0 deletions schema/usergroups.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$anchor": "usergroups",
"description": "Properties related to user group sharing",
"type": "object",
"$patch": {
"source": { "$ref": "user" },
"with": {
"properties": {
"userGroups": {
"description": "User groups the target object should be accessible to",
"type": "array",
"items": {
"type": "string",
"isObjectId": true
}
}
}
}
}
}
Loading
Loading