//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/ogcwebservices/wass/saml/SAMLDocument.java,v 1.3 2006/06/19 12:47:09 schmitz Exp $ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2004 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/exse/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Meckenheimer Allee 176 53115 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.ogcwebservices.wass.saml; import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import org.deegree.datatypes.QualifiedName; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.xml.XMLFragment; import org.deegree.framework.xml.XMLParsingException; import org.deegree.framework.xml.XMLTools; import org.w3c.dom.Element; /** * Parser class for the SAML elements. * * Namespace: http://urn:oasis:names:tc.SAML:1.0:assertion * * The classes in this package are INCOMPLETE and UNTESTED. * * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a> * @author last edited by: $Author: schmitz $ * * @version 2.0, $Revision: 1.3 $, $Date: 2006/06/19 12:47:09 $ * * @since 2.0 */ public class SAMLDocument extends XMLFragment { private DatatypeFactory datatypeFactory; private static final long serialVersionUID = -1020160309145902779L; private static final String PRE = "saml:"; private static final ILogger LOG = LoggerFactory.getLogger(SAMLDocument.class); /** * @throws DatatypeConfigurationException */ public SAMLDocument() throws DatatypeConfigurationException { LOG.entering(); datatypeFactory = DatatypeFactory.newInstance(); LOG.exiting(); } /** * @param val * @return the decision * @throws XMLParsingException */ private String parseDecision( String val ) throws XMLParsingException { LOG.entering(); if ( val.equals( "Permit" ) || val.equals( "Deny" ) || val.equals( "Indeterminate" ) ){ LOG.exiting(); return val; } throw new XMLParsingException( "The value '" + val + "' is not allowed here." ); } private URI parseAudience( Element root ) throws XMLParsingException { return XMLTools.getNodeAsURI( root, PRE + "Audience", nsContext, null ); } private Conditions parseConditions( Element elem ) throws XMLParsingException { LOG.entering(); Element root = (Element) XMLTools.getNode( elem, PRE + "Conditions", nsContext ); ArrayList<Condition> conditions = new ArrayList<Condition>(); List audiences = XMLTools.getNodes( root, PRE + "AudienceRestrictionCondition", nsContext ); for ( Object audience : audiences ) { conditions.add( new Condition( parseAudience( (Element) audience ) ) ); } // seems strange that there can be an unlimited number of these conditions, but anyway... List caches = XMLTools.getNodes( root, PRE + "DoNotCacheCondition", nsContext ); if ( caches.size() != 0 ) conditions.add( new Condition( true ) ); String notBeforeString = XMLTools.getAttrValue( root, "NotBefore" ); Date notBefore = null; if ( notBeforeString != null ) notBefore = datatypeFactory.newXMLGregorianCalendar( notBeforeString ).toGregorianCalendar().getTime(); String notOnOrAfterString = XMLTools.getAttrValue( root, "NotOnOrAfter" ); Date notOnOrAfter = null; if ( notOnOrAfterString != null ) notOnOrAfter = datatypeFactory.newXMLGregorianCalendar( notOnOrAfterString ).toGregorianCalendar().getTime(); LOG.exiting(); return new Conditions( conditions, notBefore, notOnOrAfter ); } private Subject parseSubject( Element elem ) throws XMLParsingException { LOG.entering(); Element root = (Element) XMLTools.getNode( elem, PRE + "Subject", nsContext ); // parse name identifier, if any Element nameIdentifier = (Element) XMLTools.getNode( root, PRE + "NameIdentifier", nsContext ); String name = null; String nameQualifier = null; URI format = null; if ( nameIdentifier != null ) { name = nameIdentifier.getNodeValue(); nameQualifier = XMLTools.getAttrValue( nameIdentifier, PRE + "NameQualifier" ); format = XMLTools.getNodeAsURI( nameIdentifier, "@Format", nsContext, null ); } URI[] confirmationMethods = null; String subjectConfirmationData = null; // ds:KeyInfo must be parsed as well TODO FIXME LOOKATME Element subjectConfirmation = (Element) XMLTools.getNode( root, PRE + "SubjectConfirmation", nsContext ); if ( subjectConfirmation != null ) { confirmationMethods = XMLTools.getNodesAsURIs( subjectConfirmation, PRE + "ConfirmationMethod", nsContext ); subjectConfirmationData = XMLTools.getNodeAsString( subjectConfirmation, PRE + "SubjectConfirmation", nsContext, null ); } if ( name == null ) { if ( ( confirmationMethods == null ) || ( confirmationMethods.length == 0 ) ) throw new XMLParsingException( "Invalid content of the saml:Subject element." ); return new Subject( confirmationMethods, subjectConfirmationData ); } LOG.exiting(); return new Subject( name, nameQualifier, format, confirmationMethods, subjectConfirmationData ); } private Statement parseAuthenticationStatement( Element root ) throws XMLParsingException { LOG.entering(); Subject subject = parseSubject( root ); Element locality = (Element) XMLTools.getNode( root, PRE + "SubjectLocality", nsContext ); String ip = null; String dns = null; if ( locality != null ) { ip = XMLTools.getNodeAsString( locality, "@IPAddress", nsContext, null ); dns = XMLTools.getNodeAsString( locality, "@DNSAddress", nsContext, null ); } Element authorityBinding = (Element) XMLTools.getNode( root, PRE + "AuthorityBinding", nsContext ); QualifiedName kind = null; URI location = null; URI binding = null; if ( authorityBinding != null ) { kind = XMLTools.getRequiredNodeAsQualifiedName( authorityBinding, "@AuthorityKind", nsContext ); location = XMLTools.getRequiredNodeAsURI( authorityBinding, "@Location", nsContext ); binding = XMLTools.getRequiredNodeAsURI( authorityBinding, "@Binding", nsContext ); } URI authenticationMethod = XMLTools.getRequiredNodeAsURI( root, "@AuthenticationMethod", nsContext ); Date authenticationInstant = datatypeFactory.newXMLGregorianCalendar( XMLTools.getRequiredNodeAsString( root, "@AuthenticationInstant", nsContext ) ).toGregorianCalendar().getTime(); Statement statement = new Statement( subject, authenticationMethod, authenticationInstant ); if ( ip != null ) statement.setIP( ip ); if ( dns != null ) statement.setDNS( dns ); if ( ( kind != null ) && ( ( location == null ) || ( binding == null ) ) ) throw new XMLParsingException( "An saml:AuthorityBinding element requires all of its attributes." ); if ( kind != null ) statement.setAuthorityBinding( kind, location, binding ); LOG.exiting(); return statement; } private Statement parseAuthorizationDecisionStatement( Element root ) throws XMLParsingException { LOG.entering(); Subject subject = parseSubject( root ); List actionNodes = XMLTools.getRequiredNodes( root, PRE + "Action", nsContext ); ArrayList<String> actions = new ArrayList<String>(); ArrayList<URI> actionNamespaces = new ArrayList<URI>(); for ( Object node : actionNodes ) { actions.add( ( (Element) node ).getNodeValue() ); actionNamespaces.add( XMLTools.getNodeAsURI( (Element) node, "@Namespace", nsContext, null ) ); } Element evidence = (Element) XMLTools.getNode( root, PRE + "Evidence", nsContext ); List assertionNodes = XMLTools.getNodes( evidence, PRE + "Assertion", nsContext ); ArrayList<Assertion> assertions = new ArrayList<Assertion>(); for ( Object node : assertionNodes ) { assertions.add( parseAssertion( (Element) node ) ); } String[] assertionIDs = XMLTools.getNodesAsStrings( evidence, PRE + "AssertionIDReference", nsContext ); URI resource = XMLTools.getRequiredNodeAsURI( root, "@Resource", nsContext ); String decision = parseDecision( XMLTools.getRequiredNodeAsString( root, "@Decision", nsContext ) ); LOG.exiting(); return new Statement( subject, actions, actionNamespaces, assertions, assertionIDs, resource, decision ); } private Statement parseAttributeStatement( Element root ) throws XMLParsingException { LOG.entering(); Subject subject = parseSubject( root ); List attributes = XMLTools.getRequiredNodes( root, PRE + "Attribute", nsContext ); ArrayList<String> attributeNames = new ArrayList<String>(); ArrayList<URI> attributeNamespaces = new ArrayList<URI>(); ArrayList<String[]> attributeValues = new ArrayList<String[]>(); for ( Object node : attributes ) { attributeNames.add( XMLTools.getRequiredNodeAsString( (Element) node, "@AttributeName", nsContext ) ); attributeNamespaces.add( XMLTools.getRequiredNodeAsURI( (Element) node, "@AttributeNamespace", nsContext ) ); attributeValues.add( XMLTools.getRequiredNodesAsStrings( (Element) node, PRE + "AttributeValue", nsContext ) ); } LOG.exiting(); return new Statement( subject, attributeNames, attributeNamespaces, attributeValues ); } private Assertion parseAssertion( Element root ) throws XMLParsingException { LOG.entering(); Element node = (Element) XMLTools.getNode( root, PRE + "Conditions", nsContext ); Conditions conditions = null; if ( node != null ) conditions = parseConditions( node ); node = (Element) XMLTools.getNode( root, PRE + "Advice", nsContext ); ArrayList<Assertion> advices = new ArrayList<Assertion>(); List assertionNodes = XMLTools.getNodes( node, PRE + "Assertion", nsContext ); for ( Object elem : assertionNodes ) { advices.add( parseAssertion( (Element) elem ) ); } String[] adviceIDs = XMLTools.getNodesAsStrings( node, PRE + "AssertionIDReference", nsContext ); // other stuff is not processed ArrayList<Statement> statements = new ArrayList<Statement>(); List authenticationStatements = XMLTools.getNodes( root, PRE + "AuthenticationStatement", nsContext ); for ( Object elem : authenticationStatements ) { statements.add( parseAuthenticationStatement( (Element) elem ) ); } List authorizationDecisionStatements = XMLTools.getNodes( root, PRE + "AuthorizationDecisionStatement", nsContext ); for ( Object elem : authorizationDecisionStatements ) { statements.add( parseAuthorizationDecisionStatement( (Element) elem ) ); } List attributeStatements = XMLTools.getNodes( root, PRE + "AttributeStatement", nsContext ); for ( Object elem : attributeStatements ) { statements.add( parseAttributeStatement( (Element) elem ) ); } if ( statements.size() == 0 ) throw new XMLParsingException( "You must choose at least one Statement element." ); // parse signature from ds namespace int majorVersion = Integer.parseInt( XMLTools.getRequiredNodeAsString( root, "@MajorVersion", nsContext ) ); int minorVersion = Integer.parseInt( XMLTools.getRequiredNodeAsString( root, "@MinorVersion", nsContext ) ); String assertionID = XMLTools.getRequiredNodeAsString( root, "@AssertionID", nsContext ); String issuer = XMLTools.getRequiredNodeAsString( root, "@Issuer", nsContext ); String issueInstantString = XMLTools.getRequiredNodeAsString( root, "@IssueInstant", nsContext ); Date issueInstant = datatypeFactory.newXMLGregorianCalendar( issueInstantString ).toGregorianCalendar().getTime(); LOG.exiting(); return new Assertion( conditions, advices, adviceIDs, statements, majorVersion, minorVersion, assertionID, issuer, issueInstant ); } } /*************************************************************************************************** * Changes to this class. What the people have been up to: $Log: SAMLDocument.java,v $ * Changes to this class. What the people have been up to: Revision 1.3 2006/06/19 12:47:09 schmitz * Changes to this class. What the people have been up to: Updated the documentation, fixed the warnings and implemented logging everywhere. * Changes to this class. What the people have been up to: * Changes to this class. What the people have been up to: Revision 1.2 2006/05/29 16:24:59 bezema * Changes to this class. What the people have been up to: Rearranging the layout of the wss and creating the doservice classes. The WSService class is implemented as well * Changes to this class. What the people have been up to: * Changes to this class. What the people have been up to: Revision 1.1 2006/05/29 12:00:58 bezema * Changes to this class. What the people have been up to: Refactored the security and authentication webservices into one package WASS (Web Authentication -and- Security Services), also created a common package and a saml package which could be updated to work in the future. * Changes to this class. What the people have been up to: * Changes to this class. What the people have been up to: Revision 1.4 2006/05/22 07:51:36 bezema * Changes to this class. What the people have been up to: The Saml document representation, may be used in the future * Changes to this class. What the people have been up to: * Changes to this class. What the people have been up to: Revision 1.3 2006/05/15 15:24:46 bezema * Changes to this class. What the people have been up to: Fixed formatting. * Changes to this class. What the people have been up to: Revision 1.2 2006/05/15 12:39:31 * bezema Completed parsing the SAML Assertions spec. * * Revision 1.1 2006/05/15 09:54:16 bezema New approach to the nrw:gdi specs. Including ows_1_0 spec * and saml spec * * **************************************************************************************************/