Skip to content

Commit

Permalink
feat(authorize-request): idp scoping provider (#428)
Browse files Browse the repository at this point in the history
* feat(scoping): add scoping to authnrequest

* Correct case.

Co-authored-by: Rob Gijsens <rob.gijsens@ivengi.com>
Co-authored-by: Mark Stosberg <mark@rideamigos.com>
  • Loading branch information
3 people authored Nov 2, 2020
1 parent 881208b commit a11ad61
Show file tree
Hide file tree
Showing 4 changed files with 540 additions and 4 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Passport-SAML has been tested to work with Onelogin, Okta, Shibboleth, [SimpleSA
## Installation

$ npm install passport-saml

/
## Usage

The examples utilize the [Feide OpenIdp identity provider](https://openidp.feide.no/). You need an account there to log in with this. You also need to [register your site](https://openidp.feide.no/simplesaml/module.php/metaedit/index.php) as a service provider.
Expand Down Expand Up @@ -134,6 +134,23 @@ type Profile = {
* `skipRequestCompression`: if set to true, the SAML request from the service provider won't be compressed.
* `authnRequestBinding`: if set to `HTTP-POST`, will request authentication from IDP via HTTP POST binding, otherwise defaults to HTTP Redirect
* `disableRequestACSUrl`: if truthy, SAML AuthnRequest from the service provider will not include the optional AssertionConsumerServiceURL. Default is falsy so it is automatically included.
* `scoping`: An optional configuration which implements the functionality [explained in the SAML spec paragraph "3.4.1.2 Element <Scoping>"](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf). The config object is structured as following:
```javascript
{
idpList: { // optional
entries: [ // required
{
providerId: 'yourProviderId', // required for each entry
name: 'yourName', // optional
loc: 'yourLoc', // optional
}
],
getComplete: 'URI to your complete IDP list', // optional
},
proxyCount: 2, // optional
requesterId: 'requesterId', // optional
}
```
* **InResponseTo Validation**
* `validateInResponseTo`: if truthy, then InResponseTo will be validated from incoming SAML responses
* `requestIdExpirationPeriodMs`: Defines the expiration time when a Request ID generated for a SAML request will not be valid if seen in a SAML response in the `InResponseTo` field. Default is 8 hours.
Expand Down
51 changes: 51 additions & 0 deletions src/passport-saml/saml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {CacheProvider as InMemoryCacheProvider} from './inmemory-cache-provider'
import * as algorithms from './algorithms';
import { signAuthnRequestPost } from './saml-post-signing';
import type { Request } from 'express';
import { SamlIDPEntryConfig, SamlIDPListConfig } from './types';

function processValidlySignedPostRequest(self: SAML, doc, dom, callback) {
const request = doc.LogoutRequest;
Expand Down Expand Up @@ -305,6 +306,56 @@ class SAML {
request['samlp:AuthnRequest']['@ProviderName'] = this.options.providerName;
}

if (this.options.scoping) {
const scoping = {
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
};

if (typeof this.options.scoping.proxyCount === 'number') {
scoping['@ProxyCount'] = this.options.scoping.proxyCount;
}

if (this.options.scoping.idpList) {
scoping['samlp:IDPList'] = this.options.scoping.idpList.map((idpListItem: SamlIDPListConfig) => {
const formattedIdpListItem = {
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
};

if (idpListItem.entries) {
formattedIdpListItem['samlp:IDPEntry'] = idpListItem.entries.map((entry: SamlIDPEntryConfig) => {
const formattedEntry = {
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
};

formattedEntry['@ProviderID'] = entry.providerId;

if (entry.name) {
formattedEntry['@Name'] = entry.name;
}

if (entry.loc) {
formattedEntry['@Loc'] = entry.loc;
}

return formattedEntry;
});
}

if (idpListItem.getComplete) {
formattedIdpListItem['samlp:GetComplete'] = idpListItem.getComplete;
}

return formattedIdpListItem;
});
}

if (this.options.scoping.requesterId) {
scoping['samlp:RequesterID'] = this.options.scoping.requesterId;
}

request['samlp:AuthnRequest']['samlp:Scoping'] = scoping;
}

let stringRequest = xmlbuilder.create(request).end();
if (isHttpPostBinding && this.options.privateCert) {
stringRequest = signAuthnRequestPost(stringRequest, this.options);
Expand Down
18 changes: 18 additions & 0 deletions src/passport-saml/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface SamlConfig {
passive?: boolean;
idpIssuer?: string;
audience?: string;
scoping? : SamlScopingConfig;

// InResponseTo Validation
validateInResponseTo?: boolean;
Expand All @@ -57,6 +58,23 @@ export interface SamlConfig {
logoutCallbackUrl?: string;
}

export interface SamlScopingConfig {
idpList: SamlIDPListConfig[];
proxyCount?: number;
requesterId?: string[];
}

export interface SamlIDPListConfig {
entries: SamlIDPEntryConfig[];
getComplete?: string;
}

export interface SamlIDPEntryConfig {
providerId: string;
name?: string;
loc?: string;
}

export type Profile = {
issuer?: string;
sessionIndex?: string;
Expand Down
Loading

0 comments on commit a11ad61

Please sign in to comment.