Compare commits

...

23 Commits

Author SHA1 Message Date
Deepak Prabhakara 671c721869
Merge 55d8f14d40 into 9943a06ace 2024-05-06 11:41:23 +00:00
Deepak Prabhakara 55d8f14d40 Merge branch 'main' into identity-fed-routes 2024-05-06 12:41:13 +01:00
Aswin V 9943a06ace
Extend e2e (#2601)
* Not needed with standalone build in CI as well as local runs

* Start adding tests for main sections under Admin UI

* Add SAML SSO connection

* Remove only

* Debug failing test

* Cleanup debugging changes

* Update mocksaml docker

* Increase number of workers for playwright execution

* Disable multiple workers for now

* Align folder layout with sidebar features

* Enable stdout for webserver

* Try fixture

* Fixture WIP

* WIP fixture tweak and add more test cases

* Fix locator

* Rename test file

* Remove only

* Keep track of connections inside fixture, delete all method fix

* Fix sso naming

* Use portal fixture for common utils

* Make fixture generic for OIDC SSO

* Add OIDC porvider tests

* Comment tweak

* Make client id secret dynamic

* Spec for OAuth2 wrapper + 1 SAML and 1 OIDC providers

* Fixture method to update SSO connection

* Test case for wrong redirect url

* Refactor

* WIP Wrong redirect url test for OIDC provider plus
setup for toggle connection

* WIP inactive connection test

* Set env for credentials login

* Add credentials login to portal fixture

* Fixes
2024-05-06 12:37:12 +01:00
Deepak Prabhakara f08025cc31 updated deployment 2024-05-06 12:36:58 +01:00
Deepak Prabhakara 63baf12a38 Release 1.23.7 2024-05-06 09:16:09 +01:00
Deepak Prabhakara 668a1ba499
Stats for Identity Federation Apps (#2671)
rename
2024-05-06 00:13:25 +01:00
Deepak Prabhakara b6adc308f7 updated package-lock 2024-05-04 23:23:49 +01:00
ukrocks007 c7272dae67 Merge branch 'main' into identity-fed-routes 2024-05-02 13:06:19 +05:30
Deepak Prabhakara e3ead0cdc8 Merge branch 'main' into identity-fed-routes 2024-04-30 16:56:14 +01:00
Deepak Prabhakara 0439806021 keep api/federated-saml but move to api/identity-federation 2024-04-27 22:34:11 +01:00
Deepak Prabhakara 003d602d7d Merge branch 'main' into identity-fed-routes 2024-04-27 22:25:31 +01:00
Deepak Prabhakara d4951f6f20 SAMLFederation -> IdentityFederation 2024-04-25 23:19:53 +01:00
Deepak Prabhakara 382f2d6245 SAMLFederation -> IdentityFederation 2024-04-25 22:55:26 +01:00
Deepak Prabhakara 08a9b6a28c Merge branch 'main' into identity-fed-routes 2024-04-25 22:47:28 +01:00
Deepak Prabhakara a74dcfd9bb don't change ACS url for SAML federation 2024-04-25 16:54:50 +01:00
Deepak Prabhakara 2483827a7a sso-tracer -> sso-traces 2024-04-25 15:25:50 +01:00
Deepak Prabhakara b2a6ae5e67
Merge branch 'main' into identity-fed-routes 2024-04-25 15:08:57 +01:00
Deepak Prabhakara a4ff7cebb0 separate section for Identity Federation 2024-04-25 14:06:58 +01:00
Deepak Prabhakara 7658fcfa8e renamed test folder 2024-04-25 13:14:54 +01:00
Deepak Prabhakara e5b068cb9e renamed routes 2024-04-25 13:14:03 +01:00
Deepak Prabhakara eedb1c91e6 Merge branch 'main' into identity-fed-routes 2024-04-25 12:46:26 +01:00
Deepak Prabhakara 3e33d7415b updated swagger file 2024-04-25 12:45:02 +01:00
Deepak Prabhakara 0aaa8f5762 WIP 2024-04-25 11:38:59 +01:00
88 changed files with 967 additions and 2308 deletions

View File

@ -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:

View File

@ -40,7 +40,7 @@ export const Sidebar = ({ isOpen, setIsOpen }: SidebarProps) => {
href: '/admin/sso-connection',
text: t('enterprise_sso'),
icon: SSOLogo,
active: asPath.includes('/admin/sso-connection') || asPath.includes('/admin/federated-saml'),
active: asPath.includes('/admin/sso-connection'),
items: [
{
href: '/admin/sso-connection',
@ -54,14 +54,22 @@ export const Sidebar = ({ isOpen, setIsOpen }: SidebarProps) => {
active: asPath.includes('/admin/sso-connection/setup-link'),
},
{
href: '/admin/federated-saml',
text: t('saml_federation'),
active: asPath.includes('/admin/federated-saml'),
},
{
href: '/admin/sso-tracer',
href: '/admin/sso-traces',
text: t('bui-tracer-title'),
active: asPath.includes('/admin/sso-tracer'),
active: asPath.includes('/admin/sso-traces'),
},
],
},
{
href: '/admin/identity-federation',
text: t('identity_federation'),
icon: SSOLogo,
active: asPath.includes('/admin/identity-federation'),
items: [
{
href: '/admin/identity-federation',
text: t('apps'),
active: asPath.includes('/admin/identity-federation'),
},
],
},

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
import { SAMLFederationApp } from '@boxyhq/saml-jackson';
import { IdentityFederationApp } from '@boxyhq/saml-jackson';
test.use({
extraHTTPHeaders: {
@ -17,10 +17,10 @@ const expectedApp = {
acsUrl: 'https://boxyhq.com/acs',
};
let app = {} as SAMLFederationApp;
let app = {} as IdentityFederationApp;
test.beforeAll(async ({ request }) => {
const response = await request.post('/api/v1/federated-saml', {
const response = await request.post('/api/v1/identity-federation', {
data: {
...expectedApp,
},
@ -32,9 +32,9 @@ test.beforeAll(async ({ request }) => {
expect(response.status()).toBe(201);
});
test.describe('GET /api/v1/federated-saml', () => {
test.describe('GET /api/v1/identity-federation', () => {
test('Fetch app by id', async ({ request }) => {
const response = await request.get(`/api/v1/federated-saml?id=${app?.id}`);
const response = await request.get(`/api/v1/identity-federation?id=${app?.id}`);
const { data } = await response.json();
@ -45,7 +45,7 @@ test.describe('GET /api/v1/federated-saml', () => {
test('Fetch app by tenant and product', async ({ request }) => {
const response = await request.get(
`/api/v1/federated-saml?tenant=${app?.tenant}&product=${app?.product}`
`/api/v1/identity-federation?tenant=${app?.tenant}&product=${app?.product}`
);
const { data } = await response.json();
@ -56,7 +56,7 @@ test.describe('GET /api/v1/federated-saml', () => {
});
test('Fetch app by product', async ({ request }) => {
const response = await request.get(`/api/v1/federated-saml/product?product=${app?.product}`);
const response = await request.get(`/api/v1/identity-federation/product?product=${app?.product}`);
const { data } = await response.json();
@ -66,9 +66,9 @@ test.describe('GET /api/v1/federated-saml', () => {
});
});
test.describe('PATCH /api/v1/federated-saml', () => {
test.describe('PATCH /api/v1/identity-federation', () => {
test('Update app by id', async ({ request }) => {
const response = await request.patch(`/api/v1/federated-saml`, {
const response = await request.patch(`/api/v1/identity-federation`, {
data: {
id: app?.id,
name: 'Updated App',
@ -86,7 +86,7 @@ test.describe('PATCH /api/v1/federated-saml', () => {
});
test('Update app by tenant and product', async ({ request }) => {
const response = await request.patch(`/api/v1/federated-saml`, {
const response = await request.patch(`/api/v1/identity-federation`, {
data: {
id: app?.id,
name: 'Updated App 2',
@ -104,9 +104,9 @@ test.describe('PATCH /api/v1/federated-saml', () => {
});
});
test.describe('DELETE /api/v1/federated-saml', () => {
test.describe('DELETE /api/v1/identity-federation', () => {
test('Delete app by id', async ({ request }) => {
const response = await request.delete(`/api/v1/federated-saml?id=${app?.id}`);
const response = await request.delete(`/api/v1/identity-federation?id=${app?.id}`);
const { data } = await response.json();
@ -115,7 +115,7 @@ test.describe('DELETE /api/v1/federated-saml', () => {
expect(data).toMatchObject({});
// Confirm app is deleted
const response2 = await request.get(`/api/v1/federated-saml?id=${app?.id}`);
const response2 = await request.get(`/api/v1/identity-federation?id=${app?.id}`);
expect(response2.ok()).toBe(false);
expect(response2.status()).toBe(404);

View File

@ -0,0 +1,2 @@
export { Portal } from './portal';
export { SSOPage } from './sso-page';

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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();

View File

@ -18,19 +18,19 @@ const UpdateApp = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
return (
<div className='space-y-4'>
<LinkBack href='/admin/federated-saml' />
<LinkBack href='/admin/identity-federation' />
<EditFederatedSAMLApp
urls={{
getApp: `/api/admin/federated-saml/${id}`,
updateApp: `/api/admin/federated-saml/${id}`,
deleteApp: `/api/admin/federated-saml/${id}`,
getApp: `/api/admin/identity-federation/${id}`,
updateApp: `/api/admin/identity-federation/${id}`,
deleteApp: `/api/admin/identity-federation/${id}`,
}}
onUpdate={() => {
successToast(t('saml_federation_update_success'));
}}
onDelete={() => {
successToast(t('saml_federation_delete_success'));
router.push('/admin/federated-saml');
router.push('/admin/identity-federation');
}}
onError={(error) => {
errorToast(error.message);

View File

@ -9,10 +9,10 @@ const AppsList = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
return (
<FederatedSAMLApps
urls={{ getApps: '/api/admin/federated-saml' }}
onEdit={(app) => router.push(`/admin/federated-saml/${app.id}/edit`)}
urls={{ getApps: '/api/admin/identity-federation' }}
onEdit={(app) => router.push(`/admin/identity-federation/${app.id}/edit`)}
actions={{
newApp: '/admin/federated-saml/new',
newApp: '/admin/identity-federation/new',
samlConfiguration: '/.well-known/idp-configuration',
oidcConfiguration: '/.well-known/openid-configuration',
}}

View File

@ -1,12 +1,12 @@
import Link from 'next/link';
import { useTranslation } from 'next-i18next';
import type { SAMLFederationAppWithMetadata } from '@boxyhq/saml-jackson';
import type { IdentityFederationAppWithMetadata } from '@boxyhq/saml-jackson';
import { Toaster } from '@components/Toaster';
import { InputWithCopyButton, CopyToClipboardButton, LinkOutline } from '@boxyhq/internal-ui';
import LicenseRequired from '@components/LicenseRequired';
type MetadataProps = {
metadata: Pick<SAMLFederationAppWithMetadata, 'metadata'>['metadata'];
metadata: Pick<IdentityFederationAppWithMetadata, 'metadata'>['metadata'];
hasValidLicense: boolean;
};

View File

@ -16,12 +16,12 @@ const NewApp = ({ hasValidLicense, samlAudience }: { hasValidLicense: boolean; s
return (
<div className='space-y-4'>
<LinkBack href='/admin/federated-saml' />
<LinkBack href='/admin/identity-federation' />
<NewFederatedSAMLApp
urls={{ createApp: '/api/admin/federated-saml' }}
urls={{ createApp: '/api/admin/identity-federation' }}
onSuccess={(data) => {
successToast(t('saml_federation_new_success'));
router.replace(`/admin/federated-saml/${data.id}/edit`);
router.replace(`/admin/identity-federation/${data.id}/edit`);
}}
onError={(error) => {
errorToast(error.message);

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
export { NewFederatedSAMLApp } from './NewFederatedSAMLApp';
export { EditFederatedSAMLApp } from './EditFederatedSAMLApp';
export { FederatedSAMLApps } from './FederatedSAMLApps';

View File

@ -1,6 +1,6 @@
import { useState } from 'react';
import { Button } from 'react-daisyui';
import type { SAMLFederationApp } from '../types';
import type { IdentityFederationApp } from '../types';
import TagsInput from 'react-tagsinput';
import { useTranslation } from 'next-i18next';
import { useFormik } from 'formik';
@ -13,7 +13,7 @@ import { ItemList } from '@boxyhq/react-ui/shared';
import { CopyToClipboardButton } from '../shared/InputWithCopyButton';
import { IconButton } from '../shared/IconButton';
type EditApp = Pick<SAMLFederationApp, 'name' | 'acsUrl' | 'tenants' | 'redirectUrl'>;
type EditApp = Pick<IdentityFederationApp, 'name' | 'acsUrl' | 'tenants' | 'redirectUrl'>;
export const Edit = ({
app,
@ -22,9 +22,9 @@ export const Edit = ({
onUpdate,
excludeFields,
}: {
app: SAMLFederationApp;
app: IdentityFederationApp;
urls: { patch: string };
onUpdate?: (data: SAMLFederationApp) => void;
onUpdate?: (data: IdentityFederationApp) => void;
onError?: (error: Error) => void;
excludeFields?: 'product'[];
}) => {

View File

@ -1,12 +1,12 @@
import { Button } from 'react-daisyui';
import { useTranslation } from 'next-i18next';
import type { SAMLFederationApp } from '../types';
import type { IdentityFederationApp } from '../types';
import { useFormik } from 'formik';
import { defaultHeaders } from '../utils';
import { Card } from '../shared';
import { AttributesMapping } from './AttributesMapping';
type Mappings = Pick<SAMLFederationApp, 'mappings'>;
type Mappings = Pick<IdentityFederationApp, 'mappings'>;
export const EditAttributesMapping = ({
app,
@ -14,9 +14,9 @@ export const EditAttributesMapping = ({
onUpdate,
onError,
}: {
app: SAMLFederationApp;
app: IdentityFederationApp;
urls: { patch: string };
onUpdate?: (data: SAMLFederationApp) => void;
onUpdate?: (data: IdentityFederationApp) => void;
onError?: (error: Error) => void;
}) => {
const { t } = useTranslation('common');

View File

@ -1,11 +1,11 @@
import { Button } from 'react-daisyui';
import { useTranslation } from 'next-i18next';
import type { SAMLFederationApp } from '../types';
import type { IdentityFederationApp } from '../types';
import { useFormik } from 'formik';
import { defaultHeaders } from '../utils';
import { Card } from '../shared';
type Branding = Pick<SAMLFederationApp, 'logoUrl' | 'faviconUrl' | 'primaryColor'>;
type Branding = Pick<IdentityFederationApp, 'logoUrl' | 'faviconUrl' | 'primaryColor'>;
export const EditBranding = ({
app,
@ -13,9 +13,9 @@ export const EditBranding = ({
onUpdate,
onError,
}: {
app: SAMLFederationApp;
app: IdentityFederationApp;
urls: { patch: string };
onUpdate?: (data: SAMLFederationApp) => void;
onUpdate?: (data: IdentityFederationApp) => void;
onError?: (error: Error) => void;
}) => {
const { t } = useTranslation('common');

View File

@ -1,5 +1,5 @@
import useSWR from 'swr';
import type { SAMLFederationApp } from '../types';
import type { IdentityFederationApp } from '../types';
import { EditBranding } from './EditBranding';
import { Edit } from './Edit';
import { EditAttributesMapping } from './EditAttributesMapping';
@ -9,7 +9,7 @@ import { useEffect, useState } from 'react';
import { defaultHeaders, fetcher } from '../utils';
import { PageHeader } from '../shared';
export const EditFederatedSAMLApp = ({
export const EditIdentityFederationApp = ({
urls,
onError,
onUpdate,
@ -17,7 +17,7 @@ export const EditFederatedSAMLApp = ({
excludeFields,
}: {
urls: { getApp: string; updateApp: string; deleteApp: string };
onUpdate?: (data: SAMLFederationApp) => void;
onUpdate?: (data: IdentityFederationApp) => void;
onError?: (error: Error) => void;
onDelete?: () => void;
excludeFields?: 'product'[];
@ -25,7 +25,7 @@ export const EditFederatedSAMLApp = ({
const { t } = useTranslation('common');
const [delModalVisible, setDelModalVisible] = useState(false);
const { data, isLoading, error, mutate } = useSWR<{ data: SAMLFederationApp }>(urls.getApp, fetcher);
const { data, isLoading, error, mutate } = useSWR<{ data: IdentityFederationApp }>(urls.getApp, fetcher);
useEffect(() => {
if (error) {

View File

@ -11,7 +11,7 @@ import {
ButtonPrimary,
} from '../shared';
import { useTranslation } from 'next-i18next';
import type { SAMLFederationApp } from '../types';
import type { IdentityFederationApp } from '../types';
import PencilIcon from '@heroicons/react/24/outline/PencilIcon';
import { TableBodyType } from '../shared/Table';
import { pageLimit } from '../shared/Pagination';
@ -19,9 +19,9 @@ import { usePaginate } from '../hooks';
import { useRouter } from '../hooks';
import { useEffect } from 'react';
type ExcludeFields = keyof Pick<SAMLFederationApp, 'product'>;
type ExcludeFields = keyof Pick<IdentityFederationApp, 'product'>;
export const FederatedSAMLApps = ({
export const IdentityFederationApps = ({
urls,
excludeFields,
onEdit,
@ -30,9 +30,9 @@ export const FederatedSAMLApps = ({
}: {
urls: { getApps: string };
excludeFields?: ExcludeFields[];
onEdit?: (app: SAMLFederationApp) => void;
onEdit?: (app: IdentityFederationApp) => void;
actions: { newApp: string; samlConfiguration: string; oidcConfiguration: string };
actionCols?: { text: string; onClick: (app: SAMLFederationApp) => void; icon: JSX.Element }[];
actionCols?: { text: string; onClick: (app: IdentityFederationApp) => void; icon: JSX.Element }[];
}) => {
const { router } = useRouter();
const { t } = useTranslation('common');
@ -45,7 +45,7 @@ export const FederatedSAMLApps = ({
getAppsUrl += `&pageToken=${pageTokenMap[paginate.offset - pageLimit]}`;
}
const { data, isLoading, error } = useSWR<{ data: SAMLFederationApp[]; pageToken?: string }>(
const { data, isLoading, error } = useSWR<{ data: IdentityFederationApp[]; pageToken?: string }>(
getAppsUrl,
fetcher
);

View File

@ -2,19 +2,19 @@ import { useFormik } from 'formik';
import TagsInput from 'react-tagsinput';
import { Card, Button } from 'react-daisyui';
import { useTranslation } from 'next-i18next';
import type { SAMLFederationApp } from '../types';
import type { IdentityFederationApp } from '../types';
import QuestionMarkCircleIcon from '@heroicons/react/24/outline/QuestionMarkCircleIcon';
import { defaultHeaders } from '../utils';
import { AttributesMapping } from './AttributesMapping';
import { PageHeader } from '../shared';
import { ItemList } from '@boxyhq/react-ui/shared';
type NewSAMLFederationApp = Pick<
SAMLFederationApp,
type NewIdentityFederationApp = Pick<
IdentityFederationApp,
'name' | 'tenant' | 'product' | 'acsUrl' | 'entityId' | 'tenants' | 'mappings' | 'type' | 'redirectUrl'
>;
export const NewFederatedSAMLApp = ({
export const NewIdentityFederationApp = ({
samlAudience = 'https://saml.boxyhq.com',
urls,
onSuccess,
@ -24,14 +24,14 @@ export const NewFederatedSAMLApp = ({
}: {
samlAudience?: string;
urls: { createApp: string };
onSuccess?: (data: SAMLFederationApp) => void;
onSuccess?: (data: IdentityFederationApp) => void;
onError?: (error: Error) => void;
onEntityIdGenerated?: (entityId: string) => void;
excludeFields?: 'product'[];
}) => {
const { t } = useTranslation('common');
const initialValues: NewSAMLFederationApp = {
const initialValues: NewIdentityFederationApp = {
type: 'saml',
name: '',
tenant: '',
@ -44,11 +44,11 @@ export const NewFederatedSAMLApp = ({
if (excludeFields) {
excludeFields.forEach((key) => {
delete initialValues[key as keyof NewSAMLFederationApp];
delete initialValues[key as keyof NewIdentityFederationApp];
});
}
const formik = useFormik<NewSAMLFederationApp>({
const formik = useFormik<NewIdentityFederationApp>({
initialValues: initialValues,
onSubmit: async (values) => {
const rawResponse = await fetch(urls.createApp, {

View File

@ -0,0 +1,3 @@
export { NewIdentityFederationApp as NewFederatedSAMLApp } from './NewIdentityFederationApp';
export { EditIdentityFederationApp as EditFederatedSAMLApp } from './EditIdentityFederationApp';
export { IdentityFederationApps as FederatedSAMLApps } from './IdentityFederationApps';

View File

@ -1,7 +1,7 @@
export * from './well-known';
export * from './federated-saml';
export * from './identity-federation';
export * from './shared';
export * from './dsync';
export * from './provider';
export * from './sso-tracer';
export * from './sso-traces';
export * from './setup-link';

View File

@ -10,7 +10,7 @@ import { addQueryParamsToPath, copyToClipboard, fetcher } from '../utils';
import { TableBodyType } from '../shared/Table';
import { pageLimit } from '../shared/Pagination';
import { usePaginate, useRouter } from '../hooks';
import type { SAMLFederationApp, SetupLink, SetupLinkService } from '../types';
import type { IdentityFederationApp, SetupLink, SetupLinkService } from '../types';
import {
Loading,
Table,
@ -24,7 +24,7 @@ import {
} from '../shared';
import { SetupLinkInfoModal } from './SetupLinkInfoModal';
type ExcludeFields = keyof Pick<SAMLFederationApp, 'product'>;
type ExcludeFields = keyof Pick<IdentityFederationApp, 'product'>;
export const SetupLinks = ({
urls,

View File

@ -90,7 +90,7 @@ export type AttributeMapping = {
value: string;
};
export type SAMLFederationApp = {
export type IdentityFederationApp = {
id: string;
type?: string;
clientID?: string;

View File

@ -22,4 +22,4 @@ patches:
images:
- name: boxyhq/jackson
newTag: 1.23.5
newTag: 1.23.7

View File

@ -22,4 +22,4 @@ patches:
images:
- name: boxyhq/jackson
newTag: 1.23.5
newTag: 1.23.7

View File

@ -1,4 +1,5 @@
{
"apps": "Apps",
"error_loading_page": "Unable to load this page. Maybe you don't have enough rights.",
"documentation": "Documentation",
"back": "Back",
@ -41,7 +42,7 @@
"directory_created_successfully": "Directory created successfully",
"directory_updated_successfully": "Directory updated successfully",
"dashboard": "Dashboard",
"saml_federation": "Identity Federation",
"identity_federation": "Identity Federation",
"settings": "Settings",
"admin_portal_sso": "SSO for Admin Portal",
"configuration": "Configuration",
@ -224,7 +225,7 @@
"bui-dsync-authorization-google": "Authorize Google Workspace",
"bui-dsync-authorization-google-desc": "You should authorize Google Workspace to sync your directory. Click the button below start the authorization process. Make sure you have the necessary permissions to authorize Google Workspace.",
"bui-dsync-google-domain": "Google Domain",
"bui-tracer-title": "SSO Tracer",
"bui-tracer-title": "SSO Traces",
"bui-tracer-id": "Trace ID",
"bui-tracer-description": "Description",
"bui-tracer-assertion-type": "Assertion Type",

View File

@ -11,6 +11,7 @@ const unAuthenticatedApiRoutes = [
'/api/hello',
'/api/auth/**',
'/api/federated-saml/**',
'/api/identity-federation/**',
'/api/logout/**',
'/api/oauth/**',
'/api/scim/v2.0/**',

View File

@ -101,6 +101,10 @@ module.exports = {
source: '/api/v1/saml-traces/:path*',
destination: '/api/v1/sso-traces/:path*',
},
{
source: '/api/v1/federated-saml/:path*',
destination: '/api/v1/identity-federation/:path*',
},
];
},
images: {

View File

@ -26,8 +26,8 @@ const map = {
'src/directory-sync/request.ts',
],
'test/dsync/events.test.ts': ['src/directory-sync/events.ts'],
'test/federated-saml/app.test.ts': ['src/ee/federated-saml/app.ts'],
'test/federated-saml/sso.test.ts': ['src/ee/federated-saml/sso.ts'],
'test/identity-federation/app.test.ts': ['src/ee/identity-federation/app.ts'],
'test/identity-federation/sso.test.ts': ['src/ee/identity-federation/sso.ts'],
'test/event/index.test.ts': ['src/event/*'],
'test/dsync/google_oauth.test.ts': [
'src/directory-sync/non-scim/google/oauth.ts',

115
npm/package-lock.json generated
View File

@ -1151,7 +1151,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"devOptional": true,
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@ -1339,7 +1339,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -1348,13 +1348,13 @@
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"devOptional": true
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"devOptional": true,
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
@ -3064,13 +3064,13 @@
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"devOptional": true
"dev": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"devOptional": true
"dev": true
},
"node_modules/@tsconfig/node14": {
"version": "14.1.2",
@ -3228,7 +3228,7 @@
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"devOptional": true,
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3240,7 +3240,7 @@
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@ -3339,7 +3339,7 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"devOptional": true
"dev": true
},
"node_modules/array-buffer-byte-length": {
"version": "1.0.1",
@ -4148,7 +4148,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
"dev": true
},
"node_modules/cross-env": {
"version": "7.0.3",
@ -4813,63 +4813,6 @@
"node": ">=14"
}
},
"node_modules/gcp-metadata": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
"optional": true,
"peer": true,
"dependencies": {
"gaxios": "^5.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/gcp-metadata/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"optional": true,
"peer": true,
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/gcp-metadata/node_modules/gaxios": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
"integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
"optional": true,
"peer": true,
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^5.0.0",
"is-stream": "^2.0.0",
"node-fetch": "^2.6.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/gcp-metadata/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"optional": true,
"peer": true,
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
@ -5400,7 +5343,7 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"devOptional": true,
"dev": true,
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
@ -5843,7 +5786,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"devOptional": true
"dev": true
},
"node_modules/json-bigint": {
"version": "1.0.0",
@ -6081,7 +6024,7 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"devOptional": true
"dev": true
},
"node_modules/make-fetch-happen": {
"version": "13.0.0",
@ -7429,20 +7372,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dev": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/react-element-to-jsx-string": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz",
@ -7921,7 +7850,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
@ -7931,7 +7860,7 @@
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"devOptional": true,
"dev": true,
"dependencies": {
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
@ -8521,7 +8450,7 @@
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"devOptional": true,
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@ -8564,19 +8493,19 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"devOptional": true
"dev": true
},
"node_modules/ts-node/node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"devOptional": true
"dev": true
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=0.3.1"
}
@ -8879,7 +8808,7 @@
},
"node_modules/typescript": {
"version": "5.4.5",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@ -8967,7 +8896,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"devOptional": true
"dev": true
},
"node_modules/v8-to-istanbul": {
"version": "9.2.0",
@ -9417,7 +9346,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=6"
}

View File

@ -21,7 +21,7 @@ import type {
SSOTracerInstance,
OAuthErrorHandlerParams,
OIDCAuthzResponsePayload,
SAMLFederationApp,
IdentityFederationApp,
} from '../typings';
import {
relayStatePrefix,
@ -45,7 +45,7 @@ import { getDefaultCertificate } from '../saml/x509';
import { SSOHandler } from './sso-handler';
import { ValidateOption, extractSAMLResponseAttributes } from '../saml/lib';
import { oidcIssuerInstance } from './oauth/oidc-issuer';
import { App } from '../ee/federated-saml/app';
import { App } from '../ee/identity-federation/app';
const deflateRawAsync = promisify(deflateRaw);
@ -96,7 +96,7 @@ export class OAuthController implements IOAuthController {
let requestedOIDCFlow: boolean | undefined;
let isOIDCFederated: boolean | undefined;
let connection: SAMLSSORecord | OIDCSSORecord | undefined;
let fedApp: SAMLFederationApp | undefined;
let fedApp: IdentityFederationApp | undefined;
try {
const tenant = 'tenant' in body ? body.tenant : undefined;

View File

@ -5,7 +5,13 @@ import { deflateRaw } from 'zlib';
import type { SAMLProfile } from '@boxyhq/saml20/dist/typings';
import { generators } from 'openid-client';
import type { JacksonOption, Storable, SAMLSSORecord, OIDCSSORecord, SAMLFederationApp } from '../typings';
import type {
JacksonOption,
Storable,
SAMLSSORecord,
OIDCSSORecord,
IdentityFederationApp,
} from '../typings';
import { getDefaultCertificate } from '../saml/x509';
import * as dbutils from '../db/utils';
import { JacksonError } from './error';
@ -167,7 +173,7 @@ export class SSOHandler {
}: {
connection: SAMLSSORecord;
requestParams: Record<string, any>;
mappings: SAMLFederationApp['mappings'];
mappings: IdentityFederationApp['mappings'];
}) {
// We have a connection now, so we can create the SAML request
const certificate = await getDefaultCertificate();
@ -240,7 +246,7 @@ export class SSOHandler {
}: {
connection: OIDCSSORecord;
requestParams: Record<string, any>;
mappings: SAMLFederationApp['mappings'];
mappings: IdentityFederationApp['mappings'];
}) {
if (!this.opts.oidcPath) {
throw new JacksonError('OpenID response handler path (oidcPath) is not set', 400);
@ -347,7 +353,7 @@ export class SSOHandler {
requested: any;
oidcCodeVerifier?: string;
oidcNonce?: string;
mappings: SAMLFederationApp['mappings'];
mappings: IdentityFederationApp['mappings'];
}) => {
const sessionId = crypto.randomBytes(16).toString('hex');

View File

@ -4,7 +4,7 @@ import saml from '@boxyhq/saml20';
import type {
Storable,
JacksonOption,
SAMLFederationApp,
IdentityFederationApp,
Records,
GetByProductParams,
AppRequestParams,
@ -17,7 +17,7 @@ import { IndexNames, validateTenantAndProduct } from '../../controller/utils';
import { throwIfInvalidLicense } from '../common/checkLicense';
type NewAppParams = Pick<
SAMLFederationApp,
IdentityFederationApp,
'name' | 'tenant' | 'product' | 'acsUrl' | 'entityId' | 'tenants' | 'mappings' | 'type' | 'redirectUrl'
> & {
logoUrl?: string;
@ -32,7 +32,7 @@ export class App {
/**
* @swagger
* definitions:
* SAMLFederationApp:
* IdentityFederationApp:
* type: object
* properties:
* id:
@ -71,7 +71,7 @@ export class App {
/**
* @swagger
* /api/v1/federated-saml:
* /api/v1/identity-federation:
* post:
* summary: Create an Identity Federation app
* parameters:
@ -147,7 +147,7 @@ export class App {
* schema:
* type: array
* items:
* $ref: '#/definitions/SAMLFederationApp'
* $ref: '#/definitions/IdentityFederationApp'
*/
public async create({
name,
@ -201,7 +201,7 @@ export class App {
value: entityId,
});
const apps: SAMLFederationApp[] = result.data;
const apps: IdentityFederationApp[] = result.data;
if (apps && apps.length > 0) {
throw new JacksonError(
@ -219,7 +219,7 @@ export class App {
_tenants.push(tenant);
}
const app: SAMLFederationApp = {
const app: IdentityFederationApp = {
id,
type,
redirectUrl,
@ -261,7 +261,7 @@ export class App {
/**
* @swagger
* /api/v1/federated-saml:
* /api/v1/identity-federation:
* get:
* summary: Get an Identity Federation app
* parameters:
@ -288,7 +288,7 @@ export class App {
* '200':
* description: Success
* schema:
* $ref: '#/definitions/SAMLFederationApp'
* $ref: '#/definitions/IdentityFederationApp'
*/
public async get(params: AppRequestParams) {
await throwIfInvalidLicense(this.opts.boxyhqLicenseKey);
@ -300,7 +300,7 @@ export class App {
throw new JacksonError('Identity Federation app not found', 404);
}
return app as SAMLFederationApp;
return app as IdentityFederationApp;
}
if ('tenant' in params && 'product' in params) {
@ -310,7 +310,7 @@ export class App {
throw new JacksonError('Identity Federation app not found', 404);
}
return app as SAMLFederationApp;
return app as IdentityFederationApp;
}
throw new JacksonError('Provide either the `id` or `tenant` and `product` to get the app', 400);
@ -318,7 +318,7 @@ export class App {
/**
* @swagger
* /api/v1/federated-saml/product:
* /api/v1/identity-federation/product:
* get:
* summary: Get Identity Federation apps by product
* parameters:
@ -345,7 +345,7 @@ export class App {
* data:
* type: array
* items:
* $ref: '#/definitions/SAMLFederationApp'
* $ref: '#/definitions/IdentityFederationApp'
* pageToken:
* type: string
* description: token for pagination
@ -378,7 +378,7 @@ export class App {
throw new JacksonError('Missing required parameters. Required parameters are: entityId', 400);
}
const apps: SAMLFederationApp[] = (
const apps: IdentityFederationApp[] = (
await this.store.getByIndex({
name: IndexNames.EntityID,
value: entityId,
@ -394,7 +394,7 @@ export class App {
/**
* @swagger
* /api/v1/federated-saml:
* /api/v1/identity-federation:
* patch:
* summary: Update an Identity Federation app
* parameters:
@ -459,9 +459,9 @@ export class App {
* '200':
* description: Success
* schema:
* $ref: '#/definitions/SAMLFederationApp'
* $ref: '#/definitions/IdentityFederationApp'
*/
public async update(params: Partial<SAMLFederationApp>) {
public async update(params: Partial<IdentityFederationApp>) {
await throwIfInvalidLicense(this.opts.boxyhqLicenseKey);
const { id, tenant, product, type } = params;
@ -470,7 +470,7 @@ export class App {
throw new JacksonError('Provide either the `id` or `tenant` and `product` to update the app', 400);
}
let app: null | SAMLFederationApp = null;
let app: null | IdentityFederationApp = null;
if (id) {
app = await this.get({ id });
@ -482,7 +482,7 @@ export class App {
throw new JacksonError('Identity Federation app not found', 404);
}
const toUpdate: Partial<SAMLFederationApp> = {};
const toUpdate: Partial<IdentityFederationApp> = {};
// Support partial updates
@ -556,14 +556,18 @@ export class App {
}) {
await throwIfInvalidLicense(this.opts.boxyhqLicenseKey);
const apps = (await this.store.getAll(pageOffset, pageLimit, pageToken)) as Records<SAMLFederationApp>;
const apps = (await this.store.getAll(
pageOffset,
pageLimit,
pageToken
)) as Records<IdentityFederationApp>;
return apps;
}
/**
* @swagger
* /api/v1/federated-saml:
* /api/v1/identity-federation:
* delete:
* summary: Delete an Identity Federation app
* parameters:
@ -590,7 +594,7 @@ export class App {
* '200':
* description: Success
* schema:
* $ref: '#/definitions/SAMLFederationApp'
* $ref: '#/definitions/IdentityFederationApp'
*/
public async delete(params: AppRequestParams): Promise<void> {
await throwIfInvalidLicense(this.opts.boxyhqLicenseKey);
@ -613,7 +617,7 @@ export class App {
const { publicKey } = await getDefaultCertificate();
const ssoUrl = `${this.opts.externalUrl}/api/federated-saml/sso`;
const ssoUrl = `${this.opts.externalUrl}/api/identity-federation/sso`;
const entityId = `${this.opts.samlAudience}`;
const xml = saml.createIdPMetadataXML({

View File

@ -4,7 +4,7 @@ import type { JacksonOption, SSOTracerInstance } from '../../typings';
import { SSOHandler } from '../../controller/sso-handler';
// This is the main entry point for the Identity Federation module
const SAMLFederation = async ({
const IdentityFederation = async ({
db,
opts,
ssoTracer,
@ -34,7 +34,7 @@ const SAMLFederation = async ({
return response;
};
export default SAMLFederation;
export default IdentityFederation;
export * from './types';

View File

@ -6,7 +6,7 @@ import { SSOHandler } from '../../controller/sso-handler';
import type {
JacksonOption,
OIDCSSORecord,
SAMLFederationApp,
IdentityFederationApp,
SAMLSSORecord,
SSOTracerInstance,
} from '../../typings';
@ -56,7 +56,7 @@ export class SSO {
const isPostBinding = samlBinding === 'HTTP-POST';
let connection: SAMLSSORecord | OIDCSSORecord | undefined;
let app: SAMLFederationApp | undefined;
let app: IdentityFederationApp | undefined;
let id, acsUrl, entityId, publicKey, providerName, decodedRequest;
try {

View File

@ -1,13 +1,13 @@
import SAMLFederation from '.';
import IdentityFederation from '.';
export type ISAMLFederationController = Awaited<ReturnType<typeof SAMLFederation>>;
export type IIdentityFederationController = Awaited<ReturnType<typeof IdentityFederation>>;
export type AttributeMapping = {
key: string;
value: string;
};
export type SAMLFederationApp = {
export type IdentityFederationApp = {
id: string;
type?: string;
clientID?: string;
@ -25,7 +25,7 @@ export type SAMLFederationApp = {
mappings?: AttributeMapping[] | null;
};
export type SAMLFederationAppWithMetadata = SAMLFederationApp & {
export type IdentityFederationAppWithMetadata = IdentityFederationApp & {
metadata: {
entityId: string;
ssoUrl: string;

View File

@ -13,10 +13,10 @@ import { SPSSOConfig } from './controller/sp-config';
import { SetupLinkController } from './controller/setup-link';
import { AnalyticsController } from './controller/analytics';
import * as x509 from './saml/x509';
import initFederatedSAML, { type ISAMLFederationController } from './ee/federated-saml';
import initFederatedSAML, { type IIdentityFederationController } from './ee/identity-federation';
import checkLicense from './ee/common/checkLicense';
import { BrandingController } from './ee/branding';
import SSOTracer from './sso-tracer';
import SSOTracer from './sso-traces';
import EventController from './event';
import { ProductController } from './ee/product';
import { OryController } from './ee/ory/ory';
@ -71,7 +71,7 @@ export const controllers = async (
directorySyncController: IDirectorySyncController;
oidcDiscoveryController: OidcDiscoveryController;
spConfig: SPSSOConfig;
samlFederatedController: ISAMLFederationController;
samlFederatedController: IIdentityFederationController;
brandingController: IBrandingController;
checkLicense: () => Promise<boolean>;
productController: ProductController;
@ -194,7 +194,7 @@ export const controllers = async (
export default controllers;
export * from './typings';
export * from './ee/federated-saml/types';
export * from './ee/identity-federation/types';
export type SAMLJackson = Awaited<ReturnType<typeof controllers>>;
export type ISetupLinkController = InstanceType<typeof SetupLinkController>;
export type IBrandingController = InstanceType<typeof BrandingController>;

View File

@ -1,8 +1,8 @@
import type { JWK } from 'jose';
import type { CallbackParamsType, IssuerMetadata } from 'openid-client';
export * from './ee/federated-saml/types';
export * from './sso-tracer/types';
export * from './ee/identity-federation/types';
export * from './sso-traces/types';
export * from './directory-sync/types';
export * from './event/types';

View File

@ -1 +0,0 @@
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_082f89d9-a32a-441d-ae49-ab6fc13fe73b" Version="2.0" IssueInstant="2022-11-29T09:04:48Z" ProviderName="Twilio" Destination="http://localhost:5225/api/federated-saml/sso" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://twilio.com/saml2/acs"><saml:Issuer>https://twilio.com/saml2/entityId</saml:Issuer><samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate="true" /><samlp:RequestedAuthnContext Comparison="exact"><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>

View File

@ -1,11 +1,11 @@
import tap from 'tap';
import { ISAMLFederationController } from '../../src';
import { IIdentityFederationController } from '../../src';
import { jacksonOptions } from '../utils';
import { tenant, product, serviceProvider, appId } from './constants';
import { getDefaultCertificate } from '../../src/saml/x509';
let samlFederatedController: ISAMLFederationController;
let samlFederatedController: IIdentityFederationController;
tap.before(async () => {
const jackson = await (await import('../../src/index')).default(jacksonOptions);
@ -149,7 +149,7 @@ tap.test('Federated SAML App', async () => {
t.ok(response);
t.match(response.entityId, jacksonOptions.samlAudience);
t.match(response.ssoUrl, `${jacksonOptions.externalUrl}/api/federated-saml/sso`);
t.match(response.ssoUrl, `${jacksonOptions.externalUrl}/api/identity-federation/sso`);
t.match(response.x509cert, certs.publicKey);
});

View File

@ -0,0 +1 @@
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_082f89d9-a32a-441d-ae49-ab6fc13fe73b" Version="2.0" IssueInstant="2022-11-29T09:04:48Z" ProviderName="Twilio" Destination="http://localhost:5225/api/identity-federation/sso" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://twilio.com/saml2/acs"><saml:Issuer>https://twilio.com/saml2/entityId</saml:Issuer><samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate="true" /><samlp:RequestedAuthnContext Comparison="exact"><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>

View File

@ -11,18 +11,18 @@ const deflateRawAsync = promisify(deflateRaw);
import { jacksonOptions } from '../utils';
import { tenant, product, serviceProvider } from './constants';
import type {
ISAMLFederationController,
IIdentityFederationController,
IConnectionAPIController,
IOAuthController,
SAMLFederationApp,
IdentityFederationApp,
SAMLSSORecord,
} from '../../src';
let oauthController: IOAuthController;
let samlFederatedController: ISAMLFederationController;
let samlFederatedController: IIdentityFederationController;
let connectionAPIController: IConnectionAPIController;
let app: SAMLFederationApp;
let app: IdentityFederationApp;
let connection: SAMLSSORecord;
tap.before(async () => {

View File

@ -1,5 +1,5 @@
import tap from 'tap';
import SSOTracer from '../../src/sso-tracer';
import SSOTracer from '../../src/sso-traces';
import { jacksonOptions } from '../utils';
import DB from '../../src/db/db';

386
package-lock.json generated
View File

@ -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": {
@ -304,14 +304,16 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@aws-sdk/client-cognito-identity": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.568.0.tgz",
"integrity": "sha512-qZyPpvtjEj8Tv7Xkp4CfF10SqyjamfzY8VWHKVBYMLRgxlRRKQsuTlsP+MSsUA32mU9Gid021aR8WWl4bdo8OQ==",
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.569.0.tgz",
"integrity": "sha512-cD1HcdJNpUZgrATWCAQs2amQKI69pG+jF4b5ySq9KJkVi6gv2PWsD6QGDG8H12lMWaIKYlOpKbpnYTpcuvqUcg==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/client-sso-oidc": "3.569.0",
"@aws-sdk/client-sts": "3.569.0",
"@aws-sdk/core": "3.567.0",
"@aws-sdk/credential-provider-node": "3.568.0",
"@aws-sdk/credential-provider-node": "3.569.0",
"@aws-sdk/middleware-host-header": "3.567.0",
"@aws-sdk/middleware-logger": "3.568.0",
"@aws-sdk/middleware-recursion-detection": "3.567.0",
@ -353,14 +355,16 @@
}
},
"node_modules/@aws-sdk/client-dynamodb": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.568.0.tgz",
"integrity": "sha512-5e22HEIGN5ZUaUe+PQ5HxxddnVClLYEfrxaNK9iWNuH4Fy/RSTZwlrSADr0KRFk7qierb51UMi3LaQovArBNbA==",
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.569.0.tgz",
"integrity": "sha512-o3Nh9826AzYgqGhKdII9ymMRxNJ1doIrvohv3wW1UjquVQLI3aquVRAU9SavVhwgRrv3LcG3nzEtlea5hKuppA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/client-sso-oidc": "3.569.0",
"@aws-sdk/client-sts": "3.569.0",
"@aws-sdk/core": "3.567.0",
"@aws-sdk/credential-provider-node": "3.568.0",
"@aws-sdk/credential-provider-node": "3.569.0",
"@aws-sdk/middleware-endpoint-discovery": "3.568.0",
"@aws-sdk/middleware-host-header": "3.567.0",
"@aws-sdk/middleware-logger": "3.568.0",
@ -456,7 +460,6 @@
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.569.0.tgz",
"integrity": "sha512-u5DEjNEvRvlKKh1QLCDuQ8GIrx+OFvJFLfhorsp4oCxDylvORs+KfyKKnJAw4wYEEHyxyz9GzHD7p6a8+HLVHw==",
"peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
@ -503,34 +506,10 @@
"node": ">=16.0.0"
}
},
"node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": {
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.569.0.tgz",
"integrity": "sha512-7jH4X2qlPU3PszZP1zvHJorhLARbU1tXvp8ngBe8ArXBrkFpl/dQ2Y/IRAICPm/pyC1IEt8L/CvKp+dz7v/eRw==",
"peer": true,
"dependencies": {
"@aws-sdk/credential-provider-env": "3.568.0",
"@aws-sdk/credential-provider-http": "3.568.0",
"@aws-sdk/credential-provider-ini": "3.568.0",
"@aws-sdk/credential-provider-process": "3.568.0",
"@aws-sdk/credential-provider-sso": "3.568.0",
"@aws-sdk/credential-provider-web-identity": "3.568.0",
"@aws-sdk/types": "3.567.0",
"@smithy/credential-provider-imds": "^2.3.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/shared-ini-file-loader": "^2.4.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-sdk/client-sts": {
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.569.0.tgz",
"integrity": "sha512-3AyipQ2zHszkcTr8n1Sp7CiMUi28aMf1vOhEo0KKi0DWGo1Z1qJEpWeRP363KG0n9/8U3p1IkXGz5FRbpXZxIw==",
"peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
@ -577,29 +556,6 @@
"node": ">=16.0.0"
}
},
"node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": {
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.569.0.tgz",
"integrity": "sha512-7jH4X2qlPU3PszZP1zvHJorhLARbU1tXvp8ngBe8ArXBrkFpl/dQ2Y/IRAICPm/pyC1IEt8L/CvKp+dz7v/eRw==",
"peer": true,
"dependencies": {
"@aws-sdk/credential-provider-env": "3.568.0",
"@aws-sdk/credential-provider-http": "3.568.0",
"@aws-sdk/credential-provider-ini": "3.568.0",
"@aws-sdk/credential-provider-process": "3.568.0",
"@aws-sdk/credential-provider-sso": "3.568.0",
"@aws-sdk/credential-provider-web-identity": "3.568.0",
"@aws-sdk/types": "3.567.0",
"@smithy/credential-provider-imds": "^2.3.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/shared-ini-file-loader": "^2.4.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-sdk/core": {
"version": "3.567.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.567.0.tgz",
@ -618,11 +574,11 @@
}
},
"node_modules/@aws-sdk/credential-provider-cognito-identity": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.568.0.tgz",
"integrity": "sha512-FoQZVeRDvM7s2dpsiXaU7zIukY1ojwofqRU4k7VXU4cpsDZV29JscrAkmKMCbQqujIWA/EwxhiYNgdbo1CiRLQ==",
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.569.0.tgz",
"integrity": "sha512-CHS0Zyuazh5cYLaJr2/I9up0xAu8Y+um/h0o4xNf00cKGT0Sdhoby5vyelHjVTeZt+OeOMTBt6IdqGwVbVG9gQ==",
"dependencies": {
"@aws-sdk/client-cognito-identity": "3.568.0",
"@aws-sdk/client-cognito-identity": "3.569.0",
"@aws-sdk/types": "3.567.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/types": "^2.12.0",
@ -689,9 +645,9 @@
}
},
"node_modules/@aws-sdk/credential-provider-node": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.568.0.tgz",
"integrity": "sha512-gGvBHzVAwDPMwgiAOkigm6eKg0EHIStpwDW4z4XRoSJCcSTyBQ8qqUkoJY7b5Hoju25zBsrB8PvQnZgJU/NYmA==",
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.569.0.tgz",
"integrity": "sha512-7jH4X2qlPU3PszZP1zvHJorhLARbU1tXvp8ngBe8ArXBrkFpl/dQ2Y/IRAICPm/pyC1IEt8L/CvKp+dz7v/eRw==",
"dependencies": {
"@aws-sdk/credential-provider-env": "3.568.0",
"@aws-sdk/credential-provider-http": "3.568.0",
@ -760,18 +716,18 @@
}
},
"node_modules/@aws-sdk/credential-providers": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.568.0.tgz",
"integrity": "sha512-V2x+35XzRphs9bpussynNu1Eme/4qFjF9KoSOvFvSqBHRt8hf7+C21pzYeHgrbsXowv5plON0sldyqOqpMhPmg==",
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.569.0.tgz",
"integrity": "sha512-UL7EewaM1Xk6e4XLsxrCBv/owVSDI6Katnok6uMfqA8dA0x3ELjO7W35DW4wpWejQHErN5Gp1zloV9y3t34FMQ==",
"dependencies": {
"@aws-sdk/client-cognito-identity": "3.568.0",
"@aws-sdk/client-cognito-identity": "3.569.0",
"@aws-sdk/client-sso": "3.568.0",
"@aws-sdk/client-sts": "3.568.0",
"@aws-sdk/credential-provider-cognito-identity": "3.568.0",
"@aws-sdk/client-sts": "3.569.0",
"@aws-sdk/credential-provider-cognito-identity": "3.569.0",
"@aws-sdk/credential-provider-env": "3.568.0",
"@aws-sdk/credential-provider-http": "3.568.0",
"@aws-sdk/credential-provider-ini": "3.568.0",
"@aws-sdk/credential-provider-node": "3.568.0",
"@aws-sdk/credential-provider-node": "3.569.0",
"@aws-sdk/credential-provider-process": "3.568.0",
"@aws-sdk/credential-provider-sso": "3.568.0",
"@aws-sdk/credential-provider-web-identity": "3.568.0",
@ -785,55 +741,6 @@
"node": ">=16.0.0"
}
},
"node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sts": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.568.0.tgz",
"integrity": "sha512-eDO0S1YYi8eCDcwH5TvJuzJ+Irsx/W6bsQ8fL7rF3aPmsoYQvZh2arpysTUWa4SHpSOkiQFHnii0FqFpw34wyA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.567.0",
"@aws-sdk/credential-provider-node": "3.568.0",
"@aws-sdk/middleware-host-header": "3.567.0",
"@aws-sdk/middleware-logger": "3.568.0",
"@aws-sdk/middleware-recursion-detection": "3.567.0",
"@aws-sdk/middleware-user-agent": "3.567.0",
"@aws-sdk/region-config-resolver": "3.567.0",
"@aws-sdk/types": "3.567.0",
"@aws-sdk/util-endpoints": "3.567.0",
"@aws-sdk/util-user-agent-browser": "3.567.0",
"@aws-sdk/util-user-agent-node": "3.568.0",
"@smithy/config-resolver": "^2.2.0",
"@smithy/core": "^1.4.2",
"@smithy/fetch-http-handler": "^2.5.0",
"@smithy/hash-node": "^2.2.0",
"@smithy/invalid-dependency": "^2.2.0",
"@smithy/middleware-content-length": "^2.2.0",
"@smithy/middleware-endpoint": "^2.5.1",
"@smithy/middleware-retry": "^2.3.1",
"@smithy/middleware-serde": "^2.3.0",
"@smithy/middleware-stack": "^2.2.0",
"@smithy/node-config-provider": "^2.3.0",
"@smithy/node-http-handler": "^2.5.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"@smithy/url-parser": "^2.2.0",
"@smithy/util-base64": "^2.3.0",
"@smithy/util-body-length-browser": "^2.2.0",
"@smithy/util-body-length-node": "^2.3.0",
"@smithy/util-defaults-mode-browser": "^2.2.1",
"@smithy/util-defaults-mode-node": "^2.3.1",
"@smithy/util-endpoints": "^1.2.0",
"@smithy/util-middleware": "^2.2.0",
"@smithy/util-retry": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-sdk/endpoint-cache": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.568.0.tgz",
@ -965,9 +872,9 @@
}
},
"node_modules/@aws-sdk/util-dynamodb": {
"version": "3.568.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.568.0.tgz",
"integrity": "sha512-bCLjvMzKrAyt3/YZ85eO02RpgRcajaK1v40Jy3MxkLFJ2LUg7MNaMdCdBSOGfe+4v/SepOtwUMLDddPr/mFEQA==",
"version": "3.569.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.569.0.tgz",
"integrity": "sha512-cTNwY1eSycZJhetaj0Xl0E65lHl65mwfe9bsq6cSRuMxUXYpuo5yEwiT5OMQiAC8QtlwpCHokFHI12TqqlZbFQ==",
"dependencies": {
"tslib": "^2.6.2"
},
@ -975,7 +882,7 @@
"node": ">=16.0.0"
},
"peerDependencies": {
"@aws-sdk/client-dynamodb": "^3.568.0"
"@aws-sdk/client-dynamodb": "^3.569.0"
}
},
"node_modules/@aws-sdk/util-endpoints": {
@ -1797,9 +1704,9 @@
"link": true
},
"node_modules/@boxyhq/saml20": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@boxyhq/saml20/-/saml20-1.5.0.tgz",
"integrity": "sha512-4/Nj4DjyYrIZXerp+VkiNwvsvFn5rLftk9idAgfbXSEHz4TtXrRZofsl80s73fipS5LUENk453uE3uLVeN+BMQ==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@boxyhq/saml20/-/saml20-1.5.1.tgz",
"integrity": "sha512-SpM/i7s11v05akleLCpcYqNoQdlykVy51TEOt6i186KJ2ZqMX5FsPSNxjjluM3v3FpfvAqiy3Vf9eCw0B0JtYQ==",
"dependencies": {
"@xmldom/xmldom": "0.8.10",
"xml-crypto": "6.0.0",
@ -2387,9 +2294,9 @@
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
},
"node_modules/@googleapis/admin": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/@googleapis/admin/-/admin-17.1.0.tgz",
"integrity": "sha512-rJcUkz+vFYQTvnkXhqC9LAtRUGmUth2yuTVtp9ih6fETVjzsWyjZtD0g2IPxBTC5shyLQXVBYbysmcO+WS/4Uw==",
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@googleapis/admin/-/admin-18.0.0.tgz",
"integrity": "sha512-HFm2xlRrQpx6YySA51QfcBcMsoUVlGkXIuaCakyflCyIiQFMD8AMt4iIEQvwU+zn91cHE4i1rj8BX7C3kaqz3A==",
"dependencies": {
"googleapis-common": "^7.0.0"
},
@ -23580,13 +23487,13 @@
"version": "0.0.0",
"license": "Apache 2.0",
"dependencies": {
"@aws-sdk/client-dynamodb": "3.568.0",
"@aws-sdk/credential-providers": "3.568.0",
"@aws-sdk/util-dynamodb": "3.568.0",
"@aws-sdk/client-dynamodb": "3.569.0",
"@aws-sdk/credential-providers": "3.569.0",
"@aws-sdk/util-dynamodb": "3.569.0",
"@boxyhq/error-code-mnemonic": "0.1.1",
"@boxyhq/metrics": "0.2.6",
"@boxyhq/saml20": "1.5.0",
"@googleapis/admin": "17.1.0",
"@boxyhq/metrics": "0.2.7",
"@boxyhq/saml20": "1.5.1",
"@googleapis/admin": "18.0.0",
"axios": "1.6.8",
"encoding": "0.1.13",
"jose": "5.2.4",
@ -23623,6 +23530,211 @@
"npm": ">=8"
}
},
"npm/node_modules/@boxyhq/metrics": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@boxyhq/metrics/-/metrics-0.2.7.tgz",
"integrity": "sha512-2J5mzX8eooTSAuNKUHJhZzhYTq2cFIWSvng1hB+HywC4xO3ERAhLTahgdVky3e5lbEqxcNviLbDpI2Yehxx/NA==",
"dependencies": {
"@opentelemetry/api": "1.8.0",
"@opentelemetry/exporter-metrics-otlp-grpc": "0.51.0",
"@opentelemetry/exporter-metrics-otlp-http": "0.51.0",
"@opentelemetry/resources": "1.24.0",
"@opentelemetry/sdk-metrics": "1.24.0",
"@opentelemetry/semantic-conventions": "1.24.0"
}
},
"npm/node_modules/@opentelemetry/api": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz",
"integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==",
"engines": {
"node": ">=8.0.0"
}
},
"npm/node_modules/@opentelemetry/api-logs": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.51.0.tgz",
"integrity": "sha512-m/jtfBPEIXS1asltl8fPQtO3Sb1qMpuL61unQajUmM8zIxeMF1AlqzWXM3QedcYgTTFiJCew5uJjyhpmqhc0+g==",
"dependencies": {
"@opentelemetry/api": "^1.0.0"
},
"engines": {
"node": ">=14"
}
},
"npm/node_modules/@opentelemetry/core": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.24.0.tgz",
"integrity": "sha512-FP2oN7mVPqcdxJDTTnKExj4mi91EH+DNuArKfHTjPuJWe2K1JfMIVXNfahw1h3onJxQnxS8K0stKkogX05s+Aw==",
"dependencies": {
"@opentelemetry/semantic-conventions": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.9.0"
}
},
"npm/node_modules/@opentelemetry/exporter-metrics-otlp-grpc": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.51.0.tgz",
"integrity": "sha512-Ezps36AN5ukLlH3k3Or+im7MiJDq7Sm2rqOggSfCB4NY94RAHlL0VI9vgUlBmaq1Kn+pGEZwb39FC6vv0S0mzw==",
"dependencies": {
"@grpc/grpc-js": "^1.7.1",
"@opentelemetry/core": "1.24.0",
"@opentelemetry/exporter-metrics-otlp-http": "0.51.0",
"@opentelemetry/otlp-grpc-exporter-base": "0.51.0",
"@opentelemetry/otlp-transformer": "0.51.0",
"@opentelemetry/resources": "1.24.0",
"@opentelemetry/sdk-metrics": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"npm/node_modules/@opentelemetry/exporter-metrics-otlp-http": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.51.0.tgz",
"integrity": "sha512-e4x0Ybb4qrk7Ux3fTFdU6OBv9qbbp4413zddbjTJlcyoXu8S6Twc0waX3wfkEBbAotut9JtoZG1kp4RQ435yTQ==",
"dependencies": {
"@opentelemetry/core": "1.24.0",
"@opentelemetry/otlp-exporter-base": "0.51.0",
"@opentelemetry/otlp-transformer": "0.51.0",
"@opentelemetry/resources": "1.24.0",
"@opentelemetry/sdk-metrics": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"npm/node_modules/@opentelemetry/otlp-exporter-base": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.51.0.tgz",
"integrity": "sha512-hR4c9vWVz1QgzCBSyy9zSDkvfTgaK96E6/tfVP6O4dzdZW9HqWimA3lXV/KXadEGqShvM4GToz9EHp2A5RU5bQ==",
"dependencies": {
"@opentelemetry/core": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": "^1.0.0"
}
},
"npm/node_modules/@opentelemetry/otlp-grpc-exporter-base": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.51.0.tgz",
"integrity": "sha512-oTRtDvvB0bTRTBVrvKA/oM1gIAqQ6DVQS07pvqiL1cZS8wBrGgpw+2iTd0nV661Y/MhDn/kNWp8lRhMEIKN9bw==",
"dependencies": {
"@grpc/grpc-js": "^1.7.1",
"@opentelemetry/core": "1.24.0",
"@opentelemetry/otlp-exporter-base": "0.51.0",
"protobufjs": "^7.2.3"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": "^1.0.0"
}
},
"npm/node_modules/@opentelemetry/otlp-transformer": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.51.0.tgz",
"integrity": "sha512-ylLgx2xumVoSefDHP9GMAU/LG+TU3+8eacVDXV5o1RqWxsdVOaQmCTY0XyDgeRTn6hIOVAq/HHQbRq3iWOrt2A==",
"dependencies": {
"@opentelemetry/api-logs": "0.51.0",
"@opentelemetry/core": "1.24.0",
"@opentelemetry/resources": "1.24.0",
"@opentelemetry/sdk-logs": "0.51.0",
"@opentelemetry/sdk-metrics": "1.24.0",
"@opentelemetry/sdk-trace-base": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.9.0"
}
},
"npm/node_modules/@opentelemetry/resources": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.24.0.tgz",
"integrity": "sha512-mxC7E7ocUS1tLzepnA7O9/G8G6ZTdjCH2pXme1DDDuCuk6n2/53GADX+GWBuyX0dfIxeMInIbJAdjlfN9GNr6A==",
"dependencies": {
"@opentelemetry/core": "1.24.0",
"@opentelemetry/semantic-conventions": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.9.0"
}
},
"npm/node_modules/@opentelemetry/sdk-logs": {
"version": "0.51.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.51.0.tgz",
"integrity": "sha512-K4fMBRFD8hQ6khk0rvYFuo6L9ymeGgByir6BcuFIgQuQ00OhYwBi9AruZz5V733Ejq7P8ObR3YyubkOUIbeVAw==",
"dependencies": {
"@opentelemetry/core": "1.24.0",
"@opentelemetry/resources": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.4.0 <1.9.0",
"@opentelemetry/api-logs": ">=0.39.1"
}
},
"npm/node_modules/@opentelemetry/sdk-metrics": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.24.0.tgz",
"integrity": "sha512-4tJ+E6N019OZVB/nUW/LoK9xHxfeh88TCoaTqHeLBE9wLYfi6irWW6J9cphMav7J8Qk0D5b7/RM4VEY4dArWOA==",
"dependencies": {
"@opentelemetry/core": "1.24.0",
"@opentelemetry/resources": "1.24.0",
"lodash.merge": "^4.6.2"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.9.0"
}
},
"npm/node_modules/@opentelemetry/sdk-trace-base": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.24.0.tgz",
"integrity": "sha512-H9sLETZ4jw9UJ3totV8oM5R0m4CW0ZIOLfp4NV3g0CM8HD5zGZcaW88xqzWDgiYRpctFxd+WmHtGX/Upoa2vRg==",
"dependencies": {
"@opentelemetry/core": "1.24.0",
"@opentelemetry/resources": "1.24.0",
"@opentelemetry/semantic-conventions": "1.24.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.9.0"
}
},
"npm/node_modules/@opentelemetry/semantic-conventions": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.0.tgz",
"integrity": "sha512-yL0jI6Ltuz8R+Opj7jClGrul6pOoYrdfVmzQS4SITXRPH7I5IRZbrwe/6/v8v4WYMa6MYZG480S1+uc/IGfqsA==",
"engines": {
"node": ">=14"
}
},
"npm/node_modules/@types/node": {
"version": "20.12.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "jackson",
"version": "1.23.6",
"version": "1.23.7",
"private": true,
"description": "SAML 2.0 service",
"keywords": [

View File

@ -2,7 +2,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import jackson from '@lib/jackson';
export { default } from 'ee/federated-saml/pages/edit';
export { default } from '@ee/identity-federation/pages/edit';
export async function getServerSideProps({ locale }) {
const { checkLicense } = await jackson();

View File

@ -2,7 +2,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import jackson from '@lib/jackson';
export { default } from 'ee/federated-saml/pages/index';
export { default } from '@ee/identity-federation/pages/index';
export async function getServerSideProps({ locale }) {
const { checkLicense } = await jackson();

View File

@ -3,7 +3,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import jackson from '@lib/jackson';
import { jacksonOptions } from '@lib/env';
export { default } from 'ee/federated-saml/pages/new';
export { default } from '@ee/identity-federation/pages/new';
export async function getServerSideProps({ locale }) {
const { checkLicense } = await jackson();

View File

@ -10,8 +10,8 @@ const SSOTraceInspector: NextPage = () => {
return (
<div className='space-y-4'>
<LinkBack href='/admin/sso-tracer' />
<SSOTracerInfo urls={{ getTracer: `/api/admin/sso-tracer/${traceId}` }} />
<LinkBack href='/admin/sso-traces' />
<SSOTracerInfo urls={{ getTracer: `/api/admin/sso-traces/${traceId}` }} />
</div>
);
};

View File

@ -8,8 +8,8 @@ const SSOTraceViewer: NextPage = () => {
return (
<SSOTracers
urls={{ getTracers: '/api/admin/sso-tracer' }}
onView={(trace) => router.push(`/admin/sso-tracer/${trace.traceId}/inspect`)}
urls={{ getTracers: '/api/admin/sso-traces' }}
onView={(trace) => router.push(`/admin/sso-traces/${trace.traceId}/inspect`)}
/>
);
};

View File

@ -1 +0,0 @@
export { default } from 'ee/federated-saml/api/admin/[id]/index';

View File

@ -1 +0,0 @@
export { default } from 'ee/federated-saml/api/admin/index';

View File

@ -0,0 +1 @@
export { default } from '@ee/identity-federation/api/admin/[id]/index';

View File

@ -0,0 +1 @@
export { default } from '@ee/identity-federation/api/admin/index';

View File

@ -1 +1 @@
export { default } from 'ee/federated-saml/api/sso';
export { default } from '@ee/identity-federation/api/sso';

View File

@ -0,0 +1 @@
export { default } from '@ee/identity-federation/api/sso';

View File

@ -1 +0,0 @@
export { default } from 'ee/federated-saml/api/v1/index';

View File

@ -1 +0,0 @@
export { default } from 'ee/federated-saml/api/v1/product';

View File

@ -0,0 +1 @@
export { default } from '@ee/identity-federation/api/v1/index';

View File

@ -0,0 +1 @@
export { default } from '@ee/identity-federation/api/v1/product';

View File

@ -20,15 +20,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
const { connectionAPIController, directorySyncController } = await jackson();
const { connectionAPIController, directorySyncController, samlFederatedController } = await jackson();
const sso_connections_count = await connectionAPIController.getCount();
const dsync_connections_count = await directorySyncController.directories.getCount();
const identity_federation_count = await samlFederatedController.app.getCount();
return res.json({
data: {
sso_connections: sso_connections_count,
dsync_connections: dsync_connections_count,
identity_federation_apps: identity_federation_count,
},
});
};

View File

@ -38,7 +38,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
// Get counts for product
let sso_connections_count = 0;
let dsync_connections_count = 0;
let saml_federation_count = 0;
let identity_federation_count = 0;
for (const product of products) {
if (product) {
@ -60,7 +60,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
name: IndexNames.Product,
value: product,
});
saml_federation_count += count || 0;
identity_federation_count += count || 0;
}
}
}
@ -69,7 +69,7 @@ const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
data: {
sso_connections: sso_connections_count,
dsync_connections: dsync_connections_count,
saml_federation: saml_federation_count,
identity_federation_apps: identity_federation_count,
},
});
}

View File

@ -1 +1 @@
export { default } from 'ee/federated-saml/api/metadata';
export { default } from '@ee/identity-federation/api/metadata';

View File

@ -203,7 +203,7 @@ export const getServerSideProps = async ({ query, locale, req }) => {
const params = new URLSearchParams(paramsToRelay);
const destination =
samlFedAppId && fedType !== 'oidc'
? `/api/federated-saml/sso?${params}`
? `/api/identity-federation/sso?${params}`
: `/api/oauth/authorize?${params}`;
return {

View File

@ -1,7 +1,7 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import jackson from '@lib/jackson';
export { default } from 'ee/federated-saml/pages/metadata';
export { default } from '@ee/identity-federation/pages/metadata';
export async function getServerSideProps({ locale }) {
const { samlFederatedController, checkLicense } = await jackson();

View File

@ -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',
},
},

View File

@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "Enterprise SSO & Directory Sync",
"version": "1.20.6",
"version": "1.23.5",
"description": "This is the API documentation for SAML Jackson service.",
"termsOfService": "https://boxyhq.com/terms.html",
"contact": {
@ -1354,7 +1354,7 @@
}
}
},
"/api/v1/federated-saml": {
"/api/v1/identity-federation": {
"post": {
"summary": "Create an Identity Federation app",
"parameters": [
@ -1459,7 +1459,7 @@
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/SAMLFederationApp"
"$ref": "#/definitions/IdentityFederationApp"
}
}
}
@ -1500,7 +1500,7 @@
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/SAMLFederationApp"
"$ref": "#/definitions/IdentityFederationApp"
}
}
}
@ -1593,7 +1593,7 @@
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/SAMLFederationApp"
"$ref": "#/definitions/IdentityFederationApp"
}
}
}
@ -1633,13 +1633,13 @@
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/SAMLFederationApp"
"$ref": "#/definitions/IdentityFederationApp"
}
}
}
}
},
"/api/v1/federated-saml/product": {
"/api/v1/identity-federation/product": {
"get": {
"summary": "Get Identity Federation apps by product",
"parameters": [
@ -1677,7 +1677,7 @@
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/SAMLFederationApp"
"$ref": "#/definitions/IdentityFederationApp"
}
},
"pageToken": {
@ -2034,7 +2034,7 @@
}
}
},
"SAMLFederationApp": {
"IdentityFederationApp": {
"type": "object",
"properties": {
"id": {