@live-change/access-control-service 0.2.37 → 0.2.40
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/access.js +276 -17
- package/accessControlParents.js +22 -0
- package/index.js +1 -0
- package/invite.js +8 -0
- package/limitedAccess.js +107 -0
- package/model.js +1 -1
- package/package.json +3 -3
- package/request.js +6 -0
- package/view.js +72 -0
package/access.js
CHANGED
|
@@ -1,38 +1,297 @@
|
|
|
1
|
+
const { parents, parentsSourcesMap } = require('./accessControlParents.js')
|
|
2
|
+
const App = require('@live-change/framework')
|
|
3
|
+
const app = App.app()
|
|
1
4
|
|
|
2
5
|
module.exports = (definition) => {
|
|
3
6
|
|
|
4
|
-
const Access = definition.foreignModel('
|
|
5
|
-
const PublicAccess = definition.foreignModel('
|
|
7
|
+
const Access = definition.foreignModel('accessControl', 'Access')
|
|
8
|
+
const PublicAccess = definition.foreignModel('accessControl', 'PublicAccess')
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
const config = definition?.config?.access ?? {}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
hasAny = (roles, client, { objectType, object }) => roles.length > 0,
|
|
14
|
+
isAdmin = (roles, client, { objectType, object }) => roles.includes('administrator'),
|
|
15
|
+
canInvite = (roles, client, { objectType, object }) => roles.length > 0,
|
|
16
|
+
canRequest = (roles, client, { objectType, object }) => false
|
|
17
|
+
} = config
|
|
18
|
+
|
|
19
|
+
async function clientHasAnyAccess(client, { objectType, object, objects }) {
|
|
20
|
+
return checkRoles(client, { objectType, object, objects }, hasAny)
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
function clientHasAdminAccess(client, { objectType, object }) {
|
|
13
|
-
|
|
14
|
-
return true
|
|
23
|
+
function clientHasAdminAccess(client, { objectType, object, objects }) {
|
|
24
|
+
return checkRoles(client, { objectType, object, objects }, isAdmin)
|
|
15
25
|
}
|
|
16
26
|
|
|
17
|
-
function clientCanInvite(client, {
|
|
18
|
-
|
|
19
|
-
return true
|
|
27
|
+
function clientCanInvite(client, { objectType, object, objects }) {
|
|
28
|
+
return checkRoles(client, { objectType, object, objects }, canInvite, true)
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
function clientCanRequest(client, {
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
function clientCanRequest(client, { objectType, object, objects }) {
|
|
32
|
+
return checkRoles(client, { objectType, object, objects }, canRequest)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function clientHasAccessRole(client, { objectType, object, objects }, role) {
|
|
36
|
+
return checkRoles(client, { objectType, object, objects }, (roles) => roles.includes(role) )
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clientHasAccessRoles(client, { objectType, object, objects }, roles) {
|
|
40
|
+
return checkRoles(client, { objectType, object, objects },
|
|
41
|
+
(clientRoles) => roles.every(role => clientRoles.includes(role))
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getUnitClientRoles(client, { objectType, object }, ignorePublic) {
|
|
46
|
+
const [ sessionOrUserType, sessionOrUser ] = client.user ? ['user_User', client.user] : ['session_Session']
|
|
47
|
+
const [
|
|
48
|
+
publicAccessData,
|
|
49
|
+
sessionAccess,
|
|
50
|
+
userAccess,
|
|
51
|
+
] = await Promise.all([
|
|
52
|
+
ignorePublic ? null : PublicAccess.get(App.encodeIdentifier([ objectType, object ])),
|
|
53
|
+
Access.get(App.encodeIdentifier([ 'session_Session', client.session, objectType, object ])),
|
|
54
|
+
client.user
|
|
55
|
+
? Access.get(App.encodeIdentifier([ 'user_User', client.user, objectType, object ]))
|
|
56
|
+
: Promise.resolve(null)
|
|
57
|
+
])
|
|
58
|
+
let roles = []
|
|
59
|
+
if(publicAccessData) {
|
|
60
|
+
roles.push(...publicAccessData.sessionRoles)
|
|
61
|
+
if(client.user) roles.push(...publicAccessData.userRoles)
|
|
62
|
+
}
|
|
63
|
+
if(sessionAccess) roles.push(...sessionAccess.roles)
|
|
64
|
+
if(userAccess) roles.push(...userAccess.roles)
|
|
65
|
+
return Array.from(new Set(roles))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function getClientObjectRoles(client, { objectType, object }, ignorePublic) {
|
|
69
|
+
const unitRolesPromise = getUnitClientRoles(client, { objectType, object}, ignorePublic)
|
|
70
|
+
const accessParentsPromise = parents[objectType]
|
|
71
|
+
? parents[objectType]({ objectType, object })
|
|
72
|
+
: Promise.resolve([])
|
|
73
|
+
const parentRolesPromise = accessParentsPromise.then(accessParents => Promise.all(
|
|
74
|
+
accessParents.map(
|
|
75
|
+
({ objectType, object }) =>
|
|
76
|
+
getClientObjectRoles(client, { objectType, object }, ignorePublic)
|
|
77
|
+
)
|
|
78
|
+
).then(rolesArrays => rolesArrays.flat()))
|
|
79
|
+
const [ unitRoles, parentRoles ] = await Promise.all([ unitRolesPromise, parentRolesPromise ])
|
|
80
|
+
return Array.from(new Set([ ...client.roles, ...unitRoles, ...parentRoles]))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function getClientObjectsRoles(client, objects, ignorePublic) {
|
|
84
|
+
const objectsRoles = await Promise.all(objects.map(obj => getClientObjectRoles(client, obj, ignorePublic)))
|
|
85
|
+
const firstObjectRoles = objectsRoles.shift()
|
|
86
|
+
let roles = firstObjectRoles
|
|
87
|
+
for(const objectRoles of objectsRoles) {
|
|
88
|
+
roles = roles.filter(role => objectRoles.includes(role))
|
|
89
|
+
}
|
|
90
|
+
return roles
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function checkRoles(client, { objectType, object, objects }, callback, ignorePublic) {
|
|
94
|
+
const allObjects = ((objectType && object) ? [{ objectType, object }] : []).concat(objects || [])
|
|
95
|
+
const roles = await getClientObjectsRoles(client, allObjects, ignorePublic)
|
|
96
|
+
return await callback(roles, client, { objectType, object })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// QUERIES:
|
|
100
|
+
|
|
101
|
+
function dbAccessFunctions({ input, publicAccessTable, accessTable, updateRoles, isLoaded }) {
|
|
102
|
+
async function treeNode(objectType, object) {
|
|
103
|
+
const node = {
|
|
104
|
+
objectType, object,
|
|
105
|
+
data: null,
|
|
106
|
+
parents: [],
|
|
107
|
+
publicSessionRoles: [],
|
|
108
|
+
publicUserRoles: [],
|
|
109
|
+
sessionRoles: [],
|
|
110
|
+
userRoles: []
|
|
111
|
+
}
|
|
112
|
+
let objectObserver, publicAccessObserver, sessionAccessObserver, userAccessObserver
|
|
113
|
+
|
|
114
|
+
const publicAccessObject = publicAccessTable.object(`${JSON.stringify(objectType)}:${JSON.stringify(object)}`)
|
|
115
|
+
publicAccessObserver = publicAccessObject.onChange((accessData, oldAccessData) => {
|
|
116
|
+
node.publicSessionRoles = accessData?.sessionRoles ?? []
|
|
117
|
+
node.publicUserRoles = (client.user && accessData?.userRoles) ?? []
|
|
118
|
+
if(isLoaded()) updateRoles()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const sessionAccessObject = accessTable.object(
|
|
122
|
+
`session_Session:${JSON.stringify(client.session)}:${JSON.stringify(objectType)}:${JSON.stringify(object)}`
|
|
123
|
+
)
|
|
124
|
+
sessionAccessObserver = sessionAccessObject && sessionAccessObject.onChange((accessData, oldAccessData) => {
|
|
125
|
+
node.sessionRoles = accessData?.roles ?? []
|
|
126
|
+
if(isLoaded()) updateRoles()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const userAccessObject = client.user && accessTable.object(
|
|
130
|
+
`user_User:${JSON.stringify(client.user)}:${JSON.stringify(objectType)}:${JSON.stringify(object)}`
|
|
131
|
+
)
|
|
132
|
+
userAccessObserver = userAccessObject && userAccessObject.onChange((accessData, oldAccessData) => {
|
|
133
|
+
node.sessionRoles = accessData?.roles ?? []
|
|
134
|
+
if(isLoaded()) updateRoles()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
async function disposeParents() {
|
|
138
|
+
const oldParents = node.parents
|
|
139
|
+
return Promise.all(oldParents.map(parent => parent.dispose()))
|
|
140
|
+
}
|
|
141
|
+
const parentsSources = parentsSourcesMap[objectType]
|
|
142
|
+
if(parentsSources) {
|
|
143
|
+
const objectTable = input.table(objectType)
|
|
144
|
+
const objectTableObject = objectTable.object(object)
|
|
145
|
+
objectObserver = objectTableObject.onChange(async (objectData, oldObjectData) => {
|
|
146
|
+
await disposeParents()
|
|
147
|
+
node.parents = objectData ? await Promise.all(parentsSources.map(parentSource => {
|
|
148
|
+
const parentType = parentSource.type || objectData[parentSource.property + 'Type']
|
|
149
|
+
const parent = objectData[parentSource.property]
|
|
150
|
+
return treeNode(parentType, parent)
|
|
151
|
+
})) : []
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
node.dispose = async function() {
|
|
155
|
+
const disposePromises = []
|
|
156
|
+
if(objectObserver) disposePromises.push(objectObserver.then(obs => obs.dispose()))
|
|
157
|
+
if(publicAccessObserver) disposePromises.push(publicAccessObserver.then(obs => obs.dispose()))
|
|
158
|
+
if(sessionAccessObserver) disposePromises.push(sessionAccessObserver.then(obs => obs.dispose()))
|
|
159
|
+
if(userAccessObserver) disposePromises.push(userAccessObserver.then(obs => obs.dispose()))
|
|
160
|
+
disposePromises.push(disposeParents())
|
|
161
|
+
return Promise.all(disposePromises)
|
|
162
|
+
}
|
|
163
|
+
await Promise.all([ objectObserver, publicAccessObserver, sessionAccessObserver, userAccessObserver ])
|
|
164
|
+
return node
|
|
165
|
+
}
|
|
166
|
+
function computeNodeRoles(node) {
|
|
167
|
+
const parentsRoles = node.parents.map(parent => computeNodeRoles(parent)).flat()
|
|
168
|
+
return Array.from(new Set([
|
|
169
|
+
...parentsRoles,
|
|
170
|
+
...node.publicUserRoles,
|
|
171
|
+
...node.publicSessionRoles,
|
|
172
|
+
...node.userRoles,
|
|
173
|
+
...node.sessionRoles
|
|
174
|
+
]))
|
|
175
|
+
}
|
|
176
|
+
return { treeNode, computeNodeRoles }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function accessPath(client, objects) {
|
|
180
|
+
return ['database', 'queryObject', app.databaseName, `(${
|
|
181
|
+
async (input, output, {
|
|
182
|
+
objects, parentsSourcesMap, client,
|
|
183
|
+
accessTableName, publicAccessTableName, dbAccessFunctions
|
|
184
|
+
}) => {
|
|
185
|
+
const accessTable = input.table(accessTableName)
|
|
186
|
+
const publicAccessTable = input.table(publicAccessTableName)
|
|
187
|
+
let loaded = false
|
|
188
|
+
|
|
189
|
+
const { treeNode, computeNodeRoles } =
|
|
190
|
+
eval(dbAccessFunctions)({ input, publicAccessTable, accessTable, updateRoles, isLoaded: () => loaded })
|
|
191
|
+
|
|
192
|
+
let rolesTreesRoots = objects.map(({ object, objectType }) => treeNode(objectType, object))
|
|
193
|
+
|
|
194
|
+
const outputObjectId = `${JSON.stringify(client.session)}:${JSON.stringify(client.user)}:` +
|
|
195
|
+
objects.map( obj => `${JSON.stringify(objectType)}:${JSON.stringify(object)}`)
|
|
196
|
+
.join(':')
|
|
197
|
+
let oldOutputObject = null
|
|
198
|
+
async function updateRoles() {
|
|
199
|
+
const roots = await Promise.all(rolesTreesRoots)
|
|
200
|
+
const accesses = roots.map(root => computeNodeRoles(root))
|
|
201
|
+
const firstAccess = accesses.shift()
|
|
202
|
+
let roles = firstAccess.roles
|
|
203
|
+
for(const access of accesses) {
|
|
204
|
+
roles = roles.filter(role => access.roles.includes(role))
|
|
205
|
+
}
|
|
206
|
+
const accessControlRoles = computeNodeRoles()
|
|
207
|
+
const outputObject = {
|
|
208
|
+
id: outputObjectId,
|
|
209
|
+
roles: Array.from(new Set([...accessControlRoles, ...client.roles]))
|
|
210
|
+
}
|
|
211
|
+
output.change(outputObject, oldOutputObject)
|
|
212
|
+
oldOutputObject = outputObject
|
|
213
|
+
}
|
|
214
|
+
await Promise.all(rolesTreesRoots)
|
|
215
|
+
loaded = true
|
|
216
|
+
await updateRoles()
|
|
217
|
+
}
|
|
218
|
+
})`, {
|
|
219
|
+
objectType, object, parentsSourcesMap, client,
|
|
220
|
+
accessTableName: Access.tableName, publicAccessTableName: PublicAccess.tableName,
|
|
221
|
+
dbAccessFunctions: `(${dbAccessFunctions})`
|
|
222
|
+
}]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function accessesPath(client, objects) {
|
|
226
|
+
return ['database', 'query', app.databaseName, `(${
|
|
227
|
+
async (input, output, {
|
|
228
|
+
objects, parentsSourcesMap, client,
|
|
229
|
+
accessTableName, publicAccessTableName, dbAccessFunctions
|
|
230
|
+
}) => {
|
|
231
|
+
const accessTable = input.table(accessTableName)
|
|
232
|
+
const publicAccessTable = input.table(publicAccessTableName)
|
|
233
|
+
let loaded = false
|
|
234
|
+
|
|
235
|
+
const { treeNode, computeNodeRoles } =
|
|
236
|
+
eval(dbAccessFunctions)({ input, publicAccessTable, accessTable, updateRoles, isLoaded: () => loaded })
|
|
237
|
+
|
|
238
|
+
let rolesTreesRoots = objects.map(({ object, objectType }) => treeNode(objectType, object))
|
|
239
|
+
const accesses = []
|
|
240
|
+
async function updateRoles() {
|
|
241
|
+
const roots = await Promise.all(rolesTreesRoots)
|
|
242
|
+
for(let root of roots) {
|
|
243
|
+
const outputObjectId = `${JSON.stringify(client.session)}:${JSON.stringify(client.user)}` +
|
|
244
|
+
`:${JSON.stringify(root.objectType)}:${JSON.stringify(root.object)}`
|
|
245
|
+
const nodeRoles = computeNodeRoles(root)
|
|
246
|
+
const outputObject = {
|
|
247
|
+
id: outputObjectId,
|
|
248
|
+
roles: Array.from(new Set([...nodeRoles, ...client.roles]))
|
|
249
|
+
}
|
|
250
|
+
const existingAccessIndex = accesses.findIndex(acc => acc.id == outputObjectId)
|
|
251
|
+
if(existingAccessIndex != -1) {
|
|
252
|
+
if(JSON.stringify(outputObject) != JSON.stringify(accesses[existingAccessIndex])) {
|
|
253
|
+
output.change(outputObject, accesses[existingAccessIndex])
|
|
254
|
+
accesses[existingAccessIndex] = outputObject
|
|
255
|
+
} /// else ignore
|
|
256
|
+
} else {
|
|
257
|
+
output.change(outputObject, null)
|
|
258
|
+
accesses.push(outputObject)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
await Promise.all(rolesTreesRoots)
|
|
263
|
+
loaded = true
|
|
264
|
+
await updateRoles()
|
|
265
|
+
}
|
|
266
|
+
})`, {
|
|
267
|
+
objects, parentsSourcesMap, client,
|
|
268
|
+
accessTableName: Access.tableName, publicAccessTableName: PublicAccess.tableName,
|
|
269
|
+
}]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function accessLimitedGet(client, objects, requiredRoles, path) {
|
|
273
|
+
const roles = getClientObjectsRoles(client, objects)
|
|
274
|
+
for(const requiredRole of requiredRoles) {
|
|
275
|
+
|
|
276
|
+
}
|
|
25
277
|
}
|
|
26
278
|
|
|
27
|
-
function
|
|
28
|
-
|
|
279
|
+
function accessLimitedObservable(client, objects, path) {
|
|
280
|
+
if(path[0] != 'database') throw new Error("non database path "+ JSON.stringify(path))
|
|
281
|
+
const isObject = path[1] == 'queryObject' || path[1] == ''
|
|
29
282
|
}
|
|
30
283
|
|
|
31
284
|
return {
|
|
32
285
|
clientHasAnyAccess, clientHasAdminAccess,
|
|
33
286
|
clientCanInvite,
|
|
34
287
|
clientCanRequest,
|
|
35
|
-
clientHasAccessRole
|
|
288
|
+
clientHasAccessRole,
|
|
289
|
+
clientHasAccessRoles,
|
|
290
|
+
getClientObjectRoles,
|
|
291
|
+
getClientObjectsRoles,
|
|
292
|
+
checkRoles,
|
|
293
|
+
accessPath,
|
|
294
|
+
accessesPath
|
|
36
295
|
}
|
|
37
296
|
|
|
38
297
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const definition = require("./definition.js")
|
|
2
|
+
|
|
3
|
+
const parents = { }
|
|
4
|
+
const parentsSources = { }
|
|
5
|
+
|
|
6
|
+
definition.processor(function(service, app) {
|
|
7
|
+
|
|
8
|
+
for(let modelName in service.models) {
|
|
9
|
+
const model = service.models[modelName]
|
|
10
|
+
if(!model.accessControlParents) continue
|
|
11
|
+
parents[service.name + '_' + modelName] = model.accessControlParents
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for(let modelName in service.models) {
|
|
15
|
+
const model = service.models[modelName]
|
|
16
|
+
if(!model.accessControlParentsSource) continue
|
|
17
|
+
parentsSources[service.name + '_' + modelName] = model.accessControlParentsSource
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
module.exports = { parents, parentsSources }
|
package/index.js
CHANGED
package/invite.js
CHANGED
|
@@ -221,6 +221,14 @@ for(const contactType of config.contactTypes) {
|
|
|
221
221
|
access: (params, { client, context, visibilityTest }) =>
|
|
222
222
|
visibilityTest || access.clientCanInvite(client, params),
|
|
223
223
|
async execute(params, { client, service }, emit) {
|
|
224
|
+
const { roles } = params
|
|
225
|
+
const myRoles = await access.getClientObjectRoles(client, { objectType, object }, true)
|
|
226
|
+
if(!myRoles.includes('administrator')) {
|
|
227
|
+
for(const requestedRole of roles) {
|
|
228
|
+
if(!myRoles.includes(requestedRole)) throw 'notAuthorized'
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
224
232
|
const [ fromType, from ] = client.user ? ['user_User', client.user] : ['session_Session', client.session]
|
|
225
233
|
const { [contactTypeName]: contact } = params
|
|
226
234
|
const { objectType, object } = params
|
package/limitedAccess.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
const App = require("@live-change/framework")
|
|
3
|
+
const { ObservableValue, ObservableList, ObservableProxy } = require("@live-change/dao")
|
|
4
|
+
const app = App.app()
|
|
5
|
+
const access = require('./access.js')(definition)
|
|
6
|
+
|
|
7
|
+
definition.processor(function(service, app) {
|
|
8
|
+
|
|
9
|
+
for(const actionName in service.actions) {
|
|
10
|
+
const action = service.actions[actionName]
|
|
11
|
+
if(!action.limitedAccess) continue
|
|
12
|
+
const config = action.limitedAccess
|
|
13
|
+
|
|
14
|
+
console.log("LIMITED ACCESS", service.name, "ACTION", action.name)
|
|
15
|
+
|
|
16
|
+
const oldExec = action.execute
|
|
17
|
+
action.execute = async (...args) => {
|
|
18
|
+
const [ properties, context, emit ] = args
|
|
19
|
+
const { client } = context
|
|
20
|
+
|
|
21
|
+
const objects = [].concat(
|
|
22
|
+
config.objects ? config.objects(properties) : [],
|
|
23
|
+
(objectType && object) ? [{ objectType, object }] : []
|
|
24
|
+
)
|
|
25
|
+
if(objects.length == 0) {
|
|
26
|
+
throw new Error('no objects for access control to work')
|
|
27
|
+
}
|
|
28
|
+
const accessible = access.clientHasAccessRoles(client, { objects }, config.roles)
|
|
29
|
+
if(!accessible) throw 'notAuthorized'
|
|
30
|
+
|
|
31
|
+
return oldExec.apply(action, args)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for(const viewName in service.views) {
|
|
36
|
+
const view = service.view[viewName]
|
|
37
|
+
if(!view.limitedAccess) continue
|
|
38
|
+
const config = view.limitedAccess
|
|
39
|
+
|
|
40
|
+
console.log("LIMITED ACCESS", service.name, "VIEW", view.name)
|
|
41
|
+
|
|
42
|
+
const oldGet = view.get
|
|
43
|
+
const oldObservable = view.observable
|
|
44
|
+
view.get = async (...args) => {
|
|
45
|
+
const [ properties, context ] = args
|
|
46
|
+
const { client } = context
|
|
47
|
+
const { objectType, object } = properties
|
|
48
|
+
const objects = [].concat(
|
|
49
|
+
config.objects ? config.objects(properties) : [],
|
|
50
|
+
(objectType && object) ? [{ objectType, object }] : []
|
|
51
|
+
)
|
|
52
|
+
if(objects.length == 0) {
|
|
53
|
+
throw new Error('no objects for access control to work')
|
|
54
|
+
}
|
|
55
|
+
const accessible = access.clientHasAccessRoles(client, { objects }, config.roles)
|
|
56
|
+
if(!accessible) throw 'notAuthorized'
|
|
57
|
+
return oldGet.apply(view, args)
|
|
58
|
+
}
|
|
59
|
+
view.observable = (...args) => {
|
|
60
|
+
const [ properties, context ] = args
|
|
61
|
+
const { client } = context
|
|
62
|
+
const { objectType, object } = properties
|
|
63
|
+
const objects = [].concat(
|
|
64
|
+
config.objects ? config.objects(properties) : [],
|
|
65
|
+
(objectType && object) ? [{ objectType, object }] : []
|
|
66
|
+
)
|
|
67
|
+
if(objects.length == 0) {
|
|
68
|
+
throw new Error('no objects for access control to work')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rolesPath = access.accessPath(client, objects)
|
|
72
|
+
|
|
73
|
+
const errorObservable = new ObservableValue()
|
|
74
|
+
errorObservable.handleError('notAuthorized')
|
|
75
|
+
|
|
76
|
+
const observableProxy = new ObservableProxy(null)
|
|
77
|
+
|
|
78
|
+
let valueObservable
|
|
79
|
+
|
|
80
|
+
let accessible
|
|
81
|
+
const rolesObservable = app.dao.observable(rolesPath)
|
|
82
|
+
const rolesObserver = (signal, value) => {
|
|
83
|
+
const accessObject = rolesObservable.getValue()
|
|
84
|
+
const newAccessible = config.roles.every(role => accessObject.roles.includes(role))
|
|
85
|
+
if(newAccessible !== accessible) {
|
|
86
|
+
if(newAccessible === true /*&& !valueObservable*/) {
|
|
87
|
+
valueObservable = oldObservable.apply(view, args)
|
|
88
|
+
}
|
|
89
|
+
observableProxy.setTarget(accessible ? valueObservable : errorObservable)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
rolesObservable.observe(rolesObserver)
|
|
93
|
+
|
|
94
|
+
const oldDispose = observableProxy.dispose
|
|
95
|
+
const oldRespawn = observableProxy.respawn
|
|
96
|
+
observableProxy.dispose = () => {
|
|
97
|
+
rolesObservable.unobserve(rolesObserver)
|
|
98
|
+
oldDispose.apply(observableProxy)
|
|
99
|
+
}
|
|
100
|
+
observableProxy.respawn = () => {
|
|
101
|
+
rolesObservable.observe(rolesObserver)
|
|
102
|
+
oldRespawn.apply(observableProxy)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
})
|
package/model.js
CHANGED
|
@@ -39,7 +39,7 @@ const PublicAccess = definition.model({
|
|
|
39
39
|
to: 'object',
|
|
40
40
|
readAccess: (params, { client, context, visibilityTest }) =>
|
|
41
41
|
visibilityTest || access.clientHasAnyAccess(client, params),
|
|
42
|
-
writeAccess: (params, { client, context, visibilityTest }) =>
|
|
42
|
+
writeAccess: async (params, { client, context, visibilityTest }) =>
|
|
43
43
|
visibilityTest || access.clientHasAdminAccess(client, params)
|
|
44
44
|
},
|
|
45
45
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/access-control-service",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.40",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"url": "https://www.viamage.com/"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@live-change/framework": "0.6.
|
|
24
|
+
"@live-change/framework": "0.6.6"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "4a920b328b0a7f3f25c67cdba3e574687971ee22"
|
|
27
27
|
}
|
package/request.js
CHANGED
|
@@ -43,6 +43,12 @@ definition.action({
|
|
|
43
43
|
access: (params, { client, context, visibilityTest }) =>
|
|
44
44
|
visibilityTest || access.clientCanInvite(client, params),
|
|
45
45
|
async execute({ objectType, object, sessionOrUserType, sessionOrUser, roles }, { client, service }, emit) {
|
|
46
|
+
const myRoles = await access.getClientObjectRoles(client, { objectType, object }, true)
|
|
47
|
+
if(!myRoles.includes('administrator')) {
|
|
48
|
+
for(const requestedRole of roles) {
|
|
49
|
+
if(!myRoles.includes(requestedRole)) throw 'notAuthorized'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
46
52
|
const request = App.encodeIdentifier([ sessionOrUserType, sessionOrUser, objectType, object ])
|
|
47
53
|
const requestData = await AccessRequest.get(request)
|
|
48
54
|
if(!requestData) throw 'not_found'
|
package/view.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const definition = require("./definition.js")
|
|
2
|
+
const App = require("@live-change/framework")
|
|
3
|
+
const app = App.app()
|
|
4
|
+
|
|
5
|
+
const access = require('./access.js')(definition)
|
|
6
|
+
|
|
7
|
+
definition.view({
|
|
8
|
+
name: "myAccessTo",
|
|
9
|
+
properties: {
|
|
10
|
+
objectType: {
|
|
11
|
+
type: String
|
|
12
|
+
},
|
|
13
|
+
object: {
|
|
14
|
+
type: String
|
|
15
|
+
},
|
|
16
|
+
objects: {
|
|
17
|
+
type: Array,
|
|
18
|
+
of: {
|
|
19
|
+
type: Object,
|
|
20
|
+
properties: {
|
|
21
|
+
objectType: {
|
|
22
|
+
type: String
|
|
23
|
+
},
|
|
24
|
+
object: {
|
|
25
|
+
type: String
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
returns: {
|
|
32
|
+
type: Array,
|
|
33
|
+
of: {
|
|
34
|
+
type: String
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
async daoPath({ objectType, object, objects }, { client, service }, method) {
|
|
38
|
+
const allObjects = ((objectType && object) ? [{ objectType, object }] : []).concat(objects || [])
|
|
39
|
+
if(allObjects.length == 0) throw 'empty_objects_list'
|
|
40
|
+
return access.accessPath(client, allObjects)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
definition.view({
|
|
45
|
+
name: "myAccessesTo",
|
|
46
|
+
properties: {
|
|
47
|
+
objects: {
|
|
48
|
+
type: Array,
|
|
49
|
+
of: {
|
|
50
|
+
type: Object,
|
|
51
|
+
properties: {
|
|
52
|
+
objectType: {
|
|
53
|
+
type: String
|
|
54
|
+
},
|
|
55
|
+
object: {
|
|
56
|
+
type: String
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
returns: {
|
|
63
|
+
type: Array,
|
|
64
|
+
of: {
|
|
65
|
+
type: String
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async daoPath({ objects }, { client, service }, method) {
|
|
69
|
+
if(objects.length == 0) throw 'empty_objects_list'
|
|
70
|
+
return access.accessesPath(client, objects)
|
|
71
|
+
}
|
|
72
|
+
})
|