595c10fb23b69c27dbd8ec0a18208b1c4d266b01
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / AbstractSAMLProfileHandler.java
1 /*
2  * Copyright [2007] [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.profile;
18
19 import java.util.List;
20 import java.util.Map;
21
22 import javax.servlet.http.HttpServletRequest;
23
24 import org.opensaml.common.IdentifierGenerator;
25 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
26 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
27 import org.opensaml.saml2.metadata.Endpoint;
28 import org.opensaml.saml2.metadata.provider.MetadataProvider;
29 import org.opensaml.ws.message.encoder.MessageEncodingException;
30 import org.opensaml.ws.security.SecurityPolicyResolver;
31 import org.opensaml.ws.transport.InTransport;
32 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
33 import org.opensaml.xml.security.credential.Credential;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
38 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
39 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
40 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
41 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
42 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
43 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
44 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
45 import edu.internet2.middleware.shibboleth.idp.session.Session;
46
47 /**
48  * Base class for SAML profile handlers.
49  */
50 public abstract class AbstractSAMLProfileHandler extends
51         AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
52
53     /** SAML message audit log. */
54     private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
55
56     /** Class logger. */
57     private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
58
59     /** Generator of IDs which may be used for SAML assertions, requests, etc. */
60     private IdentifierGenerator idGenerator;
61
62     /** All the SAML message decoders configured for the IdP. */
63     private Map<String, SAMLMessageDecoder> messageDecoders;
64
65     /** All the SAML message encoders configured for the IdP. */
66     private Map<String, SAMLMessageEncoder> messageEncoders;
67
68     /** SAML message binding used by inbound messages. */
69     private String inboundBinding;
70
71     /** SAML message bindings that may be used by outbound messages. */
72     private List<String> supportedOutboundBindings;
73
74     /** Resolver used to determine active security policy for an incoming request. */
75     private SecurityPolicyResolver securityPolicyResolver;
76
77     /** Constructor. */
78     protected AbstractSAMLProfileHandler() {
79         super();
80     }
81
82     /**
83      * Gets the resolver used to determine active security policy for an incoming request.
84      * 
85      * @return resolver used to determine active security policy for an incoming request
86      */
87     public SecurityPolicyResolver getSecurityPolicyResolver() {
88         if (securityPolicyResolver == null) {
89             setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
90         }
91
92         return securityPolicyResolver;
93     }
94
95     /**
96      * Sets the resolver used to determine active security policy for an incoming request.
97      * 
98      * @param resolver resolver used to determine active security policy for an incoming request
99      */
100     public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
101         securityPolicyResolver = resolver;
102     }
103
104     /**
105      * Gets the audit log for this handler.
106      * 
107      * @return audit log for this handler
108      */
109     protected Logger getAduitLog() {
110         return auditLog;
111     }
112
113     /**
114      * Gets an ID generator which may be used for SAML assertions, requests, etc.
115      * 
116      * @return ID generator
117      */
118     public IdentifierGenerator getIdGenerator() {
119         return idGenerator;
120     }
121
122     /**
123      * Gets the SAML message binding used by inbound messages.
124      * 
125      * @return SAML message binding used by inbound messages
126      */
127     public String getInboundBinding() {
128         return inboundBinding;
129     }
130
131     /**
132      * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
133      * 
134      * @return SAML message decoders configured for the IdP indexed by SAML binding URI
135      */
136     public Map<String, SAMLMessageDecoder> getMessageDecoders() {
137         return messageDecoders;
138     }
139
140     /**
141      * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
142      * 
143      * @return SAML message encoders configured for the IdP indexed by SAML binding URI
144      */
145     public Map<String, SAMLMessageEncoder> getMessageEncoders() {
146         return messageEncoders;
147     }
148
149     /**
150      * A convenience method for retrieving the SAML metadata provider from the relying party manager.
151      * 
152      * @return the metadata provider or null
153      */
154     public MetadataProvider getMetadataProvider() {
155         SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
156         if (rpcManager != null) {
157             return rpcManager.getMetadataProvider();
158         }
159
160         return null;
161     }
162
163     /**
164      * Gets the SAML message bindings that may be used by outbound messages.
165      * 
166      * @return SAML message bindings that may be used by outbound messages
167      */
168     public List<String> getSupportedOutboundBindings() {
169         return supportedOutboundBindings;
170     }
171
172     /**
173      * Gets the user's session, if there is one.
174      * 
175      * @param inTransport current inbound transport
176      * 
177      * @return user's session
178      */
179     protected Session getUserSession(InTransport inTransport) {
180         String sessionId = getUserSessionId(inTransport);
181         return getSessionManager().getSession(sessionId);
182     }
183
184     /**
185      * Gets the user's session ID from the current request.
186      * 
187      * @param inTransport current inbound transport
188      * 
189      * @return user's session ID
190      */
191     protected String getUserSessionId(InTransport inTransport) {
192         HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
193
194         if (rawRequest != null) {
195             return (String) rawRequest.getSession().getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
196         }
197
198         return null;
199     }
200
201     /**
202      * Gets an ID generator which may be used for SAML assertions, requests, etc.
203      * 
204      * @param generator an ID generator which may be used for SAML assertions, requests, etc
205      */
206     public void setIdGenerator(IdentifierGenerator generator) {
207         idGenerator = generator;
208     }
209
210     /**
211      * Sets the SAML message binding used by inbound messages.
212      * 
213      * @param binding SAML message binding used by inbound messages
214      */
215     public void setInboundBinding(String binding) {
216         inboundBinding = binding;
217     }
218
219     /**
220      * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
221      * 
222      * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
223      */
224     public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
225         messageDecoders = decoders;
226     }
227
228     /**
229      * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
230      * 
231      * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
232      */
233     public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
234         messageEncoders = encoders;
235     }
236
237     /**
238      * Sets the SAML message bindings that may be used by outbound messages.
239      * 
240      * @param bindings SAML message bindings that may be used by outbound messages
241      */
242     public void setSupportedOutboundBindings(List<String> bindings) {
243         supportedOutboundBindings = bindings;
244     }
245
246     /**
247      * Encodes the request's SAML response and writes it to the servlet response.
248      * 
249      * @param requestContext current request context
250      * 
251      * @throws ProfileException thrown if no message encoder is registered for this profiles binding
252      */
253     protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
254         try {
255
256             Endpoint peerEndpoint = requestContext.getPeerEntityEndpoint();
257             if (peerEndpoint == null) {
258                 log
259                         .error("No return endpoint available for relying party {}", requestContext
260                                 .getInboundMessageIssuer());
261                 throw new ProfileException("No peer endpoint available to which to send SAML response");
262             }
263
264             SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
265             if (encoder == null) {
266                 log.error("No outbound message encoder configured for binding {}", requestContext
267                         .getPeerEntityEndpoint().getBinding());
268                 throw new ProfileException("No outbound message encoder configured for binding "
269                         + requestContext.getPeerEntityEndpoint().getBinding());
270             }
271
272             AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
273                     .getProfileConfiguration();
274             if (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
275                     || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
276                             .providesMessageIntegrity(requestContext))) {
277                 Credential signingCredential = null;
278                 if (profileConfig.getSigningCredential() != null) {
279                     signingCredential = profileConfig.getSigningCredential();
280                 } else if (requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential() != null) {
281                     signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
282                 }
283
284                 if (signingCredential == null) {
285                     throw new ProfileException(
286                             "Signing of responses is required but no signing credential is available");
287                 }
288
289                 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
290             }
291
292             log.debug("Encoding response to SAML request {} from relying party {}", requestContext
293                     .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
294
295             requestContext.setMessageEncoder(encoder);
296             encoder.encode(requestContext);
297         } catch (MessageEncodingException e) {
298             throw new ProfileException("Unable to encode response to relying party: "
299                     + requestContext.getInboundMessageIssuer(), e);
300         }
301     }
302
303     /**
304      * Writes an aduit log entry indicating the successful response to the attribute request.
305      * 
306      * @param context current request context
307      */
308     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
309         AuditLogEntry auditLogEntry = new AuditLogEntry();
310         auditLogEntry.setMessageProfile(getProfileId());
311         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
312         auditLogEntry.setPrincipalName(context.getPrincipalName());
313         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
314         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
315         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
316         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
317         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
318         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
319         if (context.getReleasedAttributes() != null) {
320             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
321         }
322
323         getAduitLog().info(auditLogEntry.toString());
324     }
325 }