Skip to content

Commit dae70f8

Browse files
committed
Add Auth.getLastMessageIssueInstant and Auth.getLastRequestIssueInstant
Each message (AuthnRequest, SamlResponse, LogoutRequest and LogoutResponse) have been enhanced to expose their issue instant. This is useful for logging purposes (i.e.: you want to track each request you generate and each response you receive, along with their issue instant), which is also required when implementing a SPID Service Provider (SPID is a SAML 2.0-based federated system used by the Italian government). I also tried to implement some tests for Auth, but I did not succeed for the "received message" scenario, because, to be coherent with the "last message id" case, the "last message issue instant" information is set on the Auth instance only if the message processing succeeds (i.e.: the processed message is valid). The test data used here seems to fail validation because of time expiration (which is reasonable), so testing these methods in the "receive" scenario would require to write test messages with valid timestamps dynamically.
1 parent 523786b commit dae70f8

File tree

11 files changed

+321
-15
lines changed

11 files changed

+321
-15
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,13 @@ public String getId()
261261
{
262262
return id;
263263
}
264+
265+
/**
266+
* Returns the issue instant of this message.
267+
*
268+
* @return a new {@link Calendar} instance carrying the issue instant of this message
269+
*/
270+
public Calendar getIssueInstant() {
271+
return issueInstant == null? null: (Calendar) issueInstant.clone();
272+
}
264273
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.security.PrivateKey;
55
import java.security.cert.X509Certificate;
66
import java.util.ArrayList;
7+
import java.util.Calendar;
78
import java.util.HashMap;
89
import java.util.List;
910
import java.util.Map;
@@ -1239,4 +1240,30 @@ protected void validateSpNameQualifier(final String spNameQualifier) throws Vali
12391240
throw new ValidationError("The SPNameQualifier value mismatch the SP entityID value.", ValidationError.SP_NAME_QUALIFIER_NAME_MISMATCH);
12401241
}
12411242
}
1243+
1244+
/**
1245+
* Returns the issue instant of this message.
1246+
*
1247+
* @return a new {@link Calendar} instance carrying the issue instant of this message
1248+
* @throws ValidationError
1249+
* if the found IssueInstant attribute is not in the expected
1250+
* UTC form of ISO-8601 format
1251+
*/
1252+
public Calendar getResponseIssueInstant() throws ValidationError {
1253+
final Element rootElement = getSAMLResponseDocument()
1254+
.getDocumentElement();
1255+
final String issueInstantString = rootElement.hasAttribute(
1256+
"IssueInstant")? rootElement.getAttribute("IssueInstant"): null;
1257+
if(issueInstantString == null)
1258+
return null;
1259+
final Calendar result = Calendar.getInstance();
1260+
try {
1261+
result.setTimeInMillis(Util.parseDateTime(issueInstantString).getMillis());
1262+
} catch (final IllegalArgumentException e) {
1263+
throw new ValidationError(
1264+
"The Response IssueInstant attribute is not in the expected UTC form of ISO-8601 format",
1265+
ValidationError.INVALID_ISSUE_INSTANT_FORMAT);
1266+
}
1267+
return result;
1268+
}
12421269
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class ValidationError extends SAMLException {
5353
public static final int NOT_SUPPORTED = 46;
5454
public static final int KEY_ALGORITHM_ERROR = 47;
5555
public static final int MISSING_ENCRYPTED_ELEMENT = 48;
56+
public static final int INVALID_ISSUE_INSTANT_FORMAT = 49;
5657

5758
private int errorCode;
5859

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

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ public LogoutRequest(Saml2Settings settings, HttpRequest request, String nameId,
144144
logoutRequestString = substitutor.replace(getLogoutRequestTemplate());
145145
} else {
146146
logoutRequestString = Util.base64decodedInflated(samlLogoutRequest);
147-
id = getId(logoutRequestString);
147+
Document doc = Util.loadXML(logoutRequestString);
148+
id = getId(doc);
149+
issueInstant = getIssueInstant(doc);
148150
}
149151
}
150152

@@ -487,14 +489,14 @@ public Boolean isValid() throws Exception {
487489
}
488490
}
489491

