001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wass/saml/SAMLDocument.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Meckenheimer Allee 176
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    
044    package org.deegree.ogcwebservices.wass.saml;
045    
046    import java.net.URI;
047    import java.util.ArrayList;
048    import java.util.Date;
049    import java.util.List;
050    
051    import javax.xml.datatype.DatatypeConfigurationException;
052    import javax.xml.datatype.DatatypeFactory;
053    
054    import org.deegree.datatypes.QualifiedName;
055    import org.deegree.framework.xml.XMLFragment;
056    import org.deegree.framework.xml.XMLParsingException;
057    import org.deegree.framework.xml.XMLTools;
058    import org.w3c.dom.Element;
059    import org.w3c.dom.Node;
060    
061    /**
062     * Parser class for the SAML elements.
063     * 
064     * Namespace: http://urn:oasis:names:tc.SAML:1.0:assertion
065     * 
066     * The classes in this package are INCOMPLETE and UNTESTED.
067     * 
068     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
069     * @author last edited by: $Author: aschmitz $
070     * 
071     * @version 2.0, $Revision: 6888 $, $Date: 2007-05-08 13:32:27 +0200 (Di, 08 Mai 2007) $
072     * 
073     * @since 2.0
074     */
075    public class SAMLDocument extends XMLFragment {
076    
077        private DatatypeFactory datatypeFactory;
078    
079        private static final long serialVersionUID = -1020160309145902779L;
080    
081        private static final String PRE = "saml:";
082    
083        /**
084         * @throws DatatypeConfigurationException
085         */
086        public SAMLDocument() throws DatatypeConfigurationException {
087            
088            datatypeFactory = DatatypeFactory.newInstance();
089            
090        }
091    
092        /**
093         * @param val
094         * @return the decision
095         * @throws XMLParsingException
096         */
097        private String parseDecision( String val )
098                                throws XMLParsingException {
099            
100            if ( val.equals( "Permit" ) || val.equals( "Deny" ) || val.equals( "Indeterminate" ) ) {
101                
102                return val;
103            }
104            throw new XMLParsingException( "The value '" + val + "' is not allowed here." );
105        }
106    
107        private URI parseAudience( Element root )
108                                throws XMLParsingException {
109            return XMLTools.getNodeAsURI( root, PRE + "Audience", nsContext, null );
110        }
111    
112        private Conditions parseConditions( Element elem )
113                                throws XMLParsingException {
114            Element root = (Element) XMLTools.getNode( elem, PRE + "Conditions", nsContext );
115    
116            ArrayList<Condition> conditions = new ArrayList<Condition>();
117    
118            List<Node> audiences = XMLTools.getNodes( root, PRE + "AudienceRestrictionCondition", nsContext );
119            for ( Object audience : audiences ) {
120                conditions.add( new Condition( parseAudience( (Element) audience ) ) );
121            }
122    
123            // seems strange that there can be an unlimited number of these conditions, but anyway...
124            List<Node> caches = XMLTools.getNodes( root, PRE + "DoNotCacheCondition", nsContext );
125            if ( caches.size() != 0 )
126                conditions.add( new Condition( true ) );
127    
128            String notBeforeString = XMLTools.getAttrValue( root, null, "NotBefore", null );
129            Date notBefore = null;
130            if ( notBeforeString != null ) {
131                notBefore = datatypeFactory.newXMLGregorianCalendar( notBeforeString ).toGregorianCalendar().getTime();
132            }
133            String notOnOrAfterString = XMLTools.getAttrValue( root, null, "NotOnOrAfter", null );
134            Date notOnOrAfter = null;
135            if ( notOnOrAfterString != null ) {
136                notOnOrAfter = datatypeFactory.newXMLGregorianCalendar( notOnOrAfterString ).toGregorianCalendar().getTime();
137            }
138            
139            return new Conditions( conditions, notBefore, notOnOrAfter );
140        }
141    
142        private Subject parseSubject( Element elem )
143                                throws XMLParsingException {
144            
145            Element root = (Element) XMLTools.getNode( elem, PRE + "Subject", nsContext );
146    
147            // parse name identifier, if any
148            Element nameIdentifier = (Element) XMLTools.getNode( root, PRE + "NameIdentifier", nsContext );
149            String name = null;
150            String nameQualifier = null;
151            URI format = null;
152            if ( nameIdentifier != null ) {
153                name = nameIdentifier.getNodeValue();
154                nameQualifier = XMLTools.getAttrValue( nameIdentifier, null, PRE + "NameQualifier", null );
155                format = XMLTools.getNodeAsURI( nameIdentifier, "@Format", nsContext, null );
156            }
157    
158            URI[] confirmationMethods = null;
159            String subjectConfirmationData = null;
160            // ds:KeyInfo must be parsed as well TODO FIXME LOOKATME
161    
162            Element subjectConfirmation = (Element) XMLTools.getNode( root, PRE + "SubjectConfirmation", nsContext );
163    
164            if ( subjectConfirmation != null ) {
165                confirmationMethods = XMLTools.getNodesAsURIs( subjectConfirmation, PRE + "ConfirmationMethod", nsContext );
166                subjectConfirmationData = XMLTools.getNodeAsString( subjectConfirmation, PRE + "SubjectConfirmation",
167                                                                    nsContext, null );
168            }
169    
170            if ( name == null ) {
171                if ( ( confirmationMethods == null ) || ( confirmationMethods.length == 0 ) )
172                    throw new XMLParsingException( "Invalid content of the saml:Subject element." );
173    
174                return new Subject( confirmationMethods, subjectConfirmationData );
175            }
176            
177            return new Subject( name, nameQualifier, format, confirmationMethods, subjectConfirmationData );
178        }
179    
180        private Statement parseAuthenticationStatement( Element root )
181                                throws XMLParsingException {
182            
183            Subject subject = parseSubject( root );
184    
185            Element locality = (Element) XMLTools.getNode( root, PRE + "SubjectLocality", nsContext );
186            String ip = null;
187            String dns = null;
188            if ( locality != null ) {
189                ip = XMLTools.getNodeAsString( locality, "@IPAddress", nsContext, null );
190                dns = XMLTools.getNodeAsString( locality, "@DNSAddress", nsContext, null );
191            }
192    
193            Element authorityBinding = (Element) XMLTools.getNode( root, PRE + "AuthorityBinding", nsContext );
194            QualifiedName kind = null;
195            URI location = null;
196            URI binding = null;
197            if ( authorityBinding != null ) {
198                kind = XMLTools.getRequiredNodeAsQualifiedName( authorityBinding, "@AuthorityKind", nsContext );
199                location = XMLTools.getRequiredNodeAsURI( authorityBinding, "@Location", nsContext );
200                binding = XMLTools.getRequiredNodeAsURI( authorityBinding, "@Binding", nsContext );
201            }
202    
203            URI authenticationMethod = XMLTools.getRequiredNodeAsURI( root, "@AuthenticationMethod", nsContext );
204            Date authenticationInstant = datatypeFactory.newXMLGregorianCalendar(
205                                                                                  XMLTools.getRequiredNodeAsString(
206                                                                                                                    root,
207                                                                                                                    "@AuthenticationInstant",
208                                                                                                                    nsContext ) ).toGregorianCalendar().getTime();
209    
210            Statement statement = new Statement( subject, authenticationMethod, authenticationInstant );
211            if ( ip != null )
212                statement.setIP( ip );
213            if ( dns != null )
214                statement.setDNS( dns );
215    
216            if ( ( kind != null ) && ( ( location == null ) || ( binding == null ) ) )
217                throw new XMLParsingException( "An saml:AuthorityBinding element requires all of its attributes." );
218            if ( kind != null )
219                statement.setAuthorityBinding( kind, location, binding );
220    
221            
222            return statement;
223        }
224    
225        private Statement parseAuthorizationDecisionStatement( Element root )
226                                throws XMLParsingException {
227            
228            Subject subject = parseSubject( root );
229    
230            List<Node> actionNodes = XMLTools.getRequiredNodes( root, PRE + "Action", nsContext );
231            ArrayList<String> actions = new ArrayList<String>();
232            ArrayList<URI> actionNamespaces = new ArrayList<URI>();
233    
234            for ( Object node : actionNodes ) {
235                actions.add( ( (Element) node ).getNodeValue() );
236                actionNamespaces.add( XMLTools.getNodeAsURI( (Element) node, "@Namespace", nsContext, null ) );
237            }
238    
239            Element evidence = (Element) XMLTools.getNode( root, PRE + "Evidence", nsContext );
240            List<Node> assertionNodes = XMLTools.getNodes( evidence, PRE + "Assertion", nsContext );
241            ArrayList<Assertion> assertions = new ArrayList<Assertion>();
242            for ( Object node : assertionNodes ) {
243                assertions.add( parseAssertion( (Element) node ) );
244            }
245            String[] assertionIDs = XMLTools.getNodesAsStrings( evidence, PRE + "AssertionIDReference", nsContext );
246    
247            URI resource = XMLTools.getRequiredNodeAsURI( root, "@Resource", nsContext );
248            String decision = parseDecision( XMLTools.getRequiredNodeAsString( root, "@Decision", nsContext ) );
249    
250            
251            return new Statement( subject, actions, actionNamespaces, assertions, assertionIDs, resource, decision );
252        }
253    
254        private Statement parseAttributeStatement( Element root )
255                                throws XMLParsingException {
256            
257            Subject subject = parseSubject( root );
258    
259            List<Element> attributes = XMLTools.getRequiredElements( root, PRE + "Attribute", nsContext );
260    
261            ArrayList<String> attributeNames = new ArrayList<String>();
262            ArrayList<URI> attributeNamespaces = new ArrayList<URI>();
263            ArrayList<String[]> attributeValues = new ArrayList<String[]>();
264    
265            for ( Element node : attributes ) {
266                attributeNames.add( XMLTools.getRequiredNodeAsString( node, "@AttributeName", nsContext ) );
267                attributeNamespaces.add( XMLTools.getRequiredNodeAsURI( node, "@AttributeNamespace", nsContext ) );
268                attributeValues.add( XMLTools.getRequiredNodesAsStrings( node, PRE + "AttributeValue", nsContext ) );
269            }
270    
271            
272            return new Statement( subject, attributeNames, attributeNamespaces, attributeValues );
273        }
274    
275        private Assertion parseAssertion( Element root )
276                                throws XMLParsingException {
277            
278            Element node = (Element) XMLTools.getNode( root, PRE + "Conditions", nsContext );
279            Conditions conditions = null;
280            if ( node != null )
281                conditions = parseConditions( node );
282    
283            node = (Element) XMLTools.getNode( root, PRE + "Advice", nsContext );
284    
285            ArrayList<Assertion> advices = new ArrayList<Assertion>();
286            List<Node> assertionNodes = XMLTools.getNodes( node, PRE + "Assertion", nsContext );
287            for ( Object elem : assertionNodes ) {
288                advices.add( parseAssertion( (Element) elem ) );
289            }
290            String[] adviceIDs = XMLTools.getNodesAsStrings( node, PRE + "AssertionIDReference", nsContext );
291    
292            // other stuff is not processed
293    
294            ArrayList<Statement> statements = new ArrayList<Statement>();
295    
296            List<Node> authenticationStatements = XMLTools.getNodes( root, PRE + "AuthenticationStatement", nsContext );
297            for ( Object elem : authenticationStatements ) {
298                statements.add( parseAuthenticationStatement( (Element) elem ) );
299            }
300    
301            List<Node> authorizationDecisionStatements = XMLTools.getNodes( root, PRE + "AuthorizationDecisionStatement",
302                                                                      nsContext );
303            for ( Object elem : authorizationDecisionStatements ) {
304                statements.add( parseAuthorizationDecisionStatement( (Element) elem ) );
305            }
306    
307            List<Node> attributeStatements = XMLTools.getNodes( root, PRE + "AttributeStatement", nsContext );
308            for ( Object elem : attributeStatements ) {
309                statements.add( parseAttributeStatement( (Element) elem ) );
310            }
311    
312            if ( statements.size() == 0 )
313                throw new XMLParsingException( "You must choose at least one Statement element." );
314    
315            // parse signature from ds namespace
316            int majorVersion = Integer.parseInt( XMLTools.getRequiredNodeAsString( root, "@MajorVersion", nsContext ) );
317            int minorVersion = Integer.parseInt( XMLTools.getRequiredNodeAsString( root, "@MinorVersion", nsContext ) );
318            String assertionID = XMLTools.getRequiredNodeAsString( root, "@AssertionID", nsContext );
319            String issuer = XMLTools.getRequiredNodeAsString( root, "@Issuer", nsContext );
320            String issueInstantString = XMLTools.getRequiredNodeAsString( root, "@IssueInstant", nsContext );
321            Date issueInstant = datatypeFactory.newXMLGregorianCalendar( issueInstantString ).toGregorianCalendar().getTime();
322    
323            
324            return new Assertion( conditions, advices, adviceIDs, statements, majorVersion, minorVersion, assertionID,
325                                  issuer, issueInstant );
326        }
327    
328    }