Update to use new profile handler code.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml1 / ShibbolethSSO.java
1 /*
2  * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.profile.saml1;
18
19 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
20 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
21
22 import java.io.IOException;
23 import java.net.URLEncoder;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.util.List;
27
28 import javax.servlet.RequestDispatcher;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.ServletResponse;
32 import javax.servlet.http.Cookie;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 import javax.servlet.http.HttpSession;
36
37 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
38 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
39 import edu.internet2.middleware.shibboleth.common.relyingparty.saml1.ShibbolethSSOConfiguration;
40 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
41
42 import org.apache.log4j.Logger;
43 import org.bouncycastle.util.encoders.Hex;
44 import org.joda.time.DateTime;
45 import org.opensaml.Configuration;
46 import org.opensaml.common.SAMLVersion;
47 import org.opensaml.common.binding.SAMLArtifact;
48 import org.opensaml.common.binding.SAMLArtifactFactory;
49 import org.opensaml.common.binding.ArtifactMap;
50 import org.opensaml.saml1.core.Assertion;
51 import org.opensaml.saml1.core.AttributeStatement;
52 import org.opensaml.saml1.core.Audience;
53 import org.opensaml.saml1.core.AudienceRestrictionCondition;
54 import org.opensaml.saml1.core.AuthenticationStatement;
55 import org.opensaml.saml1.core.Conditions;
56 import org.opensaml.saml1.core.ConfirmationMethod;
57 import org.opensaml.saml1.core.NameIdentifier;
58 import org.opensaml.saml1.core.Response;
59 import org.opensaml.saml1.core.Status;
60 import org.opensaml.saml1.core.StatusCode;
61 import org.opensaml.saml1.core.StatusMessage;
62 import org.opensaml.saml1.core.Subject;
63 import org.opensaml.saml1.core.SubjectConfirmation;
64 import org.opensaml.saml1.core.impl.AuthenticationStatementBuilder;
65 import org.opensaml.saml2.metadata.AssertionConsumerService;
66 import org.opensaml.saml2.metadata.EntityDescriptor;
67 import org.opensaml.saml2.metadata.SPSSODescriptor;
68 import org.opensaml.saml2.metadata.provider.MetadataProvider;
69 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
70 import org.opensaml.xml.ConfigurationException;
71 import org.opensaml.xml.XMLObjectBuilder;
72 import org.opensaml.xml.signature.KeyInfo;
73 import org.opensaml.xml.signature.SignableXMLObject;
74 import org.opensaml.xml.signature.Signature;
75
76 /**
77  * Shibboleth, version 1.X, single sign-on profile handler.
78  * 
79  * This profile implements the SSO profile from "Shibboleth Architecture Protocols and Profiles" - 10 September 2005.
80  */
81 public class ShibbolethSSO extends AbstractSAML1ProfileHandler {
82
83     /** log4j. */
84     private static final Logger log = Logger.getLogger(ShibbolethSSO.class);
85
86     /** SAML 1 bearer confirmation method URI. */
87     protected static final String BEARER_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:bearer";
88
89     /** SAML 1 artifact confirmation method URI */
90     protected static final String ARTIFACT_CONF_METHOD_URI = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
91
92     /** SAML 1.1 SPSSO protocol URI */
93     protected static final String SAML11_PROTOCOL_URI = "urn:oasis:names:tc:SAML:1.1:protocol";
94
95     /** SAML 1 Browser/POST protocol URI. */
96     protected static final String PROFILE_BROWSER_POST_URI = "urn:oasis:names:tc:SAML:1.0:profiles:browser-post";
97
98     /** SAML 1 Artifact protocol URI. */
99     protected static final String PROFILE_ARTIFACT_URI = "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01";
100
101     /** The digest algorithm for generating SP cookies. */
102     protected static final String RP_COOKIE_DIGEST_ALG = "SHA-1";
103
104     /** The RelyingPartyManager. */
105     protected RelyingPartyConfigurationManager rpManager;
106
107     /**
108      * Backing store for artifacts. This must be shared between ShibbolethSSO and AttributeQuery.
109      */
110     protected ArtifactMap artifactMap;
111
112     /** The path to the IdP's AuthenticationManager servlet */
113     protected String authnMgrURL;
114
115     /** The URI of the default authentication method */
116     protected String authenticationMethodURI;
117
118     /** Builder for AuthenticationStatement objects. */
119     protected XMLObjectBuilder authnStmtBuilder;
120
121     /** Builder for Subject elements. */
122     protected XMLObjectBuilder subjectbuilder;
123
124     /** Builder for SubjectConfirmation objects. */
125     protected XMLObjectBuilder subjConfBuilder;
126
127     /** Builder for SubjectConfirmationMethod objects. */
128     protected XMLObjectBuilder confMethodBuilder;
129
130     /** Builder for Artifacts. */
131     protected XMLObjectBuilder artifactBuilder;
132
133     /** Builder for NameIdentifiers. */
134     protected XMLObjectBuilder nameIdentifierBuilder;
135
136     /** Builder for Audience elements. */
137     protected XMLObjectBuilder audienceBuilder;
138
139     /** Builder for AudienceRestrictionCondition elements. */
140     protected XMLObjectBuilder audienceRestrictionBuilder;
141
142     /** Builder for Assertions. */
143     protected XMLObjectBuilder assertionBuilder;
144
145     /** Builder for Status objects. */
146     protected XMLObjectBuilder statusBuilder;
147
148     /** Builder for StatusCode objects. */
149     protected XMLObjectBuilder statusCodeBuilder;
150
151     /** Builder for StatusMessage objects. */
152     protected XMLObjectBuilder statusMessageBuilder;
153
154     /** Builder for Response objects. */
155     protected XMLObjectbuilder responseBuilder;
156
157     /** Block stale requests. */
158     protected boolean blockStaleRequests = false;
159
160     /** Blame the SP if requests are malformed. */
161     protected boolean blameSP = false;
162
163     /**
164      * Time after which an authn request is considered stale(in seconds). Defaults to 30 minutes.
165      */
166     protected int requestTTL = 1800;
167
168     /** Protocol binding to use to the Authentication Assertion */
169     protected enum ENDPOINT_BINDING {
170         BROWSER_POST, ARTIFACT
171     };
172
173     /** ArtifactFactory used to make artifacts. */
174     protected SAMLArtifactFactory artifactFactory;
175
176     /** PRNG for Artifact assertionHandles. */
177     protected SecureRandom prng;
178
179     /**
180      * Default constructor.
181      */
182     public ShibbolethSSO() {
183
184         // setup SAML object builders
185
186         assertionBuilder = getBuilderFactory().getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
187         authnStmtBuilder = getBuilderFactory().getBuilder(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
188         subjectbuilder = getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
189         subjConfBuilder = getBuilderFactory().getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
190         confMethodBuilder = getBuilderFactory().getBuilder(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
191         nameIdentifierBuilder = getBuilderFactory().getBuilder(NameIdentifier.DEFAULT_ELEMENT_NAME);
192         audienceBuilder = getBuilderFactory().getBbuilder(Audience.DEFAULT_ELEMENT_NAME);
193         audienceRestrictionBuilder = getBuilderFactory().getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
194         statusBuilder = getBuilderFactory().getBuidler(Status.DEFAULT_ELEMENT_NAME);
195         statusCodeBuilder = getBuilderFactory().getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
196         statusMessageBuilder = getBuilderFactory().getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
197         responseBuilder = getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
198
199         artifactFactory = new SAMLArtifactFactory();
200     }
201
202     /**
203      * Set the authentication manager.
204      * 
205      * @param authnManagerURL The URL of the IdP's AuthenticationManager servlet
206      */
207     public void setAuthenticationManager(String authnManagerURL) {
208         authnMgrURL = authnManagerURL;
209     }
210
211     /**
212      * Set the RelyingPartyManager.
213      * 
214      * @param rpManager A RelyingPartyManager.
215      */
216     public void setRelyingPartyManager(RelyingPartyConfigurationManager rpManager) {
217         this.rpManager = rpManager;
218     }
219
220     /**
221      * Set the authentication method URI.
222      * 
223      * The URI SHOULD come from oasis-sstc-saml-core-1.1, section 7.1
224      * 
225      * @param authMethod The authentication method's URI
226      */
227     public void setAuthenticationMethodURI(String authMethod) {
228         authenticationMethodURI = authMethod;
229     }
230
231     /**
232      * Set if old requests should be blocked.
233      * 
234      * @param blockStaleRequests boolean flag.
235      */
236     public void setBlockStaleRequests(boolean blockStaleRequests) {
237         this.blockStaleRequests = blockStaleRequests;
238     }
239
240     /**
241      * Return if stale requests are blocked.
242      * 
243      * @return <code>true</code> if old requests are blocked.
244      */
245     public boolean getBlockStaleRequests() {
246         return blockStaleRequests;
247     }
248
249     /**
250      * Set request TTL.
251      * 
252      * @param ttl Request timeout (in seconds).
253      */
254     public void setRequestTTL(int ttl) {
255         requestTTL = ttl;
256     }
257
258     /**
259      * Get Request TTL. This is the time after which a request is considered stale.
260      * 
261      * @return request timeout (in seconds).
262      */
263     public int getRequestTTL() {
264         return requestTTL;
265     }
266
267     /**
268      * Set the artifact map backing store.
269      * 
270      * @param artifactMap the Artifact mapping backing store.
271      */
272     public void setArtifactMap(ArtifactMap artifactMap) {
273         this.artifactMap = artifactMap;
274     }
275
276     /**
277      * Get the artifact map backing store.
278      * 
279      * @return An ArtifactMap instance.
280      */
281     public ArtifactMap getArtifactMap() {
282         return artifactMap;
283     }
284
285     /** {@inheritDoc} */
286     public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
287
288         // Only http servlets are supported for now.
289         if (!(request.getRequest() instanceof HttpServletRequest)) {
290             log.error("Received a non-HTTP request from " + request.getRequest().getRemoteHost());
291             return false;
292         }
293
294         HttpServletRequest httpReq = (HttpServletRequest) request.getRequest();
295         HttpServletResponse httpResp = (HttpServletResponse) response.getResponse();
296         HttpSession httpSession = httpReq.getSession();
297         LoginContext loginCtx;
298
299         String shire = null;
300         String target = null;
301         String providerId = null;
302         String remoteAddr = null;
303
304         // extract the (mandatory) request parameters.
305         if (!getRequestParameters(httpReq, shire, target, providerId, remoteAddr)) {
306
307             if (blameSP) {
308                 httpReq.setAttribute("errorPage", "/IdPErrorBlameSP.jsp");
309                 // XXX: flesh this out more.
310             }
311
312             return false;
313         }
314
315         // check for stale requests
316         if (blockStaleRequests) {
317             String cookieName = getRPCookieName(providerName);
318             if (!validateFreshness(httpReq, httpResp, cookieName)) {
319                 return false;
320             }
321
322             writeFreshnessCookie(httpReq, httpResp, cookieName);
323         }
324
325         // check if the user has already been authenticated
326         Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
327         if (o == null) {
328
329             // the user hasn't been authenticated, so forward the request
330             // to the AuthenticationManager. When the AuthenticationManager
331             // is done it will forward the request back to this servlet.
332
333             // don't force reauth or passive auth
334             loginCtx = new LoginContext(false, false);
335             loginCtx.setProfileHandlerURL(httpReq.getPathInfo());
336             httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginCtx);
337             try {
338                 RequestDispatcher dispatcher = request.getRequest().getRequestDispatcher(authnMgrURL);
339                 dispatcher.forward(request.getRequest(), response.getResponse());
340             } catch (IOException ex) {
341                 log.error("Error forwarding SAML 1 SSO request to AuthenticationManager", ex);
342                 return false;
343             }
344         }
345
346         // The user has been authenticated.
347         // Process the SAML 1 authn request.
348
349         if (!(o instanceof LoginContext)) {
350             log.error("Invalid login context object -- object is not an instance of LoginContext.");
351             return false;
352         }
353
354         loginCtx = (LoginContext) o;
355
356         if (!loginCtx.getAuthenticationOK()) {
357             // issue error message.
358             String failureMessage = loginCtx.getAuthenticationFailureMessage();
359
360             // generate SAML failure message
361
362             return true;
363         }
364
365         // The user successfully authenticated,
366         // so build the appropriate AuthenticationStatement.
367
368         DateTime now = new DateTime();
369         RelyingPartyConfiguration relyingParty = rpManager.getRelyingPartyConfiguration(providerId);
370         ShibbolethSSOConfiguration ssoConfig = relyingParty.getProfileConfigurations().get(
371                 ShibbolethSSOConfiguration.PROFILE_ID);
372         SPSSODescriptor spDescriptor;
373
374         try {
375             spDescriptor = rpManager.getMetadataProvider().getEntityDescriptor(relyingParty.getRelyingPartyID())
376                     .getSPSSODescriptor(SAML11_PROTOCOL_URI);
377         } catch (MetadataProviderException ex) {
378             log.error("Unable to locate metadata for SP " + providerId + " for protocol " + SAML11_PROTOCOL_URI, ex);
379             return false;
380         }
381
382         if (spDescriptor == null) {
383             log.error("Unable to locate metadata for SP " + providerId + " for protocol " + SAML11_PROTOCOL_URI);
384             // handle error
385             return true;
386         }
387
388         // validate the AssertionConsumer URL
389         List<AssertionConsumerService> consumerEndpoints = validateAssertionConsumerURL(spDescriptor, shire);
390         if (consumerEndpoints.length == 0) {
391             // handle error
392             return true;
393         }
394
395         ENDPOINT_BINDING endpointBinding = getProtocolBinding(spDescriptor, consumerEndpoints, shire);
396
397         String confMethod;
398         if (endpointBinding = ENDPOINT_BINDING.BROWSER_POST) {
399             confMethod = BEARER_CONF_METHOD_URI;
400         } else if (endpointBinding = ENDPOINT_BINDING.ARTIFACT) {
401             confMethod = ARTIFACT_CONF_METHOD_URI;
402         }
403
404         Assertion authenticationAssertion = generateAuthenticationAssertion(loginCtx, relyingParty, ssoConfig,
405                 providerId, spDescriptor, confMethod, now);
406         if (authenticationAssertion == null) {
407             // do error handling
408             return true;
409         }
410
411         if (endpointBinging == ENDPOINT_BINDING.BROWSER_POST) {
412             // do post
413         } else if (endpointBinding == ENDPOINT_BINDING.ARTIFACT) {
414             respondWithArtifact(httpReq, httpResp, shire, target, new Assertion[] { authenticationAssertion });
415         }
416
417         return true;
418     }
419
420     /**
421      * Respond with a SAML Artifact.
422      * 
423      * @param request The HttpServletRequest.
424      * @param response The HttpServletResponse.
425      * @param shire The AssertionConsumerService URL.
426      * @parma target The target parameter from the request.
427      * @param assertions One or more SAML assertions.
428      */
429     protected void respondWithArtifact(HttpServletRequest request, HttpServletResponse response, String shire,
430             String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ServletException,
431             NoSuchProviderException {
432
433         if (assertions.length < 1) {
434             return;
435         }
436
437         StringBuilder buf = new StringBuilder(shire);
438         buf.append("?TARGET=");
439         buf.append(URLEncoder.encode(target), "UTF-8");;
440
441         // We construct the type 1 Artifact's sourceID by SHA-1 hashing the
442         // IdP's providerID.
443         // This is legacy holdover from Shib 1.x.
444         MessageDigest digester = MessageDigest.getInstance("SHA-1");
445         byte[] sourceID = digester.digest(relyingParty.getProviderID);
446
447         for (Assertion assertion : assertions) {
448
449             // XXX: todo: log the assertion to log4j @ debug level.
450
451             byte artifactType = (byte) relyingParty.getDefaultArtifactType();
452
453             SAMLArtifact artifact = artifactFactory.buildArtifact(SAML_VERSION, new byte[] { 0, artifactType },
454                     relyingParty.getProviderID());
455
456             String artifactID = artifact.hexEncode();
457             artifactMap.put(artifact, assertion);
458
459             log.debug("encoding assertion " + assertion.getID() + " into artifact " + artifactID);
460             log.debug("appending artifact " + artifactID + " for URL " + shire);
461             buf.append("&SAMLArt=");
462             buf.append(URLEncoder.encode(artifact.base64Encode(), "UTF-8"));
463         }
464
465         String url = buf.toString();
466         response.sendRedirect(url);
467     }
468
469     /**
470      * Respond with the SAML 1 Browser/POST profile.
471      * 
472      * @param request The HttpServletRequest.
473      * @param response The HttpServletResponse.
474      * @param shire The AssertionConsumerService URL.
475      * @parma target The target parameter from the request.
476      * @param assertions One or more SAML assertions.
477      */
478     protected void respondWithPOST(HttpServletRequest request, HttpServletResponse response, String shire,
479             String target, RelyingPartyConfiguration relyingParty, Assertion[] assertions) throws ServletException {
480
481         Response samlResponse = (Response) responseBuilder.buildObject(Response.DEFAULT_ELEMENT_NAME);
482         Status status = buildStatus("Success", null);
483         samlResponse.setStatus(status);
484         samlResponse.setIssueInstant(new DateTime());
485         samlResponse.setVersion(SAML_VERSION);
486         samlResponse.setID(getIdGenerator().generateIdentifier());
487         samlResponse.setRecipient(relyingParty.getRelyingPartyID());
488
489         List<Assertion> assertionList = samlResponse.getAssertions();
490         for (Assertion assertion : assertions) {
491             assertionList.add(assertion);
492         }
493
494         request.setAttribute("acceptanceURL", shire);
495         request.setAttribute("target", target);
496
497         RequestDispatcher dispatcher = request.getRequestDispatcher("/IdP_SAML1_POST.jdp");
498         dispatcher.forward(request, response);
499     }
500
501     /**
502      * Get the Shibboleth profile-specific request parameters. The shire, target, providerId and remoteAddr parameters
503      * will be populated upon successful return.
504      * 
505      * @param request The servlet request from the SP.
506      * @param shire The AttributeConsumerService URL
507      * @param target The location to which to POST the response.
508      * @param providerId The SP's provider ID in the metadata.
509      * @param remoteAddr The address of the requestor.
510      * 
511      * @return <code>true</code> if the request contains valid parameters.
512      */
513     protected boolean getRequestParameters(HttpServletRequest request, String shire, String target, String providerId,
514             String remoteAddr) {
515
516         target = request.getParameter("target");
517         providerId = request.getParameter("providerId");
518         shire = request.getParameter("shire");
519         remoteAddr = request.getRemoteAddr();
520
521         if (target == null || target.equals("")) {
522             log.error("Shib 1 SSO request is missing or contains an invalid target parameter");
523             return false;
524         }
525
526         if (providerId == null || providerId.equals("")) {
527             log.error("Shib 1 SSO request is missing or contains an invalid provierId parameter");
528             return false;
529         }
530
531         if (shire == null || providerId.equals("")) {
532             log.error("Shib 1 SSO request is missing or contains an invalid shire parameter");
533             return false;
534         }
535
536         if (remoteAddr == null || remoteAddr.equals("")) {
537             log.error("Unable to obtain requestor address when processing Shib 1 SSO request");
538             return false;
539         }
540
541         return true;
542     }
543
544     /**
545      * Generate a SAML 1 AuthenticationStatement.
546      * 
547      * @param loginCtx The LoginContext.
548      * @param relyingParty The Replying Party configuration for the SP.
549      * @param ssoConfig The ShibbolethSSOConfiguration data.
550      * @param spID The providerID of the SP that sent the request.
551      * @param spDescriptor The SPSSO Descriptor from the metadata.
552      * @param subjectConfirmationMethod The SubjectConfirmationMethod. If <code>null</code> no
553      *            SubjectConfirmationMethod element will be generated.
554      * @param now The current timestamp
555      * 
556      * @return A SAML 1 Authentication Assertion or <code>null</code> on error.
557      */
558     protected Assertion generateAuthenticationAssertion(final LoginContext loginCtx,
559             final RelyingPartyConfiguration relyingParty, final ShibbolethSSOConfiguration ssoConfig, String spID,
560             final SPSSODescriptor spDescriptor, String subjectConfirmationMethod, final DateTime now) {
561
562         Assertion authenticationAssertion = (Assertion) assertionBuilder.build(Assertion.DEFAULT_ELEMENT_NAME);
563
564         authenticationAssertion.setIssueInstant(now);
565         authenticationAssertion.setVersion(SAMLVersion.VERSION_11);
566         authenticationAssertion.setIssuer(relyingParty.getProviderID());
567         authenticationAssertion.setID(getIdGenerator().generateIdentifier());
568         authenticationAssertion.setIssuer(relyingParty.getProviderID());
569
570         Conditions conditions = authenticationAssertion.getConditions();
571         conditions.setNotBefore(now.minusSeconds(30)); // for now, clock skew
572         // is hard-coded to 30
573         // seconds.
574         conditions.setNotOnOrAfter(now.plusMillis(ssoConfig.getAssertionLifetime()));
575
576         List<AudienceRestrictionCondition> audiences = conditions.getAudienceRestrictionConditions();
577         AudienceRestrictionCondition restrictionCondition = (AudienceRestrictionCondition) audienceRestrictionBuilder
578                 .buildObject(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
579         Audience rpAudience = (Audience) audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
580         rpAudience.setURI(relyingParty.getProviderID());
581         audiences.add(rpAudience);
582         if (!relyingParty.getProviderID().equals(spID)) {
583             Audience spAudience = (Audience) audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
584             spAudience.setURI(spID);
585             audiences.add(spAudience);
586         }
587
588         AuthenticationStatement authenticationStatement = (AuthenticationStatement) authnStmtBuilder
589                 .buildObject(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
590
591         authenticationStatement.setSubject(buildSubject(loginCtx, subjectConfirmationMethod, relyingParty));
592         authenticationStatement.setAuthenticationInstant(loginCtx.getAuthenticationInstant());
593         authenticationStatement.setAuthenticationMethod(authenticationMethodURI);
594
595         authenticationAssertion.getAuthenticationStatements().add(authenticationStatement);
596
597         if (spDescriptor.getWantAssertionsSigned()) {
598             // sign the assertion
599         }
600
601         return authenticationStatement;
602     }
603
604     /**
605      * Get the protocol binding to use for sending the authentication assertion. Currently, only Browser/POST and
606      * Artifact are supported. This method will return the first recognized binding that it locates.
607      * 
608      * @param spDescriptor The SP's SPSSODescriptor
609      * @param endpoints The list of AssertionConsumerEndpoints with the "shire" URL as their location.
610      * @param shireURL The "shire" url from the authn request.
611      * 
612      * @return The protocol binding for a given SPSSODescriptor.
613      * 
614      * @throws MetadataException if no Browswer/POST or Artifact binding can be found.
615      */
616     protected ENDPOINT_BINDING getProtocolBinding(final SPSSODescriptor spDecsriptor,
617             final List<AssertionConsumerService> endpoints, String shireURL) throws MetadataException {
618
619         // check the default AssertionConsumerService first.
620         AssertionConsumerService defaultConsumer = spDescriptor.getDefaultAssertionConsumerService();
621
622         if (defaultConsumer != null && defaultConsumer.getLocation().equals(acceptanceURL)) {
623
624             if (defaultConsumer.getBinding().equals(PROFILE_ARTIFACT_URI)) {
625                 return ENDPOINT_BINDING.ARTIFACT;
626             } else if (defaultConsumer.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
627                 return ENDPOINT_BINDING.BROWSER_POST;
628             }
629         }
630
631         // check the (already filtered) list of AssertionConsumer endpoints
632         for (AssertionConsumerService endpoint : endpoints) {
633             if (endpoint.getBinding().equals(PROFILE_ARTIFACT_URI)) {
634                 return ENDPOINT_BINDING.ARTIFACT;
635             } else if (endpoint.getBinding().equals(PROFILE_BROWSER_POST_URI)) {
636                 return ENDPOINT_BINDING.BROWSER_POST;
637             }
638         }
639
640         // no AssertionConsumerServices were found, or none had a recognized
641         // binding
642         log.error("Unable to find a Browswer/POST or Artifact binding " + " for an AssertionConsumerService in "
643                 + spDecsriptor.getID());
644
645         throw new MetadataException("Unable to find a Browswer/POST or Artifact binding "
646                 + " for an AssertionConsumerService in " + spDecsriptor.getID());
647     }
648
649     /**
650      * Sign an XMLObject.
651      * 
652      * @param object The XMLObject to be signed
653      */
654     protected void SignXMLObject(final SignableXMLObject object) throws KeyException {
655         // sign the object
656     }
657
658     /**
659      * Validate the AssertionConsumer ("shire") URL against the metadata.
660      * 
661      * @param spDescriptor The SPSSO element from the metadata
662      * @param URL The "shire" URL.
663      * 
664      * @return a {@link List} of AssertionConsumerServices which have <code>url</code> as their location.
665      */
666     protected List<AssertionConsumerService> validateAssertionConsumerURL(final SPSSODescriptor spDescriptor, String url) {
667
668         // spDescriptor returns a reference to an internal mutable copy, so make
669         // a copy of it.
670         List<AssertionConsumerService> consumerURLs = new FastList<AssertionConsumerService>();
671
672         // filter out any list elements that don't have the correct location
673         // field
674         // copy any consumerURLs with the correct location
675         for (AssertionConsumerService service : spDescriptor.getAssertionConsumerServices()) {
676             if (service.getLocation().equals(url)) {
677                 consumerURLs.add(service);
678             }
679         }
680
681         return consumerURLs;
682     }
683
684     /**
685      * Validate the "freshness" of an authn request. If the reqeust is more than 30 minutes old, reject it.
686      * 
687      * @param request The HttpServletRequest
688      * @param response The HttpServletResponse
689      * @param cookieName The name of the RP's cookie.
690      * 
691      * @return <code>true</code> if the cookie is fresh; otherwise <code>false</code>
692      */
693     protected boolean validateFreshness(HttpServletRequest request, HttpServletResponse response, String cookieName)
694             throws IOException, ServletException {
695
696         if (cookieName == null) {
697             return false;
698         }
699
700         String timestamp = request.getParameter("time");
701         if (timestamp == null || timestamp.equals("")) {
702             return true;
703         }
704
705         long reqtime;
706         try {
707             reqtime = Long.parseLong(timestamp);
708         } catch (NumberFormatException ex) {
709             log.error("Unable to parse Authentication Request's timestamp", ex);
710             return false;
711         }
712
713         if (reqtime * 1000 < System.currentTimeMillis() - requestTTL * 1000) {
714             RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
715             rd.forward(request, response);
716             return false;
717         }
718
719         for (Cookie cookie : request.getCookies()) {
720             if (cookieName.equals(cookie.getName())) {
721                 try {
722                     long cookieTime = Long.parseLong(cookie.getValue());
723                     if (reqtime <= cookieTime) {
724                         RequestDispatcher rd = request.getRequestDispatcher("/IdPStale.jsp");
725                         rd.forward(request, response);
726                         return false;
727                     }
728                 } catch (NumberFormatException ex) {
729                     log.error("Unable to parse freshness cookie's timestamp", ex);
730                     return false;
731                 }
732             }
733         }
734
735         return true;
736     }
737
738     /**
739      * Generate the RP's cookie name
740      * 
741      * @param providerID The RP's providerID
742      * 
743      * @throws NoSuchAlgorithmException If unable to find a JCE provider for SHA-1
744      * 
745      * @return the RP's cookie name
746      */
747     protected String getRPCookieName(String providerID) throws NoSuchAlgorithmException {
748
749         MessageDigest digester = MessageDigest.getInstance(RP_COOKIE_DIGEST_ALG);
750         return "shib_sp_" + new String(Hex.encode(digester.digest(providerID.getBytes("UTF-8"))));
751     }
752
753     /**
754      * Write the current time into the freshness cookie.
755      */
756     protected void writeFreshnessCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
757
758         String timestamp = request.getParameter("time");
759         if (timestamp == null || timestamp.equals("")) {
760             return;
761         }
762
763         Cookie cookie = new Cookie(cookieName, timestamp);
764         cookie.setSecure(true);
765         response.addCookie(cookie);
766     }
767
768     /**
769      * Generate a SAML 1 Subject element.
770      * 
771      * @param loginContext The LoginContext for an authenticated user.
772      * @param confirmationMethod The SubjectConfirmationMethod URI, or <code>null</code> is none is to be set.
773      * @param relyingParty The RelyingPartyConfiguration for the request.
774      * 
775      * @return a Subject object.
776      */
777     protected Subject buildSubject(final LoginContext loginCtx, String confirmationMethod,
778             final RelyingPartyConfiguration relyingParty) {
779
780         Subject subject = (Subject) subjectBuilder.buildObject(Subject.DEFAULT_ELEMENT_NAME);
781
782         NameIdentifier nameID = (NameIdentifier) nameIdentifierBuilder.buildObject(NameIdentifier.DEFAULT_ELEMENT_NAME);
783         nameID.setFormat(relyingParty.getDefaultNameIDFormat());
784         String username = loginCtx.getUserID();
785         // XXX: todo: map the username onto an appropriate format
786         nameID.setNameQualifier(username);
787
788         if (subjectConfirmationMethod != null) {
789
790             SubjectConfirmation subjConf = (SubjectConfirmation) subjConfBuilder
791                     .buildObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
792
793             ConfirmationMethod m = (ConfirmationMethod) confMethodBuilder
794                     .buildObject(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
795
796             m.setConfirmationMethod(subjectConfirmationMethod);
797             subjConf.getConfirmationMethods().add(m);
798             subject.setSubjectConfirmation(subjConf);
799         }
800
801         return subject;
802     }
803
804     /**
805      * Build a SAML 1 Status element.
806      * 
807      * @param statusCode The status code - see oasis-sstc-saml-core-1.1, section 3.4.3.1.
808      * @param statusMessage The status message, or <code>null</code> if none is to be set.
809      * 
810      * @return The Status object, or <code>null</code> on error.
811      */
812     protected Status buildStatus(String statusCode, String statusMessage) {
813
814         if (statusCode == null || statusCode.equals("")) {
815             return null;
816         }
817
818         Status status = (Status) statusBuilder.buildObject(Status.DEFAULT_ELEMENT_NAME);
819         StatusCode sc = (StatusCode) statusCodeBuilder.buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
820         sc.setValue(statusCode);
821         status.setStatusCode(sc);
822
823         if (statusMessage != null || !(statusMessage.equals(""))) {
824
825             StatusMessage sm = (StatusMessage) statusMessageBuilder.buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
826             sm.setMessage(statusMessage);
827             status.setStatusMessage(sm);
828         }
829
830         return status;
831     }
832
833     /**
834      * Get an Attribute Statement.
835      * 
836      * @param rpConfig The RelyingPartyConfiguration for the request.
837      * @param subject The Subject of the request.
838      * @param request The ServletRequest.
839      * 
840      * @return An AttributeStatement.
841      * 
842      * @throws ServletException On error.
843      */
844     protected AttributeStatement getAttributeStatement(RelyingPartyConfiguration rpConfig, Subject subject,
845             ServletRequest request) throws ServletException {
846
847         // build a dummy AttributeQuery object for the AA.
848
849         AttributeAuthority aa = new AttributeAuthority();
850         aa.setAttributeResolver(getAttributeResolver());
851         aa.setFilteringEngine(getFilteringEngine());
852         // aa.setSecurityPolicy(getDecoder().getSecurityPolicy()); //
853         // super.getDecoder() will need to change.
854         aa.setRequest(request);
855         aa.setRelyingPartyConfiguration(rpConfig);
856         AttributeStatement statement = null;
857         try {
858             statement = aa.performAttributeQuery(message);
859         } catch (AttributeResolutionException e) {
860             log.error("Error resolving attributes", e);
861             throw new ServletException("Error resolving attributes");
862         } catch (FilteringException e) {
863             log.error("Error filtering attributes", e);
864             throw new ServletException("Error filtering attributes");
865         }
866
867         return statement;
868     }
869 }