Potentially handle logout differently
wparad opened this issue · comments
public async generateLogoutRequest(user: Profile, options: LogoutOptions) : Promise<string> {
const id = options.generateUniqueId();
const instant = generateInstant();
const request = {
"samlp:LogoutRequest": {
"@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
"@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
"@ID": id,
"@Version": "2.0",
"@IssueInstant": instant,
"@Destination": options.logoutUrl,
"saml:Issuer": {
"@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
"#text": options.issuer,
},
"saml:NameID": {
"@Format": user!.nameIDFormat,
"#text": user!.nameID,
},
},
} as LogoutRequestXML;
if (user!.nameQualifier != null) {
request["samlp:LogoutRequest"]["saml:NameID"]["@NameQualifier"] = user!.nameQualifier;
}
if (user!.spNameQualifier != null) {
request["samlp:LogoutRequest"]["saml:NameID"]["@SPNameQualifier"] = user!.spNameQualifier;
}
if (user!.sessionIndex) {
request["samlp:LogoutRequest"]["saml2p:SessionIndex"] = {
"@xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol",
"#text": user!.sessionIndex,
};
}
await this.cacheProvider.save(id, instant);
const request = buildXmlBuilderObject(request, false);
await this._requestToUrl(request, null, "logout");
}
public async generateLogoutResponse(user: Profile, options: LogoutOptions) : Promise<string> {
const id = options.generateUniqueId();
const instant = generateInstant();
const request = {
"samlp:LogoutResponse": {
"@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
"@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
"@ID": id,
"@Version": "2.0",
"@IssueInstant": instant,
"@Destination": options.logoutUrl,
"@InResponseTo": logoutRequest.ID,
"saml:Issuer": {
"#text": options.issuer,
},
"samlp:Status": {
"samlp:StatusCode": {
"@Value": "urn:oasis:names:tc:SAML:2.0:status:Success",
},
},
},
};
return buildXmlBuilderObject(request, false);
}
private async _requestToUrl(
request: string | null | undefined,
response: string | null,
operation: string,
additionalParameters: querystring.ParsedUrlQuery
): Promise<string> {
providerSingleSignOnUrl = assertRequired(options.providerSingleSignOnUrl, "providerSingleSignOnUrl is required");
let buffer: Buffer;
if (options.skipRequestCompression) {
buffer = Buffer.from((request || response)!, "utf8");
} else {
buffer = await deflateRaw((request || response)!);
}
const base64 = buffer.toString("base64");
let target = new URL(providerSingleSignOnUrl);
if (operation === "logout") {
if (options.logoutUrl) {
target = new URL(options.logoutUrl);
}
} else if (operation !== "authorize") {
throw new Error("Unknown operation: " + operation);
}
const samlMessage: querystring.ParsedUrlQuery = request
? {
SAMLRequest: base64,
}
: {
SAMLResponse: base64,
};
Object.keys(additionalParameters).forEach((k) => {
samlMessage[k] = additionalParameters[k];
});
if (options.privateKey != null) {
if (!providerSingleSignOnUrl) {
throw new Error('"providerSingleSignOnUrl" config parameter is required for signed messages');
}
// sets .SigAlg and .Signature
this.signRequest(samlMessage);
}
Object.keys(samlMessage).forEach((k) => {
target.searchParams.set(k, samlMessage[k] as string);
});
return target.toString();
}
public async getLogoutResponseUrl(options: LogoutResponseOptions) : Promise<string> {
const response = this._generateLogoutResponse(samlLogoutRequest);
return await this._requestToUrl(null, response, 'logout');
}