mirror of https://github.com/boxyhq/jackson.git
Compare commits
14 Commits
6ad37ba3b9
...
f3dca9255c
Author | SHA1 | Date |
---|---|---|
Utkarsh Mehta | f3dca9255c | |
ukrocks007 | dd256bbaea | |
ukrocks007 | eefba3a921 | |
Aswin V | 9943a06ace | |
Deepak Prabhakara | f08025cc31 | |
ukrocks007 | 7e363e8231 | |
ukrocks007 | ab52092548 | |
Deepak Prabhakara | 63baf12a38 | |
ukrocks007 | d4e46ab679 | |
ukrocks007 | 75fd2ff400 | |
ukrocks007 | 2ded090cb8 | |
ukrocks007 | 28503e7360 | |
ukrocks007 | 3ac932085e | |
ukrocks007 | b7421dd62d |
|
@ -111,7 +111,7 @@ jobs:
|
|||
ports:
|
||||
- '8000:8000'
|
||||
mocksaml:
|
||||
image: boxyhq/mock-saml:1.2.0
|
||||
image: boxyhq/mock-saml:1.3.9
|
||||
ports:
|
||||
- 4000:4000
|
||||
env:
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export const options = {
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key ${process.env.JACKSON_API_KEYS}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, type APIRequestContext } from '@playwright/test';
|
||||
import { Directory } from 'npm/src';
|
||||
|
||||
const directoryBase = {
|
||||
tenant: 'api-boxyhq',
|
||||
|
@ -25,6 +26,18 @@ export const directoryExpected = {
|
|||
webhook: { endpoint: 'https://example.com', secret: 'secret' },
|
||||
};
|
||||
|
||||
export const updateDirectory = async (request: APIRequestContext, directory: Directory, data: any) => {
|
||||
const response = await request.patch(`/api/v1/dsync/${directory.id}`, {
|
||||
data,
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const { data: updatedDirectory } = await response.json();
|
||||
return updatedDirectory;
|
||||
};
|
||||
|
||||
export const createDirectory = async (request: APIRequestContext, payload: typeof directoryPayload) => {
|
||||
const response = await request.post('/api/v1/dsync', {
|
||||
data: {
|
||||
|
@ -59,6 +72,21 @@ export const getDirectory = async (
|
|||
return data;
|
||||
};
|
||||
|
||||
export const getDirectoryByProduct = async (request: APIRequestContext, { product }: { product: string }) => {
|
||||
const response = await request.get('/api/v1/dsync/product', {
|
||||
params: {
|
||||
product,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const { data } = await response.json();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteDirectory = async (request: APIRequestContext, directoryId: string) => {
|
||||
const response = await request.delete(`/api/v1/dsync/${directoryId}`);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { expect, type APIRequestContext } from '@playwright/test';
|
||||
import type { Directory } from '@boxyhq/saml-jackson';
|
||||
import type { Directory, Group } from '@boxyhq/saml-jackson';
|
||||
|
||||
export const createGroup = async (request: APIRequestContext, directory: Directory, group: any) => {
|
||||
const response = await request.post(`${directory.scim.path}/Groups`, {
|
||||
|
@ -15,6 +15,38 @@ export const createGroup = async (request: APIRequestContext, directory: Directo
|
|||
return await response.json();
|
||||
};
|
||||
|
||||
export const addGroupMember = async (
|
||||
request: APIRequestContext,
|
||||
directory: Directory,
|
||||
group: Group,
|
||||
member: string
|
||||
) => {
|
||||
const response = await request.patch(`${directory.scim.path}/Groups/${group.id}`, {
|
||||
data: {
|
||||
Operations: [
|
||||
{
|
||||
action: 'addGroupMember',
|
||||
op: 'add',
|
||||
path: 'members',
|
||||
value: [
|
||||
{
|
||||
value: member,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${directory.scim.secret}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const getGroupByDisplayName = async (
|
||||
request: APIRequestContext,
|
||||
directory: Directory,
|
||||
|
@ -47,3 +79,17 @@ export const getGroupById = async (request: APIRequestContext, directory: Direct
|
|||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const getGroupsByDirectoryId = async (request: APIRequestContext, directory: Directory) => {
|
||||
const response = await request.get(`${directory.scim.path}/Groups`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${directory.scim.secret}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
return data.Resources;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { type APIRequestContext, expect } from '@playwright/test';
|
||||
import { OAuthReq } from 'npm/src';
|
||||
|
||||
// Make oauth autorize request
|
||||
export const oauthAuthorize = async (request: APIRequestContext, data: OAuthReq, isFailure = false) => {
|
||||
try {
|
||||
const response = await request.post('/api/oauth/authorize', {
|
||||
data,
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
if (!isFailure) {
|
||||
expect(response.status()).toBe(302);
|
||||
}
|
||||
} catch (ex: any) {
|
||||
if (isFailure) {
|
||||
expect(ex.message).toBeDefined();
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -71,6 +71,20 @@ export const getConnection = async (
|
|||
return await response.json();
|
||||
};
|
||||
|
||||
// Get connections by product
|
||||
export const getConnectionByProduct = async (request: APIRequestContext, product: string) => {
|
||||
const response = await request.get('/api/v1/sso/product', {
|
||||
params: {
|
||||
product,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Delete a connection
|
||||
export const deleteConnection = async (
|
||||
request: APIRequestContext,
|
||||
|
@ -86,3 +100,60 @@ export const deleteConnection = async (
|
|||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(204);
|
||||
};
|
||||
|
||||
// get a sso trace by id
|
||||
export const getSSOTraceById = async (request: APIRequestContext, { id }: { id: string }) => {
|
||||
const response = await request.get('/api/v1/sso-traces', {
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// get sso traces by product
|
||||
export const getSSOTracesByProduct = async (request: APIRequestContext, { product }: { product: string }) => {
|
||||
const response = await request.get('/api/v1/sso-traces/product', {
|
||||
params: {
|
||||
product,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Delete sso traces by product
|
||||
export const deleteSSOTraces = async (request: APIRequestContext, { product }: { product: string }) => {
|
||||
const response = await request.delete('/api/v1/sso-traces/product', {
|
||||
params: {
|
||||
product,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(204);
|
||||
};
|
||||
|
||||
// Count sso traces by product
|
||||
export const countSSOTracesByProduct = async (
|
||||
request: APIRequestContext,
|
||||
{ product }: { product: string }
|
||||
) => {
|
||||
const response = await request.get('/api/v1/sso-traces/product/count', {
|
||||
params: {
|
||||
product,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
|
|
@ -4,13 +4,9 @@ import { createUser, getUser } from '../../helpers/users';
|
|||
import { createGroup, getGroupByDisplayName, getGroupById } from '../../helpers/groups';
|
||||
import groups from '../../../../npm/test/dsync/data/groups';
|
||||
import users from '../../../../npm/test/dsync/data/users';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = { ...directoryPayload, tenant: 'api-boxyhq-1' };
|
||||
|
||||
|
|
|
@ -2,13 +2,9 @@ import { test, expect } from '@playwright/test';
|
|||
import users from '../../../../npm/test/dsync/data/users';
|
||||
import { createDirectory, deleteDirectory, directoryPayload, getDirectory } from '../../helpers/directories';
|
||||
import { createUser, getUser } from '../../helpers/users';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = { ...directoryPayload, tenant: 'api-boxyhq-2' };
|
||||
|
||||
|
|
|
@ -5,14 +5,11 @@ import {
|
|||
directoryExpected,
|
||||
directoryPayload,
|
||||
getDirectory,
|
||||
getDirectoryByProduct,
|
||||
} from '../../helpers/directories';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = directoryPayload;
|
||||
|
||||
|
@ -25,6 +22,9 @@ test.beforeAll(async ({ request }) => {
|
|||
test.afterAll(async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
if (!directory) {
|
||||
return;
|
||||
}
|
||||
await deleteDirectory(request, directory.id);
|
||||
});
|
||||
|
||||
|
@ -157,3 +157,15 @@ test.describe('PATCH /api/v1/dsync/{directoryId}', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('GET /api/v1/dsync/product', () => {
|
||||
test('should be able to get a directory by product', async ({ request }) => {
|
||||
let directories = await getDirectoryByProduct(request, { product });
|
||||
expect(directories.length).toBe(1);
|
||||
|
||||
await deleteDirectory(request, directories[0].id);
|
||||
|
||||
directories = await getDirectoryByProduct(request, { product });
|
||||
expect(directories.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
createDirectory,
|
||||
deleteDirectory,
|
||||
directoryPayload,
|
||||
getDirectory,
|
||||
updateDirectory,
|
||||
} from '../../helpers/directories';
|
||||
import groups from '@boxyhq/saml-jackson/test/dsync/data/groups';
|
||||
import { addGroupMember, createGroup } from '../../helpers/groups';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = { ...directoryPayload, tenant: 'api-boxyhq-3' };
|
||||
const memberId = 'member1';
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
let directory = await createDirectory(request, {
|
||||
...directoryPayload,
|
||||
tenant,
|
||||
});
|
||||
|
||||
directory = await updateDirectory(request, directory, {
|
||||
log_webhook_events: true,
|
||||
});
|
||||
|
||||
const group = await createGroup(request, directory, groups[0]);
|
||||
await addGroupMember(request, directory, group, memberId);
|
||||
});
|
||||
|
||||
test.afterAll(async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
await deleteDirectory(request, directory.id);
|
||||
});
|
||||
|
||||
test.describe('GET /api/v1/dsync/events', () => {
|
||||
test('should be able to get list of events from a directory', async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
const response = await request.get(`/api/v1/dsync/events`, {
|
||||
params: {
|
||||
tenant,
|
||||
product,
|
||||
directoryId: directory.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: events } = await response.json();
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
expect(events.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('GET /api/v1/dsync/events/:event', () => {
|
||||
test('should be able to delete all the events from directory', async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
let response = await request.get(`/api/v1/dsync/events`, {
|
||||
params: {
|
||||
directoryId: directory.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: events } = await response.json();
|
||||
|
||||
response = await request.get(`/api/v1/dsync/events/${events[0].id}`, {
|
||||
params: {
|
||||
tenant,
|
||||
product,
|
||||
directoryId: directory.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: event } = await response.json();
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
expect(event.status_code).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('DELETE /api/v1/dsync/events', () => {
|
||||
test('should be able to delete all the events from directory', async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
let response = await request.delete(`/api/v1/dsync/events`, {
|
||||
params: {
|
||||
directoryId: directory.id,
|
||||
},
|
||||
});
|
||||
|
||||
response = await request.get(`/api/v1/dsync/events`, {
|
||||
params: {
|
||||
tenant,
|
||||
product,
|
||||
directoryId: directory.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: events } = await response.json();
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { createDirectory, deleteDirectory, directoryPayload, getDirectory } from '../../helpers/directories';
|
||||
import groups from '@boxyhq/saml-jackson/test/dsync/data/groups';
|
||||
import { addGroupMember, createGroup, getGroupsByDirectoryId } from '../../helpers/groups';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = { ...directoryPayload, tenant: 'api-boxyhq-3' };
|
||||
const memberId = 'member1';
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
const directory = await createDirectory(request, {
|
||||
...directoryPayload,
|
||||
tenant,
|
||||
});
|
||||
|
||||
const group = await createGroup(request, directory, groups[0]);
|
||||
await addGroupMember(request, directory, group, memberId);
|
||||
});
|
||||
|
||||
test.afterAll(async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
await deleteDirectory(request, directory.id);
|
||||
});
|
||||
|
||||
test.describe('GET /api/v1/dsync/groups/:id/members', () => {
|
||||
test('should be able to get a group members from a directory', async ({ request }) => {
|
||||
const [directory] = await getDirectory(request, { tenant, product });
|
||||
|
||||
const groups = await getGroupsByDirectoryId(request, directory);
|
||||
const response = await request.get(`/api/v1/dsync/groups/${groups[0].id}/members`, {
|
||||
params: {
|
||||
tenant,
|
||||
product,
|
||||
},
|
||||
});
|
||||
|
||||
const { data: directoryMembers } = await response.json();
|
||||
|
||||
expect(response.ok()).toBe(true);
|
||||
expect(response.status()).toBe(200);
|
||||
expect(directoryMembers.length).toBe(1);
|
||||
expect(directoryMembers).toMatchObject([
|
||||
{
|
||||
user_id: memberId,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -2,13 +2,9 @@ import { test, expect } from '@playwright/test';
|
|||
import { createDirectory, deleteDirectory, directoryPayload, getDirectory } from '../../helpers/directories';
|
||||
import groups from '@boxyhq/saml-jackson/test/dsync/data/groups';
|
||||
import { createGroup } from '../../helpers/groups';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = { ...directoryPayload, tenant: 'api-boxyhq-3' };
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
const tenant = 'tenant-1';
|
||||
const product = 'product-1';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
// POST /api/v1/dsync/setuplinks
|
||||
test('create the setup link', async ({ request }) => {
|
||||
|
|
|
@ -2,13 +2,9 @@ import { test, expect } from '@playwright/test';
|
|||
import users from '../../../../npm/test/dsync/data/users';
|
||||
import { createDirectory, deleteDirectory, directoryPayload, getDirectory } from '../../helpers/directories';
|
||||
import { createUser } from '../../helpers/users';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
const { tenant, product } = { ...directoryPayload, tenant: 'api-boxyhq-4' };
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { SAMLFederationApp } from '@boxyhq/saml-jackson';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
const expectedApp = {
|
||||
name: 'Test App',
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import { test } from '@playwright/test';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
test.describe('OIDC SSO Connection', () => {
|
||||
//
|
||||
|
|
|
@ -6,14 +6,11 @@ import {
|
|||
getRawMetadata,
|
||||
newConnection,
|
||||
expectedConnection,
|
||||
getConnectionByProduct,
|
||||
} from '../../helpers/sso';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
const { tenant, product } = newConnection;
|
||||
|
@ -210,3 +207,23 @@ test.describe('GET /api/v1/sso/exists', () => {
|
|||
expect(response.status()).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('GET /api/v1/sso/product', () => {
|
||||
const { product } = newConnection;
|
||||
|
||||
test('should get empty array for SSO connection by product', async ({ request }) => {
|
||||
const response = await getConnectionByProduct(request, product);
|
||||
|
||||
expect(response).toMatchObject([]);
|
||||
expect(response.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should be able to get SSO Connections by product', async ({ request }) => {
|
||||
await createConnection(request, newConnection);
|
||||
|
||||
const response = await getConnectionByProduct(request, product);
|
||||
|
||||
expect(response).toMatchObject([expectedConnection]);
|
||||
expect(response.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { options } from '../../helpers/api';
|
||||
|
||||
const tenant = 'tenant-1';
|
||||
const product = 'product-1';
|
||||
|
||||
test.use({
|
||||
extraHTTPHeaders: {
|
||||
Authorization: `Api-Key secret`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
test.use(options);
|
||||
|
||||
// POST /api/v1/sso/setuplinks
|
||||
test('create the setup link', async ({ request }) => {
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
createConnection,
|
||||
deleteConnection,
|
||||
newConnection,
|
||||
deleteSSOTraces,
|
||||
getSSOTracesByProduct,
|
||||
getSSOTraceById,
|
||||
countSSOTracesByProduct,
|
||||
} from '../../helpers/sso';
|
||||
import { options } from '../../helpers/api';
|
||||
import { oauthAuthorize } from '../../helpers/oauth';
|
||||
|
||||
test.use(options);
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
const { tenant, product } = newConnection;
|
||||
|
||||
// Delete the connection & traces after each test
|
||||
await deleteConnection(request, { tenant, product });
|
||||
await deleteSSOTraces(request, { product });
|
||||
});
|
||||
|
||||
test.describe('POST /api/v1/sso', () => {
|
||||
test('should be able to get empty list of traces', async ({ request }) => {
|
||||
await createConnection(request, newConnection);
|
||||
|
||||
const list = await getSSOTracesByProduct(request, { product: newConnection.product });
|
||||
|
||||
expect(list.data.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should be able to get non empty list of traces', async ({ request }) => {
|
||||
await createConnection(request, newConnection);
|
||||
await oauthAuthorize(
|
||||
request,
|
||||
{
|
||||
client_id: 'dummy',
|
||||
tenant: 'dummy',
|
||||
product: newConnection.product,
|
||||
state: 'Bb-w_AqDxZh90BBVz4PRhtRIRetOgo0AR0pmrhzyICU',
|
||||
response_type: 'code',
|
||||
redirect_uri: newConnection.redirectUrl[0].replaceAll('*', ''),
|
||||
code_challenge: 'OcMni5eZvSrQ2ev7tPICbcE7q1piL8Abi8IfJtWbUtY',
|
||||
code_challenge_method: 'S256',
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const res = await countSSOTracesByProduct(request, { product: newConnection.product });
|
||||
|
||||
expect(res.count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should be able to get sso trace by Id', async ({ request }) => {
|
||||
await createConnection(request, newConnection);
|
||||
await oauthAuthorize(
|
||||
request,
|
||||
{
|
||||
client_id: 'dummy',
|
||||
tenant: 'dummy',
|
||||
product: newConnection.product,
|
||||
state: 'Bb-w_AqDxZh90BBVz4PRhtRIRetOgo0AR0pmrhzyICU',
|
||||
response_type: 'code',
|
||||
redirect_uri: newConnection.redirectUrl[0].replaceAll('*', ''),
|
||||
code_challenge: 'OcMni5eZvSrQ2ev7tPICbcE7q1piL8Abi8IfJtWbUtY',
|
||||
code_challenge_method: 'S256',
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const list = await getSSOTracesByProduct(request, { product: newConnection.product });
|
||||
expect(list.data.length).toBe(1);
|
||||
|
||||
const trace = await getSSOTraceById(request, { id: list.data[0].traceId });
|
||||
expect(trace.data).toMatchObject(list.data[0]);
|
||||
});
|
||||
|
||||
test('should be able to delete sso trace by product', async ({ request }) => {
|
||||
await createConnection(request, newConnection);
|
||||
await oauthAuthorize(
|
||||
request,
|
||||
{
|
||||
client_id: 'dummy',
|
||||
tenant: 'dummy',
|
||||
product: newConnection.product,
|
||||
state: 'Bb-w_AqDxZh90BBVz4PRhtRIRetOgo0AR0pmrhzyICU',
|
||||
response_type: 'code',
|
||||
redirect_uri: newConnection.redirectUrl[0].replaceAll('*', ''),
|
||||
code_challenge: 'OcMni5eZvSrQ2ev7tPICbcE7q1piL8Abi8IfJtWbUtY',
|
||||
code_challenge_method: 'S256',
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
let res = await countSSOTracesByProduct(request, { product: newConnection.product });
|
||||
expect(res.count).toBeGreaterThan(0);
|
||||
|
||||
await deleteSSOTraces(request, { product: newConnection.product });
|
||||
|
||||
res = await countSSOTracesByProduct(request, { product: newConnection.product });
|
||||
expect(res.count).toBe(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
export { Portal } from './portal';
|
||||
export { SSOPage } from './sso-page';
|
|
@ -0,0 +1,20 @@
|
|||
import { Locator, Page, expect } from '@playwright/test';
|
||||
|
||||
export class Portal {
|
||||
userAvatarLocator: Locator;
|
||||
constructor(public readonly page: Page) {
|
||||
this.userAvatarLocator = this.page.getByTestId('user-avatar');
|
||||
}
|
||||
|
||||
async doCredentialsLogin() {
|
||||
await this.page.goto('/admin/auth/login');
|
||||
await this.page.getByPlaceholder('Email').fill('super@boxyhq.com');
|
||||
await this.page.getByPlaceholder('Password').fill('999login');
|
||||
await this.page.getByRole('button', { name: 'Sign In' }).click();
|
||||
}
|
||||
|
||||
async isLoggedIn() {
|
||||
// assert login state
|
||||
await expect(this.userAvatarLocator).toBeVisible();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
import type { Page, Locator } from '@playwright/test';
|
||||
import { adminPortalSSODefaults } from '@lib/env';
|
||||
|
||||
const ADMIN_PORTAL_TENANT = adminPortalSSODefaults.tenant;
|
||||
const ADMIN_PORTAL_PRODUCT = adminPortalSSODefaults.product;
|
||||
|
||||
const MOCKSAML_ORIGIN = process.env.MOCKSAML_ORIGIN || 'https://mocksaml.com';
|
||||
const MOCKSAML_SIGNIN_BUTTON_NAME = 'Sign In';
|
||||
|
||||
const MOCKLAB_ORIGIN = 'https://oauth.wiremockapi.cloud';
|
||||
const MOCKLAB_CLIENT_ID = 'mocklab_oauth2';
|
||||
const MOCKLAB_CLIENT_SECRET = 'mocklab_secret';
|
||||
const MOCKLAB_SIGNIN_BUTTON_NAME = 'Login';
|
||||
const MOCKLAB_DISCOVERY_ENDPOINT = 'https://oauth.wiremockapi.cloud/.well-known/openid-configuration';
|
||||
|
||||
export class SSOPage {
|
||||
private readonly createConnection: Locator;
|
||||
private readonly nameInput: Locator;
|
||||
private readonly tenantInput: Locator;
|
||||
private readonly productInput: Locator;
|
||||
private readonly redirectURLSInput: Locator;
|
||||
private readonly defaultRedirectURLInput: Locator;
|
||||
private readonly metadataUrlInput: Locator;
|
||||
private readonly oidcDiscoveryUrlInput: Locator;
|
||||
private readonly oidcClientIdInput: Locator;
|
||||
private readonly oidcClientSecretInput: Locator;
|
||||
private readonly saveConnection: Locator;
|
||||
private readonly deleteButton: Locator;
|
||||
private readonly confirmButton: Locator;
|
||||
private readonly toggleConnectionStatusCheckbox: Locator;
|
||||
private readonly toggleConnectionStatusLabel: Locator;
|
||||
private connections: string[];
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.connections = [];
|
||||
this.createConnection = this.page.getByTestId('create-connection');
|
||||
this.nameInput = this.page.getByLabel('Connection name (Optional)');
|
||||
this.tenantInput = this.page.getByLabel('Tenant');
|
||||
this.productInput = this.page.getByLabel('Product');
|
||||
this.redirectURLSInput = page
|
||||
.getByRole('group')
|
||||
.filter({ hasText: 'Allowed redirect URLs' })
|
||||
.locator(page.getByRole('textbox').first());
|
||||
this.defaultRedirectURLInput = this.page.getByLabel('Default redirect URL');
|
||||
this.metadataUrlInput = this.page.getByLabel('Metadata URL');
|
||||
this.oidcDiscoveryUrlInput = this.page.getByLabel('Well-known URL of OpenID Provider');
|
||||
this.oidcClientIdInput = this.page.getByLabel('Client ID');
|
||||
this.oidcClientSecretInput = this.page.getByLabel('Client Secret');
|
||||
this.saveConnection = this.page.getByRole('button', { name: /save/i });
|
||||
this.toggleConnectionStatusCheckbox = this.page.getByRole('checkbox', { name: 'Active' });
|
||||
this.toggleConnectionStatusLabel = this.page.locator('label').filter({ hasText: 'Active' });
|
||||
this.deleteButton = this.page.getByRole('button', { name: 'Delete' });
|
||||
this.confirmButton = this.page.getByRole('button', { name: 'Confirm' });
|
||||
}
|
||||
|
||||
async goto() {
|
||||
const url = new URL(this.page.url());
|
||||
if (url.pathname !== '/admin/sso-connection') {
|
||||
await this.page.goto('/admin/sso-connection');
|
||||
}
|
||||
}
|
||||
|
||||
async addSSOConnection({
|
||||
name,
|
||||
type = 'saml',
|
||||
baseURL,
|
||||
}: {
|
||||
name: string;
|
||||
type: 'saml' | 'oidc';
|
||||
baseURL: string;
|
||||
}) {
|
||||
const connectionIndex = this.connections.length + 1;
|
||||
const ssoName = `${name}-${connectionIndex}`;
|
||||
// Find the new connection button and click on it
|
||||
await this.createConnection.click();
|
||||
if (type === 'oidc') {
|
||||
// Toggle connection type to OIDC
|
||||
await this.page.getByLabel('OIDC').check();
|
||||
}
|
||||
// Fill the name for the connection
|
||||
await this.nameInput.fill(ssoName);
|
||||
// Fill the tenant for the connection
|
||||
await this.tenantInput.fill(ADMIN_PORTAL_TENANT);
|
||||
// Fill the product for the connection
|
||||
await this.productInput.fill(ADMIN_PORTAL_PRODUCT);
|
||||
// Fill the Allowed redirect URLs for the connection
|
||||
|
||||
await this.redirectURLSInput.fill(baseURL!);
|
||||
// Fill the default redirect URLs for the connection
|
||||
await this.defaultRedirectURLInput.fill(`${baseURL}/admin/auth/idp-login`);
|
||||
if (type === 'saml') {
|
||||
// Enter the metadata url for mocksaml in the form
|
||||
await this.metadataUrlInput.fill(`${MOCKSAML_ORIGIN}/api/namespace/${ssoName}/saml/metadata`);
|
||||
}
|
||||
if (type === 'oidc') {
|
||||
// Enter the OIDC client credentials for mocklab in the form
|
||||
await this.oidcClientIdInput.fill(`${MOCKLAB_CLIENT_ID}-${connectionIndex}`);
|
||||
await this.oidcClientSecretInput.fill(`${MOCKLAB_CLIENT_SECRET}-${connectionIndex}`);
|
||||
// Enter the OIDC discovery url for mocklab in the form
|
||||
await this.oidcDiscoveryUrlInput.fill(MOCKLAB_DISCOVERY_ENDPOINT);
|
||||
}
|
||||
// submit the form
|
||||
await this.saveConnection.click();
|
||||
this.connections = [...this.connections, ssoName];
|
||||
}
|
||||
|
||||
async gotoEditView(name: string) {
|
||||
await this.goto();
|
||||
const editButton = this.page.getByText(name).locator('xpath=..').getByLabel('Edit');
|
||||
await editButton.click();
|
||||
}
|
||||
|
||||
async toggleConnectionStatus(newStatus: boolean) {
|
||||
const isChecked = await this.toggleConnectionStatusCheckbox.isChecked();
|
||||
if (isChecked && !newStatus) {
|
||||
await this.toggleConnectionStatusLabel.click();
|
||||
await this.confirmButton.click();
|
||||
} else if (!isChecked && newStatus) {
|
||||
await this.toggleConnectionStatusLabel.click();
|
||||
await this.confirmButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
async updateSSOConnection({ name, url, newStatus }: { name: string; url: string; newStatus?: boolean }) {
|
||||
await this.gotoEditView(name);
|
||||
await this.redirectURLSInput.fill(url);
|
||||
await this.saveConnection.click();
|
||||
if (typeof newStatus === 'boolean') {
|
||||
await this.gotoEditView(name);
|
||||
await this.toggleConnectionStatus(newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSSOConnection(name: string) {
|
||||
await this.gotoEditView(name);
|
||||
// click the delete and confirm deletion
|
||||
await this.deleteButton.click();
|
||||
await this.confirmButton.click();
|
||||
}
|
||||
|
||||
async deleteAllSSOConnections() {
|
||||
let _connection;
|
||||
while ((_connection = this.connections.shift())) {
|
||||
await this.deleteSSOConnection(_connection);
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
const userAvatarLocator = this.page.getByTestId('user-avatar');
|
||||
// Logout from the magic link authentication
|
||||
await userAvatarLocator.click();
|
||||
await this.page.getByTestId('logout').click();
|
||||
}
|
||||
|
||||
async signInWithSSO() {
|
||||
await this.page.getByTestId('sso-login-button').click();
|
||||
}
|
||||
|
||||
async selectIdP(name: string) {
|
||||
const idpSelectionTitle = 'Select an Identity Provider to continue';
|
||||
await this.page.getByText(idpSelectionTitle).waitFor();
|
||||
await this.page.getByRole('button', { name }).click();
|
||||
}
|
||||
|
||||
async signInWithMockSAML() {
|
||||
// Perform sign in at mocksaml
|
||||
await this.page.waitForURL((url) => url.origin === MOCKSAML_ORIGIN);
|
||||
await this.page.getByPlaceholder('jackson').fill('bob');
|
||||
await this.page.getByRole('button', { name: MOCKSAML_SIGNIN_BUTTON_NAME }).click();
|
||||
}
|
||||
|
||||
async signInWithMockLab() {
|
||||
// Perform sign in at mocklab
|
||||
await this.page.waitForURL((url) => url.origin === MOCKLAB_ORIGIN);
|
||||
await this.page.getByPlaceholder('yours@example.com').fill('bob@oidc.com');
|
||||
await this.page.getByRole('button', { name: MOCKLAB_SIGNIN_BUTTON_NAME }).click();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
import { test as baseTest, expect } from '@playwright/test';
|
||||
import { Portal, SSOPage } from 'e2e/support/fixtures';
|
||||
|
||||
type MyFixtures = {
|
||||
ssoPage: SSOPage;
|
||||
portal: Portal;
|
||||
};
|
||||
|
||||
export const test = baseTest.extend<MyFixtures>({
|
||||
portal: async ({ page }, use) => {
|
||||
const portal = new Portal(page);
|
||||
await use(portal);
|
||||
},
|
||||
ssoPage: async ({ page, portal }, use) => {
|
||||
const ssoPage = new SSOPage(page);
|
||||
await ssoPage.goto();
|
||||
await use(ssoPage);
|
||||
await portal.doCredentialsLogin();
|
||||
await portal.isLoggedIn();
|
||||
await ssoPage.deleteAllSSOConnections();
|
||||
},
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + SAML provider + wrong redirectUrl', async ({ ssoPage, page, baseURL }, testInfo) => {
|
||||
const ssoName = `saml-${testInfo.workerIndex}`;
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'saml', baseURL: baseURL! });
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-1`)).toBeVisible();
|
||||
await ssoPage.updateSSOConnection({
|
||||
name: `${ssoName}-1`,
|
||||
url: 'https://invalid-url.com',
|
||||
});
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
await ssoPage.signInWithSSO();
|
||||
// Wait for browser to redirect to error page
|
||||
await page.waitForURL((url) => url.origin === baseURL && url.pathname === '/error');
|
||||
// Assert error text
|
||||
await expect(page.getByText(`SSO error: Redirect URL is not allowed.`)).toBeVisible();
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + SAML provider + inactive connection', async ({ ssoPage, page, baseURL }, testInfo) => {
|
||||
const ssoName = `saml-${testInfo.workerIndex}`;
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'saml', baseURL: baseURL! });
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-1`)).toBeVisible();
|
||||
await ssoPage.updateSSOConnection({
|
||||
name: `${ssoName}-1`,
|
||||
url: baseURL!,
|
||||
newStatus: false,
|
||||
});
|
||||
// Confirm connection label inactive is displayed
|
||||
await expect(
|
||||
page.getByText(`${ssoName}-1`).locator('xpath=..').getByRole('cell', { name: 'Inactive', exact: true })
|
||||
).toBeVisible();
|
||||
// Logout and try to sign in with connection
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
await ssoPage.signInWithSSO();
|
||||
// Wait for browser to redirect to error page
|
||||
await page.waitForURL((url) => url.origin === baseURL && url.pathname === '/error');
|
||||
// Assert error text
|
||||
await expect(
|
||||
page.getByText('SSO error: SSO connection is deactivated. Please contact your administrator.')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + OIDC provider + wrong redirectUrl', async ({ ssoPage, page, baseURL }, testInfo) => {
|
||||
const ssoName = `oidc-${testInfo.workerIndex}`;
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'oidc', baseURL: baseURL! });
|
||||
// check if the oidc connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-1`)).toBeVisible();
|
||||
await ssoPage.updateSSOConnection({
|
||||
name: `${ssoName}-1`,
|
||||
url: 'https://invalid-url.com',
|
||||
});
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
await ssoPage.signInWithSSO();
|
||||
// Wait for browser to redirect to error page
|
||||
await page.waitForURL((url) => url.origin === baseURL && url.pathname === '/error');
|
||||
// Assert error text
|
||||
await expect(page.getByText('SSO error: Redirect URL is not allowed.')).toBeVisible();
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + OIDC provider + inactive connection', async ({ ssoPage, page, baseURL }, testInfo) => {
|
||||
const ssoName = `oidc-${testInfo.workerIndex}`;
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'oidc', baseURL: baseURL! });
|
||||
// check if the oidc connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-1`)).toBeVisible();
|
||||
await ssoPage.updateSSOConnection({
|
||||
name: `${ssoName}-1`,
|
||||
url: baseURL!,
|
||||
newStatus: false,
|
||||
});
|
||||
// Confirm connection label inactive is displayed
|
||||
await expect(
|
||||
page.getByText(`${ssoName}-1`).locator('xpath=..').getByRole('cell', { name: 'Inactive', exact: true })
|
||||
).toBeVisible();
|
||||
// Logout and try to sign in with connection
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
await ssoPage.signInWithSSO();
|
||||
// Wait for browser to redirect to error page
|
||||
await page.waitForURL((url) => url.origin === baseURL && url.pathname === '/error');
|
||||
// Assert error text
|
||||
await expect(
|
||||
page.getByText('SSO error: SSO connection is deactivated. Please contact your administrator.')
|
||||
).toBeVisible();
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
import { test as baseTest, expect } from '@playwright/test';
|
||||
import { Portal, SSOPage } from 'e2e/support/fixtures';
|
||||
|
||||
type MyFixtures = {
|
||||
ssoPage: SSOPage;
|
||||
portal: Portal;
|
||||
};
|
||||
|
||||
export const test = baseTest.extend<MyFixtures>({
|
||||
ssoPage: async ({ page, baseURL }, use, testInfo) => {
|
||||
const ssoPage = new SSOPage(page);
|
||||
const ssoName = `oidc-${testInfo.workerIndex}`;
|
||||
await ssoPage.goto();
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'oidc', baseURL: baseURL! });
|
||||
await use(ssoPage);
|
||||
await ssoPage.deleteAllSSOConnections();
|
||||
},
|
||||
portal: async ({ page }, use) => {
|
||||
const portal = new Portal(page);
|
||||
await use(portal);
|
||||
},
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + OIDC provider', async ({ ssoPage, portal, page, baseURL }, testInfo) => {
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`oidc-${testInfo.workerIndex}-1`)).toBeVisible();
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
await ssoPage.signInWithSSO();
|
||||
// Login using MockLab
|
||||
await ssoPage.signInWithMockLab();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// Assert logged in state
|
||||
await portal.isLoggedIn();
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + 2 OIDC providers', async ({ ssoPage, portal, page, baseURL }, testInfo) => {
|
||||
const ssoName = `oidc-${testInfo.workerIndex}`;
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-1`)).toBeVisible();
|
||||
// Add second OIDC connection
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'oidc', baseURL: baseURL! });
|
||||
// check if the second added connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-2`)).toBeVisible();
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
// Login using MockLab
|
||||
await ssoPage.signInWithSSO();
|
||||
// Select IdP from selection screen
|
||||
await ssoPage.selectIdP(`${ssoName}-2`);
|
||||
await ssoPage.signInWithMockLab();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// Assert logged in state
|
||||
await portal.isLoggedIn();
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
import { test as baseTest, expect } from '@playwright/test';
|
||||
import { Portal, SSOPage } from 'e2e/support/fixtures';
|
||||
|
||||
type MyFixtures = {
|
||||
ssoPage: SSOPage;
|
||||
portal: Portal;
|
||||
};
|
||||
|
||||
export const test = baseTest.extend<MyFixtures>({
|
||||
ssoPage: async ({ page, baseURL }, use, testInfo) => {
|
||||
const ssoPage = new SSOPage(page);
|
||||
let ssoName = `saml-${testInfo.workerIndex}`;
|
||||
await ssoPage.goto();
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'saml', baseURL: baseURL! });
|
||||
await ssoPage.goto();
|
||||
ssoName = `oidc-${testInfo.workerIndex}`;
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'oidc', baseURL: baseURL! });
|
||||
await use(ssoPage);
|
||||
await ssoPage.deleteAllSSOConnections();
|
||||
},
|
||||
portal: async ({ page }, use) => {
|
||||
const portal = new Portal(page);
|
||||
await use(portal);
|
||||
},
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + SAML provider + OIDC provider', async ({
|
||||
ssoPage,
|
||||
portal,
|
||||
page,
|
||||
baseURL,
|
||||
}, testInfo) => {
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`saml-${testInfo.workerIndex}-1`)).toBeVisible();
|
||||
// check if the second added connection appears in the connection list
|
||||
await expect(page.getByText(`oidc-${testInfo.workerIndex}-2`)).toBeVisible();
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
// Login using MockSAML
|
||||
await ssoPage.signInWithSSO();
|
||||
// Select IdP from selection screen
|
||||
await ssoPage.selectIdP(`saml-${testInfo.workerIndex}-1`);
|
||||
// Login using MockSAML
|
||||
await ssoPage.signInWithMockSAML();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// Assert logged in state
|
||||
await portal.isLoggedIn();
|
||||
|
||||
// Logout of SAML login
|
||||
await ssoPage.logout();
|
||||
// Login using MockLab
|
||||
await ssoPage.signInWithSSO();
|
||||
// Select IdP from selection screen
|
||||
await ssoPage.selectIdP(`oidc-${testInfo.workerIndex}-2`);
|
||||
// Login using MockLab
|
||||
await ssoPage.signInWithMockLab();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// Assert logged in state
|
||||
await portal.isLoggedIn();
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
import { test as baseTest, expect } from '@playwright/test';
|
||||
import { Portal, SSOPage } from 'e2e/support/fixtures';
|
||||
|
||||
type MyFixtures = {
|
||||
ssoPage: SSOPage;
|
||||
portal: Portal;
|
||||
};
|
||||
|
||||
export const test = baseTest.extend<MyFixtures>({
|
||||
ssoPage: async ({ page, baseURL }, use, testInfo) => {
|
||||
const ssoPage = new SSOPage(page);
|
||||
const ssoName = `saml-${testInfo.workerIndex}`;
|
||||
await ssoPage.goto();
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'saml', baseURL: baseURL! });
|
||||
await use(ssoPage);
|
||||
await ssoPage.deleteAllSSOConnections();
|
||||
},
|
||||
portal: async ({ page }, use) => {
|
||||
const portal = new Portal(page);
|
||||
await use(portal);
|
||||
},
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + SAML provider', async ({ ssoPage, portal, page, baseURL }, testInfo) => {
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`saml-${testInfo.workerIndex}-1`)).toBeVisible();
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
await ssoPage.signInWithSSO();
|
||||
// Login using MockSAML
|
||||
await ssoPage.signInWithMockSAML();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// Assert logged in state
|
||||
await portal.isLoggedIn();
|
||||
});
|
||||
|
||||
test('OAuth2 wrapper + 2 SAML providers', async ({ ssoPage, portal, page, baseURL }, testInfo) => {
|
||||
const ssoName = `saml-${testInfo.workerIndex}`;
|
||||
// check if the first added connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-1`)).toBeVisible();
|
||||
// Add second SAML connection
|
||||
await ssoPage.addSSOConnection({ name: ssoName, type: 'saml', baseURL: baseURL! });
|
||||
// check if the second added connection appears in the connection list
|
||||
await expect(page.getByText(`${ssoName}-2`)).toBeVisible();
|
||||
// Logout of magic link login
|
||||
await ssoPage.logout();
|
||||
// Login using MockSAML
|
||||
await ssoPage.signInWithSSO();
|
||||
// Select IdP from selection screen
|
||||
await ssoPage.selectIdP(`${ssoName}-2`);
|
||||
await ssoPage.signInWithMockSAML();
|
||||
// Wait for browser to redirect back to admin portal
|
||||
await page.waitForURL((url) => url.origin === baseURL);
|
||||
// Assert logged in state
|
||||
await portal.isLoggedIn();
|
||||
});
|
|
@ -75,7 +75,7 @@ test.describe('Admin Portal SSO - SAML', () => {
|
|||
test('delete the SAML SSO connection', async ({ page }) => {
|
||||
await page.goto('/admin/settings');
|
||||
// select the row of the connection list table, then locate the edit button
|
||||
const editButton = page.getByText(TEST_SAML_SSO_CONNECTION_NAME).locator('..').getByLabel('Edit');
|
||||
const editButton = page.getByText(TEST_SAML_SSO_CONNECTION_NAME).locator('xpath=..').getByLabel('Edit');
|
||||
await editButton.click();
|
||||
// click the delete and confirm deletion
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
|
@ -138,7 +138,7 @@ test.describe('Admin Portal SSO - OIDC', () => {
|
|||
await page.getByTestId('logout').click();
|
||||
// Click on login with sso button
|
||||
await page.getByTestId('sso-login-button').click();
|
||||
// Perform sign in at mocksaml
|
||||
// Perform sign in at mocklab
|
||||
await page.waitForURL((url) => url.origin === MOCKLAB_ORIGIN);
|
||||
await page.getByPlaceholder('yours@example.com').fill('bob@oidc.com');
|
||||
await page.getByRole('button', { name: MOCKLAB_SIGNIN_BUTTON_NAME }).click();
|
||||
|
|
|
@ -22,4 +22,4 @@ patches:
|
|||
|
||||
images:
|
||||
- name: boxyhq/jackson
|
||||
newTag: 1.23.5
|
||||
newTag: 1.23.7
|
||||
|
|
|
@ -22,4 +22,4 @@ patches:
|
|||
|
||||
images:
|
||||
- name: boxyhq/jackson
|
||||
newTag: 1.23.5
|
||||
newTag: 1.23.7
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "jackson",
|
||||
"version": "1.23.6",
|
||||
"version": "1.23.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jackson",
|
||||
"version": "1.23.6",
|
||||
"version": "1.23.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jackson",
|
||||
"version": "1.23.6",
|
||||
"version": "1.23.7",
|
||||
"private": true,
|
||||
"description": "SAML 2.0 service",
|
||||
"keywords": [
|
||||
|
|
|
@ -22,7 +22,8 @@ const config: PlaywrightTestConfig = {
|
|||
timeout: 60 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
DEBUG: 'pw:webserver',
|
||||
NEXTAUTH_ADMIN_CREDENTIALS: 'super@boxyhq.com:999login',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue