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