diff --git a/saml_sp.js b/saml_sp.js
index 0a052d7..2218e7f 100644
--- a/saml_sp.js
+++ b/saml_sp.js
@@ -100,6 +100,7 @@ async function handleSAMLMessage(messageType, r) {
/* Define necessary parameters needed to create a SAML LogoutResponse */
opt.nameID = nameID[0];
+ opt.nameIDFormat = nameID[1];
opt.inResponseTo = id;
opt.relayState = params.RelayState;
@@ -659,7 +660,8 @@ async function produceSAMLMessage(messageType, r, opt) {
break;
case "LogoutResponse":
/* Obtain the status code for the LogoutResponse message */
- opt.statusCode = getLogoutStatusCode(r.variables.saml_name_id, opt.nameID)
+ opt.statusCode = getLogoutStatusCode(r.variables.saml_name_id, opt.nameID,
+ r.variables.saml_name_id_format, opt.nameIDFormat);
break;
}
@@ -701,14 +703,17 @@ function setAuthRedirCookie(r) {
];
}
-function getLogoutStatusCode(sessionNameID, requestNameID) {
- /* If no session exists, return Logout Success */
+function getLogoutStatusCode(sessionNameID, requestNameID, sessionFormat, requestFormat) {
+ /* If no session exists, treat as already logged out */
if (!sessionNameID || sessionNameID === '-') {
return 'urn:oasis:names:tc:SAML:2.0:status:Success';
}
-
- /* If session exists, return Logout Success if NameID matches */
- return requestNameID === sessionNameID
+ /* Treat missing formats as "unspecified" for comparison */
+ const defaultFmt = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified';
+ const spFormat = (!sessionFormat || sessionFormat === '-') ? defaultFmt : sessionFormat;
+ const requestFmt = (!requestFormat || requestFormat === '-') ? defaultFmt : requestFormat;
+ /* Only return Success if both NameID value and Format match exactly */
+ return (requestNameID === sessionNameID && spFormat === requestFmt)
? 'urn:oasis:names:tc:SAML:2.0:status:Success'
: 'urn:oasis:names:tc:SAML:2.0:status:Requester';
}
@@ -722,7 +727,9 @@ async function createSAMLMessage(opt, id, messageType) {
nameIDPolicy: ``,
}),
LogoutRequest: () => ({
- nameID: `${opt.nameID}`,
+ nameID: opt.nameIDFormat && opt.nameIDFormat !== 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
+ ? `${opt.nameID}`
+ : `${opt.nameID}`,
}),
LogoutResponse: () => ({
inResponseTo: ` InResponseTo="${opt.inResponseTo}"`,
@@ -1234,6 +1241,7 @@ function parseConfigurationOptions(r, messageType) {
}
opt.relayState = r.variables.saml_logout_landing_page;
opt.nameID = r.variables.saml_name_id;
+ opt.nameIDFormat = r.variables.saml_name_id_format;
opt.allowedClockSkew = validateClockSkew('saml_allowed_clock_skew', 120);
}
diff --git a/t/js_saml.t b/t/js_saml.t
index 35119d0..b016a41 100644
--- a/t/js_saml.t
+++ b/t/js_saml.t
@@ -285,7 +285,7 @@ my $sp_pub = $t->read_file('sp.example.com.crt');
my $js_filename = 'saml_sp.js';
$t->write_file($js_filename, read_file("../$js_filename"));
-$t->try_run('no njs available')->plan(132);
+$t->try_run('no njs available')->plan(134);
my $api_version = (sort { $a <=> $b } @{ api() })[-1];
my $kv = "/api/$api_version/http/keyvals";
@@ -393,7 +393,7 @@ like($r, qr{302.*http://sp.example.com:8080/foo\?a=b}s,
like(get("$kv/saml_response_id"), qr/"_nginx_[^"]+":\s*"1"/,
'kv response id');
like(get("$kv/saml_name_id"), qr/user1/, 'kv response name id');
-like(get("$kv/saml_name_id_format"), qr/unspecified/,
+like(get("$kv/saml_name_id_format"), qr/emailAddress/,
'kv response name id format');
like(get("$kv/saml_session_index"), qr/_nginx_sessionindex_/,
'kv response session index');
@@ -654,6 +654,8 @@ is($r->{Destination}, $cfg->{saml_idp_slo_url},
is($r->{Issuer}, $cfg->{saml_sp_entity_id}, 'sp logout request issuer');
is($r->{isSigned}, 0, 'sp logout request unsigned');
is($r->{NameID}, 'user1', 'sp logout request nameid');
+is($r->{NameIDFormat}, 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
+ 'sp logout request nameid format');
like(get("$kv/saml_request_id"), qr/"$r->{ID}":"1"/,
'sp logout request id redeemed');
@@ -764,6 +766,12 @@ $r = parse_response(modify_saml_obj($xml_obj, '//saml:NameID', 'text', 'foo',
is($r->{StatusCode}, 'urn:oasis:names:tc:SAML:2.0:status:Requester',
'idp logout request wrong nameid');
+$r = parse_response(modify_saml_obj($xml_obj, '//saml:NameID',
+ 'Format', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',
+ auth_token => $auth_token));
+is($r->{StatusCode}, 'urn:oasis:names:tc:SAML:2.0:status:Requester',
+ 'idp logout request wrong nameid format');
+
# Logout Response
($r, undef) = init_slo($cfg, relay_state => '/foo?a=b');
@@ -960,6 +968,11 @@ sub extract_saml_attributes {
$result->{isSigned} = 0;
}
+ my ($name_id_node) = $xpc->findnodes('//saml:NameID');
+ if ($name_id_node) {
+ $result->{NameIDFormat} = $name_id_node->getAttribute('Format');
+ }
+
my ($name_id_policy_node) = $xpc->findnodes('//samlp:NameIDPolicy');
if ($name_id_policy_node) {
$result->{NameIDPolicyFormat} =
@@ -1563,7 +1576,7 @@ END_XML
$signature
user1