prevent cast class exception if a user starts a SAML 1 flow, leaves in the middle...
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / SSOProfileHandler.java
index 4e5aae3..d656df6 100644 (file)
@@ -20,11 +20,9 @@ import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
 
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
+import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
+import javax.servlet.http.HttpServletResponse;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -39,17 +37,22 @@ import org.opensaml.saml2.core.AuthnContextClassRef;
 import org.opensaml.saml2.core.AuthnContextDeclRef;
 import org.opensaml.saml2.core.AuthnRequest;
 import org.opensaml.saml2.core.AuthnStatement;
+import org.opensaml.saml2.core.NameID;
+import org.opensaml.saml2.core.NameIDPolicy;
 import org.opensaml.saml2.core.RequestedAuthnContext;
 import org.opensaml.saml2.core.Response;
 import org.opensaml.saml2.core.Statement;
 import org.opensaml.saml2.core.StatusCode;
 import org.opensaml.saml2.core.Subject;
 import org.opensaml.saml2.core.SubjectLocality;
+import org.opensaml.saml2.metadata.AffiliateMember;
+import org.opensaml.saml2.metadata.AffiliationDescriptor;
 import org.opensaml.saml2.metadata.AssertionConsumerService;
 import org.opensaml.saml2.metadata.Endpoint;
 import org.opensaml.saml2.metadata.EntityDescriptor;
 import org.opensaml.saml2.metadata.IDPSSODescriptor;
 import org.opensaml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
 import org.opensaml.ws.message.decoder.MessageDecodingException;
 import org.opensaml.ws.transport.http.HTTPInTransport;
 import org.opensaml.ws.transport.http.HTTPOutTransport;
@@ -63,7 +66,6 @@ import org.opensaml.xml.util.DatatypeHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Element;
-import org.xml.sax.InputSource;
 
 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
@@ -72,11 +74,11 @@ import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfi
 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.SSOConfiguration;
 import edu.internet2.middleware.shibboleth.common.util.HttpHelper;
-import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
 import edu.internet2.middleware.shibboleth.idp.authn.PassiveAuthenticationException;
 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
-import edu.internet2.middleware.shibboleth.idp.profile.saml1.ShibbolethSSOProfileHandler.ShibbolethSSORequestContext;
+import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
 import edu.internet2.middleware.shibboleth.idp.session.Session;
+import edu.internet2.middleware.shibboleth.idp.util.HttpServletHelper;
 
 /** SAML 2.0 SSO request profile handler. */
 public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
@@ -102,19 +104,26 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
     /** Builder of Endpoint objects. */
     private SAMLObjectBuilder<Endpoint> endpointBuilder;
 
