Skip to content

Commit 26def30

Browse files
committed
Implement a more specific exception class for handling some validation. Improve tests
1 parent 4c317cc commit 26def30

16 files changed

Lines changed: 1245 additions & 485 deletions

File tree

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

Lines changed: 113 additions & 78 deletions
Large diffs are not rendered by default.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.onelogin.saml2.exception;
2+
3+
public class Error extends Exception {
4+
5+
private static final long serialVersionUID = 1L;
6+
7+
public static final int SETTINGS_FILE_NOT_FOUND = 1;
8+
public static final int METADATA_SP_INVALID = 2;
9+
public static final int SAML_RESPONSE_NOT_FOUND = 3;
10+
public static final int SAML_LOGOUTMESSAGE_NOT_FOUND = 4;
11+
public static final int SAML_LOGOUTREQUEST_INVALID = 5;
12+
public static final int SAML_LOGOUTRESPONSE_INVALID = 6;
13+
public static final int SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 7;
14+
15+
private int errorCode;
16+
17+
public Error(String message, int errorCode) {
18+
super(message);
19+
this.errorCode = errorCode;
20+
}
21+
22+
public int getErrorCode() {
23+
return errorCode;
24+
}
25+
26+
}

core/src/main/java/com/onelogin/saml2/exception/SettingsException.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,22 @@ public class SettingsException extends Exception {
44

55
private static final long serialVersionUID = 1L;
66

7-
public SettingsException(String message) {
7+
public static final int SETTINGS_INVALID_SYNTAX = 1;
8+
public static final int SETTINGS_INVALID = 2;
9+
public static final int CERT_NOT_FOUND = 3;
10+
public static final int PRIVATE_KEY_NOT_FOUND = 4;
11+
public static final int PUBLIC_CERT_FILE_NOT_FOUND = 5;
12+
public static final int PRIVATE_KEY_FILE_NOT_FOUND = 6;
13+
14+
private int errorCode;
15+
16+
public SettingsException(String message, int errorCode) {
817
super(message);
18+
this.errorCode = errorCode;
919
}
20+
21+
public int getErrorCode() {
22+
return errorCode;
23+
}
1024

1125
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.onelogin.saml2.exception;
2+
3+
public class ValidationError extends Exception {
4+
5+
private static final long serialVersionUID = 1L;
6+
7+
public static final int UNSUPPORTED_SAML_VERSION = 0;
8+
public static final int MISSING_ID = 1;
9+
public static final int WRONG_NUMBER_OF_ASSERTIONS = 2;
10+
public static final int MISSING_STATUS = 3;
11+
public static final int MISSING_STATUS_CODE = 4;
12+
public static final int STATUS_CODE_IS_NOT_SUCCESS = 5;
13+
public static final int WRONG_SIGNED_ELEMENT = 6;
14+
public static final int ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7;
15+
public static final int DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8;
16+
public static final int INVALID_SIGNED_ELEMENT = 9;
17+
public static final int DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10;
18+
public static final int UNEXPECTED_SIGNED_ELEMENTS = 11;
19+
public static final int WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12;
20+
public static final int WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13;
21+
public static final int INVALID_XML_FORMAT = 14;
22+
public static final int WRONG_INRESPONSETO = 15;
23+
public static final int NO_ENCRYPTED_ASSERTION = 16;
24+
public static final int NO_ENCRYPTED_NAMEID = 17;
25+
public static final int MISSING_CONDITIONS = 18;
26+
public static final int ASSERTION_TOO_EARLY = 19;
27+
public static final int ASSERTION_EXPIRED = 20;
28+
public static final int WRONG_NUMBER_OF_AUTHSTATEMENTS = 21;
29+
public static final int NO_ATTRIBUTESTATEMENT = 22;
30+
public static final int ENCRYPTED_ATTRIBUTES = 23;
31+
public static final int WRONG_DESTINATION = 24;
32+
public static final int EMPTY_DESTINATION = 25;
33+
public static final int WRONG_AUDIENCE = 26;
34+
public static final int ISSUER_NOT_FOUND_IN_RESPONSE = 27;
35+
public static final int ISSUER_NOT_FOUND_IN_ASSERTION = 28;
36+
public static final int WRONG_ISSUER = 29;
37+
public static final int SESSION_EXPIRED = 30;
38+
public static final int WRONG_SUBJECTCONFIRMATION = 31;
39+
public static final int NO_SIGNED_MESSAGE = 32;
40+
public static final int NO_SIGNED_ASSERTION = 33;
41+
public static final int NO_SIGNATURE_FOUND = 34;
42+
public static final int KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35;
43+
public static final int CHILDREN_NODE_NOT_FOUND_IN_KEYINFO = 36;
44+
public static final int UNSUPPORTED_RETRIEVAL_METHOD = 37;
45+
public static final int NO_NAMEID = 38;
46+
public static final int EMPTY_NAMEID = 39;
47+
public static final int SP_NAME_QUALIFIER_NAME_MISMATCH = 40;
48+
public static final int DUPLICATED_ATTRIBUTE_NAME_FOUND = 41;
49+
public static final int INVALID_SIGNATURE = 42;
50+
public static final int WRONG_NUMBER_OF_SIGNATURES = 43;
51+
public static final int RESPONSE_EXPIRED = 44;
52+
public static final int UNEXPECTED_REFERENCE = 45;
53+
public static final int NOT_SUPPORTED = 46;
54+
public static final int KEY_ALGORITHM_ERROR = 47;
55+
public static final int MISSING_ENCRYPTED_ELEMENT = 48;
56+
57+
private int errorCode;
58+
59+
public ValidationError(String message, int errorCode) {
60+
super(message);
61+
this.errorCode = errorCode;
62+
}
63+
64+
public int getErrorCode() {
65+
return errorCode;
66+
}
67+
68+
}

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

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import org.w3c.dom.Element;
2121
import org.w3c.dom.NodeList;
2222

23+
import com.onelogin.saml2.exception.ValidationError;
2324
import com.onelogin.saml2.exception.XMLEntityException;
25+
import com.onelogin.saml2.exception.SettingsException;
2426
import com.onelogin.saml2.http.HttpRequest;
2527
import com.onelogin.saml2.settings.Saml2Settings;
2628
import com.onelogin.saml2.util.Util;
@@ -94,8 +96,8 @@ public class LogoutRequest {
9496
* The NameID that will be set in the LogoutRequest.
9597
* @param sessionIndex
9698
* The SessionIndex (taken from the SAML Response in the SSO process).
97-
*
9899
* @throws XMLEntityException
100+
*
99101
*/
100102
public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId, String sessionIndex) throws XMLEntityException {
101103
this.settings = settings;
@@ -256,14 +258,14 @@ private static StringBuilder getLogoutRequestTemplate() {
256258
*
257259
* @return true if the SAML LogoutRequest is valid
258260
*
259-
* @throws XMLEntityException
261+
* @throws Exception
260262
*/
261-
public Boolean isValid() throws XMLEntityException {
263+
public Boolean isValid() throws Exception {
262264
error = null;
263265

264266
try {
265267
if (this.logoutRequestString == null || logoutRequestString.isEmpty()) {
266-
throw new Exception("SAML Logout Request is not loaded");
268+
throw new ValidationError("SAML Logout Request is not loaded", ValidationError.INVALID_XML_FORMAT);
267269
}
268270

269271
if (this.request == null) {
@@ -284,7 +286,7 @@ public Boolean isValid() throws XMLEntityException {
284286

285287
if (settings.getWantXMLValidation()) {
286288
if (!Util.validateXML(logoutRequestDocument, SchemaFactory.SAML_SCHEMA_PROTOCOL_2_0)) {
287-
throw new Exception("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd");
289+
throw new ValidationError("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd", ValidationError.INVALID_XML_FORMAT);
288290
}
289291
}
290292

@@ -293,7 +295,7 @@ public Boolean isValid() throws XMLEntityException {
293295
String notOnOrAfter = rootElement.getAttribute("NotOnOrAfter");
294296
DateTime notOnOrAfterDate = Util.parseDateTime(notOnOrAfter);
295297
if (notOnOrAfterDate.isEqualNow() || notOnOrAfterDate.isBeforeNow()) {
296-
throw new Exception("Timing issues (please check your clock settings)");
298+
throw new ValidationError("Could not validate timestamp: expired. Check system clock.", ValidationError.RESPONSE_EXPIRED);
297299
}
298300
}
299301

@@ -302,8 +304,8 @@ public Boolean isValid() throws XMLEntityException {
302304
String destinationUrl = rootElement.getAttribute("Destination");
303305
if (destinationUrl != null) {
304306
if (!destinationUrl.isEmpty() && !destinationUrl.equals(currentUrl)) {
305-
throw new Exception("The LogoutRequest was received at " + currentUrl + " instead of "
306-
+ destinationUrl);
307+
throw new ValidationError("The LogoutRequest was received at " + currentUrl + " instead of "
308+
+ destinationUrl, ValidationError.WRONG_DESTINATION);
307309
}
308310
}
309311
}
@@ -314,18 +316,18 @@ public Boolean isValid() throws XMLEntityException {
314316
// Check the issuer
315317
String issuer = getIssuer(logoutRequestDocument);
316318
if (issuer != null && (issuer.isEmpty() || !issuer.equals(settings.getIdpEntityId()))) {
317-
throw new Exception("Invalid issuer in the Logout Request");
319+
throw new ValidationError("Invalid issuer in the Logout Request", ValidationError.WRONG_ISSUER);
318320
}
319321

320322
if (settings.getWantMessagesSigned() && (signature == null || signature.isEmpty())) {
321-
throw new Exception("The Message of the Logout Request is not signed and the SP requires it");
323+
throw new ValidationError("The Message of the Logout Request is not signed and the SP requires it", ValidationError.NO_SIGNED_MESSAGE);
322324
}
323325
}
324326

325327
if (signature != null && !signature.isEmpty()) {
326328
X509Certificate cert = settings.getIdpx509cert();
327329
if (cert == null) {
328-
throw new Exception("In order to validate the sign on the Logout Request, the x509cert of the IdP is required");
330+
throw new SettingsException("In order to validate the sign on the Logout Request, the x509cert of the IdP is required", SettingsException.CERT_NOT_FOUND);
329331
}
330332

331333
String signAlg = request.getParameter("SigAlg");
@@ -343,7 +345,7 @@ public Boolean isValid() throws XMLEntityException {
343345
signedQuery += "&SigAlg=" + Util.urlEncoder(signAlg);
344346

345347
if (!Util.validateBinarySignature(signedQuery, Util.base64decoder(signature), cert, signAlg)) {
346-
throw new Exception("Signature validation failed. Logout Request rejected");
348+
throw new ValidationError("Signature validation failed. Logout Request rejected", ValidationError.INVALID_SIGNATURE);
347349
}
348350
}
349351

@@ -383,9 +385,8 @@ public static String getId(Document samlLogoutRequestDocument) {
383385
*
384386
* @return the ID of the Logout Request.
385387
*
386-
* @throws XMLEntityException
387388
*/
388-
public static String getId(String samlLogoutRequestString) throws XMLEntityException {
389+
public static String getId(String samlLogoutRequestString) {
389390
Document doc = Util.loadXML(samlLogoutRequestString);
390391
return getId(doc);
391392
}
@@ -400,17 +401,16 @@ public static String getId(String samlLogoutRequestString) throws XMLEntityExcep
400401
*
401402
* @return the Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
402403
*
403-
* @throws IllegalArgumentException
404404
* @throws Exception
405405
*/
406-
public static Map<String, String> getNameIdData(Document samlLogoutRequestDocument, PrivateKey key) throws IllegalArgumentException, Exception {
406+
public static Map<String, String> getNameIdData(Document samlLogoutRequestDocument, PrivateKey key) throws Exception {
407407
NodeList encryptedIDNodes = Util.query(samlLogoutRequestDocument, "/samlp:LogoutRequest/saml:EncryptedID");
408408
NodeList nameIdNodes;
409409
Element nameIdElem;
410410

411411
if (encryptedIDNodes.getLength() == 1) {
412412
if (key == null) {
413-
throw new IllegalArgumentException("Key is required in order to decrypt the NameID");
413+
throw new SettingsException("Key is required in order to decrypt the NameID", SettingsException.PRIVATE_KEY_NOT_FOUND);
414414
}
415415

416416
Element encryptedData = (Element) encryptedIDNodes.item(0);
@@ -428,7 +428,7 @@ public static Map<String, String> getNameIdData(Document samlLogoutRequestDocume
428428
if (nameIdNodes != null && nameIdNodes.getLength() == 1) {
429429
nameIdElem = (Element) nameIdNodes.item(0);
430430
} else {
431-
throw new Exception("No name id found in Logout Request.");
431+
throw new ValidationError("No name id found in Logout Request.", ValidationError.NO_NAMEID);
432432
}
433433

434434
Map<String, String> nameIdData = new HashMap<String, String>();
@@ -459,10 +459,9 @@ public static Map<String, String> getNameIdData(Document samlLogoutRequestDocume
459459
*
460460
* @return the Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
461461
*
462-
* @throws IllegalArgumentException if PrivateKey is not provided and the NameId is encrypted
463462
* @throws Exception
464463
*/
465-
public static Map<String, String> getNameIdData(String samlLogoutRequestString, PrivateKey key) throws IllegalArgumentException, Exception {
464+
public static Map<String, String> getNameIdData(String samlLogoutRequestString, PrivateKey key) throws Exception {
466465
Document doc = Util.loadXML(samlLogoutRequestString);
467466
return getNameIdData(doc, key);
468467
}
@@ -565,10 +564,9 @@ public static String getIssuer(Document samlLogoutRequestDocument) throws XPathE
565564
*
566565
* @return the issuer of the logout request
567566
*
568-
* @throws XMLEntityException
569567
* @throws XPathExpressionException
570568
*/
571-
public static String getIssuer(String samlLogoutRequestString) throws XMLEntityException, XPathExpressionException
569+
public static String getIssuer(String samlLogoutRequestString) throws XPathExpressionException
572570
{
573571
Document doc = Util.loadXML(samlLogoutRequestString);
574572
return getIssuer(doc);
@@ -603,10 +601,9 @@ public static List<String> getSessionIndexes(Document samlLogoutRequestDocument)
603601
* A Logout Request string.
604602
* @return the SessionIndexes
605603
*
606-
* @throws XMLEntityException
607604
* @throws XPathExpressionException
608605
*/
609-
public static List<String> getSessionIndexes(String samlLogoutRequestString) throws XMLEntityException, XPathExpressionException
606+
public static List<String> getSessionIndexes(String samlLogoutRequestString) throws XPathExpressionException
610607
{
611608
Document doc = Util.loadXML(samlLogoutRequestString);
612609
return getSessionIndexes(doc);

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import org.w3c.dom.Element;
1818
import org.w3c.dom.NodeList;
1919

20-
import com.onelogin.saml2.exception.XMLEntityException;
20+
import com.onelogin.saml2.exception.SettingsException;
21+
import com.onelogin.saml2.exception.ValidationError;
2122
import com.onelogin.saml2.http.HttpRequest;
2223
import com.onelogin.saml2.settings.Saml2Settings;
2324
import com.onelogin.saml2.util.Constants;
@@ -88,9 +89,8 @@ public class LogoutResponse {
8889
* @param request
8990
* the HttpRequest object to be processed (Contains GET and POST parameters, request URL, ...).
9091
*
91-
* @throws XMLEntityException
9292
*/
93-
public LogoutResponse(Saml2Settings settings, HttpRequest request) throws XMLEntityException {
93+
public LogoutResponse(Saml2Settings settings, HttpRequest request) {
9494
this.settings = settings;
9595
this.request = request;
9696

@@ -156,7 +156,7 @@ public Boolean isValid(String requestId) {
156156

157157
try {
158158
if (this.logoutResponseDocument == null) {
159-
throw new Exception("SAML Logout Response is not loaded");
159+
throw new ValidationError("SAML Logout Response is not loaded", ValidationError.INVALID_XML_FORMAT);
160160
}
161161

162162
if (this.currentUrl == null || this.currentUrl.isEmpty()) {
@@ -171,48 +171,48 @@ public Boolean isValid(String requestId) {
171171

172172
if (settings.getWantXMLValidation()) {
173173
if (!Util.validateXML(this.logoutResponseDocument, SchemaFactory.SAML_SCHEMA_PROTOCOL_2_0)) {
174-
throw new Exception("Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd");
174+
throw new ValidationError("Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd", ValidationError.INVALID_XML_FORMAT);
175175
}
176176
}
177177

178178
String responseInResponseTo = rootElement.hasAttribute("InResponseTo") ? rootElement.getAttribute("InResponseTo") : null;
179179
if (requestId == null && responseInResponseTo != null && settings.isRejectUnsolicitedResponsesWithInResponseTo()) {
180-
throw new Exception("The Response has an InResponseTo attribute: " + responseInResponseTo +
181-
" while no InResponseTo was expected");
180+
throw new ValidationError("The Response has an InResponseTo attribute: " + responseInResponseTo +
181+
" while no InResponseTo was expected", ValidationError.WRONG_INRESPONSETO);
182182
}
183183

184184
// Check if the InResponseTo of the Response matches the ID of the AuthNRequest (requestId) if provided
185185
if (requestId != null && !Objects.equals(responseInResponseTo, requestId)) {
186-
throw new Exception("The InResponseTo of the Logout Response: " + responseInResponseTo
187-
+ ", does not match the ID of the Logout request sent by the SP: " + requestId);
186+
throw new ValidationError("The InResponseTo of the Logout Response: " + responseInResponseTo
187+
+ ", does not match the ID of the Logout request sent by the SP: " + requestId, ValidationError.WRONG_INRESPONSETO);
188188
}
189189

190190
// Check issuer
191191
String issuer = getIssuer();
192192
if (issuer != null && !issuer.isEmpty() && !issuer.equals(settings.getIdpEntityId())) {
193-
throw new Exception("Invalid issuer in the Logout Response");
193+
throw new ValidationError("Invalid issuer in the Logout Response", ValidationError.WRONG_ISSUER);
194194
}
195195

196196
// Check destination
197197
if (rootElement.hasAttribute("Destination")) {
198198
String destinationUrl = rootElement.getAttribute("Destination");
199199
if (destinationUrl != null) {
200200
if (!destinationUrl.isEmpty() && !destinationUrl.equals(currentUrl)) {
201-
throw new Exception("The LogoutResponse was received at " + currentUrl + " instead of "
202-
+ destinationUrl);
201+
throw new ValidationError("The LogoutResponse was received at " + currentUrl + " instead of "
202+
+ destinationUrl, ValidationError.WRONG_DESTINATION);
203203
}
204204
}
205205
}
206206

207207
if (settings.getWantMessagesSigned() && (signature == null || signature.isEmpty())) {
208-
throw new Exception("The Message of the Logout Response is not signed and the SP requires it");
208+
throw new ValidationError("The Message of the Logout Response is not signed and the SP requires it", ValidationError.NO_SIGNED_MESSAGE);
209209
}
210210
}
211211

212212
if (signature != null && !signature.isEmpty()) {
213213
X509Certificate cert = settings.getIdpx509cert();
214214
if (cert == null) {
215-
throw new Exception("In order to validate the sign on the Logout Response, the x509cert of the IdP is required");
215+
throw new SettingsException("In order to validate the sign on the Logout Response, the x509cert of the IdP is required", SettingsException.CERT_NOT_FOUND);
216216
}
217217

218218
String signAlg = request.getParameter("SigAlg");
@@ -230,7 +230,7 @@ public Boolean isValid(String requestId) {
230230
signedQuery += "&SigAlg=" + Util.urlEncoder(signAlg);
231231

232232
if (!Util.validateBinarySignature(signedQuery, Util.base64decoder(signature), cert, signAlg)) {
233-
throw new Exception("Signature validation failed. Logout Response rejected");
233+
throw new ValidationError("Signature validation failed. Logout Response rejected", ValidationError.INVALID_SIGNATURE);
234234
}
235235
}
236236

0 commit comments

Comments
 (0)