@budibase/worker 2.5.5 → 2.5.6-alpha.10
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/jest.config.ts +5 -5
- package/nodemon.json +6 -2
- package/package.json +9 -8
- package/scripts/dev/manage.js +1 -0
- package/src/api/controllers/global/users.ts +2 -2
- package/src/api/index.ts +1 -1
- package/src/api/routes/global/configs.ts +9 -1
- package/src/api/routes/global/tests/scim.spec.ts +1029 -0
- package/src/api/routes/global/tests/users.spec.ts +10 -4
- package/src/api/routes/index.ts +4 -6
- package/src/environment.ts +0 -1
- package/src/index.ts +7 -7
- package/src/initPro.ts +13 -0
- package/src/middleware/handleScimBody.ts +12 -0
- package/src/sdk/users/users.ts +7 -54
- package/src/tests/TestConfiguration.ts +50 -1
- package/src/tests/api/index.ts +7 -0
- package/src/tests/api/scim/groups.ts +101 -0
- package/src/tests/api/scim/shared.ts +44 -0
- package/src/tests/api/scim/users.ts +94 -0
- package/src/tests/jestEnv.ts +0 -1
- package/src/tests/jestSetup.ts +0 -2
- package/tsconfig.json +4 -14
- package/src/elasticApm.ts +0 -10
- package/src/tests/logging.ts +0 -34
- package/src/utilities/appService.ts +0 -46
|
@@ -48,7 +48,7 @@ describe("/api/global/users", () => {
|
|
|
48
48
|
400
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
-
expect(res.body.message).toBe(
|
|
51
|
+
expect(res.body.message).toBe(`Unavailable`)
|
|
52
52
|
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
53
53
|
expect(code).toBeUndefined()
|
|
54
54
|
expect(events.user.invited).toBeCalledTimes(0)
|
|
@@ -225,7 +225,9 @@ describe("/api/global/users", () => {
|
|
|
225
225
|
|
|
226
226
|
const response = await config.api.users.saveUser(user, 400)
|
|
227
227
|
|
|
228
|
-
expect(response.body.message).toBe(
|
|
228
|
+
expect(response.body.message).toBe(
|
|
229
|
+
`Email already in use: '${user.email}'`
|
|
230
|
+
)
|
|
229
231
|
expect(events.user.created).toBeCalledTimes(0)
|
|
230
232
|
})
|
|
231
233
|
|
|
@@ -237,7 +239,9 @@ describe("/api/global/users", () => {
|
|
|
237
239
|
delete user._id
|
|
238
240
|
const response = await config.api.users.saveUser(user, 400)
|
|
239
241
|
|
|
240
|
-
expect(response.body.message).toBe(
|
|
242
|
+
expect(response.body.message).toBe(
|
|
243
|
+
`Email already in use: '${user.email}'`
|
|
244
|
+
)
|
|
241
245
|
expect(events.user.created).toBeCalledTimes(0)
|
|
242
246
|
})
|
|
243
247
|
})
|
|
@@ -249,7 +253,9 @@ describe("/api/global/users", () => {
|
|
|
249
253
|
|
|
250
254
|
const response = await config.api.users.saveUser(user, 400)
|
|
251
255
|
|
|
252
|
-
expect(response.body.message).toBe(
|
|
256
|
+
expect(response.body.message).toBe(
|
|
257
|
+
`Email already in use: '${user.email}'`
|
|
258
|
+
)
|
|
253
259
|
expect(events.user.created).toBeCalledTimes(0)
|
|
254
260
|
})
|
|
255
261
|
|
package/src/api/routes/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Router from "@koa/router"
|
|
2
|
-
import { api } from "@budibase/pro"
|
|
2
|
+
import { api as pro } from "@budibase/pro"
|
|
3
3
|
import userRoutes from "./global/users"
|
|
4
4
|
import configRoutes from "./global/configs"
|
|
5
5
|
import workspaceRoutes from "./global/workspaces"
|
|
@@ -17,9 +17,6 @@ import migrationRoutes from "./system/migrations"
|
|
|
17
17
|
import accountRoutes from "./system/accounts"
|
|
18
18
|
import restoreRoutes from "./system/restore"
|
|
19
19
|
|
|
20
|
-
let userGroupRoutes = api.groups
|
|
21
|
-
let auditLogRoutes = api.auditLogs
|
|
22
|
-
|
|
23
20
|
export const routes: Router[] = [
|
|
24
21
|
configRoutes,
|
|
25
22
|
userRoutes,
|
|
@@ -33,10 +30,11 @@ export const routes: Router[] = [
|
|
|
33
30
|
statusRoutes,
|
|
34
31
|
selfRoutes,
|
|
35
32
|
licenseRoutes,
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
pro.groups,
|
|
34
|
+
pro.auditLogs,
|
|
38
35
|
migrationRoutes,
|
|
39
36
|
accountRoutes,
|
|
40
37
|
restoreRoutes,
|
|
41
38
|
eventRoutes,
|
|
39
|
+
pro.scim,
|
|
42
40
|
]
|
package/src/environment.ts
CHANGED
|
@@ -47,7 +47,6 @@ const environment = {
|
|
|
47
47
|
// flags
|
|
48
48
|
NODE_ENV: process.env.NODE_ENV,
|
|
49
49
|
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
|
|
50
|
-
LOG_LEVEL: process.env.LOG_LEVEL,
|
|
51
50
|
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
|
52
51
|
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
|
53
52
|
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,6 @@ if (process.env.DD_APM_ENABLED) {
|
|
|
2
2
|
require("./ddApm")
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
if (process.env.ELASTIC_APM_ENABLED) {
|
|
6
|
-
require("./elasticApm")
|
|
7
|
-
}
|
|
8
|
-
|
|
9
5
|
// need to load environment first
|
|
10
6
|
import env from "./environment"
|
|
11
7
|
import { Scope } from "@sentry/node"
|
|
@@ -31,10 +27,11 @@ import api from "./api"
|
|
|
31
27
|
import * as redis from "./utilities/redis"
|
|
32
28
|
const Sentry = require("@sentry/node")
|
|
33
29
|
const koaSession = require("koa-session")
|
|
34
|
-
const logger = require("koa-pino-logger")
|
|
35
30
|
const { userAgent } = require("koa-useragent")
|
|
36
31
|
|
|
37
32
|
import destroyable from "server-destroy"
|
|
33
|
+
import { initPro } from "./initPro"
|
|
34
|
+
import { handleScimBody } from "./middleware/handleScimBody"
|
|
38
35
|
|
|
39
36
|
// configure events to use the pro audit log write
|
|
40
37
|
// can't integrate directly into backend-core due to cyclic issues
|
|
@@ -54,10 +51,12 @@ const app: Application = new Koa()
|
|
|
54
51
|
app.keys = ["secret", "key"]
|
|
55
52
|
|
|
56
53
|
// set up top level koa middleware
|
|
54
|
+
app.use(handleScimBody)
|
|
57
55
|
app.use(koaBody({ multipart: true }))
|
|
56
|
+
|
|
58
57
|
app.use(koaSession(app))
|
|
59
|
-
app.use(middleware.
|
|
60
|
-
app.use(
|
|
58
|
+
app.use(middleware.correlation)
|
|
59
|
+
app.use(middleware.pino)
|
|
61
60
|
app.use(userAgent)
|
|
62
61
|
|
|
63
62
|
// authentication
|
|
@@ -108,6 +107,7 @@ const shutdown = () => {
|
|
|
108
107
|
|
|
109
108
|
export default server.listen(parseInt(env.PORT || "4002"), async () => {
|
|
110
109
|
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
|
110
|
+
await initPro()
|
|
111
111
|
await redis.init()
|
|
112
112
|
})
|
|
113
113
|
|
package/src/initPro.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { sdk as proSdk } from "@budibase/pro"
|
|
2
|
+
import * as userSdk from "./sdk/users"
|
|
3
|
+
|
|
4
|
+
export const initPro = async () => {
|
|
5
|
+
await proSdk.init({
|
|
6
|
+
scimUserServiceConfig: {
|
|
7
|
+
functions: {
|
|
8
|
+
saveUser: userSdk.save,
|
|
9
|
+
removeUser: (id: string) => userSdk.destroy(id),
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Ctx } from "@budibase/types"
|
|
2
|
+
|
|
3
|
+
export const handleScimBody = (ctx: Ctx, next: any) => {
|
|
4
|
+
var type = ctx.req.headers["content-type"] || ""
|
|
5
|
+
type = type.split(";")[0]
|
|
6
|
+
|
|
7
|
+
if (type === "application/scim+json") {
|
|
8
|
+
ctx.req.headers["content-type"] = "application/json"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return next()
|
|
12
|
+
}
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import env from "../../environment"
|
|
2
|
-
import * as apps from "../../utilities/appService"
|
|
3
2
|
import * as eventHelpers from "./events"
|
|
4
3
|
import {
|
|
5
4
|
accounts,
|
|
@@ -15,6 +14,8 @@ import {
|
|
|
15
14
|
utils,
|
|
16
15
|
ViewName,
|
|
17
16
|
env as coreEnv,
|
|
17
|
+
context,
|
|
18
|
+
EmailUnavailableError,
|
|
18
19
|
} from "@budibase/backend-core"
|
|
19
20
|
import {
|
|
20
21
|
AccountMetadata,
|
|
@@ -28,7 +29,6 @@ import {
|
|
|
28
29
|
PlatformUser,
|
|
29
30
|
PlatformUserByEmail,
|
|
30
31
|
RowResponse,
|
|
31
|
-
SearchUsersRequest,
|
|
32
32
|
User,
|
|
33
33
|
SaveUserOpts,
|
|
34
34
|
Account,
|
|
@@ -38,8 +38,6 @@ import { EmailTemplatePurpose } from "../../constants"
|
|
|
38
38
|
import * as pro from "@budibase/pro"
|
|
39
39
|
import * as accountSdk from "../accounts"
|
|
40
40
|
|
|
41
|
-
const PAGE_LIMIT = 8
|
|
42
|
-
|
|
43
41
|
export const allUsers = async () => {
|
|
44
42
|
const db = tenancy.getGlobalDB()
|
|
45
43
|
const response = await db.allDocs(
|
|
@@ -69,43 +67,6 @@ export const getUsersByAppAccess = async (appId?: string) => {
|
|
|
69
67
|
return response
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
export const paginatedUsers = async ({
|
|
73
|
-
page,
|
|
74
|
-
email,
|
|
75
|
-
appId,
|
|
76
|
-
}: SearchUsersRequest = {}) => {
|
|
77
|
-
const db = tenancy.getGlobalDB()
|
|
78
|
-
// get one extra document, to have the next page
|
|
79
|
-
const opts: any = {
|
|
80
|
-
include_docs: true,
|
|
81
|
-
limit: PAGE_LIMIT + 1,
|
|
82
|
-
}
|
|
83
|
-
// add a startkey if the page was specified (anchor)
|
|
84
|
-
if (page) {
|
|
85
|
-
opts.startkey = page
|
|
86
|
-
}
|
|
87
|
-
// property specifies what to use for the page/anchor
|
|
88
|
-
let userList,
|
|
89
|
-
property = "_id",
|
|
90
|
-
getKey
|
|
91
|
-
if (appId) {
|
|
92
|
-
userList = await usersCore.searchGlobalUsersByApp(appId, opts)
|
|
93
|
-
getKey = (doc: any) => usersCore.getGlobalUserByAppPage(appId, doc)
|
|
94
|
-
} else if (email) {
|
|
95
|
-
userList = await usersCore.searchGlobalUsersByEmail(email, opts)
|
|
96
|
-
property = "email"
|
|
97
|
-
} else {
|
|
98
|
-
// no search, query allDocs
|
|
99
|
-
const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
|
|
100
|
-
userList = response.rows.map((row: any) => row.doc)
|
|
101
|
-
}
|
|
102
|
-
return dbUtils.pagination(userList, PAGE_LIMIT, {
|
|
103
|
-
paginate: true,
|
|
104
|
-
property,
|
|
105
|
-
getKey,
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
|
|
109
70
|
export async function getUserByEmail(email: string) {
|
|
110
71
|
return usersCore.getGlobalUserByEmail(email)
|
|
111
72
|
}
|
|
@@ -198,7 +159,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
|
|
|
198
159
|
if (env.MULTI_TENANCY) {
|
|
199
160
|
const tenantUser = await getPlatformUser(email)
|
|
200
161
|
if (tenantUser != null && tenantUser.tenantId !== tenantId) {
|
|
201
|
-
throw
|
|
162
|
+
throw new EmailUnavailableError(email)
|
|
202
163
|
}
|
|
203
164
|
}
|
|
204
165
|
|
|
@@ -206,7 +167,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
|
|
|
206
167
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
|
207
168
|
const account = await accounts.getAccount(email)
|
|
208
169
|
if (account && account.verified && account.tenantId !== tenantId) {
|
|
209
|
-
throw
|
|
170
|
+
throw new EmailUnavailableError(email)
|
|
210
171
|
}
|
|
211
172
|
}
|
|
212
173
|
}
|
|
@@ -277,7 +238,7 @@ export const save = async (
|
|
|
277
238
|
// no id was specified - load from email instead
|
|
278
239
|
dbUser = await usersCore.getGlobalUserByEmail(email)
|
|
279
240
|
if (dbUser && dbUser._id !== _id) {
|
|
280
|
-
throw
|
|
241
|
+
throw new EmailUnavailableError(email)
|
|
281
242
|
}
|
|
282
243
|
}
|
|
283
244
|
|
|
@@ -317,9 +278,6 @@ export const save = async (
|
|
|
317
278
|
await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
|
|
318
279
|
await cache.user.invalidateUser(response.id)
|
|
319
280
|
|
|
320
|
-
// let server know to sync user
|
|
321
|
-
await apps.syncUserInApps(_id, dbUser)
|
|
322
|
-
|
|
323
281
|
await Promise.all(groupPromises)
|
|
324
282
|
|
|
325
283
|
// finally returned the saved user from the db
|
|
@@ -469,7 +427,6 @@ export const bulkCreate = async (
|
|
|
469
427
|
// instead of relying on looping tenant creation
|
|
470
428
|
await platform.users.addUser(tenantId, user._id, user.email)
|
|
471
429
|
await eventHelpers.handleSaveEvents(user, undefined)
|
|
472
|
-
await apps.syncUserInApps(user._id)
|
|
473
430
|
}
|
|
474
431
|
|
|
475
432
|
const saved = usersToBulkSave.map(user => {
|
|
@@ -583,7 +540,7 @@ export const bulkDelete = async (
|
|
|
583
540
|
return response
|
|
584
541
|
}
|
|
585
542
|
|
|
586
|
-
export const destroy = async (id: string
|
|
543
|
+
export const destroy = async (id: string) => {
|
|
587
544
|
const db = tenancy.getGlobalDB()
|
|
588
545
|
const dbUser = (await db.get(id)) as User
|
|
589
546
|
const userId = dbUser._id as string
|
|
@@ -593,7 +550,7 @@ export const destroy = async (id: string, currentUser: any) => {
|
|
|
593
550
|
const email = dbUser.email
|
|
594
551
|
const account = await accounts.getAccount(email)
|
|
595
552
|
if (account) {
|
|
596
|
-
if (
|
|
553
|
+
if (dbUser.userId === context.getIdentity()!._id) {
|
|
597
554
|
throw new HTTPError('Please visit "Account" to delete this user', 400)
|
|
598
555
|
} else {
|
|
599
556
|
throw new HTTPError("Account holder cannot be deleted", 400)
|
|
@@ -608,8 +565,6 @@ export const destroy = async (id: string, currentUser: any) => {
|
|
|
608
565
|
await eventHelpers.handleDeleteEvents(dbUser)
|
|
609
566
|
await cache.user.invalidateUser(userId)
|
|
610
567
|
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
|
611
|
-
// let server know to sync user
|
|
612
|
-
await apps.syncUserInApps(userId, dbUser)
|
|
613
568
|
}
|
|
614
569
|
|
|
615
570
|
const bulkDeleteProcessing = async (dbUser: User) => {
|
|
@@ -618,8 +573,6 @@ const bulkDeleteProcessing = async (dbUser: User) => {
|
|
|
618
573
|
await eventHelpers.handleDeleteEvents(dbUser)
|
|
619
574
|
await cache.user.invalidateUser(userId)
|
|
620
575
|
await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
|
|
621
|
-
// let server know to sync user
|
|
622
|
-
await apps.syncUserInApps(userId, dbUser)
|
|
623
576
|
}
|
|
624
577
|
|
|
625
578
|
export const invite = async (
|
|
@@ -20,9 +20,18 @@ import {
|
|
|
20
20
|
auth,
|
|
21
21
|
constants,
|
|
22
22
|
env as coreEnv,
|
|
23
|
+
db as dbCore,
|
|
24
|
+
encryption,
|
|
25
|
+
utils,
|
|
23
26
|
} from "@budibase/backend-core"
|
|
24
27
|
import structures, { CSRF_TOKEN } from "./structures"
|
|
25
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
SaveUserResponse,
|
|
30
|
+
User,
|
|
31
|
+
AuthToken,
|
|
32
|
+
SCIMConfig,
|
|
33
|
+
ConfigType,
|
|
34
|
+
} from "@budibase/types"
|
|
26
35
|
import API from "./api"
|
|
27
36
|
|
|
28
37
|
class TestConfiguration {
|
|
@@ -31,6 +40,7 @@ class TestConfiguration {
|
|
|
31
40
|
api: API
|
|
32
41
|
tenantId: string
|
|
33
42
|
user?: User
|
|
43
|
+
apiKey?: string
|
|
34
44
|
userPassword = "test"
|
|
35
45
|
|
|
36
46
|
constructor(opts: { openServer: boolean } = { openServer: true }) {
|
|
@@ -49,6 +59,12 @@ class TestConfiguration {
|
|
|
49
59
|
this.api = new API(this)
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
async useNewTenant() {
|
|
63
|
+
this.tenantId = structures.tenant.id()
|
|
64
|
+
|
|
65
|
+
await this.beforeAll()
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
getRequest() {
|
|
53
69
|
return this.request
|
|
54
70
|
}
|
|
@@ -201,6 +217,12 @@ class TestConfiguration {
|
|
|
201
217
|
return { [constants.Header.API_KEY]: coreEnv.INTERNAL_API_KEY }
|
|
202
218
|
}
|
|
203
219
|
|
|
220
|
+
bearerAPIHeaders() {
|
|
221
|
+
return {
|
|
222
|
+
[constants.Header.AUTHORIZATION]: `Bearer ${this.apiKey}`,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
204
226
|
adminOnlyResponse = () => {
|
|
205
227
|
return { message: "Admin user only endpoint.", status: 403 }
|
|
206
228
|
}
|
|
@@ -213,6 +235,20 @@ class TestConfiguration {
|
|
|
213
235
|
})
|
|
214
236
|
await context.doInTenant(this.tenantId!, async () => {
|
|
215
237
|
this.user = await this.createUser(user)
|
|
238
|
+
|
|
239
|
+
const db = context.getGlobalDB()
|
|
240
|
+
|
|
241
|
+
const id = dbCore.generateDevInfoID(this.user._id)
|
|
242
|
+
// TODO: dry
|
|
243
|
+
this.apiKey = encryption.encrypt(
|
|
244
|
+
`${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}`
|
|
245
|
+
)
|
|
246
|
+
const devInfo = {
|
|
247
|
+
_id: id,
|
|
248
|
+
userId: this.user._id,
|
|
249
|
+
apiKey: this.apiKey,
|
|
250
|
+
}
|
|
251
|
+
await db.put(devInfo)
|
|
216
252
|
})
|
|
217
253
|
}
|
|
218
254
|
|
|
@@ -305,6 +341,19 @@ class TestConfiguration {
|
|
|
305
341
|
controllers.config.save
|
|
306
342
|
)
|
|
307
343
|
}
|
|
344
|
+
|
|
345
|
+
// CONFIGS - SCIM
|
|
346
|
+
|
|
347
|
+
async setSCIMConfig(enabled: boolean) {
|
|
348
|
+
await this.deleteConfig(Config.SCIM)
|
|
349
|
+
const config: SCIMConfig = {
|
|
350
|
+
type: ConfigType.SCIM,
|
|
351
|
+
config: { enabled },
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
await this._req(config, null, controllers.config.save)
|
|
355
|
+
return config
|
|
356
|
+
}
|
|
308
357
|
}
|
|
309
358
|
|
|
310
359
|
export default TestConfiguration
|
package/src/tests/api/index.ts
CHANGED
|
@@ -15,6 +15,9 @@ import { RolesAPI } from "./roles"
|
|
|
15
15
|
import { TemplatesAPI } from "./templates"
|
|
16
16
|
import { LicenseAPI } from "./license"
|
|
17
17
|
import { AuditLogAPI } from "./auditLogs"
|
|
18
|
+
import { ScimUsersAPI } from "./scim/users"
|
|
19
|
+
import { ScimGroupsAPI } from "./scim/groups"
|
|
20
|
+
|
|
18
21
|
export default class API {
|
|
19
22
|
accounts: AccountAPI
|
|
20
23
|
auth: AuthAPI
|
|
@@ -32,6 +35,8 @@ export default class API {
|
|
|
32
35
|
templates: TemplatesAPI
|
|
33
36
|
license: LicenseAPI
|
|
34
37
|
auditLogs: AuditLogAPI
|
|
38
|
+
scimUsersAPI: ScimUsersAPI
|
|
39
|
+
scimGroupsAPI: ScimGroupsAPI
|
|
35
40
|
|
|
36
41
|
constructor(config: TestConfiguration) {
|
|
37
42
|
this.accounts = new AccountAPI(config)
|
|
@@ -50,5 +55,7 @@ export default class API {
|
|
|
50
55
|
this.templates = new TemplatesAPI(config)
|
|
51
56
|
this.license = new LicenseAPI(config)
|
|
52
57
|
this.auditLogs = new AuditLogAPI(config)
|
|
58
|
+
this.scimUsersAPI = new ScimUsersAPI(config)
|
|
59
|
+
this.scimGroupsAPI = new ScimGroupsAPI(config)
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ScimCreateGroupRequest,
|
|
3
|
+
ScimGroupListResponse,
|
|
4
|
+
ScimGroupResponse,
|
|
5
|
+
ScimUpdateRequest,
|
|
6
|
+
} from "@budibase/types"
|
|
7
|
+
import TestConfiguration from "../../TestConfiguration"
|
|
8
|
+
import { RequestSettings, ScimTestAPI } from "./shared"
|
|
9
|
+
|
|
10
|
+
export class ScimGroupsAPI extends ScimTestAPI {
|
|
11
|
+
constructor(config: TestConfiguration) {
|
|
12
|
+
super(config)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get = async (
|
|
16
|
+
requestSettings?: Partial<RequestSettings> & {
|
|
17
|
+
params?: {
|
|
18
|
+
startIndex?: number
|
|
19
|
+
pageSize?: number
|
|
20
|
+
filter?: string
|
|
21
|
+
excludedAttributes?: string
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
) => {
|
|
25
|
+
let url = `/api/global/scim/v2/groups?`
|
|
26
|
+
const params = requestSettings?.params
|
|
27
|
+
if (params?.pageSize) {
|
|
28
|
+
url += `count=${params.pageSize}&`
|
|
29
|
+
}
|
|
30
|
+
if (params?.startIndex) {
|
|
31
|
+
url += `startIndex=${params.startIndex}&`
|
|
32
|
+
}
|
|
33
|
+
if (params?.filter) {
|
|
34
|
+
url += `filter=${params.filter}&`
|
|
35
|
+
}
|
|
36
|
+
if (params?.excludedAttributes) {
|
|
37
|
+
url += `excludedAttributes=${params.excludedAttributes}&`
|
|
38
|
+
}
|
|
39
|
+
const res = await this.call(url, "get", requestSettings)
|
|
40
|
+
return res.body as ScimGroupListResponse
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
post = async (
|
|
44
|
+
{
|
|
45
|
+
body,
|
|
46
|
+
}: {
|
|
47
|
+
body: ScimCreateGroupRequest
|
|
48
|
+
},
|
|
49
|
+
requestSettings?: Partial<RequestSettings>
|
|
50
|
+
) => {
|
|
51
|
+
const res = await this.call(
|
|
52
|
+
`/api/global/scim/v2/groups`,
|
|
53
|
+
"post",
|
|
54
|
+
requestSettings,
|
|
55
|
+
body
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return res.body as ScimGroupResponse
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
find = async (
|
|
62
|
+
id: string,
|
|
63
|
+
requestSettings?: Partial<RequestSettings> & { qs?: string }
|
|
64
|
+
) => {
|
|
65
|
+
const res = await this.call(
|
|
66
|
+
`/api/global/scim/v2/groups/${id}?${requestSettings?.qs}`,
|
|
67
|
+
"get",
|
|
68
|
+
requestSettings
|
|
69
|
+
)
|
|
70
|
+
return res.body as ScimGroupResponse
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
delete = async (id: string, requestSettings?: Partial<RequestSettings>) => {
|
|
74
|
+
const res = await this.call(
|
|
75
|
+
`/api/global/scim/v2/groups/${id}`,
|
|
76
|
+
"delete",
|
|
77
|
+
requestSettings
|
|
78
|
+
)
|
|
79
|
+
return res.body as ScimGroupResponse
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
patch = async (
|
|
83
|
+
{
|
|
84
|
+
id,
|
|
85
|
+
body,
|
|
86
|
+
}: {
|
|
87
|
+
id: string
|
|
88
|
+
body: ScimUpdateRequest
|
|
89
|
+
},
|
|
90
|
+
requestSettings?: Partial<RequestSettings>
|
|
91
|
+
) => {
|
|
92
|
+
const res = await this.call(
|
|
93
|
+
`/api/global/scim/v2/groups/${id}`,
|
|
94
|
+
"patch",
|
|
95
|
+
requestSettings,
|
|
96
|
+
body
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return res.body as ScimGroupResponse
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import TestConfiguration from "../../TestConfiguration"
|
|
2
|
+
import { TestAPI } from "../base"
|
|
3
|
+
|
|
4
|
+
const defaultConfig = {
|
|
5
|
+
expect: 200,
|
|
6
|
+
setHeaders: true,
|
|
7
|
+
skipContentTypeCheck: false,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RequestSettings = typeof defaultConfig
|
|
11
|
+
|
|
12
|
+
export abstract class ScimTestAPI extends TestAPI {
|
|
13
|
+
constructor(config: TestConfiguration) {
|
|
14
|
+
super(config)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
call = (
|
|
18
|
+
url: string,
|
|
19
|
+
method: "get" | "post" | "patch" | "delete",
|
|
20
|
+
requestSettings?: Partial<RequestSettings>,
|
|
21
|
+
body?: object
|
|
22
|
+
) => {
|
|
23
|
+
const { expect, setHeaders } = { ...defaultConfig, ...requestSettings }
|
|
24
|
+
let request = this.request[method](url).expect(expect)
|
|
25
|
+
|
|
26
|
+
request = request.set(
|
|
27
|
+
"content-type",
|
|
28
|
+
"application/scim+json; charset=utf-8"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if (method !== "delete" && !requestSettings?.skipContentTypeCheck) {
|
|
32
|
+
request = request.expect("Content-Type", /json/)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (body) {
|
|
36
|
+
request = request.send(body)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (setHeaders) {
|
|
40
|
+
request = request.set(this.config.bearerAPIHeaders())
|
|
41
|
+
}
|
|
42
|
+
return request
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ScimUserListResponse,
|
|
3
|
+
ScimCreateUserRequest,
|
|
4
|
+
ScimUserResponse,
|
|
5
|
+
ScimUpdateRequest,
|
|
6
|
+
} from "@budibase/types"
|
|
7
|
+
import TestConfiguration from "../../TestConfiguration"
|
|
8
|
+
import { RequestSettings, ScimTestAPI } from "./shared"
|
|
9
|
+
|
|
10
|
+
export class ScimUsersAPI extends ScimTestAPI {
|
|
11
|
+
constructor(config: TestConfiguration) {
|
|
12
|
+
super(config)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get = async (
|
|
16
|
+
requestSettings?: Partial<RequestSettings> & {
|
|
17
|
+
params?: {
|
|
18
|
+
startIndex?: number
|
|
19
|
+
pageSize?: number
|
|
20
|
+
filter?: string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
) => {
|
|
24
|
+
let url = `/api/global/scim/v2/users?`
|
|
25
|
+
const params = requestSettings?.params
|
|
26
|
+
if (params?.pageSize) {
|
|
27
|
+
url += `count=${params.pageSize}&`
|
|
28
|
+
}
|
|
29
|
+
if (params?.startIndex) {
|
|
30
|
+
url += `startIndex=${params.startIndex}&`
|
|
31
|
+
}
|
|
32
|
+
if (params?.filter) {
|
|
33
|
+
url += `filter=${params.filter}&`
|
|
34
|
+
}
|
|
35
|
+
const res = await this.call(url, "get", requestSettings)
|
|
36
|
+
return res.body as ScimUserListResponse
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
find = async (id: string, requestSettings?: Partial<RequestSettings>) => {
|
|
40
|
+
const res = await this.call(
|
|
41
|
+
`/api/global/scim/v2/users/${id}`,
|
|
42
|
+
"get",
|
|
43
|
+
requestSettings
|
|
44
|
+
)
|
|
45
|
+
return res.body as ScimUserResponse
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
post = async (
|
|
49
|
+
{
|
|
50
|
+
body,
|
|
51
|
+
}: {
|
|
52
|
+
body: ScimCreateUserRequest
|
|
53
|
+
},
|
|
54
|
+
requestSettings?: Partial<RequestSettings>
|
|
55
|
+
) => {
|
|
56
|
+
const res = await this.call(
|
|
57
|
+
`/api/global/scim/v2/users`,
|
|
58
|
+
"post",
|
|
59
|
+
requestSettings,
|
|
60
|
+
body
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return res.body as ScimUserResponse
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
patch = async (
|
|
67
|
+
{
|
|
68
|
+
id,
|
|
69
|
+
body,
|
|
70
|
+
}: {
|
|
71
|
+
id: string
|
|
72
|
+
body: ScimUpdateRequest
|
|
73
|
+
},
|
|
74
|
+
requestSettings?: Partial<RequestSettings>
|
|
75
|
+
) => {
|
|
76
|
+
const res = await this.call(
|
|
77
|
+
`/api/global/scim/v2/users/${id}`,
|
|
78
|
+
"patch",
|
|
79
|
+
requestSettings,
|
|
80
|
+
body
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return res.body as ScimUserResponse
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
delete = async (id: string, requestSettings?: Partial<RequestSettings>) => {
|
|
87
|
+
const res = await this.call(
|
|
88
|
+
`/api/global/scim/v2/users/${id}`,
|
|
89
|
+
"delete",
|
|
90
|
+
requestSettings
|
|
91
|
+
)
|
|
92
|
+
return res.body as ScimUserResponse
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/tests/jestEnv.ts
CHANGED
|
@@ -2,7 +2,6 @@ process.env.SELF_HOSTED = "0"
|
|
|
2
2
|
process.env.NODE_ENV = "jest"
|
|
3
3
|
process.env.JWT_SECRET = "test-jwtsecret"
|
|
4
4
|
process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
|
|
5
|
-
process.env.ENABLE_4XX_HTTP_LOGGING = "0"
|
|
6
5
|
process.env.MULTI_TENANCY = "1"
|
|
7
6
|
process.env.MINIO_URL = "http://localhost"
|
|
8
7
|
process.env.MINIO_ACCESS_KEY = "test"
|