-    /** URL of the authentication manager servlet. */
+    /** URL of the authentication manager Servlet. */
     private String authenticationManagerPath;
 
     /**
      * Constructor.
      * 
-     * @param authnManagerPath path to the authentication manager servlet
+     * @param authnManagerPath path to the authentication manager Servlet
      */
     @SuppressWarnings("unchecked")
     public SSOProfileHandler(String authnManagerPath) {
         super();
 
-        authenticationManagerPath = authnManagerPath;
+        if (DatatypeHelper.isEmpty(authnManagerPath)) {
+            throw new IllegalArgumentException("Authentication manager path may not be null");
+        }
+        if (authnManagerPath.startsWith("/")) {
+            authenticationManagerPath = authnManagerPath;
+        } else {
+            authenticationManagerPath = "/" + authnManagerPath;
+        }
 
         authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory().getBuilder(
                 AuthnStatement.DEFAULT_ELEMENT_NAME);
@@ -137,15 +146,22 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
 
     /** {@inheritDoc} */
     public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
-        HttpServletRequest servletRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
+        HttpServletRequest httpRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
+        HttpServletResponse httpResponse = ((HttpServletResponseAdapter) outTransport).getWrappedResponse();
+        ServletContext servletContext = httpRequest.getSession().getServletContext();
 
-        LoginContext loginContext = (LoginContext) servletRequest.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-        if (loginContext == null) {
+        LoginContext loginContext = HttpServletHelper.getLoginContext(getStorageService(),
+                servletContext, httpRequest);
+        if (loginContext == null || !(loginContext instanceof Saml2LoginContext)) {
             log.debug("Incoming request does not contain a login context, processing as first leg of request");
             performAuthentication(inTransport, outTransport);
-        } else {
+        } else if (loginContext.isPrincipalAuthenticated() || loginContext.getAuthenticationFailure() != null) {
             log.debug("Incoming request contains a login context, processing as second leg of request");
-            completeAuthenticationRequest(inTransport, outTransport);
+            HttpServletHelper.unbindLoginContext(getStorageService(), servletContext, httpRequest, httpResponse);
+            completeAuthenticationRequest((Saml2LoginContext)loginContext, inTransport, outTransport);
+        } else {
+            log.debug("Incoming request contained a login context but principal was not authenticated, processing as first leg of request");
+            performAuthentication(inTransport, outTransport);
         }
     }
 
@@ -161,7 +177,9 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
      */
     protected void performAuthentication(HTTPInTransport inTransport, HTTPOutTransport outTransport)
             throws ProfileException {
-        HttpServletRequest servletRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
+        HttpServletRequest httpRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
+        HttpServletResponse httpResponse = ((HttpServletResponseAdapter) outTransport).getWrappedResponse();
+
         SSORequestContext requestContext = new SSORequestContext();
 
         try {
@@ -169,36 +187,34 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
 
             String relyingPartyId = requestContext.getInboundMessageIssuer();
             RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
-            ProfileConfiguration ssoConfig = rpConfig.getProfileConfiguration(SSOConfiguration.PROFILE_ID);
+            ProfileConfiguration ssoConfig = rpConfig.getProfileConfiguration(getProfileId());
             if (ssoConfig == null) {
-                log.error("SAML 2 SSO profile is not configured for relying party "
-                        + requestContext.getInboundMessageIssuer());
-                throw new ProfileException("SAML 2 SSO profile is not configured for relying party "
-                        + requestContext.getInboundMessageIssuer());
+                String msg = "SAML 2 SSO profile is not configured for relying party "
+                        + requestContext.getInboundMessageIssuer();
+                log.warn(msg);
+                throw new ProfileException(msg);
             }
 
             log.debug("Creating login context and transferring control to authentication engine");
             Saml2LoginContext loginContext = new Saml2LoginContext(relyingPartyId, requestContext.getRelayState(),
                     requestContext.getInboundSAMLMessage());
             loginContext.setAuthenticationEngineURL(authenticationManagerPath);
-            loginContext.setProfileHandlerURL(HttpHelper.getRequestUriWithoutContext(servletRequest));
-            if (loginContext.getRequestedAuthenticationMethods().size() == 0
-                    && rpConfig.getDefaultAuthenticationMethod() != null) {
-                loginContext.getRequestedAuthenticationMethods().add(rpConfig.getDefaultAuthenticationMethod());
-            }
+            loginContext.setProfileHandlerURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
+            loginContext.setDefaultAuthenticationMethod(rpConfig.getDefaultAuthenticationMethod());
+
+            HttpServletHelper.bindLoginContext(loginContext, getStorageService(), httpRequest.getSession()
+                    .getServletContext(), httpRequest, httpResponse);
 
-            servletRequest.setAttribute(Saml2LoginContext.LOGIN_CONTEXT_KEY, loginContext);
-            RequestDispatcher dispatcher = servletRequest.getRequestDispatcher(authenticationManagerPath);
-            dispatcher.forward(servletRequest, ((HttpServletResponseAdapter) outTransport).getWrappedResponse());
+            String authnEngineUrl = HttpServletHelper.getContextRelativeUrl(httpRequest, authenticationManagerPath)
+                    .buildURL();
+            log.debug("Redirecting user to authentication engine at {}", authnEngineUrl);
+            httpResponse.sendRedirect(authnEngineUrl);
         } catch (MarshallingException e) {
             log.error("Unable to marshall authentication request context");
             throw new ProfileException("Unable to marshall authentication request context", e);
         } catch (IOException ex) {
             log.error("Error forwarding SAML 2 AuthnRequest to AuthenticationManager", ex);
             throw new ProfileException("Error forwarding SAML 2 AuthnRequest to AuthenticationManager", ex);
-        } catch (ServletException ex) {
-            log.error("Error forwarding SAML 2 AuthnRequest to AuthenticationManager", ex);
-            throw new ProfileException("Error forwarding SAML 2 AuthnRequest to AuthenticationManager", ex);
         }
     }
 
@@ -206,23 +222,21 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
      * Creates a response to the {@link AuthnRequest} and sends the user, with response in tow, back to the relying
      * party after they've been authenticated.
      * 
+     * @param loginContext login context for this request
      * @param inTransport inbound message transport
      * @param outTransport outbound message transport
      * 
      * @throws ProfileException thrown if the response can not be created and sent back to the relying party
      */
-    protected void completeAuthenticationRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport)
-            throws ProfileException {
-        HttpServletRequest servletRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
-
-        Saml2LoginContext loginContext = (Saml2LoginContext) servletRequest
-                .getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+    protected void completeAuthenticationRequest(Saml2LoginContext loginContext, HTTPInTransport inTransport,
+            HTTPOutTransport outTransport) throws ProfileException {
         SSORequestContext requestContext = buildRequestContext(loginContext, inTransport, outTransport);
 
-        checkSamlVersion(requestContext);
-
         Response samlResponse;
         try {
+            checkSamlVersion(requestContext);
+            checkNameIDPolicy(requestContext);
+
             if (loginContext.getAuthenticationFailure() != null) {
                 if (loginContext.getAuthenticationFailure() instanceof PassiveAuthenticationException) {
                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.NO_PASSIVE_URI,
@@ -235,15 +249,13 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
             }
 
             if (requestContext.getSubjectNameIdentifier() != null) {
-                log
-                        .debug("Authentication request contained a subject with a name identifier, resolving principal from NameID");
+                log.debug("Authentication request contained a subject with a name identifier, resolving principal from NameID");
                 resolvePrincipal(requestContext);
                 String requestedPrincipalName = requestContext.getPrincipalName();
                 if (!DatatypeHelper.safeEquals(loginContext.getPrincipalName(), requestedPrincipalName)) {
-                    log
-                            .error(
-                                    "Authentication request identified principal {} but authentication mechanism identified principal {}",
-                                    requestedPrincipalName, loginContext.getPrincipalName());
+                    log.warn(
+                            "Authentication request identified principal {} but authentication mechanism identified principal {}",
+                            requestedPrincipalName, loginContext.getPrincipalName());
                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI,
                             null));
                     throw new ProfileException("User failed authentication");
@@ -285,14 +297,17 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
      */
     protected void decodeRequest(SSORequestContext requestContext, HTTPInTransport inTransport,
             HTTPOutTransport outTransport) throws ProfileException {
-        log.debug("Decoding message with decoder binding {}", getInboundBinding());
+        if (log.isDebugEnabled()) {
+            log.debug("Decoding message with decoder binding '{}'", getInboundMessageDecoder(requestContext)
+                    .getBindingURI());
+        }
 
         requestContext.setCommunicationProfileId(getProfileId());
 
         requestContext.setMetadataProvider(getMetadataProvider());
         requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
 
-        requestContext.setCommunicationProfileId(SSOConfiguration.PROFILE_ID);
+        requestContext.setCommunicationProfileId(getProfileId());
         requestContext.setInboundMessageTransport(inTransport);
         requestContext.setInboundSAMLProtocol(SAMLConstants.SAML20P_NS);
         requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
@@ -301,24 +316,74 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
         requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML20P_NS);
 
         try {
-            SAMLMessageDecoder decoder = getMessageDecoders().get(getInboundBinding());
+            SAMLMessageDecoder decoder = getInboundMessageDecoder(requestContext);
             requestContext.setMessageDecoder(decoder);
             decoder.decode(requestContext);
-            log.debug("Decoded request");
+            log.debug("Decoded request from relying party '{}'", requestContext.getInboundMessageIssuer());
 
-            if (!(requestContext.getInboundMessage() instanceof AuthnRequest)) {
-                log.error("Incomming message was not a AuthnRequest, it was a {}", requestContext.getInboundMessage()
-                        .getClass().getName());
+            if (!(requestContext.getInboundSAMLMessage() instanceof AuthnRequest)) {
+                log.warn("Incomming message was not a AuthnRequest, it was a '{}'", requestContext
+                        .getInboundSAMLMessage().getClass().getName());
                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null,
                         "Invalid SAML AuthnRequest message."));
                 throw new ProfileException("Invalid SAML AuthnRequest message.");
             }
         } catch (MessageDecodingException e) {
-            log.error("Error decoding authentication request message", e);
-            throw new ProfileException("Error decoding authentication request message", e);
+            String msg = "Error decoding authentication request message";
+            log.warn(msg, e);
+            throw new ProfileException(msg, e);
         } catch (SecurityException e) {
-            log.error("Message did not meet security requirements", e);
-            throw new ProfileException("Message did not meet security requirements", e);
+            String msg = "Message did not meet security requirements";
+            log.warn(msg, e);
+            throw new ProfileException(msg, e);
+        }
+    }
+
+    /**
+     * Checks to see, if present, if the affiliation associated with the SPNameQualifier given in the AuthnRequest
+     * NameIDPolicy lists the inbound message issuer as a member.
+     * 
+     * @param requestContext current request context
+     * 
+     * @throws ProfileException thrown if there the request is not a member of the affiliation or if there was a problem
+     *             determining membership
+     */
+    protected void checkNameIDPolicy(SSORequestContext requestContext) throws ProfileException {
+        AuthnRequest request = requestContext.getInboundSAMLMessage();
+
+        NameIDPolicy nameIdPolcy = request.getNameIDPolicy();
+        if (nameIdPolcy == null) {
+            return;
+        }
+
+        String spNameQualifier = DatatypeHelper.safeTrimOrNullString(nameIdPolcy.getSPNameQualifier());
+        if (spNameQualifier == null) {
+            return;
+        }
+
+        log.debug("Checking if message issuer is a member of affiliation '{}'", spNameQualifier);
+        try {
+            EntityDescriptor affiliation = getMetadataProvider().getEntityDescriptor(spNameQualifier);
+            if (affiliation != null) {
+                AffiliationDescriptor affiliationDescriptor = affiliation.getAffiliationDescriptor();
+                if (affiliationDescriptor != null && affiliationDescriptor.getMembers() != null) {
+                    for (AffiliateMember member : affiliationDescriptor.getMembers()) {
+                        if (DatatypeHelper.safeEquals(member.getID(), requestContext.getInboundMessageIssuer())) {
+                            return;
+                        }
+                    }
+                }
+            }
+
+            requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
+                    "Invalid SPNameQualifier for this request"));
+            throw new ProfileException("Relying party '" + requestContext.getInboundMessageIssuer()
+                    + "' is not a member of the affiliation " + spNameQualifier);
+        } catch (MetadataProviderException e) {
+            requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Internal service error"));
+            log.error("Error looking up metadata for affiliation", e);
+            throw new ProfileException("Relying party '" + requestContext.getInboundMessageIssuer()
+                    + "' is not a member of the affiliation " + spNameQualifier);
         }
     }
 
