Merge branch 'master' of github.com:budibase/budibase into budi-8067-sql-testing-more-datasource-types
This commit is contained in:
commit
b8f061df68
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.21.7",
|
"version": "2.21.8",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -49,7 +49,8 @@
|
||||||
|
|
||||||
$: group = $groups.find(x => x._id === groupId)
|
$: group = $groups.find(x => x._id === groupId)
|
||||||
$: isScimGroup = group?.scimInfo?.isSync
|
$: isScimGroup = group?.scimInfo?.isSync
|
||||||
$: readonly = !sdk.users.isAdmin($auth.user) || isScimGroup
|
$: isAdmin = sdk.users.isAdmin($auth.user)
|
||||||
|
$: readonly = !isAdmin || isScimGroup
|
||||||
$: groupApps = $apps
|
$: groupApps = $apps
|
||||||
.filter(app =>
|
.filter(app =>
|
||||||
groups.actions
|
groups.actions
|
||||||
|
@ -123,14 +124,18 @@
|
||||||
<span slot="control">
|
<span slot="control">
|
||||||
<Icon hoverable name="More" />
|
<Icon hoverable name="More" />
|
||||||
</span>
|
</span>
|
||||||
<MenuItem icon="Refresh" on:click={() => editModal.show()}>
|
<MenuItem
|
||||||
|
icon="Refresh"
|
||||||
|
on:click={() => editModal.show()}
|
||||||
|
disabled={!isAdmin}
|
||||||
|
>
|
||||||
Edit
|
Edit
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<div title={isScimGroup && "Group synced from your AD"}>
|
<div title={isScimGroup && "Group synced from your AD"}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Delete"
|
icon="Delete"
|
||||||
on:click={() => deleteModal.show()}
|
on:click={() => deleteModal.show()}
|
||||||
disabled={isScimGroup}
|
disabled={readonly}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -139,7 +144,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<GroupUsers {groupId} {readonly} />
|
<GroupUsers {groupId} {readonly} {isScimGroup} />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
export let groupId
|
export let groupId
|
||||||
export let readonly
|
export let readonly
|
||||||
|
export let isScimGroup
|
||||||
|
|
||||||
let emailSearch
|
let emailSearch
|
||||||
let fetchGroupUsers
|
let fetchGroupUsers
|
||||||
|
@ -61,10 +62,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
{#if !readonly}
|
{#if isScimGroup}
|
||||||
<EditUserPicker {groupId} onUsersUpdated={fetchGroupUsers.getInitialData} />
|
|
||||||
{:else}
|
|
||||||
<ActiveDirectoryInfo text="Users synced from your AD" />
|
<ActiveDirectoryInfo text="Users synced from your AD" />
|
||||||
|
{:else if !readonly}
|
||||||
|
<EditUserPicker {groupId} onUsersUpdated={fetchGroupUsers.getInitialData} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="controls-right">
|
<div class="controls-right">
|
||||||
|
|
|
@ -35,7 +35,9 @@ export function createGroupsStore() {
|
||||||
get: getGroup,
|
get: getGroup,
|
||||||
|
|
||||||
save: async group => {
|
save: async group => {
|
||||||
const { _scimInfo, ...dataToSave } = group
|
const { ...dataToSave } = group
|
||||||
|
delete dataToSave.scimInfo
|
||||||
|
delete dataToSave.userGroups
|
||||||
const response = await API.saveGroup(dataToSave)
|
const response = await API.saveGroup(dataToSave)
|
||||||
group._id = response._id
|
group._id = response._id
|
||||||
group._rev = response._rev
|
group._rev = response._rev
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e565db07f6c51868087e88dfebde0328493443e6
|
Subproject commit c4c98ae70f2e936009250893898ecf11f4ddf2c3
|
|
@ -104,17 +104,79 @@ describe("/api/global/groups", () => {
|
||||||
expect(events.group.permissionsEdited).not.toBeCalled()
|
expect(events.group.permissionsEdited).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("destroy", () => {
|
describe("scim", () => {
|
||||||
it("should be able to delete a basic group", async () => {
|
async function createScimGroup() {
|
||||||
const group = structures.groups.UserGroup()
|
mocks.licenses.useScimIntegration()
|
||||||
let oldGroup = await config.api.groups.saveGroup(group)
|
await config.setSCIMConfig(true)
|
||||||
await config.api.groups.deleteGroup(
|
|
||||||
oldGroup.body._id,
|
|
||||||
oldGroup.body._rev
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(events.group.deleted).toBeCalledTimes(1)
|
const scimGroup = await config.api.scimGroupsAPI.post({
|
||||||
|
body: structures.scim.createGroupRequest({
|
||||||
|
displayName: generator.word(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { body: group } = await config.api.groups.find(scimGroup.id)
|
||||||
|
|
||||||
|
expect(group).toBeDefined()
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
it("update will not allow sending SCIM fields", async () => {
|
||||||
|
const group = await createScimGroup()
|
||||||
|
|
||||||
|
const updatedGroup: UserGroup = {
|
||||||
|
...group,
|
||||||
|
name: generator.word(),
|
||||||
|
}
|
||||||
|
await config.api.groups.saveGroup(updatedGroup, {
|
||||||
|
expect: {
|
||||||
|
message: 'Invalid body - "scimInfo" is not allowed',
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(events.group.updated).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("update will not amend the SCIM fields", async () => {
|
||||||
|
const group: UserGroup = await createScimGroup()
|
||||||
|
|
||||||
|
const updatedGroup: UserGroup = {
|
||||||
|
...group,
|
||||||
|
name: generator.word(),
|
||||||
|
scimInfo: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
await config.api.groups.saveGroup(updatedGroup, {
|
||||||
|
expect: 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(events.group.updated).toBeCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await config.api.groups.find(group._id!, {
|
||||||
|
expect: 200,
|
||||||
|
})
|
||||||
|
).body
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
...group,
|
||||||
|
name: updatedGroup.name,
|
||||||
|
scimInfo: group.scimInfo,
|
||||||
|
_rev: expect.any(String),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("destroy", () => {
|
||||||
|
it("should be able to delete a basic group", async () => {
|
||||||
|
const group = structures.groups.UserGroup()
|
||||||
|
let oldGroup = await config.api.groups.saveGroup(group)
|
||||||
|
await config.api.groups.deleteGroup(oldGroup.body._id, oldGroup.body._rev)
|
||||||
|
|
||||||
|
expect(events.group.deleted).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -147,7 +209,7 @@ describe("/api/global/groups", () => {
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from({ length: 30 }).map(async (_, i) => {
|
Array.from({ length: 30 }).map(async (_, i) => {
|
||||||
const email = `user${i}@example.com`
|
const email = `user${i}+${generator.guid()}@example.com`
|
||||||
const user = await config.api.users.saveUser({
|
const user = await config.api.users.saveUser({
|
||||||
...structures.users.user(),
|
...structures.users.user(),
|
||||||
email,
|
email,
|
||||||
|
@ -257,12 +319,16 @@ describe("/api/global/groups", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("update should return 200", async () => {
|
it("update should return forbidden", async () => {
|
||||||
await config.withUser(builder, async () => {
|
await config.withUser(builder, async () => {
|
||||||
await config.api.groups.updateGroupUsers(group._id!, {
|
await config.api.groups.updateGroupUsers(
|
||||||
add: [builder._id!],
|
group._id!,
|
||||||
remove: [],
|
{
|
||||||
})
|
add: [builder._id!],
|
||||||
|
remove: [],
|
||||||
|
},
|
||||||
|
{ expect: 403 }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ import tk from "timekeeper"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { generator, mocks, structures } from "@budibase/backend-core/tests"
|
import { generator, mocks, structures } from "@budibase/backend-core/tests"
|
||||||
import {
|
import {
|
||||||
|
CloudAccount,
|
||||||
ScimCreateUserRequest,
|
ScimCreateUserRequest,
|
||||||
ScimGroupResponse,
|
ScimGroupResponse,
|
||||||
ScimUpdateRequest,
|
ScimUpdateRequest,
|
||||||
|
@ -604,6 +605,25 @@ describe("scim", () => {
|
||||||
|
|
||||||
expect(events.user.deleted).toBeCalledTimes(1)
|
expect(events.user.deleted).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("an account holder cannot be removed even when synched", async () => {
|
||||||
|
const account: CloudAccount = {
|
||||||
|
...structures.accounts.account(),
|
||||||
|
budibaseUserId: user.id,
|
||||||
|
email: user.emails![0].value,
|
||||||
|
}
|
||||||
|
mocks.accounts.getAccount.mockResolvedValue(account)
|
||||||
|
|
||||||
|
await deleteScimUser(user.id, {
|
||||||
|
expect: {
|
||||||
|
message: "Account holder cannot be deleted",
|
||||||
|
status: 400,
|
||||||
|
error: { code: "http" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.scimUsersAPI.find(user.id, { expect: 200 })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,10 @@ export class GroupsAPI extends TestAPI {
|
||||||
super(config)
|
super(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveGroup = (group: UserGroup, { expect } = { expect: 200 }) => {
|
saveGroup = (
|
||||||
|
group: UserGroup,
|
||||||
|
{ expect }: { expect: number | object } = { expect: 200 }
|
||||||
|
) => {
|
||||||
return this.request
|
return this.request
|
||||||
.post(`/api/global/groups`)
|
.post(`/api/global/groups`)
|
||||||
.send(group)
|
.send(group)
|
||||||
|
@ -44,14 +47,15 @@ export class GroupsAPI extends TestAPI {
|
||||||
|
|
||||||
updateGroupUsers = (
|
updateGroupUsers = (
|
||||||
id: string,
|
id: string,
|
||||||
body: { add: string[]; remove: string[] }
|
body: { add: string[]; remove: string[] },
|
||||||
|
{ expect } = { expect: 200 }
|
||||||
) => {
|
) => {
|
||||||
return this.request
|
return this.request
|
||||||
.post(`/api/global/groups/${id}/users`)
|
.post(`/api/global/groups/${id}/users`)
|
||||||
.send(body)
|
.send(body)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(expect)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch = ({ expect } = { expect: 200 }) => {
|
fetch = ({ expect } = { expect: 200 }) => {
|
||||||
|
@ -61,4 +65,12 @@ export class GroupsAPI extends TestAPI {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(expect)
|
.expect(expect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
find = (id: string, { expect } = { expect: 200 }) => {
|
||||||
|
return this.request
|
||||||
|
.get(`/api/global/groups/${id}`)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(expect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import TestConfiguration from "../../TestConfiguration"
|
import TestConfiguration from "../../TestConfiguration"
|
||||||
import { TestAPI } from "../base"
|
import { TestAPI } from "../base"
|
||||||
|
|
||||||
const defaultConfig = {
|
const defaultConfig: RequestSettings = {
|
||||||
expect: 200,
|
expect: 200,
|
||||||
setHeaders: true,
|
setHeaders: true,
|
||||||
skipContentTypeCheck: false,
|
skipContentTypeCheck: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RequestSettings = typeof defaultConfig
|
export type RequestSettings = {
|
||||||
|
expect: number | object
|
||||||
|
setHeaders: boolean
|
||||||
|
skipContentTypeCheck: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class ScimTestAPI extends TestAPI {
|
export abstract class ScimTestAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
|
|
Loading…
Reference in New Issue