Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion npm/src/controller/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class LogoutController {

const {
idpMetadata: { slo, provider },
samlAudienceOverride,
} = samlConnection;

const { privateKey, publicKey } = await getDefaultCertificate();
Expand All @@ -61,7 +62,7 @@ export class LogoutController {

const { id, xml } = saml.createLogoutRequest({
nameId,
providerName: this.opts.samlAudience!,
providerName: samlAudienceOverride ?? this.opts.samlAudience!,
sloUrl: slo.redirectUrl as string,
});
const sessionId = crypto.randomBytes(16).toString('hex');
Expand Down
1 change: 1 addition & 0 deletions npm/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ interface Metadata {
export interface SAMLConnection {
idpMetadata: Metadata;
defaultRedirectUrl: string;
samlAudienceOverride?: string;
}

// See Error Response section in https://www.oauth.com/oauth2-servers/authorization/the-authorization-response/
Expand Down
57 changes: 57 additions & 0 deletions npm/test/sso/logout.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import crypto from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
import { promisify } from 'util';
import { inflateRaw } from 'zlib';
import sinon from 'sinon';
import tap from 'tap';
import { IConnectionAPIController, ILogoutController } from '../../src/typings';
import { relayStatePrefix } from '../../src/controller/utils';
import { saml_connection } from './fixture';
import { addSSOConnections, jacksonOptions } from '../utils';

const inflateRawAsync = promisify(inflateRaw);

let connectionAPIController: IConnectionAPIController;
let logoutController: ILogoutController;

Expand Down Expand Up @@ -102,6 +106,59 @@ tap.test('LogoutController -> createRequest', async (t) => {
);
});

t.test(
'Should use samlAudienceOverride as Issuer in LogoutRequest when set on the connection',
async (t) => {
const overrideEntityId = 'https://saml.boxyhq.com/custom-sp-entity-id';

const exampleXml = await fs.readFile(path.join(metadataPath, 'example.xml'), 'utf8');
await connectionAPIController.createSAMLConnection({
defaultRedirectUrl: 'http://localhost:3366/sso/oauth/completed',
redirectUrl: '["http://localhost:3366"]',
tenant: 'example.com',
product: 'crm-audience-override',
encodedRawMetadata: Buffer.from(exampleXml).toString('base64'),
samlAudienceOverride: overrideEntityId,
});

const result = await logoutController.createRequest({
nameId: 'user@example.com',
tenant: 'example.com',
product: 'crm-audience-override',
redirectUrl: 'http://localhost:3366/done',
});

t.ok(result.logoutUrl, 'logoutUrl should be present');

const params = new URLSearchParams(new URL(result.logoutUrl as string).search);
const samlRequest = params.get('SAMLRequest')!;
const xml = (await inflateRawAsync(Buffer.from(samlRequest, 'base64'))).toString();

t.match(
xml,
`<saml:Issuer>${overrideEntityId}</saml:Issuer>`,
'LogoutRequest Issuer should be the samlAudienceOverride value'
);
}
);

t.test('Should fall back to global samlAudience as Issuer when no override is set', async (t) => {
const result = await logoutController.createRequest({
...body,
tenant: 'example.com',
});

const params = new URLSearchParams(new URL(result.logoutUrl as string).search);
const samlRequest = params.get('SAMLRequest')!;
const xml = (await inflateRawAsync(Buffer.from(samlRequest, 'base64'))).toString();

t.match(
xml,
`<saml:Issuer>${jacksonOptions.samlAudience}</saml:Issuer>`,
'LogoutRequest Issuer should fall back to the global samlAudience'
);
});

t.test('HTTP-Redirect logout query string signature should be verifiable', async (t) => {
const result = await logoutController.createRequest({
...body,
Expand Down