@budibase/backend-core 2.33.2 → 2.33.4
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/dist/index.js +566 -309
- package/dist/index.js.map +4 -4
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +4 -4
- package/dist/plugins.js.map +1 -1
- package/dist/plugins.js.meta.json +1 -1
- package/dist/src/context/identity.js +1 -1
- package/dist/src/context/identity.js.map +1 -1
- package/dist/src/db/couch/DatabaseImpl.js +1 -1
- package/dist/src/db/couch/DatabaseImpl.js.map +1 -1
- package/dist/src/environment.js +28 -14
- package/dist/src/environment.js.map +1 -1
- package/dist/src/events/identification.js +7 -5
- package/dist/src/events/identification.js.map +1 -1
- package/dist/src/features/features.d.ts +1 -0
- package/dist/src/features/features.js +4 -3
- package/dist/src/features/features.js.map +1 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/middleware/authenticated.js +16 -8
- package/dist/src/middleware/authenticated.js.map +1 -1
- package/dist/src/security/roles.d.ts +24 -3
- package/dist/src/security/roles.js +210 -51
- package/dist/src/security/roles.js.map +1 -1
- package/dist/src/sql/sql.js +243 -160
- package/dist/src/sql/sql.js.map +1 -1
- package/dist/src/sql/sqlTable.js +4 -1
- package/dist/src/sql/sqlTable.js.map +1 -1
- package/dist/src/tenancy/db.d.ts +0 -3
- package/dist/src/tenancy/db.js +0 -31
- package/dist/src/tenancy/db.js.map +1 -1
- package/dist/src/users/db.d.ts +2 -2
- package/dist/src/users/db.js +7 -7
- package/dist/src/users/db.js.map +1 -1
- package/dist/src/users/users.d.ts +1 -0
- package/dist/src/users/users.js +13 -0
- package/dist/src/users/users.js.map +1 -1
- package/dist/src/users/utils.d.ts +3 -3
- package/dist/src/users/utils.js +5 -14
- package/dist/src/users/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/context/identity.ts +1 -1
- package/src/db/couch/DatabaseImpl.ts +2 -1
- package/src/environment.ts +33 -17
- package/src/events/identification.ts +7 -6
- package/src/features/features.ts +4 -3
- package/src/middleware/authenticated.ts +33 -17
- package/src/security/roles.ts +238 -56
- package/src/sql/sql.ts +290 -206
- package/src/sql/sqlTable.ts +4 -1
- package/src/tenancy/db.ts +0 -23
- package/src/users/db.ts +12 -9
- package/src/users/users.ts +11 -0
- package/src/users/utils.ts +12 -18
package/src/security/roles.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import semver from "semver"
|
|
1
2
|
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
|
2
3
|
import {
|
|
3
4
|
prefixRoleID,
|
|
@@ -7,9 +8,16 @@ import {
|
|
|
7
8
|
doWithDB,
|
|
8
9
|
} from "../db"
|
|
9
10
|
import { getAppDB } from "../context"
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Screen,
|
|
13
|
+
Role as RoleDoc,
|
|
14
|
+
RoleUIMetadata,
|
|
15
|
+
Database,
|
|
16
|
+
App,
|
|
17
|
+
} from "@budibase/types"
|
|
11
18
|
import cloneDeep from "lodash/fp/cloneDeep"
|
|
12
|
-
import { RoleColor } from "@budibase/shared-core"
|
|
19
|
+
import { RoleColor, helpers } from "@budibase/shared-core"
|
|
20
|
+
import { uniqBy } from "lodash"
|
|
13
21
|
|
|
14
22
|
export const BUILTIN_ROLE_IDS = {
|
|
15
23
|
ADMIN: "ADMIN",
|
|
@@ -23,14 +31,6 @@ const BUILTIN_IDS = {
|
|
|
23
31
|
BUILDER: "BUILDER",
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
// exclude internal roles like builder
|
|
27
|
-
const EXTERNAL_BUILTIN_ROLE_IDS = [
|
|
28
|
-
BUILTIN_IDS.ADMIN,
|
|
29
|
-
BUILTIN_IDS.POWER,
|
|
30
|
-
BUILTIN_IDS.BASIC,
|
|
31
|
-
BUILTIN_IDS.PUBLIC,
|
|
32
|
-
]
|
|
33
|
-
|
|
34
34
|
export const RoleIDVersion = {
|
|
35
35
|
// original version, with a UUID based ID
|
|
36
36
|
UUID: undefined,
|
|
@@ -38,12 +38,20 @@ export const RoleIDVersion = {
|
|
|
38
38
|
NAME: "name",
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function rolesInList(roleIds: string[], ids: string | string[]) {
|
|
42
|
+
if (Array.isArray(ids)) {
|
|
43
|
+
return ids.filter(id => roleIds.includes(id)).length === ids.length
|
|
44
|
+
} else {
|
|
45
|
+
return roleIds.includes(ids)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
export class Role implements RoleDoc {
|
|
42
50
|
_id: string
|
|
43
51
|
_rev?: string
|
|
44
52
|
name: string
|
|
45
53
|
permissionId: string
|
|
46
|
-
inherits?: string
|
|
54
|
+
inherits?: string | string[]
|
|
47
55
|
version?: string
|
|
48
56
|
permissions: Record<string, PermissionLevel[]> = {}
|
|
49
57
|
uiMetadata?: RoleUIMetadata
|
|
@@ -62,12 +70,70 @@ export class Role implements RoleDoc {
|
|
|
62
70
|
this.version = RoleIDVersion.NAME
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
addInheritance(inherits
|
|
73
|
+
addInheritance(inherits?: string | string[]) {
|
|
74
|
+
// make sure IDs are correct format
|
|
75
|
+
if (inherits && typeof inherits === "string") {
|
|
76
|
+
inherits = prefixRoleIDNoBuiltin(inherits)
|
|
77
|
+
} else if (inherits && Array.isArray(inherits)) {
|
|
78
|
+
inherits = inherits.map(prefixRoleIDNoBuiltin)
|
|
79
|
+
}
|
|
66
80
|
this.inherits = inherits
|
|
67
81
|
return this
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
export class RoleHierarchyTraversal {
|
|
86
|
+
allRoles: RoleDoc[]
|
|
87
|
+
opts?: { defaultPublic?: boolean }
|
|
88
|
+
|
|
89
|
+
constructor(allRoles: RoleDoc[], opts?: { defaultPublic?: boolean }) {
|
|
90
|
+
this.allRoles = allRoles
|
|
91
|
+
this.opts = opts
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
walk(role: RoleDoc): RoleDoc[] {
|
|
95
|
+
const opts = this.opts,
|
|
96
|
+
allRoles = this.allRoles
|
|
97
|
+
// this will be a full walked list of roles - which may contain duplicates
|
|
98
|
+
let roleList: RoleDoc[] = []
|
|
99
|
+
if (!role || !role._id) {
|
|
100
|
+
return roleList
|
|
101
|
+
}
|
|
102
|
+
roleList.push(role)
|
|
103
|
+
if (Array.isArray(role.inherits)) {
|
|
104
|
+
for (let roleId of role.inherits) {
|
|
105
|
+
const foundRole = findRole(roleId, allRoles, opts)
|
|
106
|
+
if (foundRole) {
|
|
107
|
+
roleList = roleList.concat(this.walk(foundRole))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const foundRoleIds: string[] = []
|
|
112
|
+
let currentRole: RoleDoc | undefined = role
|
|
113
|
+
while (
|
|
114
|
+
currentRole &&
|
|
115
|
+
currentRole.inherits &&
|
|
116
|
+
!rolesInList(foundRoleIds, currentRole.inherits)
|
|
117
|
+
) {
|
|
118
|
+
if (Array.isArray(currentRole.inherits)) {
|
|
119
|
+
return roleList.concat(this.walk(currentRole))
|
|
120
|
+
} else {
|
|
121
|
+
foundRoleIds.push(currentRole.inherits)
|
|
122
|
+
currentRole = findRole(currentRole.inherits, allRoles, opts)
|
|
123
|
+
if (currentRole) {
|
|
124
|
+
roleList.push(currentRole)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// loop now found - stop iterating
|
|
128
|
+
if (helpers.roles.checkForRoleInheritanceLoops(roleList)) {
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return uniqBy(roleList, role => role._id)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
71
137
|
const BUILTIN_ROLES = {
|
|
72
138
|
ADMIN: new Role(
|
|
73
139
|
BUILTIN_IDS.ADMIN,
|
|
@@ -126,7 +192,15 @@ export function getBuiltinRoles(): { [key: string]: RoleDoc } {
|
|
|
126
192
|
}
|
|
127
193
|
|
|
128
194
|
export function isBuiltin(role: string) {
|
|
129
|
-
return
|
|
195
|
+
return Object.values(BUILTIN_ROLE_IDS).includes(role)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function prefixRoleIDNoBuiltin(roleId: string) {
|
|
199
|
+
if (isBuiltin(roleId)) {
|
|
200
|
+
return roleId
|
|
201
|
+
} else {
|
|
202
|
+
return prefixRoleID(roleId)
|
|
203
|
+
}
|
|
130
204
|
}
|
|
131
205
|
|
|
132
206
|
export function getBuiltinRole(roleId: string): Role | undefined {
|
|
@@ -154,7 +228,11 @@ export function builtinRoleToNumber(id: string) {
|
|
|
154
228
|
if (!role) {
|
|
155
229
|
break
|
|
156
230
|
}
|
|
157
|
-
|
|
231
|
+
if (Array.isArray(role.inherits)) {
|
|
232
|
+
throw new Error("Built-in roles don't support multi-inheritance")
|
|
233
|
+
} else {
|
|
234
|
+
role = builtins[role.inherits!]
|
|
235
|
+
}
|
|
158
236
|
count++
|
|
159
237
|
} while (role !== null)
|
|
160
238
|
return count
|
|
@@ -170,12 +248,31 @@ export async function roleToNumber(id: string) {
|
|
|
170
248
|
const hierarchy = (await getUserRoleHierarchy(id, {
|
|
171
249
|
defaultPublic: true,
|
|
172
250
|
})) as RoleDoc[]
|
|
173
|
-
|
|
174
|
-
if (role
|
|
251
|
+
const findNumber = (role: RoleDoc): number => {
|
|
252
|
+
if (!role.inherits) {
|
|
253
|
+
return 0
|
|
254
|
+
}
|
|
255
|
+
if (Array.isArray(role.inherits)) {
|
|
256
|
+
// find the built-in roles, get their number, sort it, then get the last one
|
|
257
|
+
const highestBuiltin: number | undefined = role.inherits
|
|
258
|
+
.map(roleId => {
|
|
259
|
+
const foundRole = hierarchy.find(role => role._id === roleId)
|
|
260
|
+
if (foundRole) {
|
|
261
|
+
return findNumber(foundRole) + 1
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
.filter(number => number)
|
|
265
|
+
.sort()
|
|
266
|
+
.pop()
|
|
267
|
+
if (highestBuiltin != undefined) {
|
|
268
|
+
return highestBuiltin
|
|
269
|
+
}
|
|
270
|
+
} else if (isBuiltin(role.inherits)) {
|
|
175
271
|
return builtinRoleToNumber(role.inherits) + 1
|
|
176
272
|
}
|
|
273
|
+
return 0
|
|
177
274
|
}
|
|
178
|
-
return
|
|
275
|
+
return Math.max(...hierarchy.map(findNumber))
|
|
179
276
|
}
|
|
180
277
|
|
|
181
278
|
/**
|
|
@@ -193,17 +290,31 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string {
|
|
|
193
290
|
: roleId1
|
|
194
291
|
}
|
|
195
292
|
|
|
293
|
+
export function compareRoleIds(roleId1: string, roleId2: string) {
|
|
294
|
+
// make sure both role IDs are prefixed correctly
|
|
295
|
+
return prefixRoleID(roleId1) === prefixRoleID(roleId2)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function externalRole(role: RoleDoc): RoleDoc {
|
|
299
|
+
let _id: string | undefined
|
|
300
|
+
if (role._id) {
|
|
301
|
+
_id = getExternalRoleID(role._id)
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
...role,
|
|
305
|
+
_id,
|
|
306
|
+
inherits: getExternalRoleIDs(role.inherits, role.version),
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
196
310
|
/**
|
|
197
|
-
*
|
|
198
|
-
* to check if the role inherits any others.
|
|
199
|
-
* @param roleId The level ID to lookup.
|
|
200
|
-
* @param opts options for the function, like whether to halt errors, instead return public.
|
|
201
|
-
* @returns The role object, which may contain an "inherits" property.
|
|
311
|
+
* Given a list of roles, this will pick the role out, accounting for built ins.
|
|
202
312
|
*/
|
|
203
|
-
export
|
|
313
|
+
export function findRole(
|
|
204
314
|
roleId: string,
|
|
315
|
+
roles: RoleDoc[],
|
|
205
316
|
opts?: { defaultPublic?: boolean }
|
|
206
|
-
):
|
|
317
|
+
): RoleDoc | undefined {
|
|
207
318
|
// built in roles mostly come from the in-code implementation,
|
|
208
319
|
// but can be extended by a doc stored about them (e.g. permissions)
|
|
209
320
|
let role: RoleDoc | undefined = getBuiltinRole(roleId)
|
|
@@ -211,22 +322,53 @@ export async function getRole(
|
|
|
211
322
|
// make sure has the prefix (if it has it then it won't be added)
|
|
212
323
|
roleId = prefixRoleID(roleId)
|
|
213
324
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
325
|
+
const dbRole = roles.find(
|
|
326
|
+
role => role._id && compareRoleIds(role._id, roleId)
|
|
327
|
+
)
|
|
328
|
+
if (!dbRole && !isBuiltin(roleId) && opts?.defaultPublic) {
|
|
329
|
+
return cloneDeep(BUILTIN_ROLES.PUBLIC)
|
|
330
|
+
}
|
|
331
|
+
// combine the roles
|
|
332
|
+
role = Object.assign(role || {}, dbRole)
|
|
333
|
+
// finalise the ID
|
|
334
|
+
if (role?._id) {
|
|
335
|
+
role._id = getExternalRoleID(role._id, role.version)
|
|
336
|
+
}
|
|
337
|
+
return Object.keys(role).length === 0 ? undefined : role
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Gets the role object, this is mainly useful for two purposes, to check if the level exists and
|
|
342
|
+
* to check if the role inherits any others.
|
|
343
|
+
* @param roleId The level ID to lookup.
|
|
344
|
+
* @param opts options for the function, like whether to halt errors, instead return public.
|
|
345
|
+
* @returns The role object, which may contain an "inherits" property.
|
|
346
|
+
*/
|
|
347
|
+
export async function getRole(
|
|
348
|
+
roleId: string,
|
|
349
|
+
opts?: { defaultPublic?: boolean }
|
|
350
|
+
): Promise<RoleDoc | undefined> {
|
|
351
|
+
const db = getAppDB()
|
|
352
|
+
const roleList = []
|
|
353
|
+
if (!isBuiltin(roleId)) {
|
|
354
|
+
const role = await db.tryGet<RoleDoc>(getDBRoleID(roleId))
|
|
355
|
+
if (role) {
|
|
356
|
+
roleList.push(role)
|
|
227
357
|
}
|
|
228
358
|
}
|
|
229
|
-
return
|
|
359
|
+
return findRole(roleId, roleList, opts)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function saveRoles(roles: RoleDoc[]) {
|
|
363
|
+
const db = getAppDB()
|
|
364
|
+
await db.bulkDocs(
|
|
365
|
+
roles
|
|
366
|
+
.filter(role => role._id)
|
|
367
|
+
.map(role => ({
|
|
368
|
+
...role,
|
|
369
|
+
_id: prefixRoleID(role._id!),
|
|
370
|
+
}))
|
|
371
|
+
)
|
|
230
372
|
}
|
|
231
373
|
|
|
232
374
|
/**
|
|
@@ -236,24 +378,18 @@ async function getAllUserRoles(
|
|
|
236
378
|
userRoleId: string,
|
|
237
379
|
opts?: { defaultPublic?: boolean }
|
|
238
380
|
): Promise<RoleDoc[]> {
|
|
381
|
+
const allRoles = await getAllRoles()
|
|
239
382
|
// admins have access to all roles
|
|
240
383
|
if (userRoleId === BUILTIN_IDS.ADMIN) {
|
|
241
|
-
return
|
|
384
|
+
return allRoles
|
|
242
385
|
}
|
|
243
|
-
|
|
244
|
-
let roles = currentRole ? [currentRole] : []
|
|
245
|
-
let roleIds = [userRoleId]
|
|
386
|
+
|
|
246
387
|
// get all the inherited roles
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
roleIds.push(currentRole.inherits)
|
|
253
|
-
currentRole = await getRole(currentRole.inherits)
|
|
254
|
-
if (currentRole) {
|
|
255
|
-
roles.push(currentRole)
|
|
256
|
-
}
|
|
388
|
+
const foundRole = findRole(userRoleId, allRoles, opts)
|
|
389
|
+
let roles: RoleDoc[] = []
|
|
390
|
+
if (foundRole) {
|
|
391
|
+
const traversal = new RoleHierarchyTraversal(allRoles, opts)
|
|
392
|
+
roles = traversal.walk(foundRole)
|
|
257
393
|
}
|
|
258
394
|
return roles
|
|
259
395
|
}
|
|
@@ -319,7 +455,7 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
|
|
319
455
|
}
|
|
320
456
|
return internal(appDB)
|
|
321
457
|
}
|
|
322
|
-
async function internal(db:
|
|
458
|
+
async function internal(db: Database | undefined) {
|
|
323
459
|
let roles: RoleDoc[] = []
|
|
324
460
|
if (db) {
|
|
325
461
|
const body = await db.allDocs(
|
|
@@ -334,8 +470,26 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
|
|
334
470
|
}
|
|
335
471
|
const builtinRoles = getBuiltinRoles()
|
|
336
472
|
|
|
473
|
+
// exclude internal roles like builder
|
|
474
|
+
let externalBuiltinRoles = []
|
|
475
|
+
|
|
476
|
+
if (!db || (await shouldIncludePowerRole(db))) {
|
|
477
|
+
externalBuiltinRoles = [
|
|
478
|
+
BUILTIN_IDS.ADMIN,
|
|
479
|
+
BUILTIN_IDS.POWER,
|
|
480
|
+
BUILTIN_IDS.BASIC,
|
|
481
|
+
BUILTIN_IDS.PUBLIC,
|
|
482
|
+
]
|
|
483
|
+
} else {
|
|
484
|
+
externalBuiltinRoles = [
|
|
485
|
+
BUILTIN_IDS.ADMIN,
|
|
486
|
+
BUILTIN_IDS.BASIC,
|
|
487
|
+
BUILTIN_IDS.PUBLIC,
|
|
488
|
+
]
|
|
489
|
+
}
|
|
490
|
+
|
|
337
491
|
// need to combine builtin with any DB record of them (for sake of permissions)
|
|
338
|
-
for (let builtinRoleId of
|
|
492
|
+
for (let builtinRoleId of externalBuiltinRoles) {
|
|
339
493
|
const builtinRole = builtinRoles[builtinRoleId]
|
|
340
494
|
const dbBuiltin = roles.filter(
|
|
341
495
|
dbRole =>
|
|
@@ -366,6 +520,18 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
|
|
366
520
|
}
|
|
367
521
|
}
|
|
368
522
|
|
|
523
|
+
async function shouldIncludePowerRole(db: Database) {
|
|
524
|
+
const app = await db.tryGet<App>(DocumentType.APP_METADATA)
|
|
525
|
+
const creationVersion = app?.creationVersion
|
|
526
|
+
if (!creationVersion || !semver.valid(creationVersion)) {
|
|
527
|
+
// Old apps don't have creationVersion, so we should include it for backward compatibility
|
|
528
|
+
return true
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const isGreaterThan3x = semver.gte(creationVersion, "3.0.0")
|
|
532
|
+
return !isGreaterThan3x
|
|
533
|
+
}
|
|
534
|
+
|
|
369
535
|
export class AccessController {
|
|
370
536
|
userHierarchies: { [key: string]: string[] }
|
|
371
537
|
constructor() {
|
|
@@ -390,7 +556,10 @@ export class AccessController {
|
|
|
390
556
|
this.userHierarchies[userRoleId] = roleIds
|
|
391
557
|
}
|
|
392
558
|
|
|
393
|
-
return
|
|
559
|
+
return (
|
|
560
|
+
roleIds?.find(roleId => compareRoleIds(roleId, tryingRoleId)) !==
|
|
561
|
+
undefined
|
|
562
|
+
)
|
|
394
563
|
}
|
|
395
564
|
|
|
396
565
|
async checkScreensAccess(screens: Screen[], userRoleId: string) {
|
|
@@ -432,7 +601,7 @@ export function getDBRoleID(roleName: string) {
|
|
|
432
601
|
export function getExternalRoleID(roleId: string, version?: string) {
|
|
433
602
|
// for built-in roles we want to remove the DB role ID element (role_)
|
|
434
603
|
if (
|
|
435
|
-
roleId.startsWith(DocumentType.ROLE) &&
|
|
604
|
+
roleId.startsWith(`${DocumentType.ROLE}${SEPARATOR}`) &&
|
|
436
605
|
(isBuiltin(roleId) || version === RoleIDVersion.NAME)
|
|
437
606
|
) {
|
|
438
607
|
const parts = roleId.split(SEPARATOR)
|
|
@@ -441,3 +610,16 @@ export function getExternalRoleID(roleId: string, version?: string) {
|
|
|
441
610
|
}
|
|
442
611
|
return roleId
|
|
443
612
|
}
|
|
613
|
+
|
|
614
|
+
export function getExternalRoleIDs(
|
|
615
|
+
roleIds: string | string[] | undefined,
|
|
616
|
+
version?: string
|
|
617
|
+
) {
|
|
618
|
+
if (!roleIds) {
|
|
619
|
+
return roleIds
|
|
620
|
+
} else if (typeof roleIds === "string") {
|
|
621
|
+
return getExternalRoleID(roleIds, version)
|
|
622
|
+
} else {
|
|
623
|
+
return roleIds.map(roleId => getExternalRoleID(roleId, version))
|
|
624
|
+
}
|
|
625
|
+
}
|