2 * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.idp.authn;
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;
25 import javax.xml.parsers.DocumentBuilder;
26 import javax.xml.parsers.DocumentBuilderFactory;
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;
47 * A SAML 2.0 {@link LoginContext}.
49 * This class can interpret {@link RequestedAuthnContext} and act accordingly.
51 public class Saml2LoginContext extends LoginContext implements Serializable {
53 /** Serial version UID. */
54 private static final long serialVersionUID = -2518779446947534977L;
57 private final Logger log = LoggerFactory.getLogger(Saml2LoginContext.class);
59 /** Relay state from authentication request. */
60 private String relayState;
62 /** Serialized authentication request. */
63 private String serialAuthnRequest;
65 /** Unmarshalled authentication request. */
66 private transient AuthnRequest authnRequest;
69 * Creates a new instance of Saml2LoginContext.
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
75 * @throws MarshallingException thrown if the given request can not be marshalled and serialized into a string
77 public Saml2LoginContext(String relyingParty, String state, AuthnRequest request) throws MarshallingException {
80 if (relyingParty == null || request == null) {
81 throw new IllegalArgumentException("SAML 2 authentication request and relying party ID may not be null");
83 setRelyingParty(relyingParty);
85 authnRequest = request;
86 serialAuthnRequest = serializeRequest(request);
88 setForceAuthRequired(authnRequest.isForceAuthn());
89 setPassiveAuthRequired(authnRequest.isPassive());
90 getRequestedAuthenticationMethods().addAll(extractRequestedAuthenticationMethods());
94 * Gets the authentication request that started the login process.
96 * @return authentication request that started the login process
98 * @throws UnmarshallingException thrown if the serialized form on the authentication request can be unmarshalled
100 public AuthnRequest getAuthenticationRequest() throws UnmarshallingException {
101 if (authnRequest == null) {
102 authnRequest = deserializeRequest(serialAuthnRequest);
109 * Gets the relay state from the orginating authentication request.
111 * @return relay state from the orginating authentication request
113 public String getRelayState(){
118 * Gets the requested authentication context information from the authentication request.
120 * @return requested authentication context information or null
122 public RequestedAuthnContext getRequestedAuthenticationContext() {
124 AuthnRequest request = getAuthenticationRequest();
125 return request.getRequestedAuthnContext();
126 } catch (UnmarshallingException e) {
132 * Serializes an authentication request into a string.
134 * @param request the request to serialize
136 * @return the serialized form of the string
138 * @throws MarshallingException thrown if the request can not be marshalled and serialized
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();
149 * Deserailizes an authentication request from a string.
151 * @param request request to deserialize
153 * @return the request XMLObject
155 * @throws UnmarshallingException thrown if the request can no be deserialized and unmarshalled
157 protected AuthnRequest deserializeRequest(String request) throws UnmarshallingException {
158 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
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");
171 * Extracts the authentication methods requested within the request.
173 * @return requested authentication methods, or an empty list if no preference
175 protected List<String> extractRequestedAuthenticationMethods(){
176 ArrayList<String> requestedMethods = new ArrayList<String>();
178 RequestedAuthnContext authnContext = getRequestedAuthenticationContext();
179 if (authnContext == null) {
180 return requestedMethods;
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;
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());
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());
210 if(requestedMethods.contains(AuthnContext.UNSPECIFIED_AUTHN_CTX)){
211 requestedMethods.clear();
214 return requestedMethods;