adapt-authoring-roles 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -0
- package/.eslintrc +14 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +55 -0
- package/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- package/.github/dependabot.yml +11 -0
- package/.github/pull_request_template.md +25 -0
- package/.github/workflows/labelled_prs.yml +16 -0
- package/.github/workflows/new.yml +19 -0
- package/adapt-authoring.json +5 -0
- package/conf/config.schema.json +84 -0
- package/index.js +5 -0
- package/lib/RolesModule.js +132 -0
- package/package.json +18 -0
- package/schema/role.schema.json +26 -0
- package/schema/userroles.schema.json +21 -0
package/.eslintignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
node_modules
|
package/.eslintrc
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Bug Report
|
|
2
|
+
description: File a bug report
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for taking the time to fill out this bug report!
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: description
|
|
11
|
+
attributes:
|
|
12
|
+
label: What happened?
|
|
13
|
+
description: Please describe the issue
|
|
14
|
+
validations:
|
|
15
|
+
required: true
|
|
16
|
+
- type: textarea
|
|
17
|
+
id: expected
|
|
18
|
+
attributes:
|
|
19
|
+
label: Expected behaviour
|
|
20
|
+
description: Tell us what should have happened
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: repro-steps
|
|
23
|
+
attributes:
|
|
24
|
+
label: Steps to reproduce
|
|
25
|
+
description: Tell us how to reproduce the issue
|
|
26
|
+
validations:
|
|
27
|
+
required: true
|
|
28
|
+
- type: input
|
|
29
|
+
id: aat-version
|
|
30
|
+
attributes:
|
|
31
|
+
label: Authoring tool version
|
|
32
|
+
description: What version of the Adapt authoring tool are you running?
|
|
33
|
+
validations:
|
|
34
|
+
required: true
|
|
35
|
+
- type: input
|
|
36
|
+
id: fw-version
|
|
37
|
+
attributes:
|
|
38
|
+
label: Framework version
|
|
39
|
+
description: What version of the Adapt framework are you running?
|
|
40
|
+
- type: dropdown
|
|
41
|
+
id: browsers
|
|
42
|
+
attributes:
|
|
43
|
+
label: What browsers are you seeing the problem on?
|
|
44
|
+
multiple: true
|
|
45
|
+
options:
|
|
46
|
+
- Firefox
|
|
47
|
+
- Chrome
|
|
48
|
+
- Safari
|
|
49
|
+
- Microsoft Edge
|
|
50
|
+
- type: textarea
|
|
51
|
+
id: logs
|
|
52
|
+
attributes:
|
|
53
|
+
label: Relevant log output
|
|
54
|
+
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
|
55
|
+
render: sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Request a new feature
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for taking the time to request a new feature in the Adapt authoring tool! The Adapt team will consider all new feature requests, but unfortunately cannot commit to every one.
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: description
|
|
11
|
+
attributes:
|
|
12
|
+
label: Feature description
|
|
13
|
+
description: Please describe your feature request
|
|
14
|
+
validations:
|
|
15
|
+
required: true
|
|
16
|
+
- type: checkboxes
|
|
17
|
+
id: contribute
|
|
18
|
+
attributes:
|
|
19
|
+
label: Can you work on this feature?
|
|
20
|
+
description: If you are able to commit your own time to work on this feature, it will greatly increase the liklihood of it being implemented by the core dev team. Otherwise, it will be triaged and prioritised alongside the core team's current priorities.
|
|
21
|
+
options:
|
|
22
|
+
- label: I can contribute
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[//]: # (Please title your PR according to eslint commit conventions)
|
|
2
|
+
[//]: # (See https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-eslint#eslint-convention for details)
|
|
3
|
+
|
|
4
|
+
[//]: # (Add a link to the original issue)
|
|
5
|
+
|
|
6
|
+
[//]: # (Delete as appropriate)
|
|
7
|
+
### Fix
|
|
8
|
+
* A sentence describing each fix
|
|
9
|
+
|
|
10
|
+
### Update
|
|
11
|
+
* A sentence describing each udpate
|
|
12
|
+
|
|
13
|
+
### New
|
|
14
|
+
* A sentence describing each new feature
|
|
15
|
+
|
|
16
|
+
### Breaking
|
|
17
|
+
* A sentence describing each breaking change
|
|
18
|
+
|
|
19
|
+
[//]: # (List appropriate steps for testing if needed)
|
|
20
|
+
### Testing
|
|
21
|
+
1. Steps for testing
|
|
22
|
+
|
|
23
|
+
[//]: # (Mention any other dependencies)
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Add labelled PRs to project
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [ labeled ]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
add-to-project:
|
|
9
|
+
if: ${{ github.event.label.name == 'dependencies' }}
|
|
10
|
+
name: Add to main project
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/add-to-project@v0.1.0
|
|
14
|
+
with:
|
|
15
|
+
project-url: https://github.com/orgs/adapt-security/projects/5
|
|
16
|
+
github-token: ${{ secrets.PROJECTS_SECRET }}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Add to main project
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types:
|
|
6
|
+
- opened
|
|
7
|
+
pull_request:
|
|
8
|
+
types:
|
|
9
|
+
- opened
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
add-to-project:
|
|
13
|
+
name: Add to main project
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/add-to-project@v0.1.0
|
|
17
|
+
with:
|
|
18
|
+
project-url: https://github.com/orgs/adapt-security/projects/5
|
|
19
|
+
github-token: ${{ secrets.PROJECTS_SECRET }}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"roleDefinitions": {
|
|
6
|
+
"description": "List of defined roles to be loaded on app start",
|
|
7
|
+
"type": "array",
|
|
8
|
+
"items": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"shortName": {
|
|
12
|
+
"description": "Short name for the role",
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"displayName": {
|
|
16
|
+
"description": "Human-readable representation of the role",
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"extends": {
|
|
20
|
+
"description": "The parent role that this role should inherit scopes from",
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
"scopes": {
|
|
24
|
+
"description": "Scopes relevant to this role",
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": { "type": "string" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": ["shortName", "displayName", "scopes"]
|
|
30
|
+
},
|
|
31
|
+
"default": [
|
|
32
|
+
{
|
|
33
|
+
"shortName": "authuser",
|
|
34
|
+
"displayName": "Authenticated user",
|
|
35
|
+
"scopes": [
|
|
36
|
+
"clear:session",
|
|
37
|
+
"read:config",
|
|
38
|
+
"read:lang",
|
|
39
|
+
"read:me",
|
|
40
|
+
"write:me",
|
|
41
|
+
"disavow:auth"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"shortName": "contentcreator",
|
|
46
|
+
"displayName": "Content creator",
|
|
47
|
+
"extends": "authuser",
|
|
48
|
+
"scopes": [
|
|
49
|
+
"export:adapt",
|
|
50
|
+
"import:adapt",
|
|
51
|
+
"preview:adapt",
|
|
52
|
+
"publish:adapt",
|
|
53
|
+
"read:assets",
|
|
54
|
+
"write:assets",
|
|
55
|
+
"read:content",
|
|
56
|
+
"write:content",
|
|
57
|
+
"read:contentplugins",
|
|
58
|
+
"read:roles",
|
|
59
|
+
"read:schema",
|
|
60
|
+
"read:tags",
|
|
61
|
+
"write:tags",
|
|
62
|
+
"read:users"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"shortName": "superuser",
|
|
67
|
+
"displayName": "Super user",
|
|
68
|
+
"scopes": ["*:*"]
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"defaultRoles": {
|
|
73
|
+
"description": "The roles which are applied by default to new users (expects role shortname)",
|
|
74
|
+
"type": "array",
|
|
75
|
+
"items": { "type": "string" },
|
|
76
|
+
"default": ["authuser"]
|
|
77
|
+
},
|
|
78
|
+
"defaultRolesForAuthTypes": {
|
|
79
|
+
"description": "Same as defaultRoles, but allows different roles to be specified for different auth types",
|
|
80
|
+
"type": "object",
|
|
81
|
+
"default": {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import AbstractApiModule from 'adapt-authoring-api'
|
|
2
|
+
/**
|
|
3
|
+
* Module which handles user roles
|
|
4
|
+
* @memberof roles
|
|
5
|
+
* @extends {AbstractApiModule}
|
|
6
|
+
*/
|
|
7
|
+
class RolesModule extends AbstractApiModule {
|
|
8
|
+
/** @override */
|
|
9
|
+
async init () {
|
|
10
|
+
await super.init()
|
|
11
|
+
try {
|
|
12
|
+
await this.initConfigRoles()
|
|
13
|
+
|
|
14
|
+
const hasRoles = this.getConfig('defaultRolesForAuthTypes').length || this.getConfig('defaultRoles').length
|
|
15
|
+
if (hasRoles) await this.initDefaultRoles()
|
|
16
|
+
} catch (e) {
|
|
17
|
+
this.log('error', e)
|
|
18
|
+
}
|
|
19
|
+
const [authlocal, users] = await this.app.waitForModule('auth-local', 'users')
|
|
20
|
+
authlocal.registerHook.tap(this.onUpdateRoles.bind(this))
|
|
21
|
+
users.requestHook.tap(this.onUpdateRoles.bind(this))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Adds any role definitions from the current config file to the database
|
|
26
|
+
* @return {Promise}
|
|
27
|
+
*/
|
|
28
|
+
async initConfigRoles () {
|
|
29
|
+
const mongodb = await this.app.waitForModule('mongodb')
|
|
30
|
+
return Promise.allSettled(this.getConfig('roleDefinitions').map(async r => {
|
|
31
|
+
const [doc] = await this.find({ shortName: r.shortName })
|
|
32
|
+
if (doc) {
|
|
33
|
+
try {
|
|
34
|
+
await mongodb.replace(this.collectionName, { _id: doc._id }, r)
|
|
35
|
+
this.log('debug', 'REPLACE', this.schemaName, r.shortName)
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e.code !== 11000) this.log('warn', `failed to update '${r.shortName}' role, ${e.message}`)
|
|
38
|
+
}
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await this.insert(r)
|
|
43
|
+
this.log('debug', 'INSERT', this.schemaName, r.shortName)
|
|
44
|
+
} catch (e) {
|
|
45
|
+
if (e.code !== 11000) this.log('warn', `failed to add '${r.shortName}' role, ${e.message}`)
|
|
46
|
+
}
|
|
47
|
+
}))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds the specified default roles during new user creation
|
|
52
|
+
* @return {Promise}
|
|
53
|
+
*/
|
|
54
|
+
async shortNamesToIds (roles) {
|
|
55
|
+
return Promise.all(roles.map(async r => {
|
|
56
|
+
let role = this.roleCache[r]
|
|
57
|
+
if (!role) {
|
|
58
|
+
[role] = await this.find({ shortName: r })
|
|
59
|
+
this.roleCache[r] = role
|
|
60
|
+
}
|
|
61
|
+
return role._id.toString()
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns the list of scopes for the given role
|
|
67
|
+
* @param {String | ObjectId} _id The _id of the role
|
|
68
|
+
* @returns {Array<String>} Array of scopes
|
|
69
|
+
*/
|
|
70
|
+
async getScopesForRole (_id) {
|
|
71
|
+
const allRoles = await this.find()
|
|
72
|
+
const scopes = []
|
|
73
|
+
let role = allRoles.find(r => r._id.toString() === _id.toString())
|
|
74
|
+
do {
|
|
75
|
+
scopes.push(...role.scopes)
|
|
76
|
+
role = allRoles.find(r => r.shortName === role.extends)
|
|
77
|
+
} while (role)
|
|
78
|
+
return scopes
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Handles setting defined default roles when new users are added
|
|
83
|
+
* @return {Promise}
|
|
84
|
+
*/
|
|
85
|
+
async initDefaultRoles () {
|
|
86
|
+
/**
|
|
87
|
+
* Local store of roles
|
|
88
|
+
* @type {Object}
|
|
89
|
+
*/
|
|
90
|
+
this.roleCache = {}
|
|
91
|
+
|
|
92
|
+
const rolesforAll = await this.shortNamesToIds(this.getConfig('defaultRoles'))
|
|
93
|
+
const rolesForAuth = Object.entries(this.getConfig('defaultRolesForAuthTypes')).reduce((m, [k, v]) => {
|
|
94
|
+
return { [m[k]]: this.shortNamesToIds(v) }
|
|
95
|
+
}, {})
|
|
96
|
+
const users = await this.app.waitForModule('users')
|
|
97
|
+
users.preInsertHook.tap(data => {
|
|
98
|
+
if (!data.roles || !data.roles.length) {
|
|
99
|
+
data.roles = rolesForAuth[data.authType] || rolesforAll || []
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @override */
|
|
105
|
+
async setValues () {
|
|
106
|
+
/** @ignore */ this.root = 'roles'
|
|
107
|
+
/** @ignore */ this.schemaName = 'role'
|
|
108
|
+
/** @ignore */ this.collectionName = 'roles'
|
|
109
|
+
this.useDefaultRouteConfig()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handler for requests which attempt to update roles
|
|
114
|
+
* @param {external:ExpressRequest} req
|
|
115
|
+
* @returns {Promise}
|
|
116
|
+
*/
|
|
117
|
+
async onUpdateRoles (req) {
|
|
118
|
+
if (!req.body.roles || req.method === 'GET' || req.method === 'DELETE') {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if (!req.auth.isSuper && !req.auth.scopes.includes('assign:roles')) {
|
|
122
|
+
this.log('error', 'User does not have the correct permissions to assign user roles')
|
|
123
|
+
throw this.app.errors.UNAUTHORISED
|
|
124
|
+
}
|
|
125
|
+
if (req.method !== 'POST') {
|
|
126
|
+
const auth = await this.app.waitForModule('auth')
|
|
127
|
+
await auth.authentication.disavowUser({ userId: req.params._id || req.body._id })
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export default RolesModule
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "adapt-authoring-roles",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Module for managing user roles",
|
|
5
|
+
"homepage": "https://github.com/adapt-security/adapt-authoring-roles",
|
|
6
|
+
"license": "GPL-3.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"repository": "github:adapt-security/adapt-authoring-roles",
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"adapt-authoring-api": "github:adapt-security/adapt-authoring-api",
|
|
12
|
+
"adapt-authoring-core": "github:adapt-security/adapt-authoring-core"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"eslint": "^9.12.0",
|
|
16
|
+
"standard": "^17.1.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$anchor": "role",
|
|
4
|
+
"description": "A user role",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"shortName": {
|
|
8
|
+
"description": "Short name for the role",
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
11
|
+
"displayName": {
|
|
12
|
+
"description": "Human-readable representation of the role",
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"extends": {
|
|
16
|
+
"description": "The parent role that this role should inherit scopes from",
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"scopes": {
|
|
20
|
+
"description": "Scopes relevant to this role",
|
|
21
|
+
"type": "array",
|
|
22
|
+
"items": { "type": "string" }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["shortName", "displayName", "scopes"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$anchor": "userroles",
|
|
4
|
+
"description": "Extra user properties for specifying roles",
|
|
5
|
+
"$patch": {
|
|
6
|
+
"source": { "$ref": "user" },
|
|
7
|
+
"with": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"roles": {
|
|
10
|
+
"description": "Roles assigned to this user",
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"isObjectId": true
|
|
15
|
+
},
|
|
16
|
+
"default": []
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|