490-
/**
491-
* Returns the ID of the Logout Request Document.
492-
*
492+
/**
493+
* Returns the ID of the Logout Request Document.
494+
*
493495
* @param samlLogoutRequestDocument
494496
* A DOMDocument object loaded from the SAML Logout Request.
495497
*
496-
* @return the ID of the Logout Request.
497-
*/
498+
* @return the ID of the Logout Request.
499+
*/
498500
public static String getId(Document samlLogoutRequestDocument) {
499501
String id = null;
500502
try {
@@ -505,19 +507,55 @@ public static String getId(Document samlLogoutRequestDocument) {
505507
return id;
506508
}
507509

508-
/**
509-
* Returns the ID of the Logout Request String.
510-
*
511-
* @param samlLogoutRequestString
512-
* A Logout Request string.
513-
*
514-
* @return the ID of the Logout Request.
515-
*
516-
*/
510+
/**
511+
* Returns the issue instant of the Logout Request Document.
512+
*
513+
* @param samlLogoutRequestDocument
514+
* A DOMDocument object loaded from the SAML Logout Request.
515+
*
516+
* @return the issue instant of the Logout Request.
517+
*/
518+
public static Calendar getIssueInstant(Document samlLogoutRequestDocument) {
519+
Calendar issueInstant = null;
520+
try {
521+
Element rootElement = samlLogoutRequestDocument.getDocumentElement();
522+
rootElement.normalize();
523+
String issueInstantString = rootElement.hasAttribute(
524+
"IssueInstant")? rootElement.getAttribute("IssueInstant"): null;
525+
if(issueInstantString == null)
526+
return null;
527+
issueInstant = Calendar.getInstance();
528+
issueInstant.setTimeInMillis(Util.parseDateTime(issueInstantString).getMillis());
529+
} catch (Exception e) {}
530+
return issueInstant;
531+
}
532+
533+
/**
534+
* Returns the ID of the Logout Request String.
535+
*
536+
* @param samlLogoutRequestString
537+
* A Logout Request string.
538+
*
539+
* @return the ID of the Logout Request.
540+
*
541+
*/
517542
public static String getId(String samlLogoutRequestString) {
518543
Document doc = Util.loadXML(samlLogoutRequestString);
519544
return getId(doc);
520545
}
546+
547+
/**
548+
* Returns the issue instant of the Logout Request Document.
549+
*
550+
* @param samlLogoutRequestDocument
551+
* A DOMDocument object loaded from the SAML Logout Request.
552+
*
553+
* @return the issue instant of the Logout Request.
554+
*/
555+
public static Calendar getIssueInstant(String samlLogoutRequestString) {
556+
Document doc = Util.loadXML(samlLogoutRequestString);
557+
return getIssueInstant(doc);
558+
}
521559

522560
/**
523561
* Gets the NameID Data from the the Logout Request Document.
@@ -766,4 +804,13 @@ public String getId()
766804
{
767805
return id;
768806
}
807+
808+
/**
809+
* Returns the issue instant of this message.
810+
*
811+
* @return a new {@link Calendar} instance carrying the issue instant of this message
812+
*/
813+
public Calendar getIssueInstant() {
814+
return issueInstant == null? null: (Calendar) issueInstant.clone();
815+
}
769816
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,4 +475,33 @@ public String getError() {
475475
public Exception getValidationException() {
476476
return validationException;
477477
}
478+
479+
/**
480+
* Returns the issue instant of this message.
481+
*
482+
* @return a new {@link Calendar} instance carrying the issue instant of this message
483+
* @throws ValidationError
484+
* if this logout response was received and parsed and the found IssueInstant
485+
* attribute is not in the expected UTC form of ISO-8601 format
486+
*/
487+
public Calendar getIssueInstant() throws ValidationError {
488+
if(logoutResponseDocument != null) {
489+
final Element rootElement = logoutResponseDocument
490+
.getDocumentElement();
491+
final String issueInstantString = rootElement.hasAttribute(
492+
"IssueInstant")? rootElement.getAttribute("IssueInstant"): null;
493+
if(issueInstantString == null)
494+
return null;
495+
final Calendar result = Calendar.getInstance();
496+
try {
497+
result.setTimeInMillis(Util.parseDateTime(issueInstantString).getMillis());
498+
} catch (final IllegalArgumentException e) {
499+
throw new ValidationError(
500+
"The Response IssueInstant attribute is not in the expected UTC form of ISO-8601 format",
501+
ValidationError.INVALID_ISSUE_INSTANT_FORMAT);
502+
}
503+
return result;
504+
} else
505+
return issueInstant == null? null: (Calendar) issueInstant.clone();
506+
}
478507
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import static org.hamcrest.CoreMatchers.not;
55
import static org.junit.Assert.assertEquals;
66
import static org.junit.Assert.assertNotEquals;
7+
import static org.junit.Assert.assertNotNull;
78
import static org.junit.Assert.assertThat;
9+
import static org.junit.Assert.assertTrue;
810

911
import java.util.ArrayList;
12+
import java.util.Calendar;
1013
import java.util.List;
1114

15+
import org.junit.Assert;
1216
import org.junit.Test;
1317

1418
import com.onelogin.saml2.authn.AuthnRequest;
@@ -327,6 +331,32 @@ public void testGetId() throws Exception
327331
assertThat(authnRequestStr, containsString("ID=\"" + authnRequest.getId() + "\""));
328332
}
329333

334+
/**
335+
* Tests the getId method of AuthnRequest
336+
*
337+
* @throws Exception
338+
*
339+
* @see com.onelogin.saml2.authn.AuthnRequest.getId
340+
*/
341+
@Test
342+
public void testGetIssueInstant() throws Exception
343+
{
344+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min.properties").build();
345+
346+
final long start = System.currentTimeMillis();
347+
AuthnRequest authnRequest = new AuthnRequest(settings);
348+
final long end = System.currentTimeMillis();
349+
final String authnRequestStr = Util.base64decodedInflated(authnRequest.getEncodedAuthnRequest());
350+
351+
final Calendar issueInstant = authnRequest.getIssueInstant();
352+
assertNotNull(issueInstant);
353+
final long millis = issueInstant.getTimeInMillis();
354+
assertTrue(millis >= start && millis <= end);
355+
356+
assertThat(authnRequestStr, containsString("<samlp:AuthnRequest"));
357+
assertThat(authnRequestStr, containsString("IssueInstant=\"" + Util.formatDateTime(millis) + "\""));
358+
}
359+
330360
/**
331361
* Tests the AuthnRequest Constructor
332362
* The creation of a deflated SAML Request with and without Destination

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import java.io.IOException;
3131
import java.util.ArrayList;
32+
import java.util.Calendar;
3233
import java.util.HashMap;
3334
import java.util.List;
3435
import java.util.Map;
@@ -478,6 +479,29 @@ public void testGetNameIdData() throws Exception {
478479
assertTrue(samlResponse.getNameIdData().isEmpty());
479480
}
480481

482+
/**
483+
* Tests the getIssueInstant method of SamlResponse
484+
*
485+
* @throws Exception
486+
*
487+
* @see com.onelogin.saml2.authn.SamlResponse#getResponseIssueInstant()
488+
*/
489+
@Test
490+
public void testGetIssueInstant() throws Exception {
491+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.my.properties").build();
492+
String samlResponseEncoded = Util.getFileAsString("data/responses/response1.xml.base64");
493+
SamlResponse samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
494+
assertEquals("2010-11-18T21:57:37Z", Util.formatDateTime(samlResponse.getResponseIssueInstant().getTimeInMillis()));
495+
496+
samlResponseEncoded = Util.getFileAsString("data/responses/response_encrypted_nameid.xml.base64");
497+
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
498+
assertEquals("2014-03-09T12:23:37Z", Util.formatDateTime(samlResponse.getResponseIssueInstant().getTimeInMillis()));
499+
500+
samlResponseEncoded = Util.getFileAsString("data/responses/valid_encrypted_assertion.xml.base64");
501+
samlResponse = new SamlResponse(settings, newHttpRequest(samlResponseEncoded));
502+
assertEquals("2014-03-29T12:01:57Z", Util.formatDateTime(samlResponse.getResponseIssueInstant().getTimeInMillis()));
503+
}
504+
481505
/**
482506
* Tests the decryptAssertion method of SamlResponse
483507
* Case: EncryptedAssertion with an encryptedData element with a KeyInfo

core/src/test/java/com/onelogin/saml2/test/logout/LogoutRequestTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import static org.junit.Assert.assertNull;
88
import static org.junit.Assert.assertFalse;
99
import static org.junit.Assert.assertNotEquals;
10+
import static org.junit.Assert.assertNotNull;
1011
import static org.junit.Assert.assertTrue;
1112

1213
import java.util.ArrayList;
14+
import java.util.Calendar;
1315
import java.util.List;
1416
import java.io.IOException;
1517
import java.net.URISyntaxException;
@@ -24,6 +26,7 @@
2426
import org.junit.rules.ExpectedException;
2527

2628
import com.onelogin.saml2.logout.LogoutRequest;
29+
import com.onelogin.saml2.logout.LogoutResponse;
2730
import com.onelogin.saml2.http.HttpRequest;
2831
import com.onelogin.saml2.exception.XMLEntityException;
2932
import com.onelogin.saml2.exception.Error;
@@ -309,6 +312,56 @@ public void testGetId() throws Exception {
309312
assertNull(LogoutRequest.getId(""));
310313
}
311314

315+
/**
316+
* Tests the getIssueInstant method of LogoutRequest
317+
*
318+
* @throws Exception
319+
*
320+
* @see com.onelogin.saml2.logout.LogoutRequest#getIssueInstant(String)
321+
*/
322+
@Test
323+
public void testGetIssueInstant() throws Exception {
324+
String samlRequest = Util.getFileAsString("data/logout_requests/logout_request.xml");
325+
Calendar issueInstant = LogoutRequest.getIssueInstant(samlRequest);
326+
String expectedIssueInstant = "2013-12-10T04:39:31Z";
327+
assertEquals(expectedIssueInstant, Util.formatDateTime(issueInstant.getTimeInMillis()));
328+
329+
Document samlRequestDoc = Util.loadXML(samlRequest);
330+
issueInstant = LogoutRequest.getIssueInstant(samlRequestDoc);
331+
assertEquals(expectedIssueInstant, Util.formatDateTime(issueInstant.getTimeInMillis()));
332+
333+
assertNull(LogoutRequest.getIssueInstant(""));
334+
335+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min.properties").build();
336+
String samlRequestEncoded = Util.getFileAsString("data/logout_requests/logout_request.xml.base64");
337+
final String requestURL = "http://stuff.com/endpoints/endpoints/sls.php";
338+
HttpRequest httpRequest = newHttpRequest(requestURL, samlRequestEncoded);
339+
340+
issueInstant = new LogoutRequest(settings, httpRequest).getIssueInstant();
341+
assertEquals(expectedIssueInstant, Util.formatDateTime(issueInstant.getTimeInMillis()));
342+
}
343+
344+
/**
345+
* Tests the getIssueInstant method of LogoutRequest
346+
* <p>
347+
* Case: LogoutRequest message built by the caller
348+
*
349+
* @throws Exception
350+
*
351+
* @see com.onelogin.saml2.logout.LogoutRequest#getIssueInstant(String)
352+
*/
353+
@Test
354+
public void testGetIssueInstantBuiltMessage() throws Exception {
355+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min.properties").build();
356+
long start = System.currentTimeMillis();
357+
LogoutRequest logoutRequest = new LogoutRequest(settings, null);
358+
long end = System.currentTimeMillis();
359+
Calendar issueInstant = logoutRequest.getIssueInstant();
360+
assertNotNull(issueInstant);
361+
long millis = issueInstant.getTimeInMillis();
362+
assertTrue(millis >= start && millis <= end);
363+
}
364+
312365
/**
313366
* Tests the getNameIdData method of LogoutRequest
314367
* Case: Not able to get the NameIdData due no private key to decrypt

0 commit comments

Comments
 (0)