View Javadoc

    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.saml2;
    18
    19  import java.util.Collection;
    20  import java.util.List;
    21  import java.util.Map;
    22
    23  import org.joda.time.DateTime;
    24  import org.opensaml.Configuration;
    25  import org.opensaml.common.SAMLObjectBuilder;
    26  import org.opensaml.common.SAMLVersion;
    27  import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
    28  import org.opensaml.common.xml.SAMLConstants;
    29  import org.opensaml.saml2.core.Assertion;
    30  import org.opensaml.saml2.core.AttributeQuery;
    31  import org.opensaml.saml2.core.AttributeStatement;
    32  import org.opensaml.saml2.core.Audience;
    33  import org.opensaml.saml2.core.AudienceRestriction;
    34  import org.opensaml.saml2.core.AuthnRequest;
    35  import org.opensaml.saml2.core.Conditions;
    36  import org.opensaml.saml2.core.Issuer;
    37  import org.opensaml.saml2.core.NameID;
    38  import org.opensaml.saml2.core.NameIDPolicy;
    39  import org.opensaml.saml2.core.ProxyRestriction;
    40  import org.opensaml.saml2.core.Response;
    41  import org.opensaml.saml2.core.Statement;
    42  import org.opensaml.saml2.core.Status;
    43  import org.opensaml.saml2.core.StatusCode;
    44  import org.opensaml.saml2.core.StatusMessage;
    45  import org.opensaml.saml2.core.StatusResponseType;
    46  import org.opensaml.saml2.core.Subject;
    47  import org.opensaml.saml2.core.SubjectConfirmation;
    48  import org.opensaml.saml2.core.SubjectConfirmationData;
    49  import org.opensaml.saml2.encryption.Encrypter;
    50  import org.opensaml.saml2.encryption.Encrypter.KeyPlacement;
    51  import org.opensaml.saml2.metadata.Endpoint;
    52  import org.opensaml.saml2.metadata.SPSSODescriptor;
    53  import org.opensaml.security.MetadataCredentialResolver;
    54  import org.opensaml.security.MetadataCriteria;
    55  import org.opensaml.ws.message.encoder.MessageEncodingException;
    56  import org.opensaml.ws.transport.http.HTTPInTransport;
    57  import org.opensaml.xml.XMLObjectBuilder;
    58  import org.opensaml.xml.encryption.EncryptionException;
    59  import org.opensaml.xml.encryption.EncryptionParameters;
    60  import org.opensaml.xml.encryption.KeyEncryptionParameters;
    61  import org.opensaml.xml.io.Marshaller;
    62  import org.opensaml.xml.io.MarshallingException;
    63  import org.opensaml.xml.security.CriteriaSet;
    64  import org.opensaml.xml.security.SecurityConfiguration;
    65  import org.opensaml.xml.security.SecurityException;
    66  import org.opensaml.xml.security.SecurityHelper;
    67  import org.opensaml.xml.security.credential.Credential;
    68  import org.opensaml.xml.security.credential.UsageType;
    69  import org.opensaml.xml.security.criteria.EntityIDCriteria;
    70  import org.opensaml.xml.security.criteria.UsageCriteria;
    71  import org.opensaml.xml.signature.Signature;
    72  import org.opensaml.xml.signature.SignatureException;
    73  import org.opensaml.xml.signature.Signer;
    74  import org.opensaml.xml.util.DatatypeHelper;
    75  import org.slf4j.Logger;
    76  import org.slf4j.LoggerFactory;
    77  import org.slf4j.helpers.MessageFormatter;
    78
    79  import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
    80  import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
    81  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
    82  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
    83  import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2NameIDEncoder;
    84  import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
    85  import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
    86  import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
    87  import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
    88  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
    89  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
    90  import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
    91  import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
    92  import edu.internet2.middleware.shibboleth.idp.session.Session;
    93
    94  /** Common implementation details for profile handlers. */
    95  public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
    96
    97      /** SAML Version for this profile handler. */
    98      public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
    99
    100     /** Class logger. */
    101     private Logger log = LoggerFactory.getLogger(AbstractSAML2ProfileHandler.class);
    102
    103     /** For building response. */
    104     private SAMLObjectBuilder<Response> responseBuilder;
    105
    106     /** For building status. */
    107     private SAMLObjectBuilder<Status> statusBuilder;
    108
    109     /** For building statuscode. */
    110     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
    111
    112     /** For building StatusMessages. */
    113     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
    114
    115     /** For building assertion. */
    116     private SAMLObjectBuilder<Assertion> assertionBuilder;
    117
    118     /** For building issuer. */
    119     private SAMLObjectBuilder<Issuer> issuerBuilder;
    120
    121     /** For building subject. */
    122     private SAMLObjectBuilder<Subject> subjectBuilder;
    123
    124     /** For building subject confirmation. */
    125     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
    126
    127     /** For building subject confirmation data. */
    128     private SAMLObjectBuilder<SubjectConfirmationData> subjectConfirmationDataBuilder;
    129
    130     /** For building conditions. */
    131     private SAMLObjectBuilder<Conditions> conditionsBuilder;
    132
    133     /** For building audience restriction. */
    134     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
    135
    136     /** For building proxy restrictions. */
    137     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
    138
    139     /** For building audience. */
    140     private SAMLObjectBuilder<Audience> audienceBuilder;
    141
    142     /** For building signature. */
    143     private XMLObjectBuilder<Signature> signatureBuilder;
    144
    145     /** Constructor. */
    146     @SuppressWarnings("unchecked")
    147     protected AbstractSAML2ProfileHandler() {
    148         super();
    149
    150         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
    151         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
    152         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
    153                 StatusCode.DEFAULT_ELEMENT_NAME);
    154         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
    155                 StatusMessage.DEFAULT_ELEMENT_NAME);
    156         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
    157         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
    158                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
    159         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
    160         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
    161                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
    162         subjectConfirmationDataBuilder = (SAMLObjectBuilder<SubjectConfirmationData>) getBuilderFactory().getBuilder(
    163                 SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
    164         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
    165                 Conditions.DEFAULT_ELEMENT_NAME);
    166         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
    167                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
    168         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
    169                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
    170         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
    171         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
    172     }
    173
    174     /** {@inheritDoc} */
    175     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
    176         BaseSAML2ProfileRequestContext saml2Request = (BaseSAML2ProfileRequestContext) requestContext;
    177         try {
    178             super.populateRequestContext(requestContext);
    179         } catch (ProfileException e) {
    180             if (saml2Request.getFailureStatus() == null) {
    181                 saml2Request.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null, e.getMessage()));
    182             }
    183             throw e;
    184         }
    185     }
    186
    187     /**
    188      * Populates the request context with the information about the user.
    189      *
    190      * This method requires the the following request context properties to be populated: inbound message transport,
    191      * relying party ID
    192      *
    193      * This methods populates the following request context properties: user's session, user's principal name, and
    194      * service authentication method
    195      *
    196      * @param requestContext current request context
    197      */
    198     protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
    199         Session userSession = getUserSession(requestContext.getInboundMessageTransport());
    200         if (userSession == null) {
    201             NameID subject = (NameID) requestContext.getSubjectNameIdentifier();
    202             if (subject != null && subject.getValue() != null) {
    203                 userSession = getUserSession(subject.getValue());
    204             }
    205         }
    206
    207         if (userSession != null) {
    208             requestContext.setUserSession(userSession);
    209             requestContext.setPrincipalName(userSession.getPrincipalName());
    210             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
    211                     requestContext.getInboundMessageIssuer());
    212             if (serviceInfo != null) {
    213                 requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
    214                         .getAuthenticationMethod());
    215             }
    216         }
    217     }
    218
    219     /**
    220      * Checks that the SAML major version for a request is 2.
    221      *
    222      * @param requestContext current request context containing the SAML message
    223      *
    224      * @throws ProfileException thrown if the major version of the SAML request is not 2
    225      */
    226     protected void checkSamlVersion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    227         SAMLVersion version = requestContext.getInboundSAMLMessage().getVersion();
    228         if (version.getMajorVersion() < 2) {
    229             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
    230                     StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
    231             throw new ProfileException("SAML request version too low");
    232         } else if (version.getMajorVersion() > 2 || version.getMinorVersion() > 0) {
    233             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
    234                     StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
    235             throw new ProfileException("SAML request version too high");
    236         }
    237     }
    238
    239     /**
    240      * Builds a response to the attribute query within the request context.
    241      *
    242      * @param requestContext current request context
    243      * @param subjectConfirmationMethod confirmation method used for the subject
    244      * @param statements the statements to include in the response
    245      *
    246      * @return the built response
    247      *
    248      * @throws ProfileException thrown if there is a problem creating the SAML response
    249      */
    250     protected Response buildResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
    251             String subjectConfirmationMethod, List<Statement> statements) throws ProfileException {
    252
    253         DateTime issueInstant = new DateTime();
    254
    255         Response samlResponse = responseBuilder.buildObject();
    256         samlResponse.setIssueInstant(issueInstant);
    257         populateStatusResponse(requestContext, samlResponse);
    258
    259         Assertion assertion = null;
    260         if (statements != null && !statements.isEmpty()) {
    261             assertion = buildAssertion(requestContext, issueInstant);
    262             assertion.getStatements().addAll(statements);
    263             assertion.setSubject(buildSubject(requestContext, subjectConfirmationMethod, issueInstant));
    264
    265             postProcessAssertion(requestContext, assertion);
    266
    267             signAssertion(requestContext, assertion);
    268
    269             if (isEncryptAssertion(requestContext)) {
    270                 log.debug("Attempting to encrypt assertion to relying party '{}'", requestContext
    271                         .getInboundMessageIssuer());
    272                 try {
    273                     Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
    274                     samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
    275                 } catch (SecurityException e) {
    276                     log.error("Unable to construct encrypter", e);
    277                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
    278                     "Unable to encrypt assertion"));
    279                     throw new ProfileException("Unable to construct encrypter", e);
    280                 } catch (EncryptionException e) {
    281                     log.error("Unable to encrypt assertion", e);
    282                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
    283                     "Unable to encrypt assertion"));
    284                     throw new ProfileException("Unable to encrypt assertion", e);
    285                 }
    286             } else {
    287                 samlResponse.getAssertions().add(assertion);
    288             }
    289         }
    290
    291         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
    292         samlResponse.setStatus(status);
    293
    294         postProcessResponse(requestContext, samlResponse);
    295
    296         return samlResponse;
    297     }
    298
    299     /**
    300      * Determine whether issued assertions should be encrypted.
    301      *
    302      * @param requestContext the current request context
    303      * @return true if assertions should be encrypted, false otherwise
    304      * @throws ProfileException if there is a problem determining whether assertions should be encrypted
    305      */
    306     protected boolean isEncryptAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
    307             throws ProfileException {
    308
    309         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
    310         try {
    311             return requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
    312                 || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional
    313                     && !encoder.providesMessageConfidentiality(requestContext));
    314         } catch (MessageEncodingException e) {
    315             log.error("Unable to determine if outbound encoding '{}' can provide confidentiality", encoder
    316                     .getBindingURI());
    317             throw new ProfileException("Unable to determine if assertions should be encrypted");
    318         }
    319     }
    320
    321     /**
    322      * Extension point for for subclasses to post-process the Response before it is signed and encoded.
    323      *
    324      * @param requestContext the current request context
    325      * @param samlResponse the SAML Response being built
    326      *
    327      * @throws ProfileException if there was an error processing the response
    328      */
    329     protected void postProcessResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Response samlResponse)
    330             throws ProfileException {
    331     }
    332
    333     /**
    334      * Extension point for for subclasses to post-process the Assertion before it is signed and encrypted.
    335      *
    336      * @param requestContext the current request context
    337      * @param assertion the SAML Assertion being built
    338      *
    339      * @throws ProfileException if there is an error processing the assertion
    340      */
    341     protected void postProcessAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
    342             throws ProfileException {
    343     }
    344
    345     /**
    346      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
    347      *
    348      * @param requestContext current request context
    349      * @param issueInstant time to use as assertion issue instant
    350      *
    351      * @return the built assertion
    352      */
    353     protected Assertion buildAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
    354         Assertion assertion = assertionBuilder.buildObject();
    355         assertion.setID(getIdGenerator().generateIdentifier());
    356         assertion.setIssueInstant(issueInstant);
    357         assertion.setVersion(SAMLVersion.VERSION_20);
    358         assertion.setIssuer(buildEntityIssuer(requestContext));
    359
    360         Conditions conditions = buildConditions(requestContext, issueInstant);
    361         assertion.setConditions(conditions);
    362
    363         return assertion;
    364     }
    365
    366     /**
    367      * Creates an {@link Issuer} populated with information about the relying party.
    368      *
    369      * @param requestContext current request context
    370      *
    371      * @return the built issuer
    372      */
    373     protected Issuer buildEntityIssuer(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
    374         Issuer issuer = issuerBuilder.buildObject();
    375         issuer.setFormat(Issuer.ENTITY);
    376         issuer.setValue(requestContext.getLocalEntityId());
    377
    378         return issuer;
    379     }
    380
    381     /**
    382      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
    383      * restrictions, and proxy restrictions.
    384      *
    385      * @param requestContext current request context
    386      * @param issueInstant timestamp the assertion was created
    387      *
    388      * @return constructed conditions
    389      */
    390     protected Conditions buildConditions(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
    391         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    392
    393         Conditions conditions = conditionsBuilder.buildObject();
    394         conditions.setNotBefore(issueInstant);
    395         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
    396
    397         Collection<String> audiences;
    398
    399         // add audience restrictions
    400         AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
    401         // TODO we should only do this for certain outgoing bindings, not globally
    402         Audience audience = audienceBuilder.buildObject();
    403         audience.setAudienceURI(requestContext.getInboundMessageIssuer());
    404         audienceRestriction.getAudiences().add(audience);
    405         audiences = profileConfig.getAssertionAudiences();
    406         if (audiences != null && audiences.size() > 0) {
    407             for (String audienceUri : audiences) {
    408                 audience = audienceBuilder.buildObject();
    409                 audience.setAudienceURI(audienceUri);
    410                 audienceRestriction.getAudiences().add(audience);
    411             }
    412         }
    413         conditions.getAudienceRestrictions().add(audienceRestriction);
    414
    415         // add proxy restrictions
    416         audiences = profileConfig.getProxyAudiences();
    417         if (audiences != null && audiences.size() > 0) {
    418             ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
    419             for (String audienceUri : audiences) {
    420                 audience = audienceBuilder.buildObject();
    421                 audience.setAudienceURI(audienceUri);
    422                 proxyRestriction.getAudiences().add(audience);
    423             }
    424
    425             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
    426             conditions.getConditions().add(proxyRestriction);
    427         }
    428
    429         return conditions;
    430     }
    431
    432     /**
    433      * Populates the response's id, in response to, issue instant, version, and issuer properties.
    434      *
    435      * @param requestContext current request context
    436      * @param response the response to populate
    437      */
    438     protected void populateStatusResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
    439             StatusResponseType response) {
    440         response.setID(getIdGenerator().generateIdentifier());
    441
    442         response.setInResponseTo(requestContext.getInboundSAMLMessageId());
    443         response.setIssuer(buildEntityIssuer(requestContext));
    444
    445         response.setVersion(SAMLVersion.VERSION_20);
    446     }
    447
    448     /**
    449      * Resolves the attributes for the principal.
    450      *
    451      * @param requestContext current request context
    452      *
    453      * @throws ProfileException thrown if there is a problem resolved attributes
    454      */
    455     protected void resolveAttributes(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    456         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    457         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    458         try {
    459             log.debug("Resolving attributes for principal '{}' for SAML request from relying party '{}'",
    460                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
    461             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
    462
    463             requestContext.setAttributes(principalAttributes);
    464         } catch (AttributeRequestException e) {
    465             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
    466             String msg = MessageFormatter.format(
    467                     "Error resolving attributes for principal '{}' for SAML request from relying party '{}'",
    468                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
    469             log.error(msg, e);
    470             throw new ProfileException(msg, e);
    471         }
    472     }
    473
    474     /**
    475      * Executes a query for attributes and builds a SAML attribute statement from the results.
    476      *
    477      * @param requestContext current request context
    478      *
    479      * @return attribute statement resulting from the query
    480      *
    481      * @throws ProfileException thrown if there is a problem making the query
    482      */
    483     protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
    484             throws ProfileException {
    485         log.debug("Creating attribute statement in response to SAML request '{}' from relying party '{}'",
    486                 requestContext.getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
    487
    488         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    489         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    490         try {
    491             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
    492                 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
    493                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
    494             } else {
    495                 return attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
    496             }
    497         } catch (AttributeRequestException e) {
    498             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
    499             String msg = MessageFormatter.format("Error encoding attributes for principal '{}'", requestContext
    500                     .getPrincipalName());
    501             log.error(msg, e);
    502             throw new ProfileException(msg, e);
    503         }
    504     }
    505
    506     /**
    507      * Resolves the principal name of the subject of the request.
    508      *
    509      * @param requestContext current request context
    510      *
    511      * @throws ProfileException thrown if the principal name can not be resolved
    512      */
    513     protected void resolvePrincipal(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    514         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    515         if (profileConfiguration == null) {
    516             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
    517                     "Error resolving principal"));
    518             String msg = MessageFormatter.format(
    519                     "Unable to resolve principal, no SAML 2 profile configuration for relying party '{}'",
    520                     requestContext.getInboundMessageIssuer());
    521             log.warn(msg);
    522             throw new ProfileException(msg);
    523         }
    524         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    525         log.debug("Resolving principal name for subject of SAML request '{}' from relying party '{}'", requestContext
    526                 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
    527
    528         try {
    529             String principal = attributeAuthority.getPrincipal(requestContext);
    530             requestContext.setPrincipalName(principal);
    531         } catch (AttributeRequestException e) {
    532             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
    533                     "Error resolving principal"));
    534             String msg = MessageFormatter.format(
    535                     "Error resolving principal name for SAML request '{}' from relying party '{}'", requestContext
    536                             .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
    537             log.error(msg, e);
    538             throw new ProfileException(msg, e);
    539         }
    540     }
    541
    542     /**
    543      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
    544      * signing credentials.
    545      *
    546      * @param requestContext current request context
    547      * @param assertion assertion to sign
    548      *
    549      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
    550      *             required, if a signing credential is not configured
    551      */
    552     protected void signAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
    553             throws ProfileException {
    554         log.debug("Determining if SAML assertion to relying party '{}' should be signed", requestContext
    555                 .getInboundMessageIssuer());
    556
    557         boolean signAssertion = isSignAssertion(requestContext);
    558
    559         if (!signAssertion) {
    560             return;
    561         }
    562
    563         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    564
    565         log.debug("Determining signing credntial for assertion to relying party '{}'", requestContext
    566                 .getInboundMessageIssuer());
    567         Credential signatureCredential = profileConfig.getSigningCredential();
    568         if (signatureCredential == null) {
    569             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
    570         }
    571
    572         if (signatureCredential == null) {
    573             String msg = MessageFormatter.format(
    574                     "No signing credential is specified for relying party configuration '{}'", requestContext
    575                             .getRelyingPartyConfiguration().getProviderId());
    576             log.warn(msg);
    577             throw new ProfileException(msg);
    578         }
    579
    580         log.debug("Signing assertion to relying party {}", requestContext.getInboundMessageIssuer());
    581         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
    582
    583         signature.setSigningCredential(signatureCredential);
    584         try {
    585             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
    586             // TODO how to pull what keyInfoGenName to use?
    587             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
    588         } catch (SecurityException e) {
    589             String msg = "Error preparing signature for signing";
    590             log.error(msg);
    591             throw new ProfileException(msg, e);
    592         }
    593
    594         assertion.setSignature(signature);
    595
    596         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
    597         try {
    598             assertionMarshaller.marshall(assertion);
    599             Signer.signObject(signature);
    600         } catch (MarshallingException e) {
    601             String errMsg = "Unable to marshall assertion for signing";
    602             log.error(errMsg, e);
    603             throw new ProfileException(errMsg, e);
    604         } catch (SignatureException e) {
    605             String msg = "Unable to sign assertion";
    606             log.error(msg, e);
    607             throw new ProfileException(msg, e);
    608         }
    609     }
    610
    611     /**
    612      * Determine whether issued assertions should be signed.
    613      *
    614      * @param requestContext the current request context
    615      * @return true if assertions should be signed, false otherwise
    616      * @throws ProfileException if there is a problem determining whether assertions should be signed
    617      */
    618     protected boolean isSignAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    619
    620         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
    621         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    622
    623         try {
    624             boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
    625                 || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional
    626                     && !encoder.providesMessageIntegrity(requestContext));
    627
    628             log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
    629                     .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
    630
    631             if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
    632                 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
    633                 if (ssoDescriptor.getWantAssertionsSigned() != null) {
    634                     signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
    635                     log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
    636                             .getInboundMessageIssuer(), signAssertion);
    637                 }
    638             }
    639
    640             return signAssertion;
    641         } catch (MessageEncodingException e) {
    642             log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
    643                     .getBindingURI());
    644             throw new ProfileException("Unable to determine if outbound assertion should be signed");
    645         }
    646     }
    647
    648     /**
    649      * Build a status message, with an optional second-level failure message.
    650      *
    651      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
    652      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
    653      *            null, no second-level Status element will be set.
    654      * @param failureMessage An optional second-level failure message
    655      *
    656      * @return a Status object.
    657      */
    658     protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
    659         Status status = statusBuilder.buildObject();
    660
    661         StatusCode statusCode = statusCodeBuilder.buildObject();
    662         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
    663         status.setStatusCode(statusCode);
    664
    665         if (secondLevelCode != null) {
    666             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
    667             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
    668             statusCode.setStatusCode(secondLevelStatusCode);
    669         }
    670
    671         if (failureMessage != null) {
    672             StatusMessage msg = statusMessageBuilder.buildObject();
    673             msg.setMessage(failureMessage);
    674             status.setStatusMessage(msg);
    675         }
    676
    677         return status;
    678     }
    679
    680     /**
    681      * Builds the SAML subject for the user for the service provider.
    682      *
    683      * @param requestContext current request context
    684      * @param confirmationMethod subject confirmation method used for the subject
    685      * @param issueInstant instant the subject confirmation data should reflect for issuance
    686      *
    687      * @return SAML subject for the user for the service provider
    688      *
    689      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
    690      *             name ID attribute or because there are no supported name formats
    691      */
    692     protected Subject buildSubject(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod,
    693             DateTime issueInstant) throws ProfileException {
    694         Subject subject = subjectBuilder.buildObject();
    695         subject.getSubjectConfirmations().add(
    696                 buildSubjectConfirmation(requestContext, confirmationMethod, issueInstant));
    697
    698         NameID nameID = buildNameId(requestContext);
    699         if (nameID == null) {
    700             return subject;
    701         }
    702
    703         requestContext.setSubjectNameIdentifier(nameID);
    704
    705         if (isEncryptNameID(requestContext)) {
    706             log.debug("Attempting to encrypt NameID to relying party '{}'", requestContext
    707                     .getInboundMessageIssuer());
    708             try {
    709                 Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
    710                 subject.setEncryptedID(encrypter.encrypt(nameID));
    711             } catch (SecurityException e) {
    712                 log.error("Unable to construct encrypter", e);
    713                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
    714                 "Unable to encrypt NameID"));
    715                 throw new ProfileException("Unable to construct encrypter", e);
    716             } catch (EncryptionException e) {
    717                 log.error("Unable to encrypt NameID", e);
    718                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
    719                 "Unable to encrypt NameID"));
    720                 throw new ProfileException("Unable to encrypt NameID", e);
    721             }
    722         } else {
    723             subject.setNameID(nameID);
    724         }
    725
    726
    727         return subject;
    728     }
    729
    730
    731     /**
    732      * Determine whether NameID's should be encrypted.
    733      *
    734      * @param requestContext the current request context
    735      * @return true if NameID's should be encrypted, false otherwise
    736      * @throws ProfileException if there is a problem determining whether NameID's should be encrypted
    737      */
    738     protected boolean isEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
    739             throws ProfileException {
    740
    741         boolean nameIdEncRequiredByAuthnRequest = isRequestRequiresEncryptNameID(requestContext);
    742
    743         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
    744         boolean nameIdEncRequiredByConfig = false;
    745         try {
    746             nameIdEncRequiredByConfig =
    747                 requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
    748                     || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional
    749                             && !encoder.providesMessageConfidentiality(requestContext));
    750         } catch (MessageEncodingException e) {
    751             String msg = MessageFormatter.format(
    752                     "Unable to determine if outbound encoding '{}' provides message confidentiality protection",
    753                     encoder.getBindingURI());
    754             log.error(msg);
    755             throw new ProfileException(msg);
    756         }
    757
    758         return nameIdEncRequiredByAuthnRequest || nameIdEncRequiredByConfig;
    759     }
    760
    761     /**
    762      * Determine whether information in the SAML request requires the issued NameID to
    763      * be encrypted.
    764      *
    765      * @param requestContext the current request context
    766      * @return true if the request indicates NameID encryption is required, false otherwise
    767      */
    768     protected boolean isRequestRequiresEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
    769         boolean nameIdEncRequiredByAuthnRequest = false;
    770         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
    771             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
    772             NameIDPolicy policy = authnRequest.getNameIDPolicy();
    773             if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
    774                 nameIdEncRequiredByAuthnRequest = true;
    775             }
    776         }
    777         return nameIdEncRequiredByAuthnRequest;
    778     }
    779
    780     /**
    781      * Builds the SubjectConfirmation appropriate for this request.
    782      *
    783      * @param requestContext current request context
    784      * @param confirmationMethod confirmation method to use for the request
    785      * @param issueInstant issue instant of the response
    786      *
    787      * @return the constructed subject confirmation
    788      */
    789     protected SubjectConfirmation buildSubjectConfirmation(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
    790             String confirmationMethod, DateTime issueInstant) {
    791         SubjectConfirmationData confirmationData = subjectConfirmationDataBuilder.buildObject();
    792         HTTPInTransport inTransport = (HTTPInTransport) requestContext.getInboundMessageTransport();
    793         confirmationData.setAddress(inTransport.getPeerAddress());
    794         confirmationData.setInResponseTo(requestContext.getInboundSAMLMessageId());
    795         confirmationData.setNotOnOrAfter(issueInstant.plus(requestContext.getProfileConfiguration()
    796                 .getAssertionLifetime()));
    797
    798         Endpoint relyingPartyEndpoint = requestContext.getPeerEntityEndpoint();
    799         if (relyingPartyEndpoint != null) {
    800             if (relyingPartyEndpoint.getResponseLocation() != null) {
    801                 confirmationData.setRecipient(relyingPartyEndpoint.getResponseLocation());
    802             } else {
    803                 confirmationData.setRecipient(relyingPartyEndpoint.getLocation());
    804             }
    805         }
    806
    807         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
    808         subjectConfirmation.setMethod(confirmationMethod);
    809         subjectConfirmation.setSubjectConfirmationData(confirmationData);
    810
    811         return subjectConfirmation;
    812     }
    813
    814     /**
    815      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
    816      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
    817      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
    818      * the principals attributes are inspected for an attribute supported an attribute encoder whose category is one of
    819      * the supported name formats.
    820      *
    821      * @param requestContext current request context
    822      *
    823      * @return the NameID appropriate for this request
    824      *
    825      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
    826      *             name ID attribute or because there are no supported name formats
    827      */
    828     protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    829         log.debug("Building assertion NameID for principal/relying party:{}/{}", requestContext.getPrincipalName(),
    830                 requestContext.getInboundMessageIssuer());
    831
    832         // Check if AuthnRequest includes an explicit NameIDPolicy Format.
    833         String requiredNameFormat = null;
    834         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
    835             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
    836             if (authnRequest.getNameIDPolicy() != null) {
    837                 requiredNameFormat = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getFormat());
    838                 // Check for unspec'd or encryption formats, which aren't relevant for this section of code.
    839                 if (requiredNameFormat != null
    840                         && (requiredNameFormat.equals("urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted") || requiredNameFormat
    841                                 .equals(NameID.UNSPECIFIED))) {
    842                     requiredNameFormat = null;
    843                 }
    844             }
    845         }
    846
    847         // Get the SP's list, and filter it down by the AuthnRequest if need be.
    848         List<String> supportedNameFormats = getNameFormats(requestContext);
    849         if (requiredNameFormat != null) {
    850             supportedNameFormats.clear();
    851             supportedNameFormats.add(requiredNameFormat);
    852         }
    853         if (!supportedNameFormats.isEmpty()) {
    854             log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
    855                     supportedNameFormats);
    856         } else {
    857             log.debug("Relying party '{}' indicated no preferred name formats", requestContext
    858                     .getInboundMessageIssuer());
    859         }
    860
    861         BaseAttribute<?> nameIdAttribute = null;
    862         SAML2NameIDEncoder nameIdEncoder = null;
    863
    864         Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
    865         if (principalAttributes != null) {
    866             for (BaseAttribute<?> attribute : principalAttributes.values()) {
    867                 if (attribute == null) {
    868                     continue;
    869                 }
    870
    871                 for (AttributeEncoder encoder : attribute.getEncoders()) {
    872                     if (encoder == null) {
    873                         continue;
    874                     }
    875
    876                     if (encoder instanceof SAML2NameIDEncoder) {
    877                         nameIdEncoder = (SAML2NameIDEncoder)encoder;
    878
    879                         if (requiredNameFormat != null) {
    880                             if (nameIdEncoder.getNameFormat().equals(requiredNameFormat)) {
    881                                 nameIdAttribute = attribute;
    882                                 nameIdEncoder = (SAML2NameIDEncoder) encoder;
    883                                 break;
    884                             }
    885                         } else {
    886                             if (supportedNameFormats.isEmpty()
    887                                     || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
    888                                 nameIdAttribute = attribute;
    889                                 nameIdEncoder = (SAML2NameIDEncoder) encoder;
    890                                 break;
    891                             }
    892                         }
    893                     }
    894                 }
    895             }
    896         }
    897
    898         if (nameIdAttribute == null || nameIdEncoder == null) {
    899             if (requiredNameFormat != null) {
    900                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
    901                         StatusCode.INVALID_NAMEID_POLICY_URI, "NameID Format not supported: " + requiredNameFormat));
    902                 String msg = MessageFormatter
    903                         .format(
    904                                 "No attribute of principal '{}' can be encoded in to a NameID of required format '{}' for relying party '{}'",
    905                                 new Object[] { requestContext.getPrincipalName(), requiredNameFormat,
    906                                         requestContext.getInboundMessageIssuer() });
    907                 log.warn(msg);
    908                 throw new ProfileException(msg);
    909             } else {
    910                 return null;
    911             }
    912         }
    913
    914         log.debug("Using attribute '{}' supporting NameID format '{}' to create the NameID for relying party '{}'",
    915                 new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
    916                         requestContext.getInboundMessageIssuer() });
    917         try {
    918             return nameIdEncoder.encode(nameIdAttribute);
    919         } catch (AttributeEncodingException e) {
    920             log.error("Unable to encode NameID attribute", e);
    921             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
    922             throw new ProfileException("Unable to encode NameID attribute", e);
    923         }
    924     }
    925
    926     /**
    927      * Constructs an SAML response message carrying a request error.
    928      *
    929      * @param requestContext current request context
    930      *
    931      * @return the constructed error response
    932      */
    933     protected Response buildErrorResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
    934         Response samlResponse = responseBuilder.buildObject();
    935         samlResponse.setIssueInstant(new DateTime());
    936         populateStatusResponse(requestContext, samlResponse);
    937
    938         samlResponse.setStatus(requestContext.getFailureStatus());
    939
    940         return samlResponse;
    941     }
    942
    943     /**
    944      * Gets an encrypter that may be used encrypt content to a given peer.
    945      *
    946      * @param peerEntityId entity ID of the peer
    947      *
    948      * @return encrypter that may be used encrypt content to a given peer
    949      *
    950      * @throws SecurityException thrown if there is a problem constructing the encrypter. This normally occurs if the
    951      *             key encryption credential for the peer can not be resolved or a required encryption algorithm is not
    952      *             supported by the VM's JCE.
    953      */
    954     protected Encrypter getEncrypter(String peerEntityId) throws SecurityException {
    955         SecurityConfiguration securityConfiguration = Configuration.getGlobalSecurityConfiguration();
    956
    957         EncryptionParameters dataEncParams = SecurityHelper
    958                 .buildDataEncryptionParams(null, securityConfiguration, null);
    959
    960         Credential keyEncryptionCredential = getKeyEncryptionCredential(peerEntityId);
    961         if (keyEncryptionCredential == null) {
    962             log.error("Could not resolve a key encryption credential for peer entity: {}", peerEntityId);
    963             throw new SecurityException("Could not resolve key encryption credential");
    964         }
    965         String wrappedJCAKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(dataEncParams.getAlgorithm());
    966         KeyEncryptionParameters keyEncParams = SecurityHelper.buildKeyEncryptionParams(keyEncryptionCredential,
    967                 wrappedJCAKeyAlgorithm, securityConfiguration, null, null);
    968
    969         Encrypter encrypter = new Encrypter(dataEncParams, keyEncParams);
    970         encrypter.setKeyPlacement(KeyPlacement.INLINE);
    971         return encrypter;
    972     }
    973
    974     /**
    975      * Gets the credential that can be used to encrypt encryption keys for a peer.
    976      *
    977      * @param peerEntityId entity ID of the peer
    978      *
    979      * @return credential that can be used to encrypt encryption keys for a peer
    980      *
    981      * @throws SecurityException thrown if there is a problem resolving the credential from the peer's metadata
    982      */
    983     protected Credential getKeyEncryptionCredential(String peerEntityId) throws SecurityException {
    984         MetadataCredentialResolver kekCredentialResolver = new MetadataCredentialResolver(getMetadataProvider());
    985
    986         CriteriaSet criteriaSet = new CriteriaSet();
    987         criteriaSet.add(new EntityIDCriteria(peerEntityId));
    988         criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
    989         criteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
    990
    991         return kekCredentialResolver.resolveSingle(criteriaSet);
    992     }
    993
    994     /**
    995      * Writes an audit log entry indicating the successful response to the attribute request.
    996      *
    997      * @param context current request context
    998      */
    999     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
    1000         SAML2AuditLogEntry auditLogEntry = new SAML2AuditLogEntry();
    1001         auditLogEntry.setSAMLResponse((StatusResponseType) context.getOutboundSAMLMessage());
    1002         auditLogEntry.setMessageProfile(getProfileId());
    1003         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
    1004         auditLogEntry.setPrincipalName(context.getPrincipalName());
    1005         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
    1006         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
    1007         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
    1008         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
    1009         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
    1010         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
    1011         if (context.getReleasedAttributes() != null) {
    1012             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
    1013         }
    1014
    1015         getAduitLog().info(auditLogEntry.toString());
    1016     }
    1017
    1018     /** SAML 1 specific audit log entry. */
    1019     protected class SAML2AuditLogEntry extends AuditLogEntry {
    1020
    1021         /** The response to the SAML request. */
    1022         private StatusResponseType samlResponse;
    1023
    1024         /** The unencrypted NameID for the SAML response. */
    1025         private NameID unencryptedNameId;
    1026
    1027         /**
    1028          * Gets the response to the SAML request.
    1029          *
    1030          * @return the response to the SAML request
    1031          */
    1032         public StatusResponseType getSAMLResponse() {
    1033             return samlResponse;
    1034         }
    1035
    1036         /**
    1037          * Sets the response to the SAML request.
    1038          *
    1039          * @param response the response to the SAML request
    1040          */
    1041         public void setSAMLResponse(StatusResponseType response) {
    1042             samlResponse = response;
    1043         }
    1044
    1045         /**
    1046          * Gets the unencrypted NameID for the SAML response.
    1047          *
    1048          * @return unencrypted NameID for the SAML response
    1049          */
    1050         public NameID getUnencryptedNameId() {
    1051             return unencryptedNameId;
    1052         }
    1053
    1054         /**
    1055          * Sets the unencrypted NameID for the SAML response.
    1056          *
    1057          * @param id unencrypted NameID for the SAML response
    1058          */
    1059         public void setUnencryptedNameId(NameID id) {
    1060             unencryptedNameId = id;
    1061         }
    1062
    1063         /** {@inheritDoc} */
    1064         public String toString() {
    1065             StringBuilder entryString = new StringBuilder(super.toString());
    1066
    1067             StringBuilder assertionIds = new StringBuilder();
    1068
    1069             if (samlResponse instanceof Response) {
    1070                 List<Assertion> assertions = ((Response) samlResponse).getAssertions();
    1071                 if (assertions != null && !assertions.isEmpty()) {
    1072                     for (Assertion assertion : assertions) {
    1073                         assertionIds.append(assertion.getID());
    1074                         assertionIds.append(",");
    1075                     }
    1076                 }
    1077             }
    1078
    1079             if (unencryptedNameId != null) {
    1080                 entryString.append(unencryptedNameId.getValue());
    1081             }
    1082             entryString.append("|");
    1083
    1084             entryString.append(assertionIds.toString());
    1085             entryString.append("|");
    1086
    1087             return entryString.toString();
    1088         }
    1089     }
    1090 }