Be sure not to add null values to list
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / authn / Saml2LoginContext.java
1 /*
2  * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.authn;
18
19 import java.io.Serializable;
20 import java.io.StringReader;
21 import java.io.StringWriter;
22 import java.util.ArrayList;
23 import java.util.List;
24
25 import javax.xml.parsers.DocumentBuilder;
26 import javax.xml.parsers.DocumentBuilderFactory;
27
28 import org.opensaml.Configuration;
29 import org.opensaml.saml2.core.AuthnContext;
30 import org.opensaml.saml2.core.AuthnContextClassRef;
31 import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
32 import org.opensaml.saml2.core.AuthnContextDeclRef;
33 import org.opensaml.saml2.core.AuthnRequest;
34 import org.opensaml.saml2.core.RequestedAuthnContext;
35 import org.opensaml.xml.io.Marshaller;
36 import org.opensaml.xml.io.MarshallingException;
37 import org.opensaml.xml.io.Unmarshaller;
38 import org.opensaml.xml.io.UnmarshallingException;
39 import org.opensaml.xml.util.DatatypeHelper;
40 import org.opensaml.xml.util.XMLHelper;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.w3c.dom.Element;
44 import org.xml.sax.InputSource;
45
46 /**
47  * A SAML 2.0 {@link LoginContext}.
48  * 
49  * This class can interpret {@link RequestedAuthnContext} and act accordingly.
50  */
51 public class Saml2LoginContext extends LoginContext implements Serializable {
52
53     /** Serial version UID. */
54     private static final long serialVersionUID = -2518779446947534977L;
55
56     /** Class logger. */
57     private final Logger log = LoggerFactory.getLogger(Saml2LoginContext.class);
58     
59     /** Relay state from authentication request. */
60     private String relayState;
61
62     /** Serialized authentication request. */
63     private String serialAuthnRequest;
64
65     /** Unmarshalled authentication request. */
66     private transient AuthnRequest authnRequest;
67
68     /**
69      * Creates a new instance of Saml2LoginContext.
70      * 
71      * @param relyingParty entity ID of the relying party
72      * @param state relay state from incoming authentication request
73      * @param request SAML 2.0 Authentication Request
74      * 
75      * @throws MarshallingException thrown if the given request can not be marshalled and serialized into a string
76      */
77     public Saml2LoginContext(String relyingParty, String state, AuthnRequest request) throws MarshallingException {
78         super();
79         
80         if (relyingParty == null || request == null) {
81             throw new IllegalArgumentException("SAML 2 authentication request and relying party ID may not be null");
82         }
83         setRelyingParty(relyingParty);
84         relayState = state;
85         authnRequest = request;
86         serialAuthnRequest = serializeRequest(request);
87         
88         setForceAuthRequired(authnRequest.isForceAuthn());
89         setPassiveAuthRequired(authnRequest.isPassive());
90         getRequestedAuthenticationMethods().addAll(extractRequestedAuthenticationMethods());
91     }
92
93     /**
94      * Gets the authentication request that started the login process.
95      * 
96      * @return authentication request that started the login process
97      * 
98      * @throws UnmarshallingException thrown if the serialized form on the authentication request can be unmarshalled
99      */
100     public AuthnRequest getAuthenticationRequest() throws UnmarshallingException {
101         if (authnRequest == null) {
102             authnRequest = deserializeRequest(serialAuthnRequest);
103         }
104
105         return authnRequest;
106     }
107     
108     /**
109      * Gets the relay state from the orginating authentication request.
110      * 
111      * @return relay state from the orginating authentication request
112      */
113     public String getRelayState(){
114         return relayState;
115     }
116
117     /**
118      * Gets the requested authentication context information from the authentication request.
119      * 
120      * @return requested authentication context information or null
121      */
122     public RequestedAuthnContext getRequestedAuthenticationContext() {
123         try {
124             AuthnRequest request = getAuthenticationRequest();
125             return request.getRequestedAuthnContext();
126         } catch (UnmarshallingException e) {
127             return null;
128         }
129     }
130
131     /**
132      * Serializes an authentication request into a string.
133      * 
134      * @param request the request to serialize
135      * 
136      * @return the serialized form of the string
137      * 
138      * @throws MarshallingException thrown if the request can not be marshalled and serialized
139      */
140     protected String serializeRequest(AuthnRequest request) throws MarshallingException {
141         Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(request);
142         Element requestElem = marshaller.marshall(request);
143         StringWriter writer = new StringWriter();
144         XMLHelper.writeNode(requestElem, writer);
145         return writer.toString();
146     }
147
148     /**
149      * Deserailizes an authentication request from a string.
150      * 
151      * @param request request to deserialize
152      * 
153      * @return the request XMLObject
154      * 
155      * @throws UnmarshallingException thrown if the request can no be deserialized and unmarshalled
156      */
157     protected AuthnRequest deserializeRequest(String request) throws UnmarshallingException {
158         DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
159         try {
160             DocumentBuilder docBuilder = builderFactory.newDocumentBuilder();
161             InputSource requestInput = new InputSource(new StringReader(request));
162             Element requestElem = docBuilder.parse(requestInput).getDocumentElement();
163             Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory().getUnmarshaller(requestElem);
164             return (AuthnRequest) unmarshaller.unmarshall(requestElem);
165         } catch (Exception e) {
166             throw new UnmarshallingException("Unable to read serialized authentication request");
167         }
168     }
169     
170     /**
171      * Extracts the authentication methods requested within the request.
172      * 
173      * @return requested authentication methods, or an empty list if no preference
174      */
175     protected List<String> extractRequestedAuthenticationMethods(){
176         ArrayList<String> requestedMethods = new ArrayList<String>();
177
178         RequestedAuthnContext authnContext = getRequestedAuthenticationContext();
179         if (authnContext == null) {
180             return requestedMethods;
181         }
182
183         // For the immediate future, we only support the "exact" comparator.
184         AuthnContextComparisonTypeEnumeration comparator = authnContext.getComparison();
185         if (comparator != null && comparator != AuthnContextComparisonTypeEnumeration.EXACT) {
186             log.error("Unsupported comparision operator ( " + comparator
187                     + ") in RequestedAuthnContext. Only exact comparisions are supported.");
188             return requestedMethods;
189         }
190
191         // build a list of all requested authn classes and declrefs
192         List<AuthnContextClassRef> authnClasses = authnContext.getAuthnContextClassRefs();
193         if (authnClasses != null) {
194             for (AuthnContextClassRef classRef : authnClasses) {
195                 if (classRef != null && !DatatypeHelper.isEmpty(classRef.getAuthnContextClassRef())) {
196                     requestedMethods.add(classRef.getAuthnContextClassRef());
197                 }
198             }
199         }
200
201         List<AuthnContextDeclRef> authnDeclRefs = authnContext.getAuthnContextDeclRefs();
202         if (authnDeclRefs != null) {
203             for (AuthnContextDeclRef declRef : authnDeclRefs) {
204                 if (declRef != null&& !DatatypeHelper.isEmpty(declRef.getAuthnContextDeclRef())) {
205                     requestedMethods.add(declRef.getAuthnContextDeclRef());
206                 }
207             }
208         }
209         
210         if(requestedMethods.contains(AuthnContext.UNSPECIFIED_AUTHN_CTX)){
211             requestedMethods.clear();
212         }
213
214         return requestedMethods;
215     }
216 }