Some refactoring in profile handlers to expose explicit methods for the determination
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractSAML2ProfileHandler.java
index d987f0f..6bfb0bd 100644 (file)
@@ -266,34 +266,25 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
 
             signAssertion(requestContext, assertion);
 
-            SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
-            try {
-                if (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
-                        || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional && !encoder
-                                .providesMessageConfidentiality(requestContext))) {
-                    log.debug("Attempting to encrypt assertion to relying party '{}'", requestContext
-                            .getInboundMessageIssuer());
-                    try {
-                        Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
-                        samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
-                    } catch (SecurityException e) {
-                        log.error("Unable to construct encrypter", e);
-                        requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
-                                "Unable to encrypt assertion"));
-                        throw new ProfileException("Unable to construct encrypter", e);
-                    } catch (EncryptionException e) {
-                        log.error("Unable to encrypt assertion", e);
-                        requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
-                                "Unable to encrypt assertion"));
-                        throw new ProfileException("Unable to encrypt assertion", e);
-                    }
-                } else {
-                    samlResponse.getAssertions().add(assertion);
+            if (isEncryptAssertion(requestContext)) {
+                log.debug("Attempting to encrypt assertion to relying party '{}'", requestContext
+                        .getInboundMessageIssuer());
+                try {
+                    Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
+                    samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
+                } catch (SecurityException e) {
+                    log.error("Unable to construct encrypter", e);
+                    requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+                    "Unable to encrypt assertion"));
+                    throw new ProfileException("Unable to construct encrypter", e);
+                } catch (EncryptionException e) {
+                    log.error("Unable to encrypt assertion", e);
+                    requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+                    "Unable to encrypt assertion"));
+                    throw new ProfileException("Unable to encrypt assertion", e);
                 }
-            } catch (MessageEncodingException e) {
-                log.error("Unable to determine if outbound encoding '{}' can provide confidentiality", encoder
-                        .getBindingURI());
-                throw new ProfileException("Unable to determine if assertions should be encrypted");
+            } else {
+                samlResponse.getAssertions().add(assertion);
             }
         }
 
@@ -306,6 +297,28 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     }
 
     /**
+     * Determine whether issued assertions should be encrypted.
+     * 
+     * @param requestContext the current request context
+     * @return true if assertions should be encrypted, false otherwise
+     * @throws ProfileException if there is a problem determining whether assertions should be encrypted
+     */
+    protected boolean isEncryptAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) 
+            throws ProfileException {
+        
+        SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
+        try {
+            return requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
+                || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional
+                    && !encoder.providesMessageConfidentiality(requestContext));
+        } catch (MessageEncodingException e) {
+            log.error("Unable to determine if outbound encoding '{}' can provide confidentiality", encoder
+                    .getBindingURI());
+            throw new ProfileException("Unable to determine if assertions should be encrypted"); 
+        }
+    }
+
+    /**
      * Extension point for for subclasses to post-process the Response before it is signed and encoded.
      * 
      * @param requestContext the current request context
@@ -541,36 +554,13 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         log.debug("Determining if SAML assertion to relying party '{}' should be signed", requestContext
                 .getInboundMessageIssuer());
 
-        boolean signAssertion = false;
-
-        SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
-        AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
-        try {
-            if (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
-                    || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
-                            .providesMessageIntegrity(requestContext))) {
-                signAssertion = true;
-                log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
-                        .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
-            }
-        } catch (MessageEncodingException e) {
-            log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
-                    .getBindingURI());
-            throw new ProfileException("Unable to determine if outbound message should be signed");
-        }
-
-        if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
-            SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
-            if (ssoDescriptor.getWantAssertionsSigned() != null) {
-                signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
-                log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
-                        .getInboundMessageIssuer(), signAssertion);
-            }
-        }
+        boolean signAssertion = isSignAssertion(requestContext);
 
         if (!signAssertion) {
             return;
         }
+        
+        AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
 
         log.debug("Determining signing credntial for assertion to relying party '{}'", requestContext
                 .getInboundMessageIssuer());
@@ -619,6 +609,43 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
     }
 
     /**
+     * Determine whether issued assertions should be signed.
+     * 
+     * @param requestContext the current request context
+     * @return true if assertions should be signed, false otherwise
+     * @throws ProfileException if there is a problem determining whether assertions should be signed
+     */
+    protected boolean isSignAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
+        
+        SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
+        AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
+        
+        try {
+            boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
+                || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional 
+                    && !encoder.providesMessageIntegrity(requestContext));
+            
+            log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
+                    .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
+            
+            if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
+                SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
+                if (ssoDescriptor.getWantAssertionsSigned() != null) {
+                    signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
+                    log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
+                            .getInboundMessageIssuer(), signAssertion);
+                }
+            }
+            
+            return signAssertion;
+        } catch (MessageEncodingException e) {
+            log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
+                    .getBindingURI());
+            throw new ProfileException("Unable to determine if outbound assertion should be signed");
+        }
+    }
+
+    /**
      * Build a status message, with an optional second-level failure message.
      * 
      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
@@ -674,41 +701,52 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
         }
 
         requestContext.setSubjectNameIdentifier(nameID);
-
-        boolean nameIdEncRequiredByAuthnRequest = false;
-        if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
-            AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
-            NameIDPolicy policy = authnRequest.getNameIDPolicy();
-            if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
-                nameIdEncRequiredByAuthnRequest = true;
+        
+        if (isEncryptNameID(requestContext)) {
+            log.debug("Attempting to encrypt NameID to relying party '{}'", requestContext
+                    .getInboundMessageIssuer());
+            try {
+                Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
+                subject.setEncryptedID(encrypter.encrypt(nameID));
+            } catch (SecurityException e) {
+                log.error("Unable to construct encrypter", e);
+                requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+                "Unable to encrypt NameID"));
+                throw new ProfileException("Unable to construct encrypter", e);
+            } catch (EncryptionException e) {
+                log.error("Unable to encrypt NameID", e);
+                requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
+                "Unable to encrypt NameID"));
+                throw new ProfileException("Unable to encrypt NameID", e);
             }
+        } else {
+            subject.setNameID(nameID);
         }
 
+
+        return subject;
+    }
+    
+
+    /**
+     * Determine whether NameID's should be encrypted.
+     * 
+     * @param requestContext the current request context
+     * @return true if NameID's should be encrypted, false otherwise
+     * @throws ProfileException if there is a problem determining whether NameID's should be encrypted
+     */
+    protected boolean isEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) 
+            throws ProfileException {
+        
+        boolean nameIdEncRequiredByAuthnRequest = isRequestRequiresEncryptNameID(requestContext);
+        
         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
+        boolean nameIdEncRequiredByConfig = false;
         try {
-            if (nameIdEncRequiredByAuthnRequest
-                    || requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
-                    || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
-                            .providesMessageConfidentiality(requestContext))) {
-                log.debug("Attempting to encrypt NameID to relying party '{}'", requestContext
-                        .getInboundMessageIssuer());
-                try {
-                    Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
-                    subject.setEncryptedID(encrypter.encrypt(nameID));
-                } catch (SecurityException e) {
-                    log.error("Unable to construct encrypter", e);
-                    requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
-                            "Unable to encrypt NameID"));
-                    throw new ProfileException("Unable to construct encrypter", e);
-                } catch (EncryptionException e) {
-                    log.error("Unable to encrypt NameID", e);
-                    requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
-                            "Unable to encrypt NameID"));
-                    throw new ProfileException("Unable to encrypt NameID", e);
-                }
-            } else {
-                subject.setNameID(nameID);
-            }
+            nameIdEncRequiredByConfig = 
+                requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
+                    || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional
+                            && !encoder.providesMessageConfidentiality(requestContext));
         } catch (MessageEncodingException e) {
             String msg = MessageFormatter.format(
                     "Unable to determine if outbound encoding '{}' provides message confidentiality protection",
@@ -716,8 +754,27 @@ public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHan
             log.error(msg);
             throw new ProfileException(msg);
         }
+        
+        return nameIdEncRequiredByAuthnRequest || nameIdEncRequiredByConfig;
+    }
 
-        return subject;
+    /**
+     * Determine whether information in the SAML request requires the issued NameID to 
+     * be encrypted.
+     * 
+     * @param requestContext the current request context
+     * @return true if the request indicates NameID encryption is required, false otherwise
+     */
+    protected boolean isRequestRequiresEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
+        boolean nameIdEncRequiredByAuthnRequest = false;
+        if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
+            AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
+            NameIDPolicy policy = authnRequest.getNameIDPolicy();
+            if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
+                nameIdEncRequiredByAuthnRequest = true;
+            }
+        }
+        return nameIdEncRequiredByAuthnRequest;
     }
 
     /**