From 62c45e04c1793e60625563b61f014980b0a4ca93 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 17 Oct 2022 18:32:22 -0500 Subject: [PATCH 01/11] prefetch groups list on silo, org, and project access pages --- app/pages/OrgAccessPage.tsx | 1 + app/pages/SiloAccessPage.tsx | 1 + app/pages/project/access/ProjectAccessPage.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/app/pages/OrgAccessPage.tsx b/app/pages/OrgAccessPage.tsx index 8ab45e2ed1..af88248b0f 100644 --- a/app/pages/OrgAccessPage.tsx +++ b/app/pages/OrgAccessPage.tsx @@ -48,6 +48,7 @@ OrgAccessPage.loader = async ({ params }: LoaderFunctionArgs) => { apiQueryClient.prefetchQuery('organizationPolicyView', requireOrgParams(params)), // used to resolve user names apiQueryClient.prefetchQuery('userList', {}), + apiQueryClient.prefetchQuery('groupList', {}), ]) } diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx index 72f9993a49..54283e09f3 100644 --- a/app/pages/SiloAccessPage.tsx +++ b/app/pages/SiloAccessPage.tsx @@ -48,6 +48,7 @@ SiloAccessPage.loader = async () => { apiQueryClient.prefetchQuery('policyView', {}), // used to resolve user names apiQueryClient.prefetchQuery('userList', {}), + apiQueryClient.prefetchQuery('groupList', {}), ]) } diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 382e0ab9f8..3669c7e183 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -53,6 +53,7 @@ ProjectAccessPage.loader = async ({ params }: LoaderFunctionArgs) => { apiQueryClient.prefetchQuery('projectPolicyView', { orgName, projectName }), // used to resolve user names apiQueryClient.prefetchQuery('userList', {}), + apiQueryClient.prefetchQuery('groupList', {}), ]) } From 23cbd04ed367483c035c30a4a13b033b76eb57a4 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 17 Oct 2022 19:18:53 -0500 Subject: [PATCH 02/11] sort groups first, then users, and by name within each --- app/pages/OrgAccessPage.tsx | 12 +++--- app/pages/SiloAccessPage.tsx | 42 ++++++++++--------- .../project/access/ProjectAccessPage.tsx | 12 +++--- libs/api/roles.ts | 13 ++++++ 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/app/pages/OrgAccessPage.tsx b/app/pages/OrgAccessPage.tsx index af88248b0f..c6baabec66 100644 --- a/app/pages/OrgAccessPage.tsx +++ b/app/pages/OrgAccessPage.tsx @@ -5,6 +5,7 @@ import type { LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, + byGroupThenName, getEffectiveRole, setUserRole, useApiMutation, @@ -23,7 +24,7 @@ import { TableActions, TableEmptyBox, } from '@oxide/ui' -import { groupBy, isTruthy, sortBy } from '@oxide/util' +import { groupBy, isTruthy } from '@oxide/util' import { AccessNameCell } from 'app/components/AccessNameCell' import { RoleBadgeCell } from 'app/components/RoleBadgeCell' @@ -76,8 +77,8 @@ export function OrgAccessPage() { const orgRows = useUserRows(orgPolicy?.roleAssignments, 'org') const rows = useMemo(() => { - const users = groupBy(siloRows.concat(orgRows), (u) => u.id).map( - ([userId, userAssignments]) => { + return groupBy(siloRows.concat(orgRows), (u) => u.id) + .map(([userId, userAssignments]) => { const siloRole = userAssignments.find((a) => a.roleSource === 'silo')?.roleName const orgRole = userAssignments.find((a) => a.roleSource === 'org')?.roleName @@ -96,9 +97,8 @@ export function OrgAccessPage() { } return row - } - ) - return sortBy(users, (u) => u.name) + }) + .sort(byGroupThenName) }, [siloRows, orgRows]) const queryClient = useApiQueryClient() diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx index 54283e09f3..2733d1f3bb 100644 --- a/app/pages/SiloAccessPage.tsx +++ b/app/pages/SiloAccessPage.tsx @@ -4,6 +4,7 @@ import { useMemo, useState } from 'react' import { apiQueryClient, + byGroupThenName, getEffectiveRole, setUserRole, useApiMutation, @@ -22,7 +23,7 @@ import { TableActions, TableEmptyBox, } from '@oxide/ui' -import { groupBy, isTruthy, sortBy } from '@oxide/util' +import { groupBy, isTruthy } from '@oxide/util' import { AccessNameCell } from 'app/components/AccessNameCell' import { RoleBadgeCell } from 'app/components/RoleBadgeCell' @@ -70,25 +71,26 @@ export function SiloAccessPage() { const siloRows = useUserRows(siloPolicy?.roleAssignments, 'silo') const rows = useMemo(() => { - const users = groupBy(siloRows, (u) => u.id).map(([userId, userAssignments]) => { - const siloRole = userAssignments.find((a) => a.roleSource === 'silo')?.roleName - - const roles = [siloRole].filter(isTruthy) - - const { name, identityType } = userAssignments[0] - - const row: UserRow = { - id: userId, - identityType, - name, - siloRole, - // we know there has to be at least one - effectiveRole: getEffectiveRole(roles)!, - } - - return row - }) - return sortBy(users, (u) => u.name) + return groupBy(siloRows, (u) => u.id) + .map(([userId, userAssignments]) => { + const siloRole = userAssignments.find((a) => a.roleSource === 'silo')?.roleName + + const roles = [siloRole].filter(isTruthy) + + const { name, identityType } = userAssignments[0] + + const row: UserRow = { + id: userId, + identityType, + name, + siloRole, + // we know there has to be at least one + effectiveRole: getEffectiveRole(roles)!, + } + + return row + }) + .sort(byGroupThenName) }, [siloRows]) const queryClient = useApiQueryClient() diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 3669c7e183..33532df0fe 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -5,6 +5,7 @@ import type { LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, + byGroupThenName, getEffectiveRole, setUserRole, useApiMutation, @@ -23,7 +24,7 @@ import { TableActions, TableEmptyBox, } from '@oxide/ui' -import { groupBy, isTruthy, sortBy } from '@oxide/util' +import { groupBy, isTruthy } from '@oxide/util' import { AccessNameCell } from 'app/components/AccessNameCell' import { RoleBadgeCell } from 'app/components/RoleBadgeCell' @@ -85,8 +86,8 @@ export function ProjectAccessPage() { const projectRows = useUserRows(projectPolicy?.roleAssignments, 'project') const rows = useMemo(() => { - const users = groupBy(siloRows.concat(orgRows, projectRows), (u) => u.id).map( - ([userId, userAssignments]) => { + return groupBy(siloRows.concat(orgRows, projectRows), (u) => u.id) + .map(([userId, userAssignments]) => { const siloRole = userAssignments.find((a) => a.roleSource === 'silo')?.roleName const orgRole = userAssignments.find((a) => a.roleSource === 'org')?.roleName const projectRole = userAssignments.find( @@ -109,9 +110,8 @@ export function ProjectAccessPage() { } return row - } - ) - return sortBy(users, (u) => u.name) + }) + .sort(byGroupThenName) }, [siloRows, orgRows, projectRows]) const queryClient = useApiQueryClient() diff --git a/libs/api/roles.ts b/libs/api/roles.ts index 472479bf3d..ce6d25ffce 100644 --- a/libs/api/roles.ts +++ b/libs/api/roles.ts @@ -113,6 +113,18 @@ export function useUserRows( }, [roleAssignments, roleSource, users, groups]) } +type SortableUserRow = { identityType: IdentityType; name: string } + +/** + * Comparator for array sort. Group groups and users, then sort by name within + * groups and within users. + */ +export function byGroupThenName(a: SortableUserRow, b: SortableUserRow) { + const aGroup = Number(a.identityType === 'silo_group') + const bGroup = Number(b.identityType === 'silo_group') + return bGroup - aGroup || a.name.localeCompare(b.name) +} + /** * Fetch list of users and filter out the ones that are already in the given * policy. @@ -122,6 +134,7 @@ export function useUsersNotInPolicy( policy: Policy | undefined ) { const { data: users } = useApiQuery('userList', {}) + // const { data: groups } = useApiQuery('groupList', {}) return useMemo(() => { // IDs are UUIDs, so no need to include identity type in set value to disambiguate const usersInPolicy = new Set(policy?.roleAssignments.map((ra) => ra.identityId) || []) From c7e1b8234862668b7d9ae2f419ab2c05ce495f9e Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 17 Oct 2022 21:48:18 -0500 Subject: [PATCH 03/11] simultaneity tests for user name and group name --- app/pages/__tests__/org-access.e2e.ts | 24 ++++++++++++++++- app/pages/__tests__/project-access.e2e.ts | 32 ++++++++++++++++++++++- app/test/e2e/utils.ts | 9 ++++--- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/app/pages/__tests__/org-access.e2e.ts b/app/pages/__tests__/org-access.e2e.ts index 0462cfed15..bef59e77c7 100644 --- a/app/pages/__tests__/org-access.e2e.ts +++ b/app/pages/__tests__/org-access.e2e.ts @@ -1,6 +1,11 @@ import { test } from '@playwright/test' -import { expectNotVisible, expectRowVisible, expectVisible } from 'app/test/e2e' +import { + expectNotVisible, + expectRowVisible, + expectSimultaneous, + expectVisible, +} from 'app/test/e2e' test('Click through org access page', async ({ page }) => { await page.goto('/orgs/maze-war') @@ -9,7 +14,24 @@ test('Click through org access page', async ({ page }) => { // page is there, we see user 1 and 2 but not 3 await page.click('role=link[name*="Access & IAM"]') + + // has to be before anything else is checked. ensures we've prefetched + // users list and groups list properly + await expectSimultaneous(page, [ + 'role=cell[name="user-group-1"]', + 'role=cell[name="web-devs Group"]', + 'role=cell[name="user-1"]', + 'role=cell[name="Hannah Arendt"]', + ]) + await expectVisible(page, ['role=heading[name*="Access & IAM"]']) + await expectRowVisible(table, { + ID: 'user-group-1', + // no space because expectRowVisible uses textContent, not accessible name + Name: 'web-devsGroup', + 'Silo role': '', + 'Org role': 'collaborator', + }) await expectRowVisible(table, { ID: 'user-1', Name: 'Hannah Arendt', diff --git a/app/pages/__tests__/project-access.e2e.ts b/app/pages/__tests__/project-access.e2e.ts index 6b67aafcf7..2493c888c7 100644 --- a/app/pages/__tests__/project-access.e2e.ts +++ b/app/pages/__tests__/project-access.e2e.ts @@ -1,11 +1,25 @@ import { test } from '@playwright/test' -import { expectNotVisible, expectRowVisible, expectVisible } from 'app/test/e2e' +import { + expectNotVisible, + expectRowVisible, + expectSimultaneous, + expectVisible, +} from 'app/test/e2e' test('Click through project access page', async ({ page }) => { await page.goto('/orgs/maze-war/projects/mock-project') await page.click('role=link[name*="Access & IAM"]') + // has to be before anything else is checked. ensures we've prefetched + // users list and groups list properly + await expectSimultaneous(page, [ + 'role=cell[name="user-group-1"]', + 'role=cell[name="web-devs Group"]', + 'role=cell[name="user-1"]', + 'role=cell[name="Hannah Arendt"]', + ]) + // page is there, we see user 1-3 but not 4 await expectVisible(page, ['role=heading[name*="Access & IAM"]']) const table = page.locator('table') @@ -30,6 +44,22 @@ test('Click through project access page', async ({ page }) => { 'Org role': '', 'Project role': 'collaborator', }) + await expectRowVisible(table, { + ID: 'user-group-1', + // no space because expectRowVisible uses textContent, not accessible name + Name: 'web-devsGroup', + 'Silo role': '', + 'Org role': 'collaborator', + }) + await expectRowVisible(table, { + ID: 'user-group-2', + // no space because expectRowVisible uses textContent, not accessible name + Name: 'kernel-devsGroup', + 'Silo role': '', + 'Org role': '', + 'Project role': 'viewer', + }) + await expectNotVisible(page, ['role=cell[name="user-4"]']) // Add user 4 as collab diff --git a/app/test/e2e/utils.ts b/app/test/e2e/utils.ts index bf56067a69..06369ada11 100644 --- a/app/test/e2e/utils.ts +++ b/app/test/e2e/utils.ts @@ -85,9 +85,10 @@ async function timeToAppear(page: Page, selector: string): Promise { } /** - * Assert two elements appeared within 20ms of each other + * Assert a set of elements all appeared within a 20ms range */ -export async function expectSimultaneous(page: Page, selectors: [string, string]) { - const [t1, t2] = await Promise.all(selectors.map((sel) => timeToAppear(page, sel))) - expect(Math.abs(t1 - t2)).toBeLessThan(20) +export async function expectSimultaneous(page: Page, selectors: string[]) { + const times = await Promise.all(selectors.map((sel) => timeToAppear(page, sel))) + times.sort() + expect(times[times.length - 1] - times[0]).toBeLessThan(20) } From 40062f91f693a6b1703863df0bba435b88af2dd9 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 17 Oct 2022 21:58:23 -0500 Subject: [PATCH 04/11] silo access page e2e test --- app/pages/__tests__/silo-access.e2e.ts | 97 ++++++++++++++++++++++++++ libs/api-mocks/role-assignment.ts | 9 ++- libs/api-mocks/user-group.ts | 8 ++- 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 app/pages/__tests__/silo-access.e2e.ts diff --git a/app/pages/__tests__/silo-access.e2e.ts b/app/pages/__tests__/silo-access.e2e.ts new file mode 100644 index 0000000000..6a6910261a --- /dev/null +++ b/app/pages/__tests__/silo-access.e2e.ts @@ -0,0 +1,97 @@ +import { test } from '@playwright/test' + +import { + expectNotVisible, + expectRowVisible, + expectSimultaneous, + expectVisible, +} from 'app/test/e2e' + +test('Click through silo access page', async ({ page }) => { + await page.goto('/orgs') + + const table = page.locator('role=table') + + // page is there, we see user 1 and 2 but not 3 + await page.click('role=link[name*="Access & IAM"]') + + // has to be before anything else is checked. ensures we've prefetched + // users list and groups list properly + await expectSimultaneous(page, [ + 'role=cell[name="user-group-3"]', + 'role=cell[name="real-estate-devs Group"]', + 'role=cell[name="user-1"]', + 'role=cell[name="Hannah Arendt"]', + ]) + + await expectVisible(page, ['role=heading[name*="Access & IAM"]']) + await expectRowVisible(table, { + ID: 'user-group-3', + // no space because expectRowVisible uses textContent, not accessible name + Name: 'real-estate-devsGroup', + 'Silo role': 'admin', + }) + await expectRowVisible(table, { + ID: 'user-1', + Name: 'Hannah Arendt', + 'Silo role': 'admin', + }) + await expectNotVisible(page, ['role=cell[name="user-2"]']) + + // Add user 2 as collab + await page.click('role=button[name="Add user or group"]') + await expectVisible(page, ['role=heading[name*="Add user or group"]']) + + await page.click('role=button[name="User"]') + // only users not already on the org should be visible + await expectNotVisible(page, ['role=option[name="Hannah Arendt"]']) + await expectVisible(page, [ + 'role=option[name="Hans Jonas"]', + 'role=option[name="Jacob Klein"]', + 'role=option[name="Simone de Beauvoir"]', + ]) + + await page.click('role=option[name="Jacob Klein"]') + + await page.click('role=button[name="Role"]') + await expectVisible(page, [ + 'role=option[name="Admin"]', + 'role=option[name="Collaborator"]', + 'role=option[name="Viewer"]', + ]) + + await page.click('role=option[name="Collaborator"]') + await page.click('role=button[name="Add user"]') + + // User 3 shows up in the table + await expectRowVisible(table, { + ID: 'user-3', + Name: 'Jacob Klein', + 'Silo role': 'collaborator', + }) + + // now change user 3's role from collab to viewer + await page + .locator('role=row', { hasText: 'user-3' }) + .locator('role=button[name="Row actions"]') + .click() + await page.click('role=menuitem[name="Change role"]') + + await expectVisible(page, ['role=heading[name*="Change user role"]']) + await expectVisible(page, ['button:has-text("Collaborator")']) + + await page.click('role=button[name="Role"]') + await page.click('role=option[name="Viewer"]') + await page.click('role=button[name="Update role"]') + + await expectRowVisible(table, { ID: 'user-3', 'Silo role': 'viewer' }) + + // now delete user 2 + await page + .locator('role=row', { hasText: 'user-1' }) + .locator('role=button[name="Row actions"]') + .click() + await expectVisible(page, ['role=cell[name=user-1]']) + await page.click('role=menuitem[name="Delete"]') + await expectNotVisible(page, ['role=cell[name=user-1]']) +}) diff --git a/libs/api-mocks/role-assignment.ts b/libs/api-mocks/role-assignment.ts index 7969c34d40..338b7a3133 100644 --- a/libs/api-mocks/role-assignment.ts +++ b/libs/api-mocks/role-assignment.ts @@ -5,7 +5,7 @@ import { org } from './org' import { project } from './project' import { defaultSilo } from './silo' import { user1, user2, user3 } from './user' -import { userGroup1, userGroup2 } from './user-group' +import { userGroup1, userGroup2, userGroup3 } from './user-group' // For most other resources, we can store the API types directly in the DB. But // in this case the API response doesn't have the resource ID on it, and we need @@ -32,6 +32,13 @@ export const roleAssignments: DbRoleAssignment[] = [ identity_type: 'silo_user', role_name: 'admin', }, + { + resource_type: 'silo', + resource_id: defaultSilo.id, + identity_id: userGroup3.id, + identity_type: 'silo_group', + role_name: 'admin', + }, { resource_type: 'silo', resource_id: defaultSilo.id, diff --git a/libs/api-mocks/user-group.ts b/libs/api-mocks/user-group.ts index ef6c0be965..a0b3073044 100644 --- a/libs/api-mocks/user-group.ts +++ b/libs/api-mocks/user-group.ts @@ -15,4 +15,10 @@ export const userGroup2: Json = { display_name: 'kernel-devs', } -export const userGroups = [userGroup1, userGroup2] +export const userGroup3: Json = { + id: 'user-group-3', + silo_id: defaultSilo.id, + display_name: 'real-estate-devs', +} + +export const userGroups = [userGroup1, userGroup2, userGroup3] From a4a75b5f7675637b0e4fcae8b9cb421d7d7e63da Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 17 Oct 2022 22:02:25 -0500 Subject: [PATCH 05/11] test byGroupThenName --- libs/api/roles.spec.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/api/roles.spec.ts b/libs/api/roles.spec.ts index 2d875377e3..c573694052 100644 --- a/libs/api/roles.spec.ts +++ b/libs/api/roles.spec.ts @@ -1,5 +1,11 @@ import type { Policy, SessionMe } from './roles' -import { getEffectiveRole, roleOrder, setUserRole, userRoleFromPolicies } from './roles' +import { + byGroupThenName, + getEffectiveRole, + roleOrder, + setUserRole, + userRoleFromPolicies, +} from './roles' describe('getEffectiveRole', () => { it('returns falsy when the list of role assignments is empty', () => { @@ -119,3 +125,13 @@ describe('getEffectiveRole', () => { ).toEqual('admin') }) }) + +test('byGroupThenName sorts as expected', () => { + const a = { identityType: 'silo_group' as const, name: 'a' } + const b = { identityType: 'silo_group' as const, name: 'b' } + const c = { identityType: 'silo_user' as const, name: 'c' } + const d = { identityType: 'silo_user' as const, name: 'd' } + const e = { identityType: 'silo_user' as const, name: 'e' } + + expect([c, e, b, d, a].sort(byGroupThenName)).toEqual([a, b, c, d, e]) +}) From 0d37beef42c3c1e43269d3a9465b699e4ef40ee6 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Tue, 18 Oct 2022 14:57:09 -0400 Subject: [PATCH 06/11] Fix id references in test --- app/pages/__tests__/org-access.e2e.ts | 6 +++--- app/pages/__tests__/project-access.e2e.ts | 6 +++--- app/pages/__tests__/silo-access.e2e.ts | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/pages/__tests__/org-access.e2e.ts b/app/pages/__tests__/org-access.e2e.ts index 7c36edc85d..e98e3ff4eb 100644 --- a/app/pages/__tests__/org-access.e2e.ts +++ b/app/pages/__tests__/org-access.e2e.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test' -import { users } from '@oxide/api-mocks' +import { user1, userGroup1, users } from '@oxide/api-mocks' import { userGroups } from '@oxide/api-mocks' import { @@ -21,9 +21,9 @@ test('Click through org access page', async ({ page }) => { // has to be before anything else is checked. ensures we've prefetched // users list and groups list properly await expectSimultaneous(page, [ - 'role=cell[name="user-group-1"]', + `role=cell[name="${userGroup1}"]`, 'role=cell[name="web-devs Group"]', - 'role=cell[name="user-1"]', + `role=cell[name="${user1}"]`, 'role=cell[name="Hannah Arendt"]', ]) diff --git a/app/pages/__tests__/project-access.e2e.ts b/app/pages/__tests__/project-access.e2e.ts index 8f98cdf748..1d4e5adb89 100644 --- a/app/pages/__tests__/project-access.e2e.ts +++ b/app/pages/__tests__/project-access.e2e.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test' -import { userGroups, users } from '@oxide/api-mocks' +import { user1, userGroup1, userGroups, users } from '@oxide/api-mocks' import { expectNotVisible, @@ -16,9 +16,9 @@ test('Click through project access page', async ({ page }) => { // has to be before anything else is checked. ensures we've prefetched // users list and groups list properly await expectSimultaneous(page, [ - 'role=cell[name="user-group-1"]', + `role=cell[name="${userGroup1}"]`, 'role=cell[name="web-devs Group"]', - 'role=cell[name="user-1"]', + `role=cell[name="${user1}"]`, 'role=cell[name="Hannah Arendt"]', ]) diff --git a/app/pages/__tests__/silo-access.e2e.ts b/app/pages/__tests__/silo-access.e2e.ts index f8087933c9..20ae19e17a 100644 --- a/app/pages/__tests__/silo-access.e2e.ts +++ b/app/pages/__tests__/silo-access.e2e.ts @@ -20,9 +20,9 @@ test('Click through silo access page', async ({ page }) => { // has to be before anything else is checked. ensures we've prefetched // users list and groups list properly await expectSimultaneous(page, [ - 'role=cell[name="user-group-3"]', + `role=cell[name="${userGroups[2].id}"]`, 'role=cell[name="real-estate-devs Group"]', - 'role=cell[name="user-1"]', + `role=cell[name="${userGroups[0].id}"]`, 'role=cell[name="Hannah Arendt"]', ]) @@ -38,7 +38,7 @@ test('Click through silo access page', async ({ page }) => { Name: 'Hannah Arendt', 'Silo role': 'admin', }) - await expectNotVisible(page, ['role=cell[name="user-2"]']) + await expectNotVisible(page, [`role=cell[name="${users[3].id}"]`]) // Add user 2 as collab await page.click('role=button[name="Add user or group"]') @@ -74,7 +74,7 @@ test('Click through silo access page', async ({ page }) => { // now change user 3's role from collab to viewer await page - .locator('role=row', { hasText: 'user-3' }) + .locator('role=row', { hasText: users[2].id }) .locator('role=button[name="Row actions"]') .click() await page.click('role=menuitem[name="Change role"]') @@ -86,14 +86,14 @@ test('Click through silo access page', async ({ page }) => { await page.click('role=option[name="Viewer"]') await page.click('role=button[name="Update role"]') - await expectRowVisible(table, { ID: 'user-3', 'Silo role': 'viewer' }) + await expectRowVisible(table, { ID: users[2].id, 'Silo role': 'viewer' }) // now delete user 2 await page - .locator('role=row', { hasText: 'user-1' }) + .locator('role=row', { hasText: users[0].id }) .locator('role=button[name="Row actions"]') .click() - await expectVisible(page, ['role=cell[name=user-1]']) + await expectVisible(page, [`role=cell[${users[0].id}`]) await page.click('role=menuitem[name="Delete"]') - await expectNotVisible(page, ['role=cell[name=user-1]']) + await expectNotVisible(page, [`role=cell[name="${users[0].id}"]`]) }) From 61203b24edde891197b54821753f5fae81c3d76f Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 18 Oct 2022 14:55:31 -0500 Subject: [PATCH 07/11] update silo access test with mock user changes --- app/pages/__tests__/silo-access.e2e.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/pages/__tests__/silo-access.e2e.ts b/app/pages/__tests__/silo-access.e2e.ts index 20ae19e17a..6711d14b5f 100644 --- a/app/pages/__tests__/silo-access.e2e.ts +++ b/app/pages/__tests__/silo-access.e2e.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test' -import { userGroups, users } from '@oxide/api-mocks' +import { user1, user3, user4, userGroup3 } from '@oxide/api-mocks' import { expectNotVisible, @@ -20,25 +20,25 @@ test('Click through silo access page', async ({ page }) => { // has to be before anything else is checked. ensures we've prefetched // users list and groups list properly await expectSimultaneous(page, [ - `role=cell[name="${userGroups[2].id}"]`, + `role=cell[name="${userGroup3.id}"]`, 'role=cell[name="real-estate-devs Group"]', - `role=cell[name="${userGroups[0].id}"]`, + `role=cell[name="${user1.id}"]`, 'role=cell[name="Hannah Arendt"]', ]) await expectVisible(page, ['role=heading[name*="Access & IAM"]']) await expectRowVisible(table, { - ID: userGroups[2].id, + ID: userGroup3.id, // no space because expectRowVisible uses textContent, not accessible name Name: 'real-estate-devsGroup', 'Silo role': 'admin', }) await expectRowVisible(table, { - ID: users[0].id, + ID: user1.id, Name: 'Hannah Arendt', 'Silo role': 'admin', }) - await expectNotVisible(page, [`role=cell[name="${users[3].id}"]`]) + await expectNotVisible(page, [`role=cell[name="${user4.id}"]`]) // Add user 2 as collab await page.click('role=button[name="Add user or group"]') @@ -67,14 +67,14 @@ test('Click through silo access page', async ({ page }) => { // User 3 shows up in the table await expectRowVisible(table, { - ID: users[2].id, + ID: user3.id, Name: 'Jacob Klein', 'Silo role': 'collaborator', }) // now change user 3's role from collab to viewer await page - .locator('role=row', { hasText: users[2].id }) + .locator('role=row', { hasText: user3.id }) .locator('role=button[name="Row actions"]') .click() await page.click('role=menuitem[name="Change role"]') @@ -86,14 +86,14 @@ test('Click through silo access page', async ({ page }) => { await page.click('role=option[name="Viewer"]') await page.click('role=button[name="Update role"]') - await expectRowVisible(table, { ID: users[2].id, 'Silo role': 'viewer' }) + await expectRowVisible(table, { ID: user3.id, 'Silo role': 'viewer' }) - // now delete user 2 + // now delete user 3 await page - .locator('role=row', { hasText: users[0].id }) + .locator('role=row', { hasText: user3.id }) .locator('role=button[name="Row actions"]') .click() - await expectVisible(page, [`role=cell[${users[0].id}`]) + await expectVisible(page, [`role=cell[name="${user3.id}"]`]) await page.click('role=menuitem[name="Delete"]') - await expectNotVisible(page, [`role=cell[name="${users[0].id}"]`]) + await expectNotVisible(page, [`role=cell[name="${user3.id}"]`]) }) From 82b09fe9c431b062b2d5053f6af8d73d22d87585 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 18 Oct 2022 15:06:55 -0500 Subject: [PATCH 08/11] pull in SessionMe from API --- OMICRON_VERSION | 2 +- libs/api-mocks/msw/handlers.ts | 54 +++++++------- libs/api/__generated__/Api.ts | 97 +++++++++++++++++++++++++- libs/api/__generated__/OMICRON_VERSION | 2 +- libs/api/__generated__/msw-handlers.ts | 43 +++++++++++- libs/api/__generated__/validate.ts | 71 ++++++++++++++++++- libs/api/roles.spec.ts | 4 +- libs/api/roles.ts | 7 +- 8 files changed, 242 insertions(+), 38 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index f513caac83..1484329772 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -bce0f5ddf7f9f3be498fa9afb1fee8d3336c1f30 +5f0ae14b551e15840329ee337daee2aaa966ad9b diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index 3fbe0fcc83..0712e07d55 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -702,7 +702,7 @@ export const handlers = makeHandlers({ return body }, sessionMe() { - return currentUser + return { ...currentUser, group_ids: [] as string[] } }, sessionSshkeyList(params) { const keys = db.sshKeys.filter((k) => k.silo_user_id === currentUser.id) @@ -777,52 +777,56 @@ export const handlers = makeHandlers({ diskViewById: lookupById(db.disks), imageViewById: lookupById(db.images), - instanceViewById: lookupById(db.instances), instanceNetworkInterfaceViewById: lookupById(db.networkInterfaces), + instanceViewById: lookupById(db.instances), organizationViewById: lookupById(db.orgs), projectViewById: lookupById(db.projects), + siloViewById: lookupById(db.silos), snapshotViewById: lookupById(db.snapshots), + systemImageViewById: lookupById(db.globalImages), vpcRouterRouteViewById: lookupById(db.vpcRouterRoutes), vpcRouterViewById: lookupById(db.vpcRouters), vpcSubnetViewById: lookupById(db.vpcSubnets), vpcViewById: lookupById(db.vpcs), - systemImageViewById: lookupById(db.globalImages), - siloViewById: lookupById(db.silos), instanceMigrate: NotImplemented, - loginSpoof: NotImplemented, - loginSamlBegin: NotImplemented, - loginSaml: NotImplemented, - logout: NotImplemented, - roleList: NotImplemented, - roleView: NotImplemented, - ipPoolViewById: NotImplemented, - rackList: NotImplemented, - rackView: NotImplemented, - sledList: NotImplemented, - sledView: NotImplemented, - ipPoolList: NotImplemented, ipPoolCreate: NotImplemented, - ipPoolView: NotImplemented, - ipPoolUpdate: NotImplemented, ipPoolDelete: NotImplemented, - ipPoolRangeList: NotImplemented, + ipPoolList: NotImplemented, ipPoolRangeAdd: NotImplemented, + ipPoolRangeList: NotImplemented, ipPoolRangeRemove: NotImplemented, - ipPoolServiceView: NotImplemented, - ipPoolServiceRangeList: NotImplemented, ipPoolServiceRangeAdd: NotImplemented, + ipPoolServiceRangeList: NotImplemented, ipPoolServiceRangeRemove: NotImplemented, - systemPolicyUpdate: NotImplemented, + ipPoolServiceView: NotImplemented, + ipPoolUpdate: NotImplemented, + ipPoolView: NotImplemented, + ipPoolViewById: NotImplemented, + localIdpUserCreate: NotImplemented, + localIdpUserDelete: NotImplemented, + loginSaml: NotImplemented, + loginSamlBegin: NotImplemented, + loginSpoof: NotImplemented, + logout: NotImplemented, + rackList: NotImplemented, + rackView: NotImplemented, + roleList: NotImplemented, + roleView: NotImplemented, sagaList: NotImplemented, sagaView: NotImplemented, - siloIdentityProviderList: NotImplemented, samlIdentityProviderCreate: NotImplemented, samlIdentityProviderView: NotImplemented, - siloPolicyView: NotImplemented, + siloIdentityProviderList: NotImplemented, siloPolicyUpdate: NotImplemented, - updatesRefresh: NotImplemented, + siloPolicyView: NotImplemented, + siloUsersList: NotImplemented, + siloUserView: NotImplemented, + sledList: NotImplemented, + sledView: NotImplemented, + systemPolicyUpdate: NotImplemented, systemUserList: NotImplemented, systemUserView: NotImplemented, timeseriesSchemaGet: NotImplemented, + updatesRefresh: NotImplemented, }) diff --git a/libs/api/__generated__/Api.ts b/libs/api/__generated__/Api.ts index c01c4c9a6d..5883e5737a 100644 --- a/libs/api/__generated__/Api.ts +++ b/libs/api/__generated__/Api.ts @@ -1182,6 +1182,18 @@ export type SamlIdentityProviderCreate = { technicalContactEmail: string } +/** + * Client view of a {@link User} with some extra stuff that's useful to the console + */ +export type SessionMe = { + /** Human-readable name that can identify the user */ + displayName: string + groupIds: string[] + id: string + /** Uuid of the silo to which this user belongs */ + siloId: string +} + /** * Describes how identities are managed and users are authenticated in this Silo */ @@ -1431,6 +1443,21 @@ export type UserBuiltinResultsPage = { nextPage?: string } +/** + * A name unique within the parent collection + * + * Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID. + */ +export type UserId = string + +/** + * Create-time parameters for a {@link User} + */ +export type UserCreate = { + /** username used to log in */ + externalId: UserId +} + /** * A single page of results */ @@ -2410,6 +2437,15 @@ export interface SiloIdentityProviderListParams { sortBy?: NameSortMode } +export interface LocalIdpUserCreateParams { + siloName: Name +} + +export interface LocalIdpUserDeleteParams { + siloName: Name + userId: string +} + export interface SamlIdentityProviderCreateParams { siloName: Name } @@ -2427,6 +2463,18 @@ export interface SiloPolicyUpdateParams { siloName: Name } +export interface SiloUsersListParams { + siloName: Name + limit?: number + pageToken?: string + sortBy?: IdSortMode +} + +export interface SiloUserViewParams { + siloName: Name + userId: string +} + export interface UpdatesRefreshParams {} export interface SystemUserListParams { @@ -2496,6 +2544,7 @@ export type ApiListMethods = Pick< | 'sagaList' | 'siloList' | 'siloIdentityProviderList' + | 'siloUsersList' | 'systemUserList' | 'userList' > @@ -3718,7 +3767,7 @@ export class Api extends HttpClient { * Fetch the user associated with the current session */ sessionMe: (query: SessionMeParams, params: RequestParams = {}) => - this.request({ + this.request({ path: `/session/me`, method: 'GET', ...params, @@ -4158,6 +4207,31 @@ export class Api extends HttpClient { ...params, }), + /** + * Create a user + */ + localIdpUserCreate: ( + { siloName }: LocalIdpUserCreateParams, + body: UserCreate, + params: RequestParams = {} + ) => + this.request({ + path: `/system/silos/${siloName}/identity-providers/local/users`, + method: 'POST', + body, + ...params, + }), + + localIdpUserDelete: ( + { siloName, userId }: LocalIdpUserDeleteParams, + params: RequestParams = {} + ) => + this.request({ + path: `/system/silos/${siloName}/identity-providers/local/users/${userId}`, + method: 'DELETE', + ...params, + }), + /** * Create a SAML IDP */ @@ -4211,6 +4285,27 @@ export class Api extends HttpClient { ...params, }), + /** + * List users in a specific Silo + */ + siloUsersList: ( + { siloName, ...query }: SiloUsersListParams, + params: RequestParams = {} + ) => + this.request({ + path: `/system/silos/${siloName}/users/all`, + method: 'GET', + query, + ...params, + }), + + siloUserView: ({ siloName, userId }: SiloUserViewParams, params: RequestParams = {}) => + this.request({ + path: `/system/silos/${siloName}/users/id/${userId}`, + method: 'GET', + ...params, + }), + /** * Refresh update data */ diff --git a/libs/api/__generated__/OMICRON_VERSION b/libs/api/__generated__/OMICRON_VERSION index 4c2819ba15..6ea80e5765 100644 --- a/libs/api/__generated__/OMICRON_VERSION +++ b/libs/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -bce0f5ddf7f9f3be498fa9afb1fee8d3336c1f30 +5f0ae14b551e15840329ee337daee2aaa966ad9b diff --git a/libs/api/__generated__/msw-handlers.ts b/libs/api/__generated__/msw-handlers.ts index 062afe86a7..38f432dd15 100644 --- a/libs/api/__generated__/msw-handlers.ts +++ b/libs/api/__generated__/msw-handlers.ts @@ -484,7 +484,9 @@ export interface MSWHandlers { params: Api.RoleViewParams ) => MaybePromise | ResponseTransformer>> /** `GET /session/me` */ - sessionMe: () => MaybePromise | ResponseTransformer>> + sessionMe: () => MaybePromise< + Json | ResponseTransformer> + > /** `GET /session/me/sshkeys` */ sessionSshkeyList: ( params: Api.SessionSshkeyListParams @@ -655,6 +657,15 @@ export interface MSWHandlers { | Json | ResponseTransformer> > + /** `POST /system/silos/:siloName/identity-providers/local/users` */ + localIdpUserCreate: ( + params: Api.LocalIdpUserCreateParams, + body: Json + ) => MaybePromise | ResponseTransformer>> + /** `DELETE /system/silos/:siloName/identity-providers/local/users/:userId` */ + localIdpUserDelete: ( + params: Api.LocalIdpUserDeleteParams + ) => MaybePromise /** `POST /system/silos/:siloName/identity-providers/saml` */ samlIdentityProviderCreate: ( params: Api.SamlIdentityProviderCreateParams, @@ -681,6 +692,16 @@ export interface MSWHandlers { ) => MaybePromise< Json | ResponseTransformer> > + /** `GET /system/silos/:siloName/users/all` */ + siloUsersList: ( + params: Api.SiloUsersListParams + ) => MaybePromise< + Json | ResponseTransformer> + > + /** `GET /system/silos/:siloName/users/id/:userId` */ + siloUserView: ( + params: Api.SiloUserViewParams + ) => MaybePromise | ResponseTransformer>> /** `POST /system/updates/refresh` */ updatesRefresh: () => MaybePromise /** `GET /system/user` */ @@ -1353,6 +1374,18 @@ export function makeHandlers(handlers: MSWHandlers): RestHandler[] { null ) ), + rest.post( + '/system/silos/:siloName/identity-providers/local/users', + handler( + handlers['localIdpUserCreate'], + schema.LocalIdpUserCreateParams, + schema.UserCreate + ) + ), + rest.delete( + '/system/silos/:siloName/identity-providers/local/users/:userId', + handler(handlers['localIdpUserDelete'], schema.LocalIdpUserDeleteParams, null) + ), rest.post( '/system/silos/:siloName/identity-providers/saml', handler( @@ -1381,6 +1414,14 @@ export function makeHandlers(handlers: MSWHandlers): RestHandler[] { schema.SiloRolePolicy ) ), + rest.get( + '/system/silos/:siloName/users/all', + handler(handlers['siloUsersList'], schema.SiloUsersListParams, null) + ), + rest.get( + '/system/silos/:siloName/users/id/:userId', + handler(handlers['siloUserView'], schema.SiloUserViewParams, null) + ), rest.post('/system/updates/refresh', handler(handlers['updatesRefresh'], null, null)), rest.get( '/system/user', diff --git a/libs/api/__generated__/validate.ts b/libs/api/__generated__/validate.ts index ba26c11786..eb1ea87262 100644 --- a/libs/api/__generated__/validate.ts +++ b/libs/api/__generated__/validate.ts @@ -683,7 +683,7 @@ export const Ipv4Net = z.preprocess( z .string() .regex( - /^(10\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\/([8-9]|1[0-9]|2[0-9]|3[0-2])|172\.(1[6-9]|2[0-9]|3[0-1])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\/(1[2-9]|2[0-9]|3[0-2])|192\.168\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\/(1[6-9]|2[0-9]|3[0-2]))$/ + /^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\/([8-9]|1[0-9]|2[0-9]|3[0-2])$/ ) ) @@ -1224,6 +1224,19 @@ export const SamlIdentityProviderCreate = z.preprocess( }) ) +/** + * Client view of a {@link User} with some extra stuff that's useful to the console + */ +export const SessionMe = z.preprocess( + processResponseBody, + z.object({ + displayName: z.string(), + groupIds: z.string().uuid().array(), + id: z.string().uuid(), + siloId: z.string().uuid(), + }) +) + /** * Describes how identities are managed and users are authenticated in this Silo */ @@ -1459,6 +1472,29 @@ export const UserBuiltinResultsPage = z.preprocess( z.object({ items: UserBuiltin.array(), nextPage: z.string().optional() }) ) +/** + * A name unique within the parent collection + * + * Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID. + */ +export const UserId = z.preprocess( + processResponseBody, + z + .string() + .max(63) + .regex( + /^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$/ + ) +) + +/** + * Create-time parameters for a {@link User} + */ +export const UserCreate = z.preprocess( + processResponseBody, + z.object({ externalId: UserId }) +) + /** * A single page of results */ @@ -2764,6 +2800,21 @@ export const SiloIdentityProviderListParams = z.preprocess( }) ) +export const LocalIdpUserCreateParams = z.preprocess( + processResponseBody, + z.object({ + siloName: Name, + }) +) + +export const LocalIdpUserDeleteParams = z.preprocess( + processResponseBody, + z.object({ + siloName: Name, + userId: z.string().uuid(), + }) +) + export const SamlIdentityProviderCreateParams = z.preprocess( processResponseBody, z.object({ @@ -2793,6 +2844,24 @@ export const SiloPolicyUpdateParams = z.preprocess( }) ) +export const SiloUsersListParams = z.preprocess( + processResponseBody, + z.object({ + siloName: Name, + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + sortBy: IdSortMode.optional(), + }) +) + +export const SiloUserViewParams = z.preprocess( + processResponseBody, + z.object({ + siloName: Name, + userId: z.string().uuid(), + }) +) + export const UpdatesRefreshParams = z.preprocess(processResponseBody, z.object({})) export const SystemUserListParams = z.preprocess( diff --git a/libs/api/roles.spec.ts b/libs/api/roles.spec.ts index c573694052..735a409bdf 100644 --- a/libs/api/roles.spec.ts +++ b/libs/api/roles.spec.ts @@ -1,4 +1,4 @@ -import type { Policy, SessionMe } from './roles' +import type { Policy } from './roles' import { byGroupThenName, getEffectiveRole, @@ -54,7 +54,7 @@ describe('setUserRole', () => { }) }) -const user1: SessionMe = { +const user1 = { id: 'hi', displayName: 'bye', siloId: 'sigh', diff --git a/libs/api/roles.ts b/libs/api/roles.ts index ce6d25ffce..197f374bd5 100644 --- a/libs/api/roles.ts +++ b/libs/api/roles.ts @@ -13,8 +13,8 @@ import type { IdentityType, OrganizationRole, ProjectRole, + SessionMe, SiloRole, - User, } from './__generated__/Api' /** @@ -146,11 +146,6 @@ export function useUsersNotInPolicy( }, [users, policy]) } -// temporary until we figure out how we're getting groups from the API -export type SessionMe = User & { - groupIds?: string[] -} - export function userRoleFromPolicies(user: SessionMe, policies: Policy[]): RoleKey | null { const myIds = new Set([user.id, ...(user.groupIds || [])]) const myRoles = policies From a79067b9b1573d0bf4f5e27f9008d581c06fb636 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 18 Oct 2022 16:31:26 -0500 Subject: [PATCH 09/11] update omicron for the better comment --- OMICRON_VERSION | 2 +- libs/api/__generated__/Api.ts | 2 +- libs/api/__generated__/OMICRON_VERSION | 2 +- libs/api/__generated__/validate.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index 1484329772..31aac7fae0 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -5f0ae14b551e15840329ee337daee2aaa966ad9b +0e61d09a3fd528892fb9ee60021098a47c2acdbb diff --git a/libs/api/__generated__/Api.ts b/libs/api/__generated__/Api.ts index 5883e5737a..412f8fcbdf 100644 --- a/libs/api/__generated__/Api.ts +++ b/libs/api/__generated__/Api.ts @@ -1183,7 +1183,7 @@ export type SamlIdentityProviderCreate = { } /** - * Client view of a {@link User} with some extra stuff that's useful to the console + * Client view of a {@link User} and their groups */ export type SessionMe = { /** Human-readable name that can identify the user */ diff --git a/libs/api/__generated__/OMICRON_VERSION b/libs/api/__generated__/OMICRON_VERSION index 6ea80e5765..ee667d0d90 100644 --- a/libs/api/__generated__/OMICRON_VERSION +++ b/libs/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -5f0ae14b551e15840329ee337daee2aaa966ad9b +0e61d09a3fd528892fb9ee60021098a47c2acdbb diff --git a/libs/api/__generated__/validate.ts b/libs/api/__generated__/validate.ts index eb1ea87262..7486fcd70b 100644 --- a/libs/api/__generated__/validate.ts +++ b/libs/api/__generated__/validate.ts @@ -1225,7 +1225,7 @@ export const SamlIdentityProviderCreate = z.preprocess( ) /** - * Client view of a {@link User} with some extra stuff that's useful to the console + * Client view of a {@link User} and their groups */ export const SessionMe = z.preprocess( processResponseBody, From 9cd091a642f1ff1900bfc9e0b84a26ad5e5df2fb Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 19 Oct 2022 14:29:00 -0500 Subject: [PATCH 10/11] regen API for the new omicron commit --- OMICRON_VERSION | 2 +- libs/api/__generated__/Api.ts | 75 ++++++++++++++++++-------- libs/api/__generated__/OMICRON_VERSION | 2 +- libs/api/__generated__/msw-handlers.ts | 16 +++++- libs/api/__generated__/validate.ts | 37 +++++++++++-- 5 files changed, 105 insertions(+), 27 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index 31aac7fae0..1ada9c7760 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -0e61d09a3fd528892fb9ee60021098a47c2acdbb +2ea34d0369fd26bc4f438ea0b3c8ab1b50314048 diff --git a/libs/api/__generated__/Api.ts b/libs/api/__generated__/Api.ts index 2cb02c8582..7279932048 100644 --- a/libs/api/__generated__/Api.ts +++ b/libs/api/__generated__/Api.ts @@ -2458,6 +2458,15 @@ export interface SiloIdentityProviderListQueryParams { sortBy?: NameSortMode } +export interface LocalIdpUserCreatePathParams { + siloName: Name +} + +export interface LocalIdpUserDeletePathParams { + siloName: Name + userId: string +} + export interface SamlIdentityProviderCreatePathParams { siloName: Name } @@ -2475,6 +2484,21 @@ export interface SiloPolicyUpdatePathParams { siloName: Name } +export interface SiloUsersListPathParams { + siloName: Name +} + +export interface SiloUsersListQueryParams { + limit?: number + pageToken?: string + sortBy?: IdSortMode +} + +export interface SiloUserViewPathParams { + siloName: Name + userId: string +} + export interface SystemUserListQueryParams { limit?: number pageToken?: string @@ -3873,7 +3897,7 @@ export class Api extends HttpClient { * Fetch the user associated with the current session */ sessionMe: (_: EmptyObj, params: RequestParams = {}) => { - return this.request({ + return this.request({ path: `/session/me`, method: 'GET', ...params, @@ -4379,27 +4403,28 @@ export class Api extends HttpClient { * Create a user */ localIdpUserCreate: ( - { siloName }: LocalIdpUserCreateParams, - body: UserCreate, + { path, body }: { path: LocalIdpUserCreatePathParams; body: UserCreate }, params: RequestParams = {} - ) => - this.request({ + ) => { + const { siloName } = path + return this.request({ path: `/system/silos/${siloName}/identity-providers/local/users`, method: 'POST', body, ...params, - }), - + }) + }, localIdpUserDelete: ( - { siloName, userId }: LocalIdpUserDeleteParams, + { path }: { path: LocalIdpUserDeletePathParams }, params: RequestParams = {} - ) => - this.request({ + ) => { + const { siloName, userId } = path + return this.request({ path: `/system/silos/${siloName}/identity-providers/local/users/${userId}`, method: 'DELETE', ...params, - }), - + }) + }, /** * Create a SAML IDP */ @@ -4465,23 +4490,31 @@ export class Api extends HttpClient { * List users in a specific Silo */ siloUsersList: ( - { siloName, ...query }: SiloUsersListParams, + { + path, + query = {}, + }: { path: SiloUsersListPathParams; query?: SiloUsersListQueryParams }, params: RequestParams = {} - ) => - this.request({ + ) => { + const { siloName } = path + return this.request({ path: `/system/silos/${siloName}/users/all`, method: 'GET', query, ...params, - }), - - siloUserView: ({ siloName, userId }: SiloUserViewParams, params: RequestParams = {}) => - this.request({ + }) + }, + siloUserView: ( + { path }: { path: SiloUserViewPathParams }, + params: RequestParams = {} + ) => { + const { siloName, userId } = path + return this.request({ path: `/system/silos/${siloName}/users/id/${userId}`, method: 'GET', ...params, - }), - + }) + }, /** * Refresh update data */ diff --git a/libs/api/__generated__/OMICRON_VERSION b/libs/api/__generated__/OMICRON_VERSION index ee667d0d90..06ac96fe86 100644 --- a/libs/api/__generated__/OMICRON_VERSION +++ b/libs/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -0e61d09a3fd528892fb9ee60021098a47c2acdbb +2ea34d0369fd26bc4f438ea0b3c8ab1b50314048 diff --git a/libs/api/__generated__/msw-handlers.ts b/libs/api/__generated__/msw-handlers.ts index 19795aacb5..d0fd4f96d9 100644 --- a/libs/api/__generated__/msw-handlers.ts +++ b/libs/api/__generated__/msw-handlers.ts @@ -401,7 +401,7 @@ export interface MSWHandlers { /** `GET /roles/:roleName` */ roleView: (params: { path: Api.RoleViewPathParams }) => HandlerResult /** `GET /session/me` */ - sessionMe: () => HandlerResult + sessionMe: () => HandlerResult /** `GET /session/me/sshkeys` */ sessionSshkeyList: (params: { query: Api.SessionSshkeyListQueryParams @@ -528,6 +528,13 @@ export interface MSWHandlers { path: Api.SiloIdentityProviderListPathParams query: Api.SiloIdentityProviderListQueryParams }) => HandlerResult + /** `POST /system/silos/:siloName/identity-providers/local/users` */ + localIdpUserCreate: (params: { + path: Api.LocalIdpUserCreatePathParams + body: Json + }) => HandlerResult + /** `DELETE /system/silos/:siloName/identity-providers/local/users/:userId` */ + localIdpUserDelete: (params: { path: Api.LocalIdpUserDeletePathParams }) => StatusCode /** `POST /system/silos/:siloName/identity-providers/saml` */ samlIdentityProviderCreate: (params: { path: Api.SamlIdentityProviderCreatePathParams @@ -546,6 +553,13 @@ export interface MSWHandlers { path: Api.SiloPolicyUpdatePathParams body: Json }) => HandlerResult + /** `GET /system/silos/:siloName/users/all` */ + siloUsersList: (params: { + path: Api.SiloUsersListPathParams + query: Api.SiloUsersListQueryParams + }) => HandlerResult + /** `GET /system/silos/:siloName/users/id/:userId` */ + siloUserView: (params: { path: Api.SiloUserViewPathParams }) => HandlerResult /** `POST /system/updates/refresh` */ updatesRefresh: () => StatusCode /** `GET /system/user` */ diff --git a/libs/api/__generated__/validate.ts b/libs/api/__generated__/validate.ts index d48f4e778b..748aa1b8f1 100644 --- a/libs/api/__generated__/validate.ts +++ b/libs/api/__generated__/validate.ts @@ -3249,15 +3249,21 @@ export const SiloIdentityProviderListParams = z.preprocess( export const LocalIdpUserCreateParams = z.preprocess( processResponseBody, z.object({ - siloName: Name, + path: z.object({ + siloName: Name, + }), + query: z.object({}), }) ) export const LocalIdpUserDeleteParams = z.preprocess( processResponseBody, z.object({ - siloName: Name, - userId: z.string().uuid(), + path: z.object({ + siloName: Name, + userId: z.string().uuid(), + }), + query: z.object({}), }) ) @@ -3302,6 +3308,31 @@ export const SiloPolicyUpdateParams = z.preprocess( }) ) +export const SiloUsersListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + siloName: Name, + }), + query: z.object({ + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + sortBy: IdSortMode.optional(), + }), + }) +) + +export const SiloUserViewParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + siloName: Name, + userId: z.string().uuid(), + }), + query: z.object({}), + }) +) + export const UpdatesRefreshParams = z.preprocess( processResponseBody, z.object({ From a0993d76345f117218038cc8d9c56a3ff152860b Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 19 Oct 2022 15:44:33 -0500 Subject: [PATCH 11/11] update new code for id changes from main --- libs/api-mocks/user-group.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/api-mocks/user-group.ts b/libs/api-mocks/user-group.ts index a7d22300de..436450f2e6 100644 --- a/libs/api-mocks/user-group.ts +++ b/libs/api-mocks/user-group.ts @@ -16,7 +16,7 @@ export const userGroup2: Json = { } export const userGroup3: Json = { - id: genId('real-estate-devs'), + id: '5e30797c-cae3-4402-aeb7-d5044c4bed29', silo_id: defaultSilo.id, display_name: 'real-estate-devs', }