import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.net.URL;
import java.net.URLEncoder;
import java.security.Principal;
import java.security.cert.CertificateParsingException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import org.opensaml.SAMLResponse;
import org.opensaml.SAMLStatement;
import org.opensaml.SAMLSubject;
+import org.opensaml.artifact.Artifact;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import sun.misc.BASE64Decoder;
+import edu.internet2.middleware.shibboleth.aa.AAAttribute;
+import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
import edu.internet2.middleware.shibboleth.aa.AAException;
-import edu.internet2.middleware.shibboleth.aa.AAResponder;
import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
+import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
-import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
-import edu.internet2.middleware.shibboleth.artifact.ArtifactMapping;
-import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
import edu.internet2.middleware.shibboleth.common.AuthNPrincipal;
import edu.internet2.middleware.shibboleth.common.Credential;
import edu.internet2.middleware.shibboleth.common.Credentials;
import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
import edu.internet2.middleware.shibboleth.common.ShibBrowserProfile;
import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
-import edu.internet2.middleware.shibboleth.common.TargetFederationComponent;
-import edu.internet2.middleware.shibboleth.metadata.*;
+import edu.internet2.middleware.shibboleth.metadata.Endpoint;
+import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
+import edu.internet2.middleware.shibboleth.metadata.KeyDescriptor;
+import edu.internet2.middleware.shibboleth.metadata.Metadata;
+import edu.internet2.middleware.shibboleth.metadata.MetadataException;
+import edu.internet2.middleware.shibboleth.metadata.SPSSODescriptor;
/**
* Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
* @author Walter Hoehn
*/
-public class IdPResponder extends TargetFederationComponent {
-
- // TODO Maybe should rethink the inheritance here, since there is only one
- // servlet
-
- //TODO idp config should accept new or old element <ShibbolethOriginConfiguration/> or <IdPConfig/>
+public class IdPResponder extends HttpServlet {
private static Logger transactionLog = Logger.getLogger("Shibboleth-TRANSACTION");
private static Logger log = Logger.getLogger(IdPResponder.class.getName());
private static Random idgen = new Random();
-
private SAMLBinding binding;
private Semaphore throttle;
- private ArtifactMapper artifactMapper;
- private SSOProfileHandler[] profileHandlers;
private IdPConfig configuration;
- private NameMapper nameMapper;
- private ServiceProviderMapper spMapper;
-
- // TODO Need to rename, rework, and init
- private AAResponder responder;
+ private ProtocolHandler[] protocolHandlers;
+ private ProtocolSupport protocolSupport;
public void init() throws ServletException {
try {
binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
- nameMapper = new NameMapper();
- // TODO this needs to be pluggable
- artifactMapper = new MemoryArtifactMapper();
- loadConfiguration();
-
-
- //TODO figure out how this stuff should be configured
- // if (configuration.eAuthSupport()) {
- if (false) {
- log.debug("Starting with Shibboleth & eAuth protocol handling enabled.");
- // profileHandlers = new SSOProfileHandler[]{new ShibbolethProfileHandler(),
- // new EAuthProfileHandler(configuration.getCsid())};
- } else {
- log.debug("Starting with Shibboleth protocol handling enabled.");
- profileHandlers = new SSOProfileHandler[]{new ShibbolethProfileHandler()};
- }
-
-
-
-
-
-
-
- //TODO what should we do with the throttle now!!! default???
-
- //Load a semaphore that throttles how many requests the IdP will handle at once
- throttle = new Semaphore(configuration.getMaxThreads());
-
- log.info("Identity Provider initialization complete.");
-
- } catch (ShibbolethConfigurationException ae) {
- log.fatal("The Identity Provider could not be initialized: " + ae);
- throw new UnavailableException("Identity Provider failed to initialize.");
- } catch (SAMLException se) {
- log.fatal("SAML SOAP binding could not be loaded: " + se);
- throw new UnavailableException("Identity Provider failed to initialize.");
- }
- }
- private void loadConfiguration() throws ShibbolethConfigurationException {
+ Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
- Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
+ // Load global configuration properties
+ configuration = new IdPConfig(originConfig.getDocumentElement());
- // TODO I think some of the failure cases here are different than in the
- // HS, so when the loadConfiguration() is unified, that must be taken
- // into account
+ // Load a semaphore that throttles how many requests the IdP will handle at once
+ throttle = new Semaphore(configuration.getMaxThreads());
- // TODO do we need to check active endpoints to determine which
- // components to load, for instance artifact repository, arp engine,
- // attribute resolver
+ // Load name mappings
+ NameMapper nameMapper = new NameMapper();
+ NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
+ NameIdentifierMapping.mappingNamespace, "NameMapping");
- // Load global configuration properties
- configuration = new IdPConfig(originConfig.getDocumentElement());
+ for (int i = 0; i < itemElements.getLength(); i++) {
+ try {
+ nameMapper.addNameMapping((Element) itemElements.item(i));
+ } catch (NameIdentifierMappingException e) {
+ log.error("Name Identifier mapping could not be loaded: " + e);
+ }
+ }
- // Load name mappings
- NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
- NameIdentifierMapping.mappingNamespace, "NameMapping");
+ // Load signing credentials
+ itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
+ "Credentials");
+ if (itemElements.getLength() < 1) {
+ log.error("No credentials specified.");
+ }
+ if (itemElements.getLength() > 1) {
+ log.error("Multiple Credentials specifications found, using first.");
+ }
+ Credentials credentials = new Credentials((Element) itemElements.item(0));
- for (int i = 0; i < itemElements.getLength(); i++) {
+ // Load relying party config
+ ServiceProviderMapper spMapper;
try {
- nameMapper.addNameMapping((Element) itemElements.item(i));
- } catch (NameIdentifierMappingException e) {
- log.error("Name Identifier mapping could not be loaded: " + e);
+ spMapper = new ServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
+ nameMapper);
+ } catch (ServiceProviderMapperException e) {
+ log.error("Could not load Identity Provider configuration: " + e);
+ throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
}
- }
- // Load signing credentials
- itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
- "Credentials");
- if (itemElements.getLength() < 1) {
- log.error("No credentials specified.");
- }
- if (itemElements.getLength() > 1) {
- log.error("Multiple Credentials specifications found, using first.");
- }
- Credentials credentials = new Credentials((Element) itemElements.item(0));
+ // Startup Attribute Resolver & ARP engine
+ AttributeResolver resolver = null;
+ ArpEngine arpEngine = null;
+ try {
+ resolver = new AttributeResolver(configuration);
- // Load metadata
- itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
- "FederationProvider");
- for (int i = 0; i < itemElements.getLength(); i++) {
- addFederationProvider((Element) itemElements.item(i));
- }
- if (providerCount() < 1) {
- log.error("No Federation Provider metadata loaded.");
- throw new ShibbolethConfigurationException("Could not load federation metadata.");
- }
+ itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
+ IdPConfig.originConfigNamespace, "ReleasePolicyEngine");
- // Load relying party config
- try {
- spMapper = new ServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
- nameMapper, this);
- } catch (ServiceProviderMapperException e) {
- log.error("Could not load Identity Provider configuration: " + e);
- throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
- }
+ if (itemElements.getLength() > 1) {
+ log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements. Using first...");
+ }
+ if (itemElements.getLength() < 1) {
+ arpEngine = new ArpEngine();
+ } else {
+ arpEngine = new ArpEngine((Element) itemElements.item(0));
+ }
- try {
- // Startup Attribute Resolver
- AttributeResolver resolver = new AttributeResolver(configuration);
+ } catch (ArpException ae) {
+ log.fatal("The Identity Provider could not be initialized "
+ + "due to a problem with the ARP Engine configuration: " + ae);
+ throw new ShibbolethConfigurationException("Could not load ARP Engine.");
+ } catch (AttributeResolverException ne) {
+ log.fatal("The Identity Provider could not be initialized due "
+ + "to a problem with the Attribute Resolver configuration: " + ne);
+ throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
+ }
- // Startup ARP Engine
- ArpEngine arpEngine = null;
- itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
- "ReleasePolicyEngine");
+ // Load protocol handlers and support library
+ protocolSupport = new ProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
+ resolver);
+ log.debug("Starting with Shibboleth v1 protocol handling enabled.");
+ protocolHandlers = new ProtocolHandler[]{new Shibbolethv1SSOHandler()};
- if (itemElements.getLength() > 1) {
- log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements. Using first...");
+ // Load metadata
+ itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
+ "FederationProvider");
+ for (int i = 0; i < itemElements.getLength(); i++) {
+ protocolSupport.addFederationProvider((Element) itemElements.item(i));
}
- if (itemElements.getLength() < 1) {
- arpEngine = new ArpEngine();
- } else {
- arpEngine = new ArpEngine((Element) itemElements.item(0));
+ if (protocolSupport.providerCount() < 1) {
+ log.error("No Federation Provider metadata loaded.");
+ throw new ShibbolethConfigurationException("Could not load federation metadata.");
}
- // Startup responder
- responder = new AAResponder(arpEngine, resolver);
-
- } catch (ArpException ae) {
- log.fatal("The Identity Provider could not be initialized "
- + "due to a problem with the ARP Engine configuration: " + ae);
- throw new ShibbolethConfigurationException("Could not load ARP Engine.");
- } catch (AttributeResolverException ne) {
- log.fatal("The Identity Provider could not be initialized due "
- + "to a problem with the Attribute Resolver configuration: " + ne);
- throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
- }
+ log.info("Identity Provider initialization complete.");
+ } catch (ShibbolethConfigurationException ae) {
+ log.fatal("The Identity Provider could not be initialized: " + ae);
+ throw new UnavailableException("Identity Provider failed to initialize.");
+ } catch (SAMLException se) {
+ log.fatal("SAML SOAP binding could not be loaded: " + se);
+ throw new UnavailableException("Identity Provider failed to initialize.");
+ }
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
MDC.put("serviceId", "[IdP] " + idgen.nextInt());
MDC.put("remoteAddr", request.getRemoteAddr());
log.debug("Recieved a request via GET.");
- log.info("Handling authN request.");
try {
+ // TODO this throttle should probably just wrap signing operations...
throttle.enter();
- // Ensure that we have the required data from the servlet container
- validateEngineData(request);
-
- // Determine which profile of SAML we are responding to (at this point, Shib vs. EAuth)
- SSOProfileHandler activeHandler = null;
- for (int i = 0; i < profileHandlers.length; i++) {
- if (profileHandlers[i].validForRequest(request)) {
- activeHandler = profileHandlers[i];
+ // Determine which protocol we are responding to (at this point, Shibv1 vs. EAuth)
+ ProtocolHandler activeHandler = null;
+ for (int i = 0; i < protocolHandlers.length; i++) {
+ if (protocolHandlers[i].validForRequest(request)) {
+ activeHandler = protocolHandlers[i];
break;
}
}
+
if (activeHandler == null) { throw new InvalidClientDataException(
"The request did not contain sufficient parameter data to determine the protocol."); }
- // Run profile specific preprocessing
- if (activeHandler.preProcessHook(request, response)) { return; }
-
- // Get the authN info
- String username = configuration.getAuthHeaderName().equalsIgnoreCase("REMOTE_USER") ? request
- .getRemoteUser() : request.getHeader(configuration.getAuthHeaderName());
-
- // Select the appropriate Relying Party configuration for the request
- RelyingParty relyingParty = null;
- String remoteProviderId = activeHandler.getRemoteProviderId(request);
- // If the target did not send a Provider Id, then assume it is a Shib
- // 1.1 or older target
- if (remoteProviderId == null) {
- relyingParty = spMapper.getLegacyRelyingParty();
- } else if (remoteProviderId.equals("")) {
- throw new InvalidClientDataException("Invalid service provider id.");
- } else {
- log.debug("Remote provider has identified itself as: (" + remoteProviderId + ").");
- relyingParty = spMapper.getRelyingParty(remoteProviderId);
- }
-
- // Grab the metadata for the provider
- EntityDescriptor provider = lookup(relyingParty.getProviderId());
-
- // Use profile-specific method for determining the acceptance URL
- String acceptanceURL = activeHandler.getAcceptanceURL(request, relyingParty, provider);
-
- // Make sure that the selected relying party configuration is appropriate for this
- // acceptance URL
- if (!relyingParty.isLegacyProvider()) {
-
- if (provider == null) {
- log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
- relyingParty = spMapper.getRelyingParty(null);
-
- } else {
-
- if (isValidAssertionConsumerURL(provider, acceptanceURL)) {
- log.info("Supplied consumer URL validated for this provider.");
- } else {
- log.error("Assertion consumer service URL (" + acceptanceURL + ") is NOT valid for provider ("
- + relyingParty.getProviderId() + ").");
- throw new InvalidClientDataException("Invalid assertion consumer service URL.");
- }
- }
- }
-
- // Create SAML Name Identifier
- SAMLNameIdentifier nameId = nameMapper.getNameIdentifierName(relyingParty.getHSNameFormatId(),
- new AuthNPrincipal(username), relyingParty, relyingParty.getIdentityProvider());
-
- String authenticationMethod = request.getHeader("SAMLAuthenticationMethod");
- if (authenticationMethod == null || authenticationMethod.equals("")) {
- authenticationMethod = relyingParty.getDefaultAuthMethod().toString();
- log.debug("User was authenticated via the default method for this relying party ("
- + authenticationMethod + ").");
- } else {
- log.debug("User was authenticated via the method (" + authenticationMethod + ").");
- }
-
- // We might someday want to provide a mechanism for the authenticator to specify the auth time
- SAMLAssertion[] assertions = activeHandler.processHook(request, relyingParty, provider, nameId,
- authenticationMethod, new Date(System.currentTimeMillis()));
-
- // TODO do assertion signing here
-
- // SAML Artifact profile
- if (useArtifactProfile(provider, acceptanceURL)) {
- log.debug("Responding with Artifact profile.");
-
- // Create artifacts for each assertion
- ArrayList artifacts = new ArrayList();
- for (int i = 0; i < assertions.length; i++) {
- artifacts.add(artifactMapper.generateArtifact(assertions[i], relyingParty));
- }
-
- // Assemble the query string
- StringBuffer destination = new StringBuffer(acceptanceURL);
- destination.append("?TARGET=");
- destination.append(URLEncoder.encode(activeHandler.getSAMLTargetParameter(request, relyingParty,
- provider), "UTF-8"));
- Iterator iterator = artifacts.iterator();
- StringBuffer artifactBuffer = new StringBuffer(); // Buffer for the transaction log
- while (iterator.hasNext()) {
- destination.append("&SAMLart=");
- String artifact = (String) iterator.next();
- destination.append(URLEncoder.encode(artifact, "UTF-8"));
- artifactBuffer.append("(" + artifact + ")");
- }
- log.debug("Redirecting to (" + destination.toString() + ").");
- response.sendRedirect(destination.toString()); // Redirect to the artifact receiver
-
- transactionLog.info("Assertion artifact(s) (" + artifactBuffer.toString() + ") issued to provider ("
- + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal (" + username
- + "). Name Identifier: (" + nameId.getName() + "). Name Identifier Format: ("
- + nameId.getFormat() + ").");
-
- // SAML POST profile
- } else {
- log.debug("Responding with POST profile.");
- request.setAttribute("acceptanceURL", acceptanceURL);
- request.setAttribute("target", activeHandler.getSAMLTargetParameter(request, relyingParty, provider));
-
- SAMLResponse samlResponse = new SAMLResponse(null, acceptanceURL, Arrays.asList(assertions), null);
- addSignatures(samlResponse, relyingParty, provider, true);
- createPOSTForm(request, response, samlResponse.toBase64());
-
- // Make transaction log entry
- if (relyingParty.isLegacyProvider()) {
- transactionLog.info("Authentication assertion issued to legacy provider (SHIRE: "
- + request.getParameter("shire") + ") on behalf of principal (" + username
- + ") for resource (" + request.getParameter("target") + "). Name Identifier: ("
- + nameId.getName() + "). Name Identifier Format: (" + nameId.getFormat() + ").");
- } else {
- transactionLog.info("Authentication assertion issued to provider ("
- + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
- + username + "). Name Identifier: (" + nameId.getName() + "). Name Identifier Format: ("
- + nameId.getFormat() + ").");
- }
- }
+ log.info("Processing " + activeHandler.getHandlerName() + " request.");
+ // Pass request to the appropriate handler
+ activeHandler.processRequest(request, response, protocolSupport);
- // TODO profile specific error handling
} catch (NameIdentifierMappingException ex) {
log.error(ex);
handleSSOError(request, response, ex);
} finally {
throttle.exit();
}
-
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
MDC.put("remoteAddr", request.getRemoteAddr());
log.debug("Recieved a request via POST.");
+ // Determine which protocol we are responding to (at this point, Shibv1 vs. EAuth)
+ ProtocolHandler activeHandler = null;
+ for (int i = 0; i < protocolHandlers.length; i++) {
+ if (protocolHandlers[i].validForRequest(request)) {
+ activeHandler = protocolHandlers[i];
+ break;
+ }
+ }
+
+ // TODO some other type of error here
+ /*
+ * if (activeHandler == null) { throw new InvalidClientDataException( "The request did not contain sufficient
+ * parameter data to determine the protocol."); }
+ */
+ log.info("Processing " + activeHandler.getHandlerName() + " request.");
+ // Pass request to the appropriate handler
+ // activeHandler.processRequest(request, response, protocolSupport);
+
// Parse SOAP request and marshall SAML request object
SAMLRequest samlRequest = null;
try {
if (artifacts.hasNext()) {
artifacts = null; // get rid of the iterator
log.info("Recieved a request to dereference an assertion artifact.");
- processArtifactDereference(samlRequest, request, response);
+
+ // processArtifactDereference(samlRequest, request, response);
return;
}
then, Collections.singleton(condition), null, Collections.singleton(statement));
samlResponse = new SAMLResponse(samlRequest.getId(), null, Collections.singleton(sAssertion), exception);
- addSignatures(samlResponse, relyingParty, lookup(relyingParty.getProviderId()), false);
+ ProtocolSupport.addSignatures(samlResponse, relyingParty, protocolSupport.lookup(relyingParty
+ .getProviderId()), false);
}
} catch (SAMLException se) {
ourSE = se;
} else {
// Identify a Relying Party
- relyingParty = spMapper.getRelyingParty(attributeQuery.getResource());
+ relyingParty = protocolSupport.getServiceProviderMapper().getRelyingParty(attributeQuery.getResource());
try {
effectiveName = getEffectiveName(request, relyingParty);
if (effectiveName == null) {
log.debug("Using default Relying Party for unauthenticated provider.");
- relyingParty = spMapper.getRelyingParty(null);
+ relyingParty = protocolSupport.getServiceProviderMapper().getRelyingParty(null);
}
// Fail if we can't honor SAML Subject Confirmation
}
// Map Subject to local principal
- Principal principal = nameMapper.getPrincipal(attributeQuery.getSubject().getName(), relyingParty, relyingParty
- .getIdentityProvider());
+ Principal principal = protocolSupport.getNameMapper().getPrincipal(attributeQuery.getSubject().getName(),
+ relyingParty, relyingParty.getIdentityProvider());
log.info("Request is for principal (" + principal.getName() + ").");
SAMLAttribute[] attrs;
}
}
- attrs = responder.getReleaseAttributes(principal, effectiveName, null, (URI[]) requestedAttrs
+ attrs = protocolSupport.getReleaseAttributes(principal, effectiveName, null, (URI[]) requestedAttrs
.toArray(new URI[0]));
} else {
log.info("Request does not designate specific attributes, resolving all available.");
- attrs = responder.getReleaseAttributes(principal, effectiveName, null);
+ attrs = protocolSupport.getReleaseAttributes(principal, effectiveName, null);
}
log.info("Found " + attrs.length + " attribute(s) for " + principal.getName());
}
- private void processArtifactDereference(SAMLRequest samlRequest, HttpServletRequest request,
- HttpServletResponse response) throws SAMLException, IOException {
-
- // TODO validate that the endpoint is valid for the request type
- // TODO how about signatures on artifact dereferencing
-
- // Pull credential from request
- X509Certificate credential = getCredentialFromProvider(request);
- if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
- // The spec says that mutual authentication is required for the
- // artifact profile
- log.info("Request is from an unauthenticated service provider.");
- throw new SAMLException(SAMLException.REQUESTER,
- "SAML Artifacts cannot be dereferenced for unauthenticated requesters.");
- }
-
- log.info("Request contains credential: (" + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
- + ").");
-
- ArrayList assertions = new ArrayList();
- Iterator artifacts = samlRequest.getArtifacts();
-
- int queriedArtifacts = 0;
- StringBuffer dereferencedArtifacts = new StringBuffer(); // for
- // transaction
- // log
- while (artifacts.hasNext()) {
- queriedArtifacts++;
- String artifact = (String) artifacts.next();
- log.debug("Attempting to dereference artifact: (" + artifact + ").");
- ArtifactMapping mapping = artifactMapper.recoverAssertion(artifact);
- if (mapping != null) {
- SAMLAssertion assertion = mapping.getAssertion();
-
- // See if we have metadata for this provider
- EntityDescriptor provider = lookup(mapping.getServiceProviderId());
- if (provider == null) {
- log.info("No metadata found for provider: (" + mapping.getServiceProviderId() + ").");
- throw new SAMLException(SAMLException.REQUESTER, "Invalid service provider.");
- }
-
- // Make sure that the suppplied credential is valid for the
- // provider to which the artifact was issued
- if (!isValidCredential(provider, credential)) {
- log.error("Supplied credential ("
- + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
- + ") is NOT valid for provider (" + mapping.getServiceProviderId()
- + "), to whom this artifact was issued.");
- throw new SAMLException(SAMLException.REQUESTER, "Invalid credential.");
- }
-
- log.debug("Supplied credential validated for the provider to which this artifact was issued.");
-
- assertions.add(assertion);
- dereferencedArtifacts.append("(" + artifact + ")");
- }
- }
-
- // The spec requires that if any artifacts are dereferenced, they must
- // all be dereferenced
- if (assertions.size() > 0 && assertions.size() != queriedArtifacts) { throw new SAMLException(
- SAMLException.REQUESTER, "Unable to successfully dereference all artifacts."); }
-
- // Create and send response
- // The spec says that we should send "success" in the case where no
- // artifacts match
- SAMLResponse samlResponse = new SAMLResponse(samlRequest.getId(), null, assertions, null);
-
- if (log.isDebugEnabled()) {
- try {
- log.debug("Dumping generated SAML Response:"
- + System.getProperty("line.separator")
- + new String(new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
- "UTF8"));
- } catch (SAMLException e) {
- log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
- } catch (IOException e) {
- log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
- }
- }
-
- binding.respond(response, samlResponse, null);
-
- transactionLog.info("Succesfully dereferenced the following artifacts: " + dereferencedArtifacts.toString());
- }
+ /*
+ * private void processArtifactDereference(SAMLRequest samlRequest, HttpServletRequest request, HttpServletResponse
+ * response) throws SAMLException, IOException { // TODO validate that the endpoint is valid for the request type //
+ * TODO how about signatures on artifact dereferencing // Pull credential from request X509Certificate credential =
+ * getCredentialFromProvider(request); if (credential == null ||
+ * credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) { // The spec says that mutual
+ * authentication is required for the // artifact profile log.info("Request is from an unauthenticated service
+ * provider."); throw new SAMLException(SAMLException.REQUESTER, "SAML Artifacts cannot be dereferenced for
+ * unauthenticated requesters."); } log.info("Request contains credential: (" +
+ * credential.getSubjectX500Principal().getName(X500Principal.RFC2253) + ")."); ArrayList assertions = new
+ * ArrayList(); Iterator artifacts = samlRequest.getArtifacts(); int queriedArtifacts = 0; StringBuffer
+ * dereferencedArtifacts = new StringBuffer(); // for // transaction // log while (artifacts.hasNext()) {
+ * queriedArtifacts++; String artifact = (String) artifacts.next(); log.debug("Attempting to dereference artifact: (" +
+ * artifact + ")."); ArtifactMapping mapping = artifactMapper.recoverAssertion(artifact); if (mapping != null) {
+ * SAMLAssertion assertion = mapping.getAssertion(); // See if we have metadata for this provider EntityDescriptor
+ * provider = lookup(mapping.getServiceProviderId()); if (provider == null) { log.info("No metadata found for
+ * provider: (" + mapping.getServiceProviderId() + ")."); throw new SAMLException(SAMLException.REQUESTER, "Invalid
+ * service provider."); } // Make sure that the suppplied credential is valid for the // provider to which the
+ * artifact was issued if (!isValidCredential(provider, credential)) { log.error("Supplied credential (" +
+ * credential.getSubjectX500Principal().getName(X500Principal.RFC2253) + ") is NOT valid for provider (" +
+ * mapping.getServiceProviderId() + "), to whom this artifact was issued."); throw new
+ * SAMLException(SAMLException.REQUESTER, "Invalid credential."); } log.debug("Supplied credential validated for the
+ * provider to which this artifact was issued."); assertions.add(assertion); dereferencedArtifacts.append("(" +
+ * artifact + ")"); } } // The spec requires that if any artifacts are dereferenced, they must // all be
+ * dereferenced if (assertions.size() > 0 && assertions.size() != queriedArtifacts) { throw new SAMLException(
+ * SAMLException.REQUESTER, "Unable to successfully dereference all artifacts."); } // Create and send response //
+ * The spec says that we should send "success" in the case where no // artifacts match SAMLResponse samlResponse =
+ * new SAMLResponse(samlRequest.getId(), null, assertions, null); if (log.isDebugEnabled()) { try {
+ * log.debug("Dumping generated SAML Response:" + System.getProperty("line.separator") + new String(new
+ * BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")), "UTF8")); } catch (SAMLException e) {
+ * log.error("Encountered an error while decoding SAMLReponse for logging purposes."); } catch (IOException e) {
+ * log.error("Encountered an error while decoding SAMLReponse for logging purposes."); } } binding.respond(response,
+ * samlResponse, null); transactionLog.info("Succesfully dereferenced the following artifacts: " +
+ * dereferencedArtifacts.toString()); }
+ */
private void sendSAMLFailureResponse(HttpServletResponse httpResponse, SAMLRequest samlRequest,
SAMLException exception) throws IOException {
} else {
// See if we have metadata for this provider
- EntityDescriptor provider = lookup(relyingParty.getProviderId());
+ EntityDescriptor provider = protocolSupport.lookup(relyingParty.getProviderId());
if (provider == null) {
log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
log.info("Treating remote provider as unauthenticated.");
}
}
- private static void addSignatures(SAMLResponse response, RelyingParty relyingParty, EntityDescriptor provider,
- boolean signResponse) throws SAMLException {
-
- if (provider != null) {
- boolean signAssertions = false;
-
- SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
- if (sp == null) {
- log.info("Inappropriate metadata for provider: " + provider.getId() + ". Expected SPSSODescriptor.");
- }
- if (sp.getWantAssertionsSigned()) {
- signAssertions = true;
- }
-
- if (signAssertions && relyingParty.getIdentityProvider().getSigningCredential() != null
- && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
-
- Iterator assertions = response.getAssertions();
-
- while (assertions.hasNext()) {
- SAMLAssertion assertion = (SAMLAssertion) assertions.next();
- String assertionAlgorithm;
- if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
- assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
- } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
- assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
- } else {
- throw new InvalidCryptoException(SAMLException.RESPONDER,
- "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
- }
-
- assertion.sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
- .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
- .getX509CertificateChain()));
- }
- }
- }
-
- // Sign the response, if appropriate
- if (signResponse && relyingParty.getIdentityProvider().getSigningCredential() != null
- && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
-
- String responseAlgorithm;
- if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
- responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
- } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
- responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
- } else {
- throw new InvalidCryptoException(SAMLException.RESPONDER,
- "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
- }
-
- response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
- Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
- }
- }
-
- private static boolean useArtifactProfile(EntityDescriptor provider, String acceptanceURL) {
-
- // Default to POST if we have no metadata
- if (provider == null) { return false; }
-
- // Default to POST if we have incomplete metadata
- SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
- if (sp == null) { return false; }
-
- // TODO: This will actually favor artifact, since a given location could support
- // both profiles. If that's not what we want, needs adjustment...
- Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
- while (endpoints.hasNext()) {
- Endpoint ep = (Endpoint)endpoints.next();
- if (acceptanceURL.equals(ep.getLocation())
- && SAMLBrowserProfile.PROFILE_ARTIFACT_URI.equals(ep.getBinding())) { return true; }
- }
-
- // Default to POST if we have incomplete metadata
- return false;
- }
-
- private static void validateEngineData(HttpServletRequest req) throws InvalidClientDataException {
-
- if ((req.getRemoteUser() == null) || (req.getRemoteUser().equals(""))) { throw new InvalidClientDataException(
- "Unable to authenticate remote user"); }
- if ((req.getRemoteAddr() == null) || (req.getRemoteAddr().equals(""))) { throw new InvalidClientDataException(
- "Unable to obtain client address."); }
- }
-
- private static boolean isValidAssertionConsumerURL(EntityDescriptor provider, String shireURL)
- throws InvalidClientDataException {
-
- SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
- if (sp == null) {
- log.info("Inappropriate metadata for provider.");
- return false;
- }
-
- Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
- while (endpoints.hasNext()) {
- if (shireURL.equals(((Endpoint)endpoints.next()).getLocation())) { return true; }
- }
- log.info("Supplied consumer URL not found in metadata.");
- return false;
- }
-
private static X509Certificate getCredentialFromProvider(HttpServletRequest req) {
X509Certificate[] certArray = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
// TODO figure out what to do about this role business here
Iterator descriptors = sp.getKeyDescriptors();
while (descriptors.hasNext()) {
- KeyInfo keyInfo = ((KeyDescriptor)descriptors.next()).getKeyInfo();
+ KeyInfo keyInfo = ((KeyDescriptor) descriptors.next()).getKeyInfo();
for (int l = 0; keyInfo.lengthKeyName() > l; l++) {
try {
// First, try to match DN against metadata
try {
if (certificate.getSubjectX500Principal().getName(X500Principal.RFC2253).equals(
- new X500Principal(keyInfo.itemKeyName(l).getKeyName())
- .getName(X500Principal.RFC2253))) {
+ new X500Principal(keyInfo.itemKeyName(l).getKeyName()).getName(X500Principal.RFC2253))) {
log.debug("Matched against DN.");
return true;
}
if (altNames != null) {
for (Iterator nameIterator = altNames.iterator(); nameIterator.hasNext();) {
List altName = (List) nameIterator.next();
- if (altName.get(0).equals(new Integer(2))
- || altName.get(0).equals(new Integer(6))) { // 2 is
+ if (altName.get(0).equals(new Integer(2)) || altName.get(0).equals(new Integer(6))) { // 2 is
// DNS,
// 6 is
// URI
return true;
}
- private static void createPOSTForm(HttpServletRequest req, HttpServletResponse res, byte[] buf) throws IOException,
- ServletException {
-
- // Hardcoded to ASCII to ensure Base64 encoding compatibility
- req.setAttribute("assertion", new String(buf, "ASCII"));
-
- if (log.isDebugEnabled()) {
- try {
- log.debug("Dumping generated SAML Response:" + System.getProperty("line.separator")
- + new String(new BASE64Decoder().decodeBuffer(new String(buf, "ASCII")), "UTF8"));
- } catch (IOException e) {
- log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
- }
- }
-
- // TODO rename from hs.jsp to more appropriate name
- RequestDispatcher rd = req.getRequestDispatcher("/hs.jsp");
- rd.forward(req, res);
- }
-
+ // TODO this should be renamed
private static void handleSSOError(HttpServletRequest req, HttpServletResponse res, Exception e)
throws ServletException, IOException {
}
}
- abstract class SSOProfileHandler {
+}
- abstract String getHandlerName();
+class InvalidClientDataException extends Exception {
- abstract String getRemoteProviderId(HttpServletRequest req);
+ public InvalidClientDataException(String message) {
- abstract boolean validForRequest(HttpServletRequest request);
+ super(message);
+ }
- abstract boolean preProcessHook(HttpServletRequest request, HttpServletResponse response) throws IOException;
+}
- abstract SAMLAssertion[] processHook(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider,
- SAMLNameIdentifier nameId, String authenticationMethod, Date authTime) throws SAMLException,
- IOException;
+class ProtocolSupport implements Metadata {
- abstract String getSAMLTargetParameter(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider);
+ private static Logger log = Logger.getLogger(ProtocolSupport.class.getName());
+ private Logger transactionLog;
+ private IdPConfig config;
+ private ArrayList fedMetadata = new ArrayList();
+ private NameMapper nameMapper;
+ private ServiceProviderMapper spMapper;
+ private ArpEngine arpEngine;
+ private AttributeResolver resolver;
+
+ ProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
+ ArpEngine arpEngine, AttributeResolver resolver) {
+
+ this.transactionLog = transactionLog;
+ this.config = config;
+ this.nameMapper = nameMapper;
+ this.spMapper = spMapper;
+ spMapper.setMetadata(this);
+ this.arpEngine = arpEngine;
+ this.resolver = resolver;
+ }
+
+ public static void validateEngineData(HttpServletRequest req) throws InvalidClientDataException {
- abstract String getAcceptanceURL(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider)
- throws InvalidClientDataException;
+ if ((req.getRemoteUser() == null) || (req.getRemoteUser().equals(""))) { throw new InvalidClientDataException(
+ "Unable to authenticate remote user"); }
+ if ((req.getRemoteAddr() == null) || (req.getRemoteAddr().equals(""))) { throw new InvalidClientDataException(
+ "Unable to obtain client address."); }
}
- class ShibbolethProfileHandler extends SSOProfileHandler {
- //private static Logger log = Logger.getLogger(ShibbolethProfileHandler.class.getName());
- private final String name = "Shibboleth";
+ public Logger getTransactionLog() {
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#getHandlerName()
- */
- String getHandlerName() {
- return name;
+ return transactionLog;
+ }
+
+ public IdPConfig getIdPConfig() {
+
+ return config;
+ }
+
+ public NameMapper getNameMapper() {
+
+ return nameMapper;
+ }
+
+ public ServiceProviderMapper getServiceProviderMapper() {
+
+ return spMapper;
+ }
+
+ public static void addSignatures(SAMLResponse response, RelyingParty relyingParty, EntityDescriptor provider,
+ boolean signResponse) throws SAMLException {
+
+ if (provider != null) {
+ boolean signAssertions = false;
+
+ SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
+ if (sp == null) {
+ log.info("Inappropriate metadata for provider: " + provider.getId() + ". Expected SPSSODescriptor.");
+ }
+ if (sp.getWantAssertionsSigned()) {
+ signAssertions = true;
+ }
+
+ if (signAssertions && relyingParty.getIdentityProvider().getSigningCredential() != null
+ && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
+
+ Iterator assertions = response.getAssertions();
+
+ while (assertions.hasNext()) {
+ SAMLAssertion assertion = (SAMLAssertion) assertions.next();
+ String assertionAlgorithm;
+ if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
+ assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
+ } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
+ assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
+ } else {
+ throw new InvalidCryptoException(SAMLException.RESPONDER,
+ "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
+ }
+
+ assertion.sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
+ .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
+ .getX509CertificateChain()));
+ }
+ }
}
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#validForRequest()
- */
- boolean validForRequest(HttpServletRequest request) {
+ // Sign the response, if appropriate
+ if (signResponse && relyingParty.getIdentityProvider().getSigningCredential() != null
+ && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
- if (request.getParameter("target") != null && !request.getParameter("target").equals("")
- && request.getParameter("shire") != null && !request.getParameter("shire").equals("")) {
- log.debug("Found (target) and (shire) parameters. Request "
- + "appears to be valid for the Shibboleth profile.");
- return true;
+ String responseAlgorithm;
+ if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
+ responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
+ } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
+ responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
} else {
- return false;
+ throw new InvalidCryptoException(SAMLException.RESPONDER,
+ "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
}
+
+ response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
+ Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
}
+ }
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#preProcessHook(javax.servlet.http.HttpServletRequest)
- */
- boolean preProcessHook(HttpServletRequest request, HttpServletResponse response) {
+ protected void addFederationProvider(Element element) {
+
+ log.debug("Found Federation Provider configuration element.");
+ if (!element.getTagName().equals("FederationProvider")) {
+ log.error("Error while attemtping to load Federation Provider. Malformed provider specificaion.");
+ return;
+ }
+
+ try {
+ fedMetadata.add(FederationProviderFactory.loadProvider(element));
+ } catch (MetadataException e) {
+ log.error("Unable to load Federation Provider. Skipping...");
+ }
+ }
+
+ public int providerCount() {
+
+ return fedMetadata.size();
+ }
+
+ public EntityDescriptor lookup(String providerId) {
+
+ Iterator iterator = fedMetadata.iterator();
+ while (iterator.hasNext()) {
+ EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
+ if (provider != null) { return provider; }
+ }
+ return null;
+ }
+
+ public EntityDescriptor lookup(Artifact artifact) {
+
+ Iterator iterator = fedMetadata.iterator();
+ while (iterator.hasNext()) {
+ EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
+ if (provider != null) { return provider; }
+ }
+ return null;
+ }
+
+ public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource) throws AAException {
+
+ try {
+ URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
+ return getReleaseAttributes(principal, requester, resource, potentialAttributes);
+
+ } catch (ArpProcessingException e) {
+ log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
+ + e.getMessage());
+ throw new AAException("Error retrieving data for principal.");
+ }
+ }
+
+ public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource,
+ URI[] attributeNames) throws AAException {
+
+ try {
+ AAAttributeSet attributeSet = new AAAttributeSet();
+ for (int i = 0; i < attributeNames.length; i++) {
+ AAAttribute attribute = new AAAttribute(attributeNames[i].toString());
+ attributeSet.add(attribute);
+ }
+
+ return resolveAttributes(principal, requester, resource, attributeSet);
+
+ } catch (SAMLException e) {
+ log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
+ + e.getMessage());
+ throw new AAException("Error retrieving data for principal.");
+
+ } catch (ArpProcessingException e) {
+ log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
+ + e.getMessage());
+ throw new AAException("Error retrieving data for principal.");
+ }
+ }
+
+ private SAMLAttribute[] resolveAttributes(Principal principal, String requester, URL resource,
+ AAAttributeSet attributeSet) throws ArpProcessingException {
+
+ resolver.resolveAttributes(principal, requester, attributeSet);
+ arpEngine.filterAttributes(attributeSet, principal, requester, resource);
+ return attributeSet.getAttributes();
+ }
+
+ /**
+ * Cleanup resources that won't be released when this object is garbage-collected
+ */
+ public void destroy() {
+
+ resolver.destroy();
+ arpEngine.destroy();
+ }
+}
+
+class Shibbolethv1SSOHandler extends ProtocolHandler {
+
+ private static Logger log = Logger.getLogger(Shibbolethv1SSOHandler.class.getName());
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see edu.internet2.middleware.shibboleth.idp.IdPResponder.ProtocolHandler#validForRequest(javax.servlet.http.HttpServletRequest)
+ */
+ boolean validForRequest(HttpServletRequest request) {
+
+ if (request.getParameter("target") != null && !request.getParameter("target").equals("")
+ && request.getParameter("shire") != null && !request.getParameter("shire").equals("")) {
+ log.debug("Found (target) and (shire) parameters. Request "
+ + "appears to be valid for the Shibboleth v1 profile.");
+ return true;
+ } else {
return false;
}
+ }
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#getRemoteProviderId(javax.servlet.http.HttpServletRequest)
- */
- String getRemoteProviderId(HttpServletRequest req) {
- return req.getParameter("providerId");
+ /*
+ * (non-Javadoc)
+ *
+ * @see edu.internet2.middleware.shibboleth.idp.IdPResponder.ProtocolHandler#processRequest(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ public void processRequest(HttpServletRequest request, HttpServletResponse response, ProtocolSupport support)
+ throws InvalidClientDataException, NameIdentifierMappingException, SAMLException, IOException,
+ ServletException {
+
+ // Ensure that we have the required data from the servlet container
+ ProtocolSupport.validateEngineData(request);
+
+ // Get the authN info
+ String username = support.getIdPConfig().getAuthHeaderName().equalsIgnoreCase("REMOTE_USER") ? request
+ .getRemoteUser() : request.getHeader(support.getIdPConfig().getAuthHeaderName());
+
+ // Select the appropriate Relying Party configuration for the request
+ RelyingParty relyingParty = null;
+ String remoteProviderId = request.getParameter("providerId");
+
+ // If the target did not send a Provider Id, then assume it is a Shib
+ // 1.1 or older target
+ if (remoteProviderId == null) {
+ relyingParty = support.getServiceProviderMapper().getLegacyRelyingParty();
+ } else if (remoteProviderId.equals("")) {
+ throw new InvalidClientDataException("Invalid service provider id.");
+ } else {
+ log.debug("Remote provider has identified itself as: (" + remoteProviderId + ").");
+ relyingParty = support.getServiceProviderMapper().getRelyingParty(remoteProviderId);
}
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#processHook(javax.servlet.http.HttpServletRequest,
- * edu.internet2.middleware.shibboleth.hs.HSRelyingParty, org.opensaml.SAMLNameIdentifier, java.lang.String,
- * long)
- */
- SAMLAssertion[] processHook(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider,
- SAMLNameIdentifier nameId, String authenticationMethod, Date authTime) throws SAMLException, IOException {
- Document doc = org.opensaml.XML.parserPool.newDocument();
-
- //Determine audiences and issuer
- ArrayList audiences = new ArrayList();
- if (relyingParty.getProviderId() != null) {
- audiences.add(relyingParty.getProviderId());
+ // Grab the metadata for the provider
+ EntityDescriptor provider = support.lookup(relyingParty.getProviderId());
+
+ // Determine the acceptance URL
+ String acceptanceURL = request.getParameter("shire");
+
+ // Make sure that the selected relying party configuration is appropriate for this
+ // acceptance URL
+ if (!relyingParty.isLegacyProvider()) {
+
+ if (provider == null) {
+ log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
+ relyingParty = support.getServiceProviderMapper().getRelyingParty(null);
+
+ } else {
+
+ if (isValidAssertionConsumerURL(provider, acceptanceURL)) {
+ log.info("Supplied consumer URL validated for this provider.");
+ } else {
+ log.error("Assertion consumer service URL (" + acceptanceURL + ") is NOT valid for provider ("
+ + relyingParty.getProviderId() + ").");
+ throw new InvalidClientDataException("Invalid assertion consumer service URL.");
+ }
}
- if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
- audiences.add(relyingParty.getName());
+ }
+
+ // Create SAML Name Identifier
+ SAMLNameIdentifier nameId = support.getNameMapper().getNameIdentifierName(relyingParty.getHSNameFormatId(),
+ new AuthNPrincipal(username), relyingParty, relyingParty.getIdentityProvider());
+
+ String authenticationMethod = request.getHeader("SAMLAuthenticationMethod");
+ if (authenticationMethod == null || authenticationMethod.equals("")) {
+ authenticationMethod = relyingParty.getDefaultAuthMethod().toString();
+ log.debug("User was authenticated via the default method for this relying party (" + authenticationMethod
+ + ").");
+ } else {
+ log.debug("User was authenticated via the method (" + authenticationMethod + ").");
+ }
+
+ // TODO change name!!!
+ // TODO We might someday want to provide a mechanism for the authenticator to specify the auth time
+ SAMLAssertion[] assertions = foo(request, relyingParty, provider, nameId, authenticationMethod, new Date(System
+ .currentTimeMillis()));
+
+ // TODO do assertion signing for artifact stuff
+
+ // SAML Artifact profile
+ if (useArtifactProfile(provider, acceptanceURL)) {
+ log.debug("Responding with Artifact profile.");
+
+ // Create artifacts for each assertion
+ ArrayList artifacts = new ArrayList();
+ for (int i = 0; i < assertions.length; i++) {
+ // TODO replace the artifact stuff here!!!
+ // artifacts.add(artifactMapper.generateArtifact(assertions[i], relyingParty));
+ }
+
+ // Assemble the query string
+ StringBuffer destination = new StringBuffer(acceptanceURL);
+ destination.append("?TARGET=");
+ destination.append(URLEncoder.encode(request.getParameter("target"), "UTF-8"));
+ Iterator iterator = artifacts.iterator();
+ StringBuffer artifactBuffer = new StringBuffer(); // Buffer for the transaction log
+ while (iterator.hasNext()) {
+ destination.append("&SAMLart=");
+ String artifact = (String) iterator.next();
+ destination.append(URLEncoder.encode(artifact, "UTF-8"));
+ artifactBuffer.append("(" + artifact + ")");
}
+ log.debug("Redirecting to (" + destination.toString() + ").");
+ response.sendRedirect(destination.toString()); // Redirect to the artifact receiver
+
+ support.getTransactionLog().info(
+ "Assertion artifact(s) (" + artifactBuffer.toString() + ") issued to provider ("
+ + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
+ + username + "). Name Identifier: (" + nameId.getName() + "). Name Identifier Format: ("
+ + nameId.getFormat() + ").");
+
+ // SAML POST profile
+ } else {
+ log.debug("Responding with POST profile.");
+ request.setAttribute("acceptanceURL", acceptanceURL);
+ request.setAttribute("target", request.getParameter("target"));
+
+ SAMLResponse samlResponse = new SAMLResponse(null, acceptanceURL, Arrays.asList(assertions), null);
+ ProtocolSupport.addSignatures(samlResponse, relyingParty, provider, true);
+ createPOSTForm(request, response, samlResponse.toBase64());
- String issuer = null;
+ // Make transaction log entry
if (relyingParty.isLegacyProvider()) {
-//TODO figure this out
- /*
- log.debug("Service Provider is running Shibboleth <= 1.1. Using old style issuer.");
- if (relyingParty.getIdentityProvider().getResponseSigningCredential() == null
- || relyingParty.getIdentityProvider().getResponseSigningCredential().getX509Certificate() == null) { throw new SAMLException(
- "Cannot serve legacy style assertions without an X509 certificate"); }
- issuer = ShibBrowserProfile.getHostNameFromDN(relyingParty.getIdentityProvider()
- .getResponseSigningCredential().getX509Certificate().getSubjectX500Principal());
- if (issuer == null || issuer.equals("")) { throw new SAMLException(
- "Error parsing certificate DN while determining legacy issuer name."); }
- */
+ support.getTransactionLog().info(
+ "Authentication assertion issued to legacy provider (SHIRE: " + request.getParameter("shire")
+ + ") on behalf of principal (" + username + ") for resource ("
+ + request.getParameter("target") + "). Name Identifier: (" + nameId.getName()
+ + "). Name Identifier Format: (" + nameId.getFormat() + ").");
} else {
- issuer = relyingParty.getIdentityProvider().getProviderId();
+ support.getTransactionLog().info(
+ "Authentication assertion issued to provider ("
+ + relyingParty.getIdentityProvider().getProviderId() + ") on behalf of principal ("
+ + username + "). Name Identifier: (" + nameId.getName()
+ + "). Name Identifier Format: (" + nameId.getFormat() + ").");
}
+ }
- //For compatibility with pre-1.2 shibboleth targets, include a pointer to the AA
- ArrayList bindings = new ArrayList();
- if (relyingParty.isLegacyProvider()) {
+ }
- SAMLAuthorityBinding binding = new SAMLAuthorityBinding(SAMLBinding.SOAP, relyingParty
- .getAAUrl().toString(), new QName(org.opensaml.XML.SAMLP_NS, "AttributeQuery"));
- bindings.add(binding);
- }
+ SAMLAssertion[] foo(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider,
+ SAMLNameIdentifier nameId, String authenticationMethod, Date authTime) throws SAMLException, IOException {
- //Create the authN assertion
- Vector conditions = new Vector(1);
- if (audiences != null && audiences.size() > 0) conditions.add(new SAMLAudienceRestrictionCondition(audiences));
+ Document doc = org.opensaml.XML.parserPool.newDocument();
- String[] confirmationMethods = {SAMLSubject.CONF_BEARER};
- SAMLSubject subject = new SAMLSubject(nameId, Arrays.asList(confirmationMethods), null, null);
+ // Determine audiences and issuer
+ ArrayList audiences = new ArrayList();
+ if (relyingParty.getProviderId() != null) {
+ audiences.add(relyingParty.getProviderId());
+ }
+ if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
+ audiences.add(relyingParty.getName());
+ }
- SAMLStatement[] statements = {new SAMLAuthenticationStatement(subject, authenticationMethod, authTime, request
- .getRemoteAddr(), null, bindings)};
+ String issuer = null;
+ if (relyingParty.isLegacyProvider()) {
+ // TODO figure this out
+ /*
+ * log.debug("Service Provider is running Shibboleth <= 1.1. Using old style issuer."); if
+ * (relyingParty.getIdentityProvider().getResponseSigningCredential() == null ||
+ * relyingParty.getIdentityProvider().getResponseSigningCredential().getX509Certificate() == null) { throw
+ * new SAMLException( "Cannot serve legacy style assertions without an X509 certificate"); } issuer =
+ * ShibBrowserProfile.getHostNameFromDN(relyingParty.getIdentityProvider()
+ * .getResponseSigningCredential().getX509Certificate().getSubjectX500Principal()); if (issuer == null ||
+ * issuer.equals("")) { throw new SAMLException( "Error parsing certificate DN while determining legacy
+ * issuer name."); }
+ */
+ } else {
+ issuer = relyingParty.getIdentityProvider().getProviderId();
+ }
- SAMLAssertion[] assertions = {new SAMLAssertion(issuer, new Date(System.currentTimeMillis()), new Date(System
- .currentTimeMillis() + 300000), conditions, null, Arrays.asList(statements))};
+ // For compatibility with pre-1.2 shibboleth targets, include a pointer to the AA
+ ArrayList bindings = new ArrayList();
+ if (relyingParty.isLegacyProvider()) {
- if (log.isDebugEnabled()) {
- log.debug("Dumping generated SAML Assertions:"
- + System.getProperty("line.separator")
- + new String(new BASE64Decoder().decodeBuffer(new String(assertions[0].toBase64(), "ASCII")),
- "UTF8"));
- }
+ SAMLAuthorityBinding binding = new SAMLAuthorityBinding(SAMLBinding.SOAP, relyingParty.getAAUrl()
+ .toString(), new QName(org.opensaml.XML.SAMLP_NS, "AttributeQuery"));
+ bindings.add(binding);
+ }
+
+ // Create the authN assertion
+ Vector conditions = new Vector(1);
+ if (audiences != null && audiences.size() > 0) conditions.add(new SAMLAudienceRestrictionCondition(audiences));
+
+ String[] confirmationMethods = {SAMLSubject.CONF_BEARER};
+ SAMLSubject subject = new SAMLSubject(nameId, Arrays.asList(confirmationMethods), null, null);
- return assertions;
+ SAMLStatement[] statements = {new SAMLAuthenticationStatement(subject, authenticationMethod, authTime, request
+ .getRemoteAddr(), null, bindings)};
+
+ SAMLAssertion[] assertions = {new SAMLAssertion(issuer, new Date(System.currentTimeMillis()), new Date(System
+ .currentTimeMillis() + 300000), conditions, null, Arrays.asList(statements))};
+
+ if (log.isDebugEnabled()) {
+ log.debug("Dumping generated SAML Assertions:"
+ + System.getProperty("line.separator")
+ + new String(new BASE64Decoder().decodeBuffer(new String(assertions[0].toBase64(), "ASCII")),
+ "UTF8"));
}
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#getSAMLTargetParameter(javax.servlet.http.HttpServletRequest,
- * edu.internet2.middleware.shibboleth.hs.HSRelyingParty)
- */
- String getSAMLTargetParameter(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider) {
- return request.getParameter("target");
+ return assertions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see edu.internet2.middleware.shibboleth.idp.IdPResponder.ProtocolHandler#getHandlerName()
+ */
+ String getHandlerName() {
+
+ // TODO Auto-generated method stub
+ return "Shibboleth-v1-SSO";
+ }
+
+ private static void createPOSTForm(HttpServletRequest req, HttpServletResponse res, byte[] buf) throws IOException,
+ ServletException {
+
+ // Hardcoded to ASCII to ensure Base64 encoding compatibility
+ req.setAttribute("assertion", new String(buf, "ASCII"));
+
+ if (log.isDebugEnabled()) {
+ try {
+ log.debug("Dumping generated SAML Response:" + System.getProperty("line.separator")
+ + new String(new BASE64Decoder().decodeBuffer(new String(buf, "ASCII")), "UTF8"));
+ } catch (IOException e) {
+ log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
+ }
}
- /*
- * (non-Javadoc)
- *
- * @see edu.internet2.middleware.shibboleth.hs.AuthNProfileHandler#getAcceptanceURL(javax.servlet.http.HttpServletRequest,
- * edu.internet2.middleware.shibboleth.hs.HSRelyingParty)
- */
- String getAcceptanceURL(HttpServletRequest request, RelyingParty relyingParty, EntityDescriptor provider)
- throws InvalidClientDataException {
- return request.getParameter("shire");
+ // TODO rename from hs.jsp to more appropriate name
+ RequestDispatcher rd = req.getRequestDispatcher("/hs.jsp");
+ rd.forward(req, res);
+ }
+
+ private static boolean useArtifactProfile(EntityDescriptor provider, String acceptanceURL) {
+
+ // Default to POST if we have no metadata
+ if (provider == null) { return false; }
+
+ // Default to POST if we have incomplete metadata
+ SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
+ if (sp == null) { return false; }
+
+ // TODO: This will actually favor artifact, since a given location could support
+ // both profiles. If that's not what we want, needs adjustment...
+ Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
+ while (endpoints.hasNext()) {
+ Endpoint ep = (Endpoint) endpoints.next();
+ if (acceptanceURL.equals(ep.getLocation())
+ && SAMLBrowserProfile.PROFILE_ARTIFACT_URI.equals(ep.getBinding())) { return true; }
}
+
+ // Default to POST if we have incomplete metadata
+ return false;
}
+ private static boolean isValidAssertionConsumerURL(EntityDescriptor provider, String shireURL)
+ throws InvalidClientDataException {
+
+ SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
+ if (sp == null) {
+ log.info("Inappropriate metadata for provider.");
+ return false;
+ }
+
+ Iterator endpoints = sp.getAssertionConsumerServiceManager().getEndpoints();
+ while (endpoints.hasNext()) {
+ if (shireURL.equals(((Endpoint) endpoints.next()).getLocation())) { return true; }
+ }
+ log.info("Supplied consumer URL not found in metadata.");
+ return false;
+ }
}
-class InvalidClientDataException extends Exception {
+class ArtifactQueryHandler extends ProtocolHandler {
- public InvalidClientDataException(String message) {
+ /*
+ * (non-Javadoc)
+ *
+ * @see edu.internet2.middleware.shibboleth.idp.ProtocolHandler#validForRequest(javax.servlet.http.HttpServletRequest)
+ */
+ boolean validForRequest(HttpServletRequest request) {
- super(message);
+ // TODO Auto-generated method stub
+ return false;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see edu.internet2.middleware.shibboleth.idp.ProtocolHandler#getHandlerName()
+ */
+ String getHandlerName() {
+
+ // TODO change
+ return "foo";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see edu.internet2.middleware.shibboleth.idp.ProtocolHandler#processRequest(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse, edu.internet2.middleware.shibboleth.idp.ProtocolSupport)
+ */
+ public void processRequest(HttpServletRequest request, HttpServletResponse response, ProtocolSupport support)
+ throws SAMLException, InvalidClientDataException, NameIdentifierMappingException, IOException,
+ ServletException {
+
+ // TODO Auto-generated method stub
+
+ }
+
+}
+
+// TODO should this name say something about SSO?
+
+abstract class ProtocolHandler {
+
+ abstract boolean validForRequest(HttpServletRequest request);
+
+ abstract String getHandlerName();
+
+ /**
+ * @param request
+ * @param response
+ * @throws ServletException
+ */
+ // TODO add javadoc
+ // TODO should the name identifier mapping exception really be thrown here or covered up?
+ public abstract void processRequest(HttpServletRequest request, HttpServletResponse response,
+ ProtocolSupport support) throws SAMLException, InvalidClientDataException, NameIdentifierMappingException,
+ IOException, ServletException;
+}
+
+class FederationProviderFactory {
+
+ private static Logger log = Logger.getLogger(FederationProviderFactory.class.getName());
+
+ public static Metadata loadProvider(Element e) throws MetadataException {
+
+ String className = e.getAttribute("type");
+ if (className == null || className.equals("")) {
+ log.error("Federation Provider requires specification of the attribute \"type\".");
+ throw new MetadataException("Failed to initialize Federation Provider.");
+ } else {
+ try {
+ Class[] params = {Class.forName("org.w3c.dom.Element"),};
+ return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
+ } catch (Exception loaderException) {
+ log.error("Failed to load Federation Provider implementation class: " + loaderException);
+ Throwable cause = loaderException.getCause();
+ while (cause != null) {
+ log.error("caused by: " + cause);
+ cause = cause.getCause();
+ }
+ throw new MetadataException("Failed to initialize Federation Provider.");
+ }
+ }
+ }
}
\ No newline at end of file