View Javadoc

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