Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions saml_sp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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';
}
Expand All @@ -722,7 +727,9 @@ async function createSAMLMessage(opt, id, messageType) {
nameIDPolicy: `<samlp:NameIDPolicy Format="${opt.nameIDFormat}" AllowCreate="true"/>`,
}),
LogoutRequest: () => ({
nameID: `<saml:NameID>${opt.nameID}</saml:NameID>`,
nameID: opt.nameIDFormat && opt.nameIDFormat !== 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
? `<saml:NameID Format="${opt.nameIDFormat}">${opt.nameID}</saml:NameID>`
: `<saml:NameID>${opt.nameID}</saml:NameID>`,
}),
LogoutResponse: () => ({
inResponseTo: ` InResponseTo="${opt.inResponseTo}"`,
Expand Down Expand Up @@ -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);
}

Expand Down
19 changes: 16 additions & 3 deletions t/js_saml.t
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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} =
Expand Down Expand Up @@ -1563,7 +1576,7 @@ END_XML
$signature
<saml:Subject>
<saml:NameID SPNameQualifier=""
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
>user1</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter=""
Expand Down