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             nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
    386             return nameId;
    387         } catch (AttributeEncodingException e) {
    388             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Unable to encode NameIdentifier"));
    389             String msg = "Unable to encode NameIdentifier for relying party "
    390                     + requestContext.getInboundMessageIssuer();
    391             log.error(msg, e);
    392             throw new ProfileException(msg, e);
    393         }
    394     }
    395
    396     /**
    397      * Constructs an SAML response message carrying a request error.
    398      *
    399      * @param requestContext current request context containing the failure status
    400      *
    401      * @return the constructed error response
    402      */
    403     protected Response buildErrorResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) {
    404         Response samlResponse = responseBuilder.buildObject();
    405         samlResponse.setIssueInstant(new DateTime());
    406         populateStatusResponse(requestContext, samlResponse);
    407
    408         samlResponse.setStatus(requestContext.getFailureStatus());
    409
    410         return samlResponse;
    411     }
    412
    413     /**
    414      * Populates the response's id, in response to, issue instant, version, and issuer properties.
    415      *
    416      * @param requestContext current request context
    417      * @param response the response to populate
    418      */
    419     protected void populateStatusResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
    420             ResponseAbstractType response) {
    421         response.setID(getIdGenerator().generateIdentifier());
    422
    423         SAMLObject samlMessage = requestContext.getInboundSAMLMessage();
    424         if (samlMessage != null && samlMessage instanceof RequestAbstractType) {
    425             response.setInResponseTo(((RequestAbstractType) samlMessage).getID());
    426         }
    427
    428         response.setVersion(SAMLVersion.VERSION_11);
    429     }
    430
    431     /**
    432      * Build a status message, with an optional second-level failure message.
    433      *
    434      * @param topLevelCode top-level status code
    435      * @param secondLevelCode second-level status code
    436      * @param failureMessage An optional second-level failure message
    437      *
    438      * @return a Status object.
    439      */
    440     protected Status buildStatus(QName topLevelCode, QName secondLevelCode, String failureMessage) {
    441         Status status = statusBuilder.buildObject();
    442
    443         StatusCode statusCode = statusCodeBuilder.buildObject();
    444         statusCode.setValue(topLevelCode);
    445         status.setStatusCode(statusCode);
    446
    447         if (secondLevelCode != null) {
    448             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
    449             secondLevelStatusCode.setValue(secondLevelCode);
    450             statusCode.setStatusCode(secondLevelStatusCode);
    451         }
    452
    453         if (failureMessage != null) {
    454             StatusMessage msg = statusMessageBuilder.buildObject();
    455             msg.setMessage(failureMessage);
    456             status.setStatusMessage(msg);
    457         }
    458
    459         return status;
    460     }
    461
    462     /**
    463      * Resolved the attributes for the principal.
    464      *
    465      * @param requestContext current request context
    466      *
    467      * @throws ProfileException thrown if there is a problem resolving the attributes for the subject.
    468      */
    469     protected void resolveAttributes(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    470         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    471         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    472
    473         try {
    474             log.debug("Resolving attributes for principal '{}' for SAML request from relying party '{}'",
    475                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
    476             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
    477
    478             requestContext.setAttributes(principalAttributes);
    479         } catch (AttributeRequestException e) {
    480             log
    481                     .warn(
    482                             "Error resolving attributes for principal '{}'.  No name identifier or attribute statement will be included in response",
    483                             requestContext.getPrincipalName());
    484         }
    485     }
    486
    487     /**
    488      * Executes a query for attributes and builds a SAML attribute statement from the results.
    489      *
    490      * @param requestContext current request context
    491      * @param subjectConfMethod subject confirmation method
    492      *
    493      * @return attribute statement resulting from the query
    494      *
    495      * @throws ProfileException thrown if there is a problem making the query
    496      */
    497     protected AttributeStatement buildAttributeStatement(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
    498             String subjectConfMethod) throws ProfileException {
    499
    500         if (requestContext.getAttributes() == null) {
    501             return null;
    502         }
    503
    504         log.debug(
    505                 "Creating attribute statement about principal '{}'in response to SAML request from relying party '{}'",
    506                 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
    507         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    508         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    509
    510         try {
    511             AttributeStatement statment;
    512             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
    513                 statment = attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
    514                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
    515             } else {
    516                 statment = attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
    517             }
    518
    519             if (statment != null) {
    520                 Subject statementSubject = buildSubject(requestContext, subjectConfMethod);
    521                 statment.setSubject(statementSubject);
    522             }
    523
    524             return statment;
    525         } catch (AttributeRequestException e) {
    526             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
    527             String msg = "Error encoding attributes for principal " + requestContext.getPrincipalName();
    528             log.error(msg, e);
    529             throw new ProfileException(msg, e);
    530         }
    531     }
    532
    533     /**
    534      * Resolves the principal name of the subject of the request.
    535      *
    536      * @param requestContext current request context
    537      *
    538      * @throws ProfileException thrown if the principal name can not be resolved
    539      */
    540     protected void resolvePrincipal(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    541         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    542         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    543
    544         log.debug("Resolving principal name for subject of SAML request from relying party '{}'", requestContext
    545                 .getInboundMessageIssuer());
    546
    547         try {
    548             String principal = attributeAuthority.getPrincipal(requestContext);
    549             requestContext.setPrincipalName(principal);
    550         } catch (AttributeRequestException e) {
    551             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
    552                     "Error resolving principal"));
    553             String msg = "Error resolving principal name for SAML request from relying party '"
    554                     + requestContext.getInboundMessageIssuer() + "'. Cause: " + e.getMessage();
    555             log.warn(msg);
    556             throw new ProfileException(msg, e);
    557         }
    558     }
    559
    560     /**
    561      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
    562      * signing credentials.
    563      *
    564      * @param requestContext current request context
    565      * @param assertion assertion to sign
    566      *
    567      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
    568      *             required, if a signing credential is not configured
    569      */
    570     protected void signAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
    571             throws ProfileException {
    572         log.debug("Determining if SAML assertion to relying party '{}' should be signed", requestContext
    573                 .getInboundMessageIssuer());
    574
    575         boolean signAssertion = isSignAssertion(requestContext);
    576
    577         if (!signAssertion) {
    578             return;
    579         }
    580
    581         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    582
    583         log.debug("Determining credential to use to sign assertion to relying party '{}'", requestContext
    584                 .getInboundMessageIssuer());
    585         Credential signatureCredential = profileConfig.getSigningCredential();
    586         if (signatureCredential == null) {
    587             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
    588         }
    589
    590         if (signatureCredential == null) {
    591             String msg = "No signing credential is specified for relying party configuration "
    592                     + requestContext.getRelyingPartyConfiguration().getProviderId();
    593             log.warn(msg);
    594             throw new ProfileException(msg);
    595         }
    596
    597         log.debug("Signing assertion to relying party '{}'", requestContext.getInboundMessageIssuer());
    598         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
    599
    600         signature.setSigningCredential(signatureCredential);
    601         try {
    602             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
    603             // TODO how to pull what keyInfoGenName to use?
    604             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
    605         } catch (SecurityException e) {
    606             String msg = "Error preparing signature for signing";
    607             log.error(msg);
    608             throw new ProfileException(msg, e);
    609         }
    610
    611         assertion.setSignature(signature);
    612
    613         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
    614         try {
    615             assertionMarshaller.marshall(assertion);
    616             Signer.signObject(signature);
    617         } catch (MarshallingException e) {
    618             String errMsg = "Unable to marshall assertion for signing";
    619             log.error(errMsg, e);
    620             throw new ProfileException(errMsg, e);
    621         } catch (SignatureException e) {
    622             String msg = "Unable to sign assertion";
    623             log.error(msg, e);
    624             throw new ProfileException(msg, e);
    625         }
    626     }
    627
    628     /**
    629      * Determine whether issued assertions should be signed.
    630      *
    631      * @param requestContext the current request context
    632      * @return true if assertions should be signed, false otherwise
    633      * @throws ProfileException if there is a problem determining whether assertions should be signed
    634      */
    635     protected boolean isSignAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    636
    637         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
    638         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    639
    640         try {
    641             boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
    642                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
    643                             .providesMessageIntegrity(requestContext));
    644
    645             log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
    646                     .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
    647
    648             if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
    649                 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
    650                 if (ssoDescriptor.getWantAssertionsSigned() != null) {
    651                     signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
    652                     log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
    653                             .getInboundMessageIssuer(), signAssertion);
    654                 }
    655             }
    656
    657             return signAssertion;
    658         } catch (MessageEncodingException e) {
    659             log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
    660                     .getBindingURI());
    661             throw new ProfileException("Unable to determine if outbound assertion should be signed");
    662         }
    663     }
    664
    665     /**
    666      * Writes an audit log entry indicating the successful response to the attribute request.
    667      *
    668      * @param context current request context
    669      */
    670     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
    671         SAML1AuditLogEntry auditLogEntry = new SAML1AuditLogEntry();
    672         auditLogEntry.setSAMLResponse((Response) context.getOutboundSAMLMessage());
    673         auditLogEntry.setMessageProfile(getProfileId());
    674         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
    675         auditLogEntry.setPrincipalName(context.getPrincipalName());
    676         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
    677         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
    678         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
    679         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
    680         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
    681         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
    682         if (context.getReleasedAttributes() != null) {
    683             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
    684         }
    685
    686         if (context.getNameIdentifierAttribute() != null) {
    687             Object idValue = context.getNameIdentifierAttribute().getValues().iterator().next();
    688             if(idValue != null){
    689                 auditLogEntry.setNameIdValue(idValue.toString());
    690             }
    691         }
    692
    693         getAduitLog().info(auditLogEntry.toString());
    694     }
    695
    696     /** SAML 1 specific audit log entry. */
    697     protected class SAML1AuditLogEntry extends AuditLogEntry {
    698
    699         /** The response to the SAML 1 request. */
    700         private Response samlResponse;
    701
    702         /**
    703          * Gets the response to the SAML 1 request.
    704          *
    705          * @return the response to the SAML 1 request
    706          */
    707         public Response getSAMLResponse() {
    708             return samlResponse;
    709         }
    710
    711         /**
    712          * Sets the response to the SAML 1 request.
    713          *
    714          * @param response the response to the SAML 1 request
    715          */
    716         public void setSAMLResponse(Response response) {
    717             samlResponse = response;
    718         }
    719
    720         /** {@inheritDoc} */
    721         public String toString() {
    722             StringBuilder entryString = new StringBuilder(super.toString());
    723
    724             StringBuilder assertionIds = new StringBuilder();
    725             List<Assertion> assertions = samlResponse.getAssertions();
    726             if (assertions != null && !assertions.isEmpty()) {
    727                 for (Assertion assertion : assertions) {
    728                     assertionIds.append(assertion.getID());
    729                     assertionIds.append(",");
    730                 }
    731             }
    732
    733             if (getNameIdValue() != null) {
    734                 entryString.append(getNameIdValue());
    735             }
    736             entryString.append("|");
    737
    738             entryString.append(assertionIds.toString());
    739             entryString.append("|");
    740
    741             return entryString.toString();
    742         }
    743     }
    744 }