@@ -338,7 +403,7 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
         SSORequestContext requestContext = new SSORequestContext();
         requestContext.setCommunicationProfileId(getProfileId());
 
-        requestContext.setMessageDecoder(getMessageDecoders().get(getInboundBinding()));
+        requestContext.setMessageDecoder(getInboundMessageDecoder(requestContext));
 
         requestContext.setLoginContext(loginContext);
 
@@ -443,7 +508,7 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
         long maxSPSessionLifetime = requestContext.getProfileConfiguration().getMaximumSPSessionLifetime();
         if (maxSPSessionLifetime > 0) {
             DateTime lifetime = new DateTime(DateTimeZone.UTC).plus(maxSPSessionLifetime);
-            log.debug("Explicitly setting SP session expiration time to {}", lifetime.toString());
+            log.debug("Explicitly setting SP session expiration time to '{}'", lifetime.toString());
             statement.setSessionNotOnOrAfter(lifetime);
         }
 
@@ -453,7 +518,7 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
     }
 
     /**
-     * Creates an {@link AuthnContext} for a succesful authentication request.
+     * Creates an {@link AuthnContext} for a successful authentication request.
      * 
      * @param requestContext current request
      * 
@@ -468,7 +533,8 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
         if (requestedAuthnContext != null) {
             if (requestedAuthnContext.getAuthnContextClassRefs() != null) {
                 for (AuthnContextClassRef classRef : requestedAuthnContext.getAuthnContextClassRefs()) {
-                    if (classRef.getAuthnContextClassRef().equals(loginContext.getAuthenticationMethod())) {
+                    if (DatatypeHelper.safeEquals(classRef.getAuthnContextClassRef(),
+                            loginContext.getAuthenticationMethod())) {
                         AuthnContextClassRef ref = authnContextClassRefBuilder.buildObject();
                         ref.setAuthnContextClassRef(loginContext.getAuthenticationMethod());
                         authnContext.setAuthnContextClassRef(ref);
@@ -476,17 +542,20 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
                 }
             } else if (requestedAuthnContext.getAuthnContextDeclRefs() != null) {
                 for (AuthnContextDeclRef declRef : requestedAuthnContext.getAuthnContextDeclRefs()) {
-                    if (declRef.getAuthnContextDeclRef().equals(loginContext.getAuthenticationMethod())) {
+                    if (DatatypeHelper.safeEquals(declRef.getAuthnContextDeclRef(),
+                            loginContext.getAuthenticationMethod())) {
                         AuthnContextDeclRef ref = authnContextDeclRefBuilder.buildObject();
                         ref.setAuthnContextDeclRef(loginContext.getAuthenticationMethod());
                         authnContext.setAuthnContextDeclRef(ref);
                     }
                 }
             }
-        } else {
-            AuthnContextDeclRef ref = authnContextDeclRefBuilder.buildObject();
-            ref.setAuthnContextDeclRef(loginContext.getAuthenticationMethod());
-            authnContext.setAuthnContextDeclRef(ref);
+        }
+
+        if (authnContext.getAuthnContextClassRef() == null || authnContext.getAuthnContextDeclRef() == null) {
+            AuthnContextClassRef ref = authnContextClassRefBuilder.buildObject();
+            ref.setAuthnContextClassRef(loginContext.getAuthenticationMethod());
+            authnContext.setAuthnContextClassRef(ref);
         }
 
         return authnContext;
@@ -507,6 +576,42 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
         return subjectLocality;
     }
 
+    /** {@inheritDoc} */
+    protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
+        String requiredNameFormat = null;
+        AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
+        NameIDPolicy nameIdPolicy = authnRequest.getNameIDPolicy();
+        if (nameIdPolicy != null) {
+            requiredNameFormat = DatatypeHelper.safeTrimOrNullString(nameIdPolicy.getFormat());
+            // Check for unspec'd or encryption formats, which aren't relevant for this section of code.
+            if (requiredNameFormat != null
+                    && (NameID.ENCRYPTED.equals(requiredNameFormat) || NameID.UNSPECIFIED.equals(requiredNameFormat))) {
+                requiredNameFormat = null;
+            }
+        }
+
+        return requiredNameFormat;
+    }
+
+    /** {@inheritDoc} */
+    protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
+        NameID nameId = super.buildNameId(requestContext);
+        if (nameId != null) {
+            AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
+            NameIDPolicy nameIdPolicy = authnRequest.getNameIDPolicy();
+            if (nameIdPolicy != null) {
+                String spNameQualifier = DatatypeHelper.safeTrimOrNullString(nameIdPolicy.getSPNameQualifier());
+                if (spNameQualifier != null) {
+                    nameId.setSPNameQualifier(spNameQualifier);
+                } else {
+                    nameId.setSPNameQualifier(requestContext.getInboundMessageIssuer());
+                }
+            }
+        }
+
+        return nameId;
+    }
+
     /**
      * Selects the appropriate endpoint for the relying party and stores it in the request context.
      * 
@@ -527,8 +632,10 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
                 } else {
                     endpoint.setBinding(getSupportedOutboundBindings().get(0));
                 }
-                log.warn("Generating endpoint for anonymous relying party. ACS url {} and binding {}", new Object[] {
-                        requestContext.getInboundMessageIssuer(), endpoint.getLocation(), endpoint.getBinding(), });
+                log.warn(
+                        "Generating endpoint for anonymous relying party self-identified as '{}', ACS url '{}' and binding '{}'",
+                        new Object[] { requestContext.getInboundMessageIssuer(), endpoint.getLocation(),
+                                endpoint.getBinding(), });
             } else {
                 log.warn("Unable to generate endpoint for anonymous party.  No ACS url provided.");
             }