@node-saml/node-saml
Advanced tools
Comparing version 4.0.0-beta.6 to 4.0.0
@@ -28,3 +28,3 @@ /// <reference types="node" /> | ||
getLogoutResponseUrl(samlLogoutRequest: Profile, RelayState: string, options: AuthenticateOptions & AuthorizeOptions, success: boolean, callback: (err: Error | null, url?: string) => void): void; | ||
protected getLogoutResponseUrlAsync(samlLogoutRequest: Profile, RelayState: string, options: AuthenticateOptions & AuthorizeOptions, success: boolean): Promise<string>; | ||
getLogoutResponseUrlAsync(samlLogoutRequest: Profile, RelayState: string, options: AuthenticateOptions & AuthorizeOptions, success: boolean): Promise<string>; | ||
protected certsToCheck(): Promise<string[]>; | ||
@@ -31,0 +31,0 @@ validatePostResponseAsync(container: Record<string, string>): Promise<{ |
@@ -491,2 +491,3 @@ "use strict"; | ||
async validatePostResponseAsync(container) { | ||
var _a, _b, _c, _d; | ||
let xml; | ||
@@ -497,5 +498,3 @@ let doc; | ||
xml = Buffer.from(container.SAMLResponse, "base64").toString("utf8"); | ||
doc = (0, xml_1.parseDomFromString)(xml); | ||
if (!Object.prototype.hasOwnProperty.call(doc, "documentElement")) | ||
throw new Error("SAMLResponse is not valid base64-encoded XML"); | ||
doc = await (0, xml_1.parseDomFromString)(xml); | ||
const inResponseToNodes = xml_1.xpath.selectAttributes(doc, "/*[local-name()='Response']/@InResponseTo"); | ||
@@ -509,4 +508,3 @@ if (inResponseToNodes) { | ||
let validSignature = false; | ||
if ((0, xml_1.validateSignature)(xml, doc.documentElement, certs) && | ||
Array.from(doc.childNodes).filter((n) => n.tagName != null && n.childNodes != null).length === 1) { | ||
if ((0, xml_1.validateSignature)(xml, doc.documentElement, certs)) { | ||
validSignature = true; | ||
@@ -535,3 +533,3 @@ } | ||
const decryptedXml = await (0, xml_1.decryptXml)(encryptedAssertionXml, this.options.decryptionPvk); | ||
const decryptedDoc = (0, xml_1.parseDomFromString)(decryptedXml); | ||
const decryptedDoc = await (0, xml_1.parseDomFromString)(decryptedXml); | ||
const decryptedAssertions = xml_1.xpath.selectElements(decryptedDoc, "/*[local-name()='Assertion']"); | ||
@@ -548,7 +546,6 @@ if (decryptedAssertions.length != 1) | ||
// LogoutResponse code. | ||
const xmljsDoc = await (0, xml_1.parseXml2JsFromString)(xml); | ||
const xmljsDoc = (await (0, xml_1.parseXml2JsFromString)(xml)); | ||
const response = xmljsDoc.Response; | ||
if (response) { | ||
const assertion = response.Assertion; | ||
if (!assertion) { | ||
if (!("Assertion" in response)) { | ||
const status = response.Status; | ||
@@ -558,6 +555,6 @@ if (status) { | ||
if (statusCode && | ||
statusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:Responder") { | ||
((_a = statusCode[0].$) === null || _a === void 0 ? void 0 : _a.Value) === "urn:oasis:names:tc:SAML:2.0:status:Responder") { | ||
const nestedStatusCode = statusCode[0].StatusCode; | ||
if (nestedStatusCode && | ||
nestedStatusCode[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") { | ||
((_b = nestedStatusCode[0].$) === null || _b === void 0 ? void 0 : _b.Value) === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") { | ||
if (!validSignature) { | ||
@@ -572,11 +569,12 @@ throw new Error("Invalid signature: NoPassive"); | ||
// let's go ahead and give the potentially more helpful error. | ||
if (statusCode && statusCode[0].$.Value) { | ||
const msgType = statusCode[0].$.Value.match(/[^:]*$/)[0]; | ||
if (msgType != "Success") { | ||
if (statusCode && ((_c = statusCode[0].$) === null || _c === void 0 ? void 0 : _c.Value)) { | ||
const msgType = statusCode[0].$.Value.match(/[^:]*$/); | ||
if (msgType && msgType[0] != "Success") { | ||
let msg = "unspecified"; | ||
if (status[0].StatusMessage) { | ||
msg = status[0].StatusMessage[0]._; | ||
msg = status[0].StatusMessage[0]._ || msg; | ||
} | ||
else if (statusCode[0].StatusCode) { | ||
msg = statusCode[0].StatusCode[0].$.Value.match(/[^:]*$/)[0]; | ||
const msgValues = (_d = statusCode[0].StatusCode[0].$) === null || _d === void 0 ? void 0 : _d.Value.match(/[^:]*$/); | ||
msg = msgValues ? msgValues[0] : msg; | ||
} | ||
@@ -629,3 +627,3 @@ const statusXml = (0, xml_1.buildXml2JsObject)("Status", status[0]); | ||
const inflated = await inflateRawAsync(data); | ||
const dom = (0, xml_1.parseDomFromString)(inflated.toString()); | ||
const dom = await (0, xml_1.parseDomFromString)(inflated.toString()); | ||
const doc = await (0, xml_1.parseXml2JsFromString)(inflated); | ||
@@ -922,3 +920,3 @@ samlMessageType === "SAMLResponse" | ||
const xml = Buffer.from(container.SAMLRequest, "base64").toString("utf8"); | ||
const dom = (0, xml_1.parseDomFromString)(xml); | ||
const dom = await (0, xml_1.parseDomFromString)(xml); | ||
const doc = await (0, xml_1.parseXml2JsFromString)(xml); | ||
@@ -925,0 +923,0 @@ const certs = await this.certsToCheck(); |
@@ -28,5 +28,32 @@ /// <reference types="node" /> | ||
export declare type XMLOutput = Record<string, any>; | ||
export interface AuthorizeRequestXML { | ||
export declare type AuthorizeRequestXML = { | ||
"samlp:AuthnRequest": XMLInput; | ||
} | ||
}; | ||
export declare type XmlJsObject = { | ||
[key: string]: string | XmlJsObject | XmlJsObject[] | undefined; | ||
$?: { | ||
Value: string; | ||
}; | ||
_?: string; | ||
}; | ||
export declare type SamlResponseXmlJs = XmlJsObject & { | ||
Response?: SamlAssertionXmlJs | SamlStatusXmlJs; | ||
LogoutResponse?: unknown; | ||
}; | ||
export declare type SamlRequestXmlJs = { | ||
Request: unknown; | ||
}; | ||
export declare type SamlAssertionXmlJs = { | ||
Assertion: unknown; | ||
}; | ||
export declare type SamlStatusXmlJs = { | ||
Status: [ | ||
{ | ||
StatusCode: [XmlJsObject & { | ||
StatusCode: [XmlJsObject]; | ||
}]; | ||
StatusMessage: [XmlJsObject]; | ||
} | ||
]; | ||
}; | ||
export declare type CertCallback = (callback: (err: Error | null, cert?: string | string[]) => void) => void; | ||
@@ -49,3 +76,3 @@ /** | ||
} | ||
export interface LogoutRequestXML { | ||
export declare type LogoutRequestXML = { | ||
"samlp:LogoutRequest": { | ||
@@ -55,4 +82,4 @@ "saml:NameID": XMLInput; | ||
}; | ||
} | ||
export interface ServiceMetadataXML { | ||
}; | ||
export declare type ServiceMetadataXML = { | ||
EntityDescriptor: { | ||
@@ -62,3 +89,3 @@ [key: string]: XMLValue; | ||
}; | ||
} | ||
}; | ||
export interface NameID { | ||
@@ -65,0 +92,0 @@ value: string | null; |
/// <reference types="node" /> | ||
import { NameID, SamlSigningOptions, XMLOutput, XmlSignatureLocation } from "./types"; | ||
import { NameID, SamlSigningOptions, XmlJsObject, XMLOutput, XmlSignatureLocation } from "./types"; | ||
export declare const xpath: { | ||
@@ -21,7 +21,7 @@ selectAttributes: (node: Node, xpath: string) => Attr[]; | ||
export declare const signXml: (xml: string, xpath: string, location: XmlSignatureLocation, options: SamlSigningOptions) => string; | ||
export declare const parseDomFromString: (xml: string) => Document; | ||
export declare const parseXml2JsFromString: (xml: string | Buffer) => Promise<XMLOutput>; | ||
export declare const buildXml2JsObject: (rootName: string, xml: XMLOutput) => string; | ||
export declare const parseDomFromString: (xml: string) => Promise<Document>; | ||
export declare const parseXml2JsFromString: (xml: string | Buffer) => Promise<XmlJsObject>; | ||
export declare const buildXml2JsObject: (rootName: string, xml: XmlJsObject) => string; | ||
export declare const buildXmlBuilderObject: (xml: XMLOutput, pretty: boolean) => string; | ||
export declare const promiseWithNameId: (nameid: Node) => Promise<NameID>; | ||
export declare const getNameIdAsync: (doc: Node, decryptionPvk: string | Buffer | null) => Promise<NameID>; |
@@ -152,3 +152,28 @@ "use strict"; | ||
const parseDomFromString = (xml) => { | ||
return new xmldom.DOMParser().parseFromString(xml); | ||
return new Promise(function (resolve, reject) { | ||
function errHandler(msg) { | ||
return reject(new Error(msg)); | ||
} | ||
const dom = new xmldom.DOMParser({ | ||
/** | ||
* locator is always need for error position info | ||
*/ | ||
locator: {}, | ||
/** | ||
* you can override the errorHandler for xml parser | ||
* @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html | ||
*/ | ||
errorHandler: { | ||
error: errHandler, | ||
fatalError: errHandler, | ||
}, | ||
}).parseFromString(xml, "text/xml"); | ||
if (!Object.prototype.hasOwnProperty.call(dom, "documentElement")) { | ||
return reject(new Error("Not a valid XML document")); | ||
} | ||
if (Array.from(dom.childNodes).filter((n) => n.tagName != null && n.childNodes != null).length !== 1) { | ||
return reject(new Error("Malformed XML; multiple roots detected")); | ||
} | ||
return resolve(dom); | ||
}); | ||
}; | ||
@@ -204,3 +229,3 @@ exports.parseDomFromString = parseDomFromString; | ||
const decryptedXml = await (0, exports.decryptXml)(encryptedDataXml, decryptionPvk); | ||
const decryptedDoc = (0, exports.parseDomFromString)(decryptedXml); | ||
const decryptedDoc = await (0, exports.parseDomFromString)(decryptedXml); | ||
const decryptedIds = exports.xpath.selectElements(decryptedDoc, "/*[local-name()='NameID']"); | ||
@@ -207,0 +232,0 @@ if (decryptedIds.length !== 1) { |
{ | ||
"name": "@node-saml/node-saml", | ||
"version": "4.0.0-beta.6", | ||
"version": "4.0.0", | ||
"description": "SAML 2.0 implementation for Node.js", | ||
@@ -39,5 +39,5 @@ "keywords": [ | ||
"changelog": "gren changelog --override --generate --head master", | ||
"lint": "eslint --ext .ts \"**/*.ts\" --cache", | ||
"lint": "eslint --ext .ts \"**/*.ts\" --cache && npm run prettier-check", | ||
"lint-watch": "onchange -k -p 100 \"**/*.ts\" -- eslint {{file}}", | ||
"lint:fix": "eslint --ext .ts --fix src", | ||
"lint:fix": "eslint --ext .ts --fix src && npm run prettier-format", | ||
"prepare": "tsc", | ||
@@ -61,6 +61,6 @@ "prettier-check": "prettier --config .prettierrc.json --check .", | ||
"@types/xml2js": "^0.4.11", | ||
"@xmldom/xmldom": "^0.8.2", | ||
"@xmldom/xmldom": "^0.8.3", | ||
"debug": "^4.3.4", | ||
"xml-crypto": "^2.1.4", | ||
"xml-encryption": "^2.0.0", | ||
"xml-crypto": "^3.0.0", | ||
"xml-encryption": "^3.0.1", | ||
"xml2js": "^0.4.23", | ||
@@ -70,20 +70,20 @@ "xmlbuilder": "^15.1.1" | ||
"devDependencies": { | ||
"@cjbarth/github-release-notes": "^2.1.0", | ||
"@cjbarth/github-release-notes": "^3.0.0", | ||
"@istanbuljs/nyc-config-typescript": "^1.0.2", | ||
"@types/chai": "^4.3.3", | ||
"@types/mocha": "^9.1.1", | ||
"@types/node": "^14.18.22", | ||
"@types/mocha": "^10.0.0", | ||
"@types/node": "^14.18.32", | ||
"@types/sinon": "^10.0.13", | ||
"@typescript-eslint/eslint-plugin": "^5.38.1", | ||
"@typescript-eslint/parser": "^5.40.0", | ||
"body-parser": "^1.20.0", | ||
"@typescript-eslint/eslint-plugin": "^5.41.0", | ||
"@typescript-eslint/parser": "^5.41.0", | ||
"body-parser": "^1.20.1", | ||
"chai": "^4.3.6", | ||
"choma": "^1.2.1", | ||
"concurrently": "^7.3.0", | ||
"eslint": "^8.23.0", | ||
"concurrently": "^7.5.0", | ||
"eslint": "^8.26.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-deprecation": "^1.3.2", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"express": "^4.18.1", | ||
"mocha": "^10.0.0", | ||
"express": "^4.18.2", | ||
"mocha": "^10.1.0", | ||
"nyc": "^15.1.0", | ||
@@ -94,3 +94,3 @@ "onchange": "^7.1.0", | ||
"release-it": "^15.5.0", | ||
"sinon": "^14.0.0", | ||
"sinon": "^14.0.1", | ||
"ts-node": "^10.9.1", | ||
@@ -97,0 +97,0 @@ "typescript": "^4.8.4" |
@@ -67,3 +67,3 @@ # Node SAML | ||
- `attributeConsumingServiceIndex`: optional `AttributeConsumingServiceIndex` attribute to add to AuthnRequest to instruct the IDP which attribute set to attach to the response ([link](http://blog.aniljohn.com/2014/01/data-minimization-front-channel-saml-attribute-requests.html)) | ||
- `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is [known to help when authenticating against Active Directory](https://github.com/node-saml/passport-saml/issues/226) (AD FS) servers. | ||
- `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. | ||
- `authnContext`: if truthy, name identifier format to request auth context (default: `urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport`); array of values is also supported | ||
@@ -179,5 +179,5 @@ - `racComparison`: Requested Authentication Context comparison type. Possible values are 'exact','minimum','maximum','better'. Default is 'exact'. | ||
Passport-SAML uses the HTTP Redirect Binding for its `AuthnRequest`s (unless overridden with the `authnRequestBinding` parameter), and expects to receive the messages back via the HTTP POST binding. | ||
Node-SAML uses the HTTP Redirect Binding for its `AuthnRequest`s (unless overridden with the `authnRequestBinding` parameter), and expects to receive the messages back via the HTTP POST binding. | ||
Authentication requests sent by Passport-SAML can be signed using RSA signature with SHA1, SHA256 or SHA512 hashing algorithms. | ||
Authentication requests sent by Node-SAML can be signed using RSA signature with SHA1, SHA256 or SHA512 hashing algorithms. | ||
@@ -251,26 +251,7 @@ To select hashing algorithm, use: | ||
## Usage with Active Directory Federation Services | ||
Here is a configuration that has been proven to work with ADFS: | ||
```javascript | ||
{ | ||
entryPoint: 'https://ad.example.net/adfs/ls/', | ||
issuer: 'https://your-app.example.net/login/callback', | ||
callbackUrl: 'https://your-app.example.net/login/callback', | ||
cert: 'MIICizCCAfQCCQCY8tKaMc0BMjANBgkqh ... W==', | ||
authnContext: 'http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/windows', | ||
identifierFormat: null | ||
} | ||
``` | ||
Please note that ADFS needs to have a trust established to your service in order for this to work. | ||
For more detailed instructions, see [ADFS documentation](docs/adfs/README.md). | ||
## SAML Response Validation - NotBefore and NotOnOrAfter | ||
If the `NotBefore` or the `NotOnOrAfter` attributes are returned in the SAML response, Passport-SAML will validate them | ||
If the `NotBefore` or the `NotOnOrAfter` attributes are returned in the SAML response, Node-SAML will validate them | ||
against the current time +/- a configurable clock skew value. The default for the skew is 0s. This is to account for | ||
differences between the clock time on the client (Node server with Passport-SAML) and the server (Identity provider). | ||
differences between the clock time on the client (Node server with Node-SAML) and the server (Identity provider). | ||
@@ -282,4 +263,4 @@ `NotBefore` and `NotOnOrAfter` can be part of either the `SubjectConfirmation` element, or within in the `Assertion/Conditions` element | ||
When configured (turn `validateInResponseTo` to `always` in the Passport-SAML config), the `InResponseTo` attribute will be validated. | ||
Validation will succeed if Passport-SAML previously generated a SAML request with an id that matches the value of `InResponseTo`. | ||
When configured (turn `validateInResponseTo` to `always` in the Node-SAML config), the `InResponseTo` attribute will be validated. | ||
Validation will succeed if Node-SAML previously generated a SAML request with an id that matches the value of `InResponseTo`. | ||
@@ -290,3 +271,3 @@ Also note that `InResponseTo` is validated as an attribute of the top level `Response` element in the SAML response, as well | ||
Previous request id's generated for SAML requests will eventually expire. This is controlled with the `requestIdExpirationPeriodMs` option | ||
passed into the Passport-SAML config. The default is 28,800,000 ms (8 hours). Once expired, a subsequent SAML response | ||
passed into the Node-SAML config. The default is 28,800,000 ms (8 hours). Once expired, a subsequent SAML response | ||
received with an `InResponseTo` equal to the expired id will not validate and an error will be returned. | ||
@@ -318,3 +299,3 @@ | ||
Passport-SAML has built in support for SLO including | ||
Node-SAML has built in support for SLO including | ||
@@ -321,0 +302,0 @@ - Signature validation |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
253741
2033
1
318
+ Addedxml-crypto@3.2.0(transitive)
+ Addedxml-encryption@3.0.2(transitive)
- Removed@xmldom/xmldom@0.7.13(transitive)
- Removedxml-crypto@2.1.5(transitive)
- Removedxml-encryption@2.0.0(transitive)
Updated@xmldom/xmldom@^0.8.3
Updatedxml-crypto@^3.0.0
Updatedxml-encryption@^3.0.1