Refactor outbound message encoder selection into a distinct method to:
[java-idp.git] / src / main / java / 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.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import javax.servlet.http.HttpServletRequest;
24
25 import org.opensaml.common.IdentifierGenerator;
26 import org.opensaml.common.binding.SAMLMessageContext;
27 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
28 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
29 import org.opensaml.saml1.core.NameIdentifier;
30 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
31 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
32 import org.opensaml.saml2.metadata.Endpoint;
33 import org.opensaml.saml2.metadata.EntityDescriptor;
34 import org.opensaml.saml2.metadata.NameIDFormat;
35 import org.opensaml.saml2.metadata.PDPDescriptor;
36 import org.opensaml.saml2.metadata.RoleDescriptor;
37 import org.opensaml.saml2.metadata.SSODescriptor;
38 import org.opensaml.saml2.metadata.provider.MetadataProvider;
39 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
40 import org.opensaml.ws.message.encoder.MessageEncodingException;
41 import org.opensaml.ws.security.SecurityPolicyResolver;
42 import org.opensaml.ws.transport.InTransport;
43 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
44 import org.opensaml.xml.security.credential.Credential;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
49 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
50 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
51 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
52 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
53 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
54 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
55 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
56 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
57 import edu.internet2.middleware.shibboleth.idp.session.Session;
58
59 /**
60  * Base class for SAML profile handlers.
61  */
62 public abstract class AbstractSAMLProfileHandler extends
63         AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
64
65     /** SAML message audit log. */
66     private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
67
68     /** Class logger. */
69     private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
70
71     /** Generator of IDs which may be used for SAML assertions, requests, etc. */
72     private IdentifierGenerator idGenerator;
73
74     /** All the SAML message decoders configured for the IdP. */
75     private Map<String, SAMLMessageDecoder> messageDecoders;
76
77     /** All the SAML message encoders configured for the IdP. */
78     private Map<String, SAMLMessageEncoder> messageEncoders;
79
80     /** SAML message binding used by inbound messages. */
81     private String inboundBinding;
82
83     /** SAML message bindings that may be used by outbound messages. */
84     private List<String> supportedOutboundBindings;
85
86     /** Resolver used to determine active security policy for an incoming request. */
87     private SecurityPolicyResolver securityPolicyResolver;
88
89     /** Constructor. */
90     protected AbstractSAMLProfileHandler() {
91         super();
92     }
93
94     /**
95      * Gets the resolver used to determine active security policy for an incoming request.
96      * 
97      * @return resolver used to determine active security policy for an incoming request
98      */
99     public SecurityPolicyResolver getSecurityPolicyResolver() {
100         if (securityPolicyResolver == null) {
101             setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
102         }
103
104         return securityPolicyResolver;
105     }
106
107     /**
108      * Sets the resolver used to determine active security policy for an incoming request.
109      * 
110      * @param resolver resolver used to determine active security policy for an incoming request
111      */
112     public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
113         securityPolicyResolver = resolver;
114     }
115
116     /**
117      * Gets the audit log for this handler.
118      * 
119      * @return audit log for this handler
120      */
121     protected Logger getAduitLog() {
122         return auditLog;
123     }
124
125     /**
126      * Gets an ID generator which may be used for SAML assertions, requests, etc.
127      * 
128      * @return ID generator
129      */
130     public IdentifierGenerator getIdGenerator() {
131         return idGenerator;
132     }
133
134     /**
135      * Gets the SAML message binding used by inbound messages.
136      * 
137      * @return SAML message binding used by inbound messages
138      */
139     public String getInboundBinding() {
140         return inboundBinding;
141     }
142
143     /**
144      * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
145      * 
146      * @return SAML message decoders configured for the IdP indexed by SAML binding URI
147      */
148     public Map<String, SAMLMessageDecoder> getMessageDecoders() {
149         return messageDecoders;
150     }
151
152     /**
153      * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
154      * 
155      * @return SAML message encoders configured for the IdP indexed by SAML binding URI
156      */
157     public Map<String, SAMLMessageEncoder> getMessageEncoders() {
158         return messageEncoders;
159     }
160
161     /**
162      * A convenience method for retrieving the SAML metadata provider from the relying party manager.
163      * 
164      * @return the metadata provider or null
165      */
166     public MetadataProvider getMetadataProvider() {
167         SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
168         if (rpcManager != null) {
169             return rpcManager.getMetadataProvider();
170         }
171
172         return null;
173     }
174
175     /**
176      * Gets the SAML message bindings that may be used by outbound messages.
177      * 
178      * @return SAML message bindings that may be used by outbound messages
179      */
180     public List<String> getSupportedOutboundBindings() {
181         return supportedOutboundBindings;
182     }
183
184     /**
185      * Gets the user's session, if there is one.
186      * 
187      * @param inTransport current inbound transport
188      * 
189      * @return user's session
190      */
191     protected Session getUserSession(InTransport inTransport) {
192         HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
193         return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
194     }
195
196     /**
197      * Gets the user's session based on their principal name.
198      * 
199      * @param principalName user's principal name
200      * 
201      * @return the user's session
202      */
203     protected Session getUserSession(String principalName) {
204         return getSessionManager().getSession(principalName);
205     }
206
207     /**
208      * Gets an ID generator which may be used for SAML assertions, requests, etc.
209      * 
210      * @param generator an ID generator which may be used for SAML assertions, requests, etc
211      */
212     public void setIdGenerator(IdentifierGenerator generator) {
213         idGenerator = generator;
214     }
215
216     /**
217      * Sets the SAML message binding used by inbound messages.
218      * 
219      * @param binding SAML message binding used by inbound messages
220      */
221     public void setInboundBinding(String binding) {
222         inboundBinding = binding;
223     }
224
225     /**
226      * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
227      * 
228      * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
229      */
230     public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
231         messageDecoders = decoders;
232     }
233
234     /**
235      * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
236      * 
237      * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
238      */
239     public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
240         messageEncoders = encoders;
241     }
242
243     /**
244      * Sets the SAML message bindings that may be used by outbound messages.
245      * 
246      * @param bindings SAML message bindings that may be used by outbound messages
247      */
248     public void setSupportedOutboundBindings(List<String> bindings) {
249         supportedOutboundBindings = bindings;
250     }
251
252     /** {@inheritDoc} */
253     public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
254         try {
255             if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
256                 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
257                 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
258             }
259         } catch (MetadataProviderException e) {
260             log.error("Unable to look up relying party metadata", e);
261             return null;
262         }
263
264         return super.getRelyingPartyConfiguration(relyingPartyId);
265     }
266
267     /**
268      * Populates the request context with information.
269      * 
270      * This method requires the the following request context properties to be populated: inbound message transport,
271      * peer entity ID, metadata provider
272      * 
273      * This methods populates the following request context properties: user's session, user's principal name, service
274      * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
275      * issuer, local entity metadata
276      * 
277      * @param requestContext current request context
278      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
279      */
280     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
281         populateRelyingPartyInformation(requestContext);
282         populateAssertingPartyInformation(requestContext);
283         populateSAMLMessageInformation(requestContext);
284         populateProfileInformation(requestContext);
285         populateUserInformation(requestContext);
286     }
287
288     /**
289      * Populates the request context with information about the relying party.
290      * 
291      * This method requires the the following request context properties to be populated: peer entity ID
292      * 
293      * This methods populates the following request context properties: peer entity metadata, relying party
294      * configuration
295      * 
296      * @param requestContext current request context
297      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
298      */
299     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
300             throws ProfileException {
301         MetadataProvider metadataProvider = requestContext.getMetadataProvider();
302         String relyingPartyId = requestContext.getInboundMessageIssuer();
303
304         EntityDescriptor relyingPartyMetadata;
305         try {
306             relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
307             requestContext.setPeerEntityMetadata(relyingPartyMetadata);
308         } catch (MetadataProviderException e) {
309             log.error("Error looking up metadata for relying party " + relyingPartyId, e);
310             throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
311         }
312
313         RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
314         if (rpConfig == null) {
315             log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
316             throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
317                     + relyingPartyId);
318         }
319         requestContext.setRelyingPartyConfiguration(rpConfig);
320     }
321
322     /**
323      * Populates the request context with information about the asserting party. Unless overridden,
324      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
325      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
326      * properties it provides are available in the request context.
327      * 
328      * This method requires the the following request context properties to be populated: metadata provider, relying
329      * party configuration
330      * 
331      * This methods populates the following request context properties: local entity ID, outbound message issuer, local
332      * entity metadata
333      * 
334      * @param requestContext current request context
335      * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
336      */
337     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
338             throws ProfileException {
339         String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
340         requestContext.setLocalEntityId(assertingPartyId);
341         requestContext.setOutboundMessageIssuer(assertingPartyId);
342
343         try {
344             EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
345                     assertingPartyId);
346             if (localEntityDescriptor != null) {
347                 requestContext.setLocalEntityMetadata(localEntityDescriptor);
348             }
349         } catch (MetadataProviderException e) {
350             log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
351             throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
352         }
353     }
354
355     /**
356      * Populates the request context with information from the inbound SAML message. Unless overridden,
357      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
358      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
359      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
360      * properties they provide are available in the request context.
361      * 
362      * 
363      * @param requestContext current request context
364      * 
365      * @throws ProfileException thrown if there is a problem populating the request context with information
366      */
367     protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
368             throws ProfileException;
369
370     /**
371      * Populates the request context with the information about the profile. Unless overridden,
372      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
373      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
374      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
375      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
376      * properties they provide are available in the request context.
377      * 
378      * This method requires the the following request context properties to be populated: relying party configuration
379      * 
380      * This methods populates the following request context properties: communication profile ID, profile configuration,
381      * outbound message artifact type, peer entity endpoint
382      * 
383      * @param requestContext current request context
384      * 
385      * @throws ProfileException thrown if there is a problem populating the profile information
386      */
387     protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
388         AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
389                 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
390         if (profileConfig != null) {
391             requestContext.setProfileConfiguration(profileConfig);
392             requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
393         }
394
395         Endpoint endpoint = selectEndpoint(requestContext);
396         if (endpoint == null) {
397             log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
398             throw new ProfileException("No peer endpoint available to which to send SAML response");
399         }
400         requestContext.setPeerEntityEndpoint(endpoint);
401     }
402
403     /**
404      * Gets the name identifier formats to use when creating identifiers for the relying party.
405      * 
406      * @param requestContext current request context
407      * 
408      * @return list of formats that may be used with the relying party, or an empty list for no preference
409      * 
410      * @throws ProfileException thrown if there is a problem determining the name identifier format to use
411      */
412     protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
413         ArrayList<String> nameFormats = new ArrayList<String>();
414
415         RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
416         if (relyingPartyRole != null) {
417             List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
418             if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
419                 nameFormats.addAll(relyingPartySupportedFormats);
420             }
421         }
422
423         // If metadata contains the unspecified name format this means that any are supported
424         if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
425             nameFormats.clear();
426         }
427
428         return nameFormats;
429     }
430
431     /**
432      * Gets the list of name identifier formats supported for a given role.
433      * 
434      * @param role the role to get the list of supported name identifier formats
435      * 
436      * @return list of supported name identifier formats
437      */
438     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
439         List<NameIDFormat> nameIDFormats = null;
440
441         if (role instanceof SSODescriptor) {
442             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
443         } else if (role instanceof AuthnAuthorityDescriptor) {
444             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
445         } else if (role instanceof PDPDescriptor) {
446             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
447         } else if (role instanceof AttributeAuthorityDescriptor) {
448             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
449         }
450
451         ArrayList<String> supportedFormats = new ArrayList<String>();
452         if (nameIDFormats != null) {
453             for (NameIDFormat format : nameIDFormats) {
454                 supportedFormats.add(format.getFormat());
455             }
456         }
457
458         return supportedFormats;
459     }
460
461     /**
462      * Populates the request context with the information about the user if they have an existing session. Unless
463      * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
464      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
465      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
466      * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
467      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
468      * properties they provide are available in the request context.
469      * 
470      * This method should populate: user's session, user's principal name, and service authentication method
471      * 
472      * @param requestContext current request context
473      * 
474      * @throws ProfileException thrown if there is a problem populating the user's information
475      */
476     protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
477             throws ProfileException;
478
479     /**
480      * Selects the appropriate endpoint for the relying party and stores it in the request context.
481      * 
482      * @param requestContext current request context
483      * 
484      * @return Endpoint selected from the information provided in the request context
485      * 
486      * @throws ProfileException thrown if there is a problem selecting a response endpoint
487      */
488     protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
489
490     /**
491      * Encodes the request's SAML response and writes it to the servlet response.
492      * 
493      * @param requestContext current request context
494      * 
495      * @throws ProfileException thrown if no message encoder is registered for this profiles binding
496      */
497     protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
498         try {
499             SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
500
501             AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
502                     .getProfileConfiguration();
503             if (profileConfig != null) {
504                 if (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
505                         || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
506                                 .providesMessageIntegrity(requestContext))) {
507                     Credential signingCredential = profileConfig.getSigningCredential();
508                     if (signingCredential == null) {
509                         signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
510                     }
511
512                     if (signingCredential == null) {
513                         throw new ProfileException(
514                                 "Signing of responses is required but no signing credential is available");
515                     }
516
517                     if (signingCredential.getPrivateKey() == null) {
518                         throw new ProfileException(
519                                 "Signing of response is required but signing credential does not have a private key");
520                     }
521
522                     requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
523                 }
524             }
525
526             log.debug("Encoding response to SAML request {} from relying party {}", requestContext
527                     .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
528
529             requestContext.setMessageEncoder(encoder);
530             encoder.encode(requestContext);
531         } catch (MessageEncodingException e) {
532             throw new ProfileException("Unable to encode response to relying party: "
533                     + requestContext.getInboundMessageIssuer(), e);
534         }
535     }
536
537     /**
538      * Get the outbound message encoder to use.
539      * 
540      * <p>The default implementation uses the binding URI from the 
541      * {@link SAMLMessageContext#getPeerEntityEndpoint()} to lookup
542      * the encoder from the supported message encoders defined in {@link #getMessageEncoders()}.
543      * </p>
544      * 
545      * <p>
546      * Subclasses may override to implement a different mechanism to determine the 
547      * encoder to use, such as for example cases where an active intermediary actor
548      * sits between this provider and the peer entity endpoint (e.g. the SAML 2 ECP case).
549      * </p>
550      * 
551      * @param requestContext current request context
552      * @return the message encoder to use
553      * @throws ProfileException if the encoder to use can not be resolved based on the request context
554      */
555     protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
556             throws ProfileException {
557         SAMLMessageEncoder encoder = null;
558
559         Endpoint endpoint = requestContext.getPeerEntityEndpoint();
560         if (endpoint == null) {
561             log.warn("No peer endpoint available for peer. Unable to send response.");
562             throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
563         }
564
565         if (endpoint != null) {
566             encoder = getMessageEncoders().get(endpoint.getBinding());
567             if (encoder == null) {
568                 log.error("No outbound message encoder configured for binding: {}", requestContext
569                         .getPeerEntityEndpoint().getBinding());
570                 throw new ProfileException("No outbound message encoder configured for binding: "
571                         + requestContext.getPeerEntityEndpoint().getBinding());
572             }
573         }
574         return encoder;
575     }
576
577     /**
578      * Writes an audit log entry indicating the successful response to the attribute request.
579      * 
580      * @param context current request context
581      */
582     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
583         AuditLogEntry auditLogEntry = new AuditLogEntry();
584         auditLogEntry.setMessageProfile(getProfileId());
585         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
586         auditLogEntry.setPrincipalName(context.getPrincipalName());
587         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
588         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
589         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
590         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
591         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
592         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
593         if (context.getReleasedAttributes() != null) {
594             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
595         }
596
597         getAduitLog().info(auditLogEntry.toString());
598     }
599 }