From 080405b4c03b74814db1834d2bdd901d4abd7248 Mon Sep 17 00:00:00 2001 From: tsschmidt Date: Wed, 23 Jan 2019 08:26:35 -0800 Subject: [PATCH 1/3] Extract inlined CAS attributes in protocol 3 Ticket Validator --- .../Cas30ServiceTicketValidator.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java index 236ea6e..160c0ab 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java @@ -18,6 +18,14 @@ */ package org.jasig.cas.client.validation; +import org.jasig.cas.client.util.XmlUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.HashMap; +import java.util.Map; + /** * Service tickets validation service for the CAS protocol v3. * @@ -34,4 +42,31 @@ public class Cas30ServiceTicketValidator extends Cas20ServiceTicketValidator { protected String getUrlSuffix() { return "p3/serviceValidate"; } + + /** + * Custom attribute extractor that will account for inlined CAS attributes. Useful when CAS is acting as + * as SAML 2 IdP and returns SAML attributes with names that contains namespaces. + * + * @param xml the XML to parse. + * @return - Map of attributes + */ + @Override + protected Map extractCustomAttributes(String xml) { + final Document document = XmlUtils.newDocument(xml); + final HashMap attributes = new HashMap(); + + NodeList attributeList = document.getElementsByTagName("cas:attribute"); + + for (int i = 0; i < attributeList.getLength(); i++) { + final Node attribute = attributeList.item(i); + if (attribute.getAttributes().getLength() > 0) { + attributes.put(attribute.getAttributes().getNamedItem("name").getNodeValue(), + attribute.getAttributes().getNamedItem("value").getNodeValue()); + } else { + attributes.put(attribute.getLocalName(), attribute.getNodeValue()); + } + } + + return attributes; + } } From 18d981efaa46e05bdb33c3c27ddc82c221892afd Mon Sep 17 00:00:00 2001 From: tsschmidt Date: Wed, 23 Jan 2019 10:24:01 -0800 Subject: [PATCH 2/3] Refactor --- .../validation/Cas30ServiceTicketValidator.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java index 160c0ab..023c5f3 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java @@ -20,6 +20,7 @@ package org.jasig.cas.client.validation; import org.jasig.cas.client.util.XmlUtils; import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -58,12 +59,13 @@ public class Cas30ServiceTicketValidator extends Cas20ServiceTicketValidator { NodeList attributeList = document.getElementsByTagName("cas:attribute"); for (int i = 0; i < attributeList.getLength(); i++) { - final Node attribute = attributeList.item(i); - if (attribute.getAttributes().getLength() > 0) { - attributes.put(attribute.getAttributes().getNamedItem("name").getNodeValue(), - attribute.getAttributes().getNamedItem("value").getNodeValue()); + final Node casAttributeNode = attributeList.item(i); + final NamedNodeMap casAttributes = casAttributeNode.getAttributes(); + if (casAttributes.getLength() > 0) { + attributes.put(casAttributes.getNamedItem("name").getNodeValue(), + casAttributes.getNamedItem("value").getNodeValue()); } else { - attributes.put(attribute.getLocalName(), attribute.getNodeValue()); + attributes.put(casAttributeNode.getLocalName(), casAttributeNode.getNodeValue()); } } From f06895bc1843d9d3e47c4e8e56e120500f5fc60f Mon Sep 17 00:00:00 2001 From: tsschmidt Date: Thu, 24 Jan 2019 16:31:54 -0800 Subject: [PATCH 3/3] Refactored and added unit test. --- .../Cas30ServiceTicketValidator.java | 33 +++- .../Cas20ServiceTicketValidatorTests.java | 2 + .../Cas30ServiceTicketValidatorTests.java | 187 ++++++++++++++++++ 3 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidatorTests.java diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java index 023c5f3..5780cbf 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidator.java @@ -24,7 +24,10 @@ import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; /** @@ -54,21 +57,35 @@ public class Cas30ServiceTicketValidator extends Cas20ServiceTicketValidator { @Override protected Map extractCustomAttributes(String xml) { final Document document = XmlUtils.newDocument(xml); - final HashMap attributes = new HashMap(); - NodeList attributeList = document.getElementsByTagName("cas:attribute"); + // Check if attributes are inlined. If not return default super method results + final NodeList attributeList = document.getElementsByTagName("cas:attribute"); + if (attributeList.getLength() == 0) { + return super.extractCustomAttributes(xml); + } + + final HashMap attributes = new HashMap(); for (int i = 0; i < attributeList.getLength(); i++) { final Node casAttributeNode = attributeList.item(i); - final NamedNodeMap casAttributes = casAttributeNode.getAttributes(); - if (casAttributes.getLength() > 0) { - attributes.put(casAttributes.getNamedItem("name").getNodeValue(), - casAttributes.getNamedItem("value").getNodeValue()); + final NamedNodeMap nodeAttributes = casAttributeNode.getAttributes(); + final String name = nodeAttributes.getNamedItem("name").getNodeValue(); + final String value = nodeAttributes.getNamedItem("value").getTextContent(); + final Object mapValue = attributes.get(name); + if (mapValue != null) { + if (mapValue instanceof List) { + ((List) mapValue).add(value); + } else { + final LinkedList list = new LinkedList(); + list.add(mapValue); + list.add(value); + attributes.put(name, list); + } } else { - attributes.put(casAttributeNode.getLocalName(), casAttributeNode.getNodeValue()); + attributes.put(name, value); } } - return attributes; } + } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java index 14d3f82..f573888 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java @@ -146,6 +146,8 @@ public final class Cas20ServiceTicketValidatorTests extends AbstractTicketValida //assertEquals(PGT, assertion.getProxyGrantingTicketId()); } + + @Test public void testInvalidResponse() throws Exception { final String RESPONSE = ""; diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidatorTests.java new file mode 100644 index 0000000..748899d --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas30ServiceTicketValidatorTests.java @@ -0,0 +1,187 @@ +/* + * Licensed to Jasig under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Jasig licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.PublicTestHttpServer; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl; +import org.jasig.cas.client.proxy.ProxyRetriever; +import org.junit.Before; +import org.junit.Test; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Test cases for the {@link Cas20ServiceTicketValidator}. + * + * @author Scott Battaglia + * @version $Revision: 11737 $ $Date: 2007-10-03 09:14:02 -0400 (Tue, 03 Oct 2007) $ + * @since 3.0 + */ +public final class Cas30ServiceTicketValidatorTests extends AbstractTicketValidatorTests { + + private static final PublicTestHttpServer server = PublicTestHttpServer.instance(8088); + + private Cas30ServiceTicketValidator ticketValidator; + + private ProxyGrantingTicketStorage proxyGrantingTicketStorage; + + public Cas30ServiceTicketValidatorTests() { + super(); + } + + /*@AfterClass + public static void classCleanUp() { + server.shutdown(); + } */ + + @Before + public void setUp() throws Exception { + this.proxyGrantingTicketStorage = getProxyGrantingTicketStorage(); + this.ticketValidator = new Cas30ServiceTicketValidator(CONST_CAS_SERVER_URL_PREFIX + "8088"); + this.ticketValidator.setProxyCallbackUrl("test"); + this.ticketValidator.setProxyGrantingTicketStorage(getProxyGrantingTicketStorage()); + this.ticketValidator.setProxyRetriever(getProxyRetriever()); + this.ticketValidator.setRenew(true); + } + + private ProxyGrantingTicketStorage getProxyGrantingTicketStorage() { + return new ProxyGrantingTicketStorageImpl(); + } + + private ProxyRetriever getProxyRetriever() { + return new ProxyRetriever() { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = 1L; + + public String getProxyTicketIdFor(String proxyGrantingTicketId, String targetService) { + return "test"; + } + }; + } + + @Test + public void testNoResponse() throws UnsupportedEncodingException { + final String RESPONSE = "Ticket ST-1856339-aA5Yuvrxzpv8Tau1cYQ7 not recognized"; + server.content = RESPONSE.getBytes(server.encoding); + try { + this.ticketValidator.validate("test", "test"); + fail("ValidationException expected due to 'no' response"); + } catch (final TicketValidationException e) { + // expected + } + } + + @Test + public void testYesResponseButNoPgt() throws TicketValidationException, UnsupportedEncodingException { + final String USERNAME = "username"; + final String RESPONSE = "" + + USERNAME + ""; + server.content = RESPONSE.getBytes(server.encoding); + + final Assertion assertion = this.ticketValidator.validate("test", "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); + + } + + @Test + public void testYesResponseWithPgt() throws TicketValidationException, UnsupportedEncodingException { + final String USERNAME = "username"; + final String PGTIOU = "testPgtIou"; + final String PGT = "test"; + final String RESPONSE = "" + + USERNAME + + "" + + PGTIOU + + ""; + + server.content = RESPONSE.getBytes(server.encoding); + this.proxyGrantingTicketStorage.save(PGTIOU, PGT); + + final Assertion assertion = this.ticketValidator.validate("test", "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); + // assertEquals(PGT, assertion.getProxyGrantingTicketId()); + } + + @Test + public void testGetAttributes() throws TicketValidationException, UnsupportedEncodingException { + final String USERNAME = "username"; + final String PGTIOU = "testPgtIou"; + final String RESPONSE = "" + + USERNAME + + "" + + PGTIOU + + "testidtest1\n\ntestvalue1value2"; + + server.content = RESPONSE.getBytes(server.encoding); + final Assertion assertion = this.ticketValidator.validate("test", "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); + assertEquals("test", assertion.getPrincipal().getAttributes().get("password")); + assertEquals("id", assertion.getPrincipal().getAttributes().get("eduPersonId")); + assertEquals("test1\n\ntest", assertion.getPrincipal().getAttributes().get("longAttribute")); + try { + List multivalued = (List) assertion.getPrincipal().getAttributes().get("multivaluedAttribute"); + assertArrayEquals(new String[] { "value1", "value2" }, multivalued.toArray()); + } catch (Exception e) { + fail("'multivaluedAttribute' attribute expected as List object."); + } + //assertEquals(PGT, assertion.getProxyGrantingTicketId()); + } + + @Test + public void testGetInlinedAttributes() throws TicketValidationException, UnsupportedEncodingException { + final String USERNAME = "username"; + final String PGTIOU = "testPgtIou"; + final String RESPONSE = "" + + USERNAME + + "" + + PGTIOU + + ""; + + server.content = RESPONSE.getBytes(server.encoding); + final Assertion assertion = this.ticketValidator.validate("test", "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); + assertEquals("test", assertion.getPrincipal().getAttributes().get("password")); + assertEquals("id", assertion.getPrincipal().getAttributes().get("eduPersonId")); + assertEquals("test1\n\ntest", assertion.getPrincipal().getAttributes().get("longAttribute")); + try { + List multivalued = (List) assertion.getPrincipal().getAttributes().get("multivaluedAttribute"); + assertArrayEquals(new String[] { "value1", "value2" }, multivalued.toArray()); + } catch (Exception e) { + fail("'multivaluedAttribute' attribute expected as List object."); + } + //assertEquals(PGT, assertion.getProxyGrantingTicketId()); + } + + @Test + public void testInvalidResponse() throws Exception { + final String RESPONSE = ""; + server.content = RESPONSE.getBytes(server.encoding); + try { + this.ticketValidator.validate("test", "test"); + fail("ValidationException expected due to invalid response."); + } catch (final TicketValidationException e) { + // expected + } + } +}