Skip to content

Commit ba14169

Browse files
committed
FIx 159. Adjusted acs.jsp to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted dologout to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter
1 parent 6eced9a commit ba14169

File tree

13 files changed

+609
-180
lines changed

13 files changed

+609
-180
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,13 @@ if (!errors.isEmpty()) {
445445
} else {
446446
Map<String, List<String>> attributes = auth.getAttributes();
447447
String nameId = auth.getNameId();
448+
String nameIdFormat = auth.getNameIdFormat();
449+
String sessionIndex = auth.getSessionIndex();
450+
448451
session.setAttribute("attributes", attributes);
449452
session.setAttribute("nameId", nameId);
453+
session.setAttribute("nameIdFormat", nameIdFormat);
454+
session.setAttribute("sessionIndex", sessionIndex);
450455
451456
String relayState = request.getParameter("RelayState");
452457
@@ -517,7 +522,20 @@ If we don't want that processSLO to destroy the session, pass the keepLocalSessi
517522
In order to send a Logout Request to the IdP:
518523
```
519524
Auth auth = new Auth(request, response);
520-
auth.logout();
525+
526+
String nameId = null;
527+
if (session.getAttribute("nameId") != null) {
528+
nameId = session.getAttribute("nameId").toString();
529+
}
530+
String nameIdFormat = null;
531+
if (session.getAttribute("nameIdFormat") != null) {
532+
nameIdFormat = session.getAttribute("nameIdFormat").toString();
533+
}
534+
String sessionIndex = null;
535+
if (session.getAttribute("sessionIndex") != null) {
536+
sessionIndex = session.getAttribute("sessionIndex").toString();
537+
}
538+
auth.logout(null, nameId, sessionIndex, nameIdFormat);
521539
```
522540
The Logout Request will be sent signed or unsigned based on the security settings 'onelogin.saml2.security.logoutrequest_signed'
523541

core/src/main/java/com/onelogin/saml2/authn/SamlResponse.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public class SamlResponse {
6565
*/
6666
private Document decryptedDocument;
6767

68+
/**
69+
* NameID Data
70+
*/
71+
private HashMap<String,String> nameIdData = null;
72+
6873
/**
6974
* URL of the current host + current view
7075
*/
@@ -421,6 +426,9 @@ public boolean isValid() {
421426
*
422427
*/
423428
public HashMap<String,String> getNameIdData() throws Exception {
429+
if (this.nameIdData != null) {
430+
return this.nameIdData;
431+
}
424432
HashMap<String,String> nameIdData = new HashMap<String, String>();
425433

426434
NodeList encryptedIDNodes = this.queryAssertion("/saml:Subject/saml:EncryptedID");
@@ -477,6 +485,7 @@ public HashMap<String,String> getNameIdData() throws Exception {
477485
throw new ValidationError("No name id found in Document.", ValidationError.NO_NAMEID);
478486
}
479487
}
488+
this.nameIdData = nameIdData;
480489
return nameIdData;
481490
}
482491

@@ -514,6 +523,40 @@ public String getNameIdFormat() throws Exception {
514523
return nameidFormat;
515524
}
516525

526+
/**
527+
* Gets the NameID NameQualifier provided from the SAML Response String.
528+
*
529+
* @return string NameQualifier
530+
*
531+
* @throws Exception
532+
*/
533+
public String getNameIdNameQualifier() throws Exception {
534+
HashMap<String,String> nameIdData = getNameIdData();
535+
String nameQualifier = null;
536+
if (!nameIdData.isEmpty() && nameIdData.containsKey("NameQualifier")) {
537+
LOGGER.debug("SAMLResponse has NameID NameQualifier --> " + nameIdData.get("NameQualifier"));
538+
nameQualifier = nameIdData.get("NameQualifier");
539+
}
540+
return nameQualifier;
541+
}
542+
543+
/**
544+
* Gets the NameID SP NameQualifier provided from the SAML Response String.
545+
*
546+
* @return string SP NameQualifier
547+
*
548+
* @throws Exception
549+
*/
550+
public String getNameIdSPNameQualifier() throws Exception {
551+
HashMap<String,String> nameIdData = getNameIdData();
552+
String spNameQualifier = null;
553+
if (!nameIdData.isEmpty() && nameIdData.containsKey("SPNameQualifier")) {
554+
LOGGER.debug("SAMLResponse has NameID NameQualifier --> " + nameIdData.get("SPNameQualifier"));
555+
spNameQualifier = nameIdData.get("SPNameQualifier");
556+
}
557+
return spNameQualifier;
558+
}
559+
517560
/**
518561
* Gets the Attributes from the AttributeStatement element.
519562
*

core/src/main/java/com/onelogin/saml2/logout/LogoutRequest.java

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ public class LogoutRequest {
7070
*/
7171
private String nameIdFormat;
7272

73+
/**
74+
* nameId NameQualifier
75+
*/
76+
private String nameIdNameQualifier;
77+
78+
/**
79+
* nameId SP NameQualifier
80+
*/
81+
private String nameIdSPNameQualifier;
82+
7383
/**
7484
* SessionIndex. When the user is logged, this stored it from the AuthnStatement of the SAML Response
7585
*/
@@ -103,27 +113,33 @@ public class LogoutRequest {
103113
* The SessionIndex (taken from the SAML Response in the SSO process).
104114
* @param nameIdFormat
105115
* The nameIdFormat that will be set in the LogoutRequest.
106-
* @throws XMLEntityException
116+
* @param nameIdNameQualifier
117+
* The NameID NameQualifier that will be set in the LogoutRequest.
118+
* @param nameIdSPNameQualifier
119+
* The SP Name Qualifier that will be set in the LogoutRequest.
107120
*
121+
* @throws XMLEntityException
108122
*/
109-
public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId, String sessionIndex, String nameIdFormat) throws XMLEntityException {
123+
public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId, String sessionIndex, String nameIdFormat, String nameIdNameQualifier, String nameIdSPNameQualifier) throws XMLEntityException {
110124
this.settings = settings;
111125
this.request = request;
112-
126+
113127
String samlLogoutRequest = null;
114-
128+
115129
if (request != null) {
116130
samlLogoutRequest = request.getParameter("SAMLRequest");
117131
currentUrl = request.getRequestURL();
118132
}
119-
133+
120134
if (samlLogoutRequest == null) {
121135
id = Util.generateUniqueID();
122136
issueInstant = Calendar.getInstance();
123137
this.nameId = nameId;
124138
this.nameIdFormat = nameIdFormat;
139+
this.nameIdNameQualifier = nameIdNameQualifier;
140+
this.nameIdSPNameQualifier = nameIdSPNameQualifier;
125141
this.sessionIndex = sessionIndex;
126-
142+
127143
StrSubstitutor substitutor = generateSubstitutor(settings);
128144
logoutRequestString = substitutor.replace(getLogoutRequestTemplate());
129145
} else {
@@ -132,6 +148,48 @@ public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId,
132148
}
133149
}
134150

151+
/**
152+
* Constructs the LogoutRequest object.
153+
*
154+
* @param settings
155+
* OneLogin_Saml2_Settings
156+
* @param request
157+
* the HttpRequest object to be processed (Contains GET and POST parameters, request URL, ...).
158+
* @param nameId
159+
* The NameID that will be set in the LogoutRequest.
160+
* @param sessionIndex
161+
* The SessionIndex (taken from the SAML Response in the SSO process).
162+
* @param nameIdFormat
163+
* The nameIdFormat that will be set in the LogoutRequest.
164+
* @param nameIdNameQualifier
165+
* The NameID NameQualifier will be set in the LogoutRequest.
166+
*
167+
* @throws XMLEntityException
168+
*/
169+
public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId, String sessionIndex, String nameIdFormat, String nameIdNameQualifier) throws XMLEntityException {
170+
this(settings, request, nameId, sessionIndex, nameIdFormat, nameIdNameQualifier, null);
171+
}
172+
173+
/**
174+
* Constructs the LogoutRequest object.
175+
*
176+
* @param settings
177+
* OneLogin_Saml2_Settings
178+
* @param request
179+
* the HttpRequest object to be processed (Contains GET and POST parameters, request URL, ...).
180+
* @param nameId
181+
* The NameID that will be set in the LogoutRequest.
182+
* @param sessionIndex
183+
* The SessionIndex (taken from the SAML Response in the SSO process).
184+
* @param nameIdFormat
185+
* The nameIdFormat that will be set in the LogoutRequest.
186+
*
187+
* @throws XMLEntityException
188+
*/
189+
public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId, String sessionIndex, String nameIdFormat) throws XMLEntityException {
190+
this(settings, request, nameId, sessionIndex, nameIdFormat, null);
191+
}
192+
135193
/**
136194
* Constructs the LogoutRequest object.
137195
*
@@ -239,7 +297,8 @@ private StrSubstitutor generateSubstitutor(Saml2Settings settings) {
239297
valueMap.put("issuer", settings.getSpEntityId());
240298

241299
String nameIdFormat = null;
242-
String spNameQualifier = null;
300+
String spNameQualifier = this.nameIdSPNameQualifier;
301+
String nameQualifier = this.nameIdNameQualifier;
243302
if (nameId != null) {
244303
if (this.nameIdFormat == null && !settings.getSpNameIDFormat().equals(Constants.NAMEID_UNSPECIFIED)) {
245304
nameIdFormat = settings.getSpNameIDFormat();
@@ -248,16 +307,27 @@ private StrSubstitutor generateSubstitutor(Saml2Settings settings) {
248307
}
249308
} else {
250309
nameId = settings.getIdpEntityId();
251-
nameIdFormat = Constants.NAMEID_ENTITY;
252-
spNameQualifier = settings.getSpEntityId();
310+
nameIdFormat = Constants.NAMEID_ENTITY;
253311
}
254312

313+
// From saml-core-2.0-os 8.3.6, when the entity Format is used: "The NameQualifier, SPNameQualifier, and
314+
// SPProvidedID attributes MUST be omitted.
315+
if (nameIdFormat != null && nameIdFormat.equals(Constants.NAMEID_ENTITY)) {
316+
nameQualifier = null;
317+
spNameQualifier = null;
318+
}
319+
320+
// NameID Format UNSPECIFIED omitted
321+
if (nameIdFormat != null && nameIdFormat.equals(Constants.NAMEID_UNSPECIFIED)) {
322+
nameIdFormat = null;
323+
}
324+
255325
X509Certificate cert = null;
256326
if (settings.getNameIdEncrypted()) {
257327
cert = settings.getIdpx509cert();
258328
}
259329

260-
String nameIdStr = Util.generateNameId(nameId, spNameQualifier, nameIdFormat, cert);
330+
String nameIdStr = Util.generateNameId(nameId, spNameQualifier, nameIdFormat, nameQualifier, cert);
261331
valueMap.put("nameIdStr", nameIdStr);
262332

263333
String sessionIndexStr = "";

core/src/main/java/com/onelogin/saml2/util/Util.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ public static Boolean validateMetadataSign(Document doc, X509Certificate cert, S
948948
}
949949
return false;
950950
}
951-
951+
952952
/**
953953
* Validate signature of the Node.
954954
*
@@ -1405,24 +1405,32 @@ public static SamlResponseStatus getStatus(String statusXpath, Document dom) thr
14051405
* SP Name Qualifier
14061406
* @param format
14071407
* SP Format
1408+
* @param nq
1409+
* Name Qualifier
14081410
* @param cert
14091411
* IdP Public certificate to encrypt the nameID
14101412
*
14111413
* @return Xml contained in the document.
14121414
*/
1413-
public static String generateNameId(String value, String spnq, String format, X509Certificate cert) {
1415+
public static String generateNameId(String value, String spnq, String format, String nq, X509Certificate cert) {
14141416
String res = null;
14151417
try {
14161418
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
14171419
dbf.setNamespaceAware(true);
14181420
Document doc = dbf.newDocumentBuilder().newDocument();
14191421
Element nameId = doc.createElement("saml:NameID");
1422+
14201423
if (spnq != null && !spnq.isEmpty()) {
14211424
nameId.setAttribute("SPNameQualifier", spnq);
14221425
}
14231426
if (format != null && !format.isEmpty()) {
14241427
nameId.setAttribute("Format", format);
14251428
}
1429+
if ((nq != null) && !nq.isEmpty())
1430+
{
1431+
nameId.setAttribute("NameQualifier", nq);
1432+
}
1433+
14261434
nameId.appendChild(doc.createTextNode(value));
14271435
doc.appendChild(nameId);
14281436

@@ -1461,6 +1469,24 @@ public static String generateNameId(String value, String spnq, String format, X5
14611469
return res;
14621470
}
14631471

1472+
/**
1473+
* Generates a nameID.
1474+
*
1475+
* @param value
1476+
* The value
1477+
* @param spnq
1478+
* SP Name Qualifier
1479+
* @param format
1480+
* SP Format
1481+
* @param cert
1482+
* IdP Public certificate to encrypt the nameID
1483+
*
1484+
* @return Xml contained in the document.
1485+
*/
1486+
public static String generateNameId(String value, String spnq, String format, X509Certificate cert) {
1487+
return generateNameId(value, spnq, format, null, cert);
1488+
}
1489+
14641490
/**
14651491
* Generates a nameID.
14661492
*

core/src/test/java/com/onelogin/saml2/test/authn/AuthnResponseTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,45 @@ public void testGetNameIdFormat() throws Exception {
252252
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
253253
assertNull(samlResponse.getNameIdFormat());
254254
}
255-
255+
256+
/**
257+
* Tests the getNameIdNameQualifier method of SamlResponse
258+
*
259+
* @throws Exception
260+
*
261+
* @see com.onelogin.saml2.authn.SamlResponse#getNameIdNameQualifier
262+
*/
263+
@Test
264+
public void testGetNameIdNameQualifier() throws Exception {
265+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
266+
String samlResponseEncoded = Util.getFileAsString("data/responses/response1.xml.base64");
267+
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
268+
assertNull(samlResponse.getNameIdNameQualifier());
269+
270+
samlResponseEncoded = Util.getFileAsString("data/responses/valid_response_with_namequalifier.xml.base64");
271+
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
272+
assertEquals("example.com", samlResponse.getNameIdNameQualifier());
273+
}
274+
275+
/**
276+
* Tests the getNameIdSPNameQualifier method of SamlResponse
277+
*
278+
* @throws Exception
279+
*
280+
* @see com.onelogin.saml2.authn.SamlResponse#getNameIdSPNameQualifier
281+
*/
282+
@Test
283+
public void testGetNameIdSPNameQualifier() throws Exception {
284+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
285+
String samlResponseEncoded = Util.getFileAsString("data/responses/response1.xml.base64");
286+
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
287+
assertNull(samlResponse.getNameIdSPNameQualifier());
288+
289+
samlResponseEncoded = Util.getFileAsString("data/responses/valid_response_with_namequalifier.xml.base64");
290+
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
291+
assertEquals(settings.getSpEntityId(), samlResponse.getNameIdSPNameQualifier());
292+
}
293+
256294
/**
257295
* Tests the getNameId method of SamlResponse
258296
* Case: No NameId

0 commit comments

Comments
 (0)