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.saml1;
    18
    19  import java.util.Collection;
    20  import java.util.List;
    21  import java.util.Map;
    22
    23  import javax.xml.namespace.QName;
    24
    25  import org.joda.time.DateTime;
    26  import org.opensaml.Configuration;
    27  import org.opensaml.common.SAMLObject;
    28  import org.opensaml.common.SAMLObjectBuilder;
    29  import org.opensaml.common.SAMLVersion;
    30  import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
    31  import org.opensaml.saml1.core.Assertion;
    32  import org.opensaml.saml1.core.AttributeQuery;
    33  import org.opensaml.saml1.core.AttributeStatement;
    34  import org.opensaml.saml1.core.Audience;
    35  import org.opensaml.saml1.core.AudienceRestrictionCondition;
    36  import org.opensaml.saml1.core.Conditions;
    37  import org.opensaml.saml1.core.ConfirmationMethod;
    38  import org.opensaml.saml1.core.NameIdentifier;
    39  import org.opensaml.saml1.core.RequestAbstractType;
    40  import org.opensaml.saml1.core.Response;
    41  import org.opensaml.saml1.core.ResponseAbstractType;
    42  import org.opensaml.saml1.core.Statement;
    43  import org.opensaml.saml1.core.Status;
    44  import org.opensaml.saml1.core.StatusCode;
    45  import org.opensaml.saml1.core.StatusMessage;
    46  import org.opensaml.saml1.core.Subject;
    47  import org.opensaml.saml1.core.SubjectConfirmation;
    48  import org.opensaml.saml2.metadata.SPSSODescriptor;
    49  import org.opensaml.ws.message.encoder.MessageEncodingException;
    50  import org.opensaml.xml.XMLObjectBuilder;
    51  import org.opensaml.xml.io.Marshaller;
    52  import org.opensaml.xml.io.MarshallingException;
    53  import org.opensaml.xml.security.SecurityException;
    54  import org.opensaml.xml.security.SecurityHelper;
    55  import org.opensaml.xml.security.credential.Credential;
    56  import org.opensaml.xml.signature.Signature;
    57  import org.opensaml.xml.signature.SignatureException;
    58  import org.opensaml.xml.signature.Signer;
    59  import org.opensaml.xml.util.Pair;
    60  import org.slf4j.Logger;
    61  import org.slf4j.LoggerFactory;
    62
    63  import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
    64  import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
    65  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
    66  import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML1NameIdentifierEncoder;
    67  import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
    68  import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
    69  import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
    70  import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
    71  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
    72  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.AbstractSAML1ProfileConfiguration;
    73  import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
    74  import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
    75  import edu.internet2.middleware.shibboleth.idp.session.Session;
    76
    77  /** Common implementation details for profile handlers. */
    78  public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHandler {
    79
    80      /** SAML Version for this profile handler. */
    81      public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_11;
    82
    83      /** Class logger. */
    84      private static Logger log = LoggerFactory.getLogger(AbstractSAML1ProfileHandler.class);
    85
    86      /** Builder of Response objects. */
    87      private SAMLObjectBuilder<Response> responseBuilder;
    88
    89      /** Builder of Assertion objects. */
    90      private SAMLObjectBuilder<Assertion> assertionBuilder;
    91
    92      /** Builder of Conditions objects. */
    93      private SAMLObjectBuilder<Conditions> conditionsBuilder;
    94
    95      /** Builder of AudienceRestrictionCondition objects. */
    96      private SAMLObjectBuilder<AudienceRestrictionCondition> audienceRestrictionConditionBuilder;
    97
    98      /** Builder of AudienceRestrictionCondition objects. */
    99      private SAMLObjectBuilder<Audience> audienceBuilder;
    100
    101     /** Builder of SubjectConfirmation objects. */
    102     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
    103
    104     /** Builder of ConfirmationMethod objects. */
    105     private SAMLObjectBuilder<ConfirmationMethod> confirmationMethodBuilder;
    106
    107     /** Builder of Subject objects. */
    108     private SAMLObjectBuilder<Subject> subjectBuilder;
    109
    110     /** Builder for Status objects. */
    111     private SAMLObjectBuilder<Status> statusBuilder;
    112
    113     /** Builder for StatusCode objects. */
    114     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
    115
    116     /** Builder for StatusMessage objects. */
    117     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
    118
    119     /** For building signature. */
    120     private XMLObjectBuilder<Signature> signatureBuilder;
    121
    122     /**
    123      * Default constructor.
    124      */
    125     @SuppressWarnings("unchecked")
    126     public AbstractSAML1ProfileHandler() {
    127         super();
    128         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
    129         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
    130                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
    131         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
    132                 Conditions.DEFAULT_ELEMENT_NAME);
    133         audienceRestrictionConditionBuilder = (SAMLObjectBuilder<AudienceRestrictionCondition>) getBuilderFactory()
    134                 .getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
    135         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
    136         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
    137                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
    138         confirmationMethodBuilder = (SAMLObjectBuilder<ConfirmationMethod>) getBuilderFactory().getBuilder(
    139                 ConfirmationMethod.DEFAULT_ELEMENT_NAME);
    140         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
    141         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
    142         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
    143                 StatusCode.DEFAULT_ELEMENT_NAME);
    144         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
    145                 StatusMessage.DEFAULT_ELEMENT_NAME);
    146         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
    147     }
    148
    149     /** {@inheritDoc} */
    150     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
    151         BaseSAML1ProfileRequestContext saml1Request = (BaseSAML1ProfileRequestContext) requestContext;
    152         try {
    153             super.populateRequestContext(requestContext);
    154         } catch (ProfileException e) {
    155             if (saml1Request.getFailureStatus() == null) {
    156                 saml1Request.setFailureStatus(buildStatus(StatusCode.REQUESTER, null, e.getMessage()));
    157             }
    158             throw e;
    159         }
    160     }
    161
    162     /**
    163      * Populates the request context with the information about the user.
    164      *
    165      * This method requires the the following request context properties to be populated: inbound message transport,
    166      * relying party ID
    167      *
    168      * This methods populates the following request context properties: user's session, user's principal name, and
    169      * service authentication method
    170      *
    171      * @param requestContext current request context
    172      */
    173     protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
    174         Session userSession = getUserSession(requestContext.getInboundMessageTransport());
    175         if (userSession == null) {
    176             NameIdentifier subject = (NameIdentifier) requestContext.getSubjectNameIdentifier();
    177             if (subject != null && subject.getNameIdentifier() != null) {
    178                 userSession = getUserSession(subject.getNameIdentifier());
    179             }
    180         }
    181
    182         if (userSession != null) {
    183             requestContext.setUserSession(userSession);
    184             requestContext.setPrincipalName(userSession.getPrincipalName());
    185             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
    186                     requestContext.getInboundMessageIssuer());
    187             if (serviceInfo != null) {
    188                 requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
    189                         .getAuthenticationMethod());
    190             }
    191         }
    192     }
    193
    194     /**
    195      * Checks that the SAML major version for a request is 1.
    196      *
    197      * @param requestContext current request context containing the SAML message
    198      *
    199      * @throws ProfileException thrown if the major version of the SAML request is not 1
    200      */
    201     protected void checkSamlVersion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    202         SAMLObject samlObject = requestContext.getInboundSAMLMessage();
    203
    204         if (samlObject instanceof RequestAbstractType) {
    205             RequestAbstractType request = (RequestAbstractType) samlObject;
    206             if (request.getMajorVersion() < 1) {
    207                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_LOW,
    208                         null));
    209                 throw new ProfileException("SAML request major version too low");
    210             } else if (request.getMajorVersion() > 1) {
    211                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_HIGH,
    212                         null));
    213                 throw new ProfileException("SAML request major version too low");
    214             }
    215         }
    216     }
    217
    218     /**
    219      * Builds a response to the attribute query within the request context.
    220      *
    221      * @param requestContext current request context
    222      * @param statements the statements to include in the response
    223      *
    224      * @return the built response
    225      *
    226      * @throws ProfileException thrown if there is a problem creating the SAML response
    227      */
    228     protected Response buildResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, List<Statement> statements)
    229             throws ProfileException {
    230
    231         DateTime issueInstant = new DateTime();
    232
    233         // create the SAML response and add the assertion
    234         Response samlResponse = responseBuilder.buildObject();
    235         samlResponse.setIssueInstant(issueInstant);
    236         populateStatusResponse(requestContext, samlResponse);
    237
    238         // create the assertion and add the attribute statement
    239         Assertion assertion = null;
    240         if (statements != null && !statements.isEmpty()) {
    241             assertion = buildAssertion(requestContext, issueInstant);
    242             assertion.getStatements().addAll(statements);
    243             samlResponse.getAssertions().add(assertion);
    244             signAssertion(requestContext, assertion);
    245         }
    246
    247         Status status = buildStatus(StatusCode.SUCCESS, null, null);
    248         samlResponse.setStatus(status);
    249
    250         return samlResponse;
    251     }
    252
    253     /**
    254      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
    255      *
    256      * @param requestContext current request context
    257      * @param issueInstant time to use as assertion issue instant
    258      *
    259      * @return the built assertion
    260      */
    261     protected Assertion buildAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
    262         Assertion assertion = assertionBuilder.buildObject();
    263         assertion.setID(getIdGenerator().generateIdentifier());
    264         assertion.setIssueInstant(issueInstant);
    265         assertion.setVersion(SAMLVersion.VERSION_11);
    266         assertion.setIssuer(requestContext.getLocalEntityId());
    267
    268         Conditions conditions = buildConditions(requestContext, issueInstant);
    269         assertion.setConditions(conditions);
    270
    271         return assertion;
    272     }
    273
    274     /**
    275      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
    276      * restrictions, and proxy restrictions.
    277      *
    278      * @param requestContext current request context
    279      * @param issueInstant timestamp the assertion was created
    280      *
    281      * @return constructed conditions
    282      */
    283     protected Conditions buildConditions(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
    284         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    285
    286         Conditions conditions = conditionsBuilder.buildObject();
    287         conditions.setNotBefore(issueInstant);
    288         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
    289
    290         Collection<String> audiences;
    291
    292         AudienceRestrictionCondition audienceRestriction = audienceRestrictionConditionBuilder.buildObject();
    293         conditions.getAudienceRestrictionConditions().add(audienceRestriction);
    294
    295         Audience audience = audienceBuilder.buildObject();
    296         audience.setUri(requestContext.getInboundMessageIssuer());
    297         audienceRestriction.getAudiences().add(audience);
    298
    299         // add other audience restrictions
    300         audiences = profileConfig.getAssertionAudiences();
    301         if (audiences != null && audiences.size() > 0) {
    302             for (String audienceUri : audiences) {
    303                 audience = audienceBuilder.buildObject();
    304                 audience.setUri(audienceUri);
    305                 audienceRestriction.getAudiences().add(audience);
    306             }
    307         }
    308
    309         return conditions;
    310     }
    311
    312     /**
    313      * Builds the SAML subject for the user for the service provider.
    314      *
    315      * @param requestContext current request context
    316      * @param confirmationMethod subject confirmation method used for the subject
    317      *
    318      * @return SAML subject for the user for the service provider
    319      *
    320      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
    321      *             name ID attribute or because there are no supported name formats
    322      */
    323     protected Subject buildSubject(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod)
    324             throws ProfileException {
    325
    326         ConfirmationMethod method = confirmationMethodBuilder.buildObject();
    327         method.setConfirmationMethod(confirmationMethod);
    328
    329         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
    330         subjectConfirmation.getConfirmationMethods().add(method);
    331
    332         Subject subject = subjectBuilder.buildObject();
    333         subject.setSubjectConfirmation(subjectConfirmation);
    334
    335         NameIdentifier nameID = buildNameId(requestContext);
    336         if (nameID != null) {
    337             subject.setNameIdentifier(nameID);
    338             requestContext.setSubjectNameIdentifier(nameID);
    339         }
    340
    341         return subject;
    342     }
    343
    344     /**
    345      * Builds a NameIdentifier appropriate for this request. NameIdentifier are built by inspecting the SAML request and
    346      * metadata, picking a name format that was requested by the relying party or is mutually supported by both the
    347      * relying party and asserting party as described in their metadata entries. Once a set of supported name formats is
    348      * determined the principals attributes are inspected for an attribute supported an attribute encoder whose category
    349      * is one of the supported name formats.
    350      *
    351      * @param requestContext current request context
    352      *
    353      * @return the NameIdentifier appropriate for this request
    354      *
    355      * @throws ProfileException thrown if a NameIdentifier can not be created either because there was a problem
    356      *             encoding the name ID attribute or because there are no supported name formats
    357      */
    358     protected NameIdentifier buildNameId(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext)
    359             throws ProfileException {
    360         Pair<BaseAttribute, SAML1NameIdentifierEncoder> nameIdAttributeAndEncoder = null;
    361         try {
    362             nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(SAML1NameIdentifierEncoder.class,
    363                     requestContext);
    364         } catch (ProfileException e) {
    365             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
    366                     "Required NameIdentifier format not supported"));
    367             throw e;
    368         }
    369
    370         if (nameIdAttributeAndEncoder == null) {
    371             return null;
    372         }
    373
    374         BaseAttribute<?> nameIdAttribute = nameIdAttributeAndEncoder.getFirst();
    375         requestContext.setNameIdentifierAttribute(nameIdAttribute);
    376         SAML1NameIdentifierEncoder nameIdEncoder = nameIdAttributeAndEncoder.getSecond();
    377
    378         try {
    379             log
    380                     .debug(
    381                             "Using attribute '{}' supporting name format '{}' to create the NameIdentifier for relying party '{}'",
    382                             new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
    383                                     requestContext.getInboundMessageIssuer(), });
    384             NameIdentifier nameId = nameIdEncoder.encode(nameIdAttribute);
    385             if (nameId.getNameQualifier() == null) {
    386                 nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
    387             }
    388             return nameId;
    389         } catch (AttributeEncodingException e) {
    390             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Unable to encode NameIdentifier"));
    391             String msg = "Unable to encode NameIdentifier for relying party "
    392                     + requestContext.getInboundMessageIssuer();
    393             log.error(msg, e);
    394             throw new ProfileException(msg, e);
    395         }
    396     }
    397
    398     /**
    399      * Constructs an SAML response message carrying a request error.
    400      *
    401      * @param requestContext current request context containing the failure status
    402      *
    403      * @return the constructed error response
    404      */
    405     protected Response buildErrorResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) {
    406         Response samlResponse = responseBuilder.buildObject();
    407         samlResponse.setIssueInstant(new DateTime());
    408         populateStatusResponse(requestContext, samlResponse);
    409
    410         samlResponse.setStatus(requestContext.getFailureStatus());
    411
    412         return samlResponse;
    413     }
    414
    415     /**
    416      * Populates the response's id, in response to, issue instant, version, and issuer properties.
    417      *
    418      * @param requestContext current request context
    419      * @param response the response to populate
    420      */
    421     protected void populateStatusResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
    422             ResponseAbstractType response) {
    423         response.setID(getIdGenerator().generateIdentifier());
    424
    425         SAMLObject samlMessage = requestContext.getInboundSAMLMessage();
    426         if (samlMessage != null && samlMessage instanceof RequestAbstractType) {
    427             response.setInResponseTo(((RequestAbstractType) samlMessage).getID());
    428         }
    429
    430         response.setVersion(SAMLVersion.VERSION_11);
    431     }
    432
    433     /**
    434      * Build a status message, with an optional second-level failure message.
    435      *
    436      * @param topLevelCode top-level status code
    437      * @param secondLevelCode second-level status code
    438      * @param failureMessage An optional second-level failure message
    439      *
    440      * @return a Status object.
    441      */
    442     protected Status buildStatus(QName topLevelCode, QName secondLevelCode, String failureMessage) {
    443         Status status = statusBuilder.buildObject();
    444
    445         StatusCode statusCode = statusCodeBuilder.buildObject();
    446         statusCode.setValue(topLevelCode);
    447         status.setStatusCode(statusCode);
    448
    449         if (secondLevelCode != null) {
    450             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
    451             secondLevelStatusCode.setValue(secondLevelCode);
    452             statusCode.setStatusCode(secondLevelStatusCode);
    453         }
    454
    455         if (failureMessage != null) {
    456             StatusMessage msg = statusMessageBuilder.buildObject();
    457             msg.setMessage(failureMessage);
    458             status.setStatusMessage(msg);
    459         }
    460
    461         return status;
    462     }
    463
    464     /**
    465      * Resolved the attributes for the principal.
    466      *
    467      * @param requestContext current request context
    468      *
    469      * @throws ProfileException thrown if there is a problem resolving the attributes for the subject.
    470      */
    471     protected void resolveAttributes(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    472         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    473         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    474
    475         try {
    476             log.debug("Resolving attributes for principal '{}' for SAML request from relying party '{}'",
    477                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
    478             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
    479
    480             requestContext.setAttributes(principalAttributes);
    481         } catch (AttributeRequestException e) {
    482             log
    483                     .warn(
    484                             "Error resolving attributes for principal '{}'.  No name identifier or attribute statement will be included in response",
    485                             requestContext.getPrincipalName());
    486         }
    487     }
    488
    489     /**
    490      * Executes a query for attributes and builds a SAML attribute statement from the results.
    491      *
    492      * @param requestContext current request context
    493      * @param subjectConfMethod subject confirmation method
    494      *
    495      * @return attribute statement resulting from the query
    496      *
    497      * @throws ProfileException thrown if there is a problem making the query
    498      */
    499     protected AttributeStatement buildAttributeStatement(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
    500             String subjectConfMethod) throws ProfileException {
    501
    502         if (requestContext.getAttributes() == null) {
    503             return null;
    504         }
    505
    506         log.debug(
    507                 "Creating attribute statement about principal '{}'in response to SAML request from relying party '{}'",
    508                 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
    509         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    510         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    511
    512         try {
    513             AttributeStatement statment;
    514             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
    515                 statment = attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
    516                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
    517             } else {
    518                 statment = attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
    519             }
    520
    521             if (statment != null) {
    522                 Subject statementSubject = buildSubject(requestContext, subjectConfMethod);
    523                 statment.setSubject(statementSubject);
    524             }
    525
    526             return statment;
    527         } catch (AttributeRequestException e) {
    528             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
    529             String msg = "Error encoding attributes for principal " + requestContext.getPrincipalName();
    530             log.error(msg, e);
    531             throw new ProfileException(msg, e);
    532         }
    533     }
    534
    535     /**
    536      * Resolves the principal name of the subject of the request.
    537      *
    538      * @param requestContext current request context
    539      *
    540      * @throws ProfileException thrown if the principal name can not be resolved
    541      */
    542     protected void resolvePrincipal(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    543         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    544         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    545
    546         log.debug("Resolving principal name for subject of SAML request from relying party '{}'", requestContext
    547                 .getInboundMessageIssuer());
    548
    549         try {
    550             String principal = attributeAuthority.getPrincipal(requestContext);
    551             requestContext.setPrincipalName(principal);
    552         } catch (AttributeRequestException e) {
    553             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
    554                     "Error resolving principal"));
    555             String msg = "Error resolving principal name for SAML request from relying party '"
    556                     + requestContext.getInboundMessageIssuer() + "'. Cause: " + e.getMessage();
    557             log.warn(msg);
    558             throw new ProfileException(msg, e);
    559         }
    560     }
    561
    562     /**
    563      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
    564      * signing credentials.
    565      *
    566      * @param requestContext current request context
    567      * @param assertion assertion to sign
    568      *
    569      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
    570      *             required, if a signing credential is not configured
    571      */
    572     protected void signAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
    573             throws ProfileException {
    574         log.debug("Determining if SAML assertion to relying party '{}' should be signed", requestContext
    575                 .getInboundMessageIssuer());
    576
    577         boolean signAssertion = isSignAssertion(requestContext);
    578
    579         if (!signAssertion) {
    580             return;
    581         }
    582
    583         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    584
    585         log.debug("Determining credential to use to sign assertion to relying party '{}'", requestContext
    586                 .getInboundMessageIssuer());
    587         Credential signatureCredential = profileConfig.getSigningCredential();
    588         if (signatureCredential == null) {
    589             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
    590         }
    591
    592         if (signatureCredential == null) {
    593             String msg = "No signing credential is specified for relying party configuration "
    594                     + requestContext.getRelyingPartyConfiguration().getProviderId();
    595             log.warn(msg);
    596             throw new ProfileException(msg);
    597         }
    598
    599         log.debug("Signing assertion to relying party '{}'", requestContext.getInboundMessageIssuer());
    600         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
    601
    602         signature.setSigningCredential(signatureCredential);
    603         try {
    604             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
    605             // TODO how to pull what keyInfoGenName to use?
    606             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
    607         } catch (SecurityException e) {
    608             String msg = "Error preparing signature for signing";
    609             log.error(msg);
    610             throw new ProfileException(msg, e);
    611         }
    612
    613         assertion.setSignature(signature);
    614
    615         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
    616         try {
    617             assertionMarshaller.marshall(assertion);
    618             Signer.signObject(signature);
    619         } catch (MarshallingException e) {
    620             String errMsg = "Unable to marshall assertion for signing";
    621             log.error(errMsg, e);
    622             throw new ProfileException(errMsg, e);
    623         } catch (SignatureException e) {
    624             String msg = "Unable to sign assertion";
    625             log.error(msg, e);
    626             throw new ProfileException(msg, e);
    627         }
    628     }
    629
    630     /**
    631      * Determine whether issued assertions should be signed.
    632      *
    633      * @param requestContext the current request context
    634      * @return true if assertions should be signed, false otherwise
    635      * @throws ProfileException if there is a problem determining whether assertions should be signed
    636      */
    637     protected boolean isSignAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    638
    639         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
    640         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    641
    642         try {
    643             boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
    644                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
    645                             .providesMessageIntegrity(requestContext));
    646
    647             log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
    648                     .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
    649
    650             if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
    651                 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
    652                 if (ssoDescriptor.getWantAssertionsSigned() != null) {
    653                     signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
    654                     log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
    655                             .getInboundMessageIssuer(), signAssertion);
    656                 }
    657             }
    658
    659             return signAssertion;
    660         } catch (MessageEncodingException e) {
    661             log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
    662                     .getBindingURI());
    663             throw new ProfileException("Unable to determine if outbound assertion should be signed");
    664         }
    665     }
    666
    667     /**
    668      * Writes an audit log entry indicating the successful response to the attribute request.
    669      *
    670      * @param context current request context
    671      */
    672     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
    673         SAML1AuditLogEntry auditLogEntry = new SAML1AuditLogEntry();
    674         auditLogEntry.setSAMLResponse((Response) context.getOutboundSAMLMessage());
    675         auditLogEntry.setMessageProfile(getProfileId());
    676         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
    677         auditLogEntry.setPrincipalName(context.getPrincipalName());
    678         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
    679         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
    680         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
    681         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
    682         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
    683         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
    684         if (context.getReleasedAttributes() != null) {
    685             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
    686         }
    687
    688         if (context.getNameIdentifierAttribute() != null) {
    689             Object idValue = context.getNameIdentifierAttribute().getValues().iterator().next();
    690             if(idValue != null){
    691                 auditLogEntry.setNameIdValue(idValue.toString());
    692             }
    693         }
    694
    695         getAduitLog().info(auditLogEntry.toString());
    696     }
    697
    698     /** SAML 1 specific audit log entry. */
    699     protected class SAML1AuditLogEntry extends AuditLogEntry {
    700
    701         /** The response to the SAML 1 request. */
    702         private Response samlResponse;
    703
    704         /**
    705          * Gets the response to the SAML 1 request.
    706          *
    707          * @return the response to the SAML 1 request
    708          */
    709         public Response getSAMLResponse() {
    710             return samlResponse;
    711         }
    712
    713         /**
    714          * Sets the response to the SAML 1 request.
    715          *
    716          * @param response the response to the SAML 1 request
    717          */
    718         public void setSAMLResponse(Response response) {
    719             samlResponse = response;
    720         }
    721
    722         /** {@inheritDoc} */
    723         public String toString() {
    724             StringBuilder entryString = new StringBuilder(super.toString());
    725
    726             StringBuilder assertionIds = new StringBuilder();
    727             List<Assertion> assertions = samlResponse.getAssertions();
    728             if (assertions != null && !assertions.isEmpty()) {
    729                 for (Assertion assertion : assertions) {
    730                     assertionIds.append(assertion.getID());
    731                     assertionIds.append(",");
    732                 }
    733             }
    734
    735             if (getNameIdValue() != null) {
    736                 entryString.append(getNameIdValue());
    737             }
    738             entryString.append("|");
    739
    740             entryString.append(assertionIds.toString());
    741             entryString.append("|");
    742
    743             return entryString.toString();
    744         }
    745     }
    746 }