mirror of
https://github.com/hanzoai/passport-saml.git
synced 2026-09-24 17:01:45 +00:00
379 lines
12 KiB
TypeScript
379 lines
12 KiB
TypeScript
"use strict";
|
|
import * as express from "express";
|
|
import { Strategy } from "passport-strategy";
|
|
import * as sinon from "sinon";
|
|
import { expect } from "chai";
|
|
import { MultiSamlStrategy, AbstractStrategy } from "../src";
|
|
import { SAML } from "@node-saml/node-saml";
|
|
import {
|
|
MultiStrategyConfig,
|
|
RequestWithUser,
|
|
StrategyOptionsCallback,
|
|
PassportSamlConfig,
|
|
} from "../src/types";
|
|
import * as assert from "assert";
|
|
import { FAKE_CERT } from "./types";
|
|
|
|
const noop = () => undefined;
|
|
|
|
describe("MultiSamlStrategy()", function () {
|
|
it("extends passport Strategy", function () {
|
|
function getSamlOptions() {
|
|
return {};
|
|
}
|
|
const strategy = new MultiSamlStrategy({ getSamlOptions }, noop, noop);
|
|
expect(strategy).to.be.an.instanceOf(AbstractStrategy);
|
|
expect(strategy).to.be.an.instanceOf(Strategy);
|
|
});
|
|
|
|
it("does not require any properties in the SamlOptionsCallback", function () {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const getSamlOptions: StrategyOptionsCallback = (err, samlOptions) => {
|
|
// do nothing; the return type is void
|
|
};
|
|
|
|
getSamlOptions(null, {});
|
|
});
|
|
|
|
it("throws if wrong finder is provided", function () {
|
|
function createStrategy() {
|
|
return new MultiSamlStrategy({} as MultiStrategyConfig, noop, noop);
|
|
}
|
|
assert.throws(createStrategy);
|
|
});
|
|
|
|
describe("authenticate", function () {
|
|
beforeEach(function () {
|
|
this.superAuthenticateStub = sinon.stub(AbstractStrategy.prototype, "authenticate");
|
|
});
|
|
|
|
afterEach(function () {
|
|
this.superAuthenticateStub.restore();
|
|
});
|
|
|
|
it("calls super with request and auth options", function (done) {
|
|
const superAuthenticateStub = this.superAuthenticateStub;
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
sinon.assert.calledOnce(superAuthenticateStub);
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
}
|
|
|
|
const strategy = new MultiSamlStrategy(
|
|
{
|
|
getSamlOptions,
|
|
},
|
|
noop,
|
|
noop,
|
|
);
|
|
// @ts-expect-error `authenticate` expects a request object
|
|
strategy.authenticate("random", "random");
|
|
});
|
|
|
|
it("passes options on to saml strategy", function (done) {
|
|
const passportOptions = {
|
|
passReqToCallback: true,
|
|
getSamlOptions: function (req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
expect(strategy._passReqToCallback).to.equal(true);
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
},
|
|
};
|
|
|
|
const strategy = new MultiSamlStrategy(passportOptions, noop, noop);
|
|
// @ts-expect-error `authenticate` expects a request object
|
|
strategy.authenticate("random", "random");
|
|
});
|
|
|
|
it("uses given options to setup internal saml provider", function (done) {
|
|
const superAuthenticateStub = this.superAuthenticateStub;
|
|
const samlOptions: PassportSamlConfig = {
|
|
issuer: "http://foo.issuer",
|
|
callbackUrl: "http://foo.callback",
|
|
idpCert: "deadbeef",
|
|
logoutCallbackUrl: "http://lvh/saml/callback",
|
|
|
|
acceptedClockSkewMs: -1,
|
|
identifierFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
logoutUrl: "http://foo.slo",
|
|
signatureAlgorithm: "sha256",
|
|
};
|
|
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, samlOptions);
|
|
sinon.assert.calledOnce(superAuthenticateStub);
|
|
superAuthenticateStub.calledWith(
|
|
Object.assign({}, { cacheProvider: "mock cache provider" }, samlOptions),
|
|
);
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
}
|
|
|
|
const strategy = new MultiSamlStrategy(
|
|
// @ts-expect-error `cacheProvider` expects a `CacheProvider` object
|
|
{ getSamlOptions, cacheProvider: "mock cache provider" },
|
|
noop,
|
|
noop,
|
|
);
|
|
// @ts-expect-error `authenticate` expects a request object
|
|
strategy.authenticate("random", "random");
|
|
});
|
|
});
|
|
|
|
describe("authorize", function () {
|
|
let getAuthorizeFormStub: sinon.SinonStub;
|
|
let getAuthorizeUrlStub: sinon.SinonStub;
|
|
let errorStub: sinon.SinonStub;
|
|
|
|
beforeEach(function () {
|
|
getAuthorizeFormStub = sinon.stub(SAML.prototype, "getAuthorizeFormAsync").resolves();
|
|
getAuthorizeUrlStub = sinon.stub(SAML.prototype, "getAuthorizeUrlAsync").resolves();
|
|
errorStub = sinon.stub(MultiSamlStrategy.prototype, "error");
|
|
});
|
|
|
|
afterEach(function () {
|
|
getAuthorizeFormStub.restore();
|
|
getAuthorizeUrlStub.restore();
|
|
errorStub.restore();
|
|
});
|
|
|
|
it("calls getAuthorizeForm when authnRequestBinding is HTTP-POST", function () {
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
fn(null, {
|
|
authnRequestBinding: "HTTP-POST",
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
}
|
|
const strategy = new MultiSamlStrategy({ getSamlOptions }, noop, noop);
|
|
strategy.authenticate({} as RequestWithUser, {});
|
|
sinon.assert.notCalled(errorStub);
|
|
sinon.assert.calledOnce(getAuthorizeFormStub);
|
|
});
|
|
|
|
it("calls getAuthorizeUrl when authnRequestBinding is not HTTP-POST", function () {
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
}
|
|
const strategy = new MultiSamlStrategy({ getSamlOptions }, noop, noop);
|
|
strategy.authenticate({} as RequestWithUser, {});
|
|
sinon.assert.notCalled(errorStub);
|
|
sinon.assert.calledOnce(getAuthorizeUrlStub);
|
|
});
|
|
});
|
|
|
|
describe("logout", function () {
|
|
beforeEach(function () {
|
|
this.superLogoutMock = sinon.stub(AbstractStrategy.prototype, "logout");
|
|
});
|
|
|
|
afterEach(function () {
|
|
this.superLogoutMock.restore();
|
|
});
|
|
|
|
it("calls super with request and auth options", function (done) {
|
|
const superLogoutMock = this.superLogoutMock;
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
sinon.assert.calledOnce(superLogoutMock);
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
}
|
|
|
|
const strategy = new MultiSamlStrategy({ getSamlOptions }, noop, noop);
|
|
// @ts-expect-error `logout` expects a request object
|
|
strategy.logout("random", "random");
|
|
});
|
|
|
|
it("passes options on to saml strategy", function (done) {
|
|
const passportOptions = {
|
|
passReqToCallback: true,
|
|
getSamlOptions: function (req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
expect(strategy._passReqToCallback).to.equal(true);
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
},
|
|
};
|
|
|
|
const strategy = new MultiSamlStrategy(passportOptions, noop, noop);
|
|
// @ts-expect-error `logout` expects a request object
|
|
strategy.logout("random", "random");
|
|
});
|
|
|
|
it("uses given options to setup internal saml provider", function (done) {
|
|
const superLogoutMock = this.superLogoutMock;
|
|
const samlOptions: PassportSamlConfig = {
|
|
issuer: "http://foo.issuer",
|
|
callbackUrl: "http://foo.callback",
|
|
authnRequestBinding: "HTTP-POST",
|
|
idpCert: "deadbeef",
|
|
logoutCallbackUrl: "http://lvh/saml/callback",
|
|
acceptedClockSkewMs: -1,
|
|
identifierFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
logoutUrl: "http://foo.slo",
|
|
signatureAlgorithm: "sha256",
|
|
};
|
|
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, samlOptions);
|
|
sinon.assert.calledOnce(superLogoutMock);
|
|
superLogoutMock.calledWith(Object.assign({}, samlOptions));
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
}
|
|
|
|
const strategy = new MultiSamlStrategy({ getSamlOptions }, noop, noop);
|
|
// @ts-expect-error `logout` expects a request object
|
|
strategy.logout("random", sinon.spy());
|
|
});
|
|
});
|
|
|
|
describe("generateServiceProviderMetadata", function () {
|
|
beforeEach(function () {
|
|
this.superGenerateServiceProviderMetadata = sinon
|
|
.stub(SAML.prototype, "generateServiceProviderMetadata")
|
|
.returns("My Metadata Result");
|
|
});
|
|
|
|
afterEach(function () {
|
|
this.superGenerateServiceProviderMetadata.restore();
|
|
});
|
|
|
|
it("calls super with request and generateServiceProviderMetadata options", function (done) {
|
|
const superGenerateServiceProviderMetadata = this.superGenerateServiceProviderMetadata;
|
|
function getSamlOptions(req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
sinon.assert.calledOnce(superGenerateServiceProviderMetadata);
|
|
superGenerateServiceProviderMetadata.calledWith("bar", "baz");
|
|
expect(req).to.equal("foo");
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
}
|
|
|
|
const strategy = new MultiSamlStrategy({ getSamlOptions }, noop, noop);
|
|
// @ts-expect-error `generateServiceProviderMetadata` expects a request object
|
|
strategy.generateServiceProviderMetadata("foo", "bar", "baz", noop);
|
|
});
|
|
|
|
it("passes options on to saml strategy", function (done) {
|
|
const passportOptions: MultiStrategyConfig = {
|
|
passReqToCallback: true,
|
|
|
|
getSamlOptions: function (req: express.Request, fn: StrategyOptionsCallback) {
|
|
try {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
expect(strategy._passReqToCallback).to.equal(true);
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
},
|
|
};
|
|
|
|
const strategy = new MultiSamlStrategy(passportOptions, noop, noop);
|
|
// @ts-expect-error `generateServiceProviderMetadata` expects a request object
|
|
strategy.generateServiceProviderMetadata("foo", "bar", "baz", noop);
|
|
});
|
|
|
|
it("should pass error to callback function", function (done) {
|
|
const passportOptions = {
|
|
getSamlOptions: function (req: express.Request, fn: StrategyOptionsCallback) {
|
|
fn(new Error("My error"));
|
|
},
|
|
};
|
|
|
|
const strategy = new MultiSamlStrategy(passportOptions, noop, noop);
|
|
// @ts-expect-error `generateServiceProviderMetadata` expects a request object
|
|
strategy.generateServiceProviderMetadata("foo", "bar", "baz", function (error) {
|
|
try {
|
|
expect(error?.message).to.equal("My error");
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("should pass result to callback function", function (done) {
|
|
const passportOptions = {
|
|
getSamlOptions: function (req: express.Request, fn: StrategyOptionsCallback) {
|
|
fn(null, {
|
|
idpCert: FAKE_CERT,
|
|
issuer: "onesaml_login",
|
|
callbackUrl: "https://www.example.com",
|
|
});
|
|
},
|
|
};
|
|
|
|
const strategy = new MultiSamlStrategy(passportOptions, noop, noop);
|
|
strategy.generateServiceProviderMetadata(
|
|
// @ts-expect-error `generateServiceProviderMetadata` expects a request object
|
|
"foo",
|
|
"bar",
|
|
"baz",
|
|
function (error, result) {
|
|
try {
|
|
expect(result).to.equal("My Metadata Result");
|
|
done();
|
|
} catch (err2) {
|
|
done(err2);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
});
|
|
});
|