Begin fleshing out request/response profile objects
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractProfileHandler.java
1 /*
2  * Copyright [2007] [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.saml2;
18
19 import java.util.List;
20
21 import javax.servlet.ServletRequest;
22 import javax.servlet.ServletResponse;
23
24 import org.apache.log4j.Logger;
25 import org.joda.time.DateTime;
26 import org.opensaml.Configuration;
27 import org.opensaml.common.IdentifierGenerator;
28 import org.opensaml.common.SAMLObject;
29 import org.opensaml.common.SAMLVersion;
30 import org.opensaml.common.binding.BindingException;
31 import org.opensaml.common.binding.MessageDecoder;
32 import org.opensaml.common.binding.MessageEncoder;
33 import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
34 import org.opensaml.saml2.core.Assertion;
35 import org.opensaml.saml2.core.Audience;
36 import org.opensaml.saml2.core.AudienceRestriction;
37 import org.opensaml.saml2.core.Conditions;
38 import org.opensaml.saml2.core.Issuer;
39 import org.opensaml.saml2.core.Response;
40 import org.opensaml.saml2.core.Status;
41 import org.opensaml.saml2.core.StatusCode;
42 import org.opensaml.saml2.core.StatusMessage;
43 import org.opensaml.saml2.core.Subject;
44 import org.opensaml.xml.XMLObjectBuilder;
45 import org.opensaml.xml.XMLObjectBuilderFactory;
46
47 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
48 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyManager;
49
50 /**
51  * Common implementation details for profile handlers.
52  */
53 public abstract class AbstractProfileHandler implements ProfileHandler {
54
55     /** SAML Version for this profile handler. */
56     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
57
58     /** Class logger. */
59     private static Logger log = Logger.getLogger(AbstractProfileHandler.class);
60
61     /** For building XML. */
62     private XMLObjectBuilderFactory builderFactory;
63
64     /** For generating random ids. */
65     private IdentifierGenerator idGenerator;
66
67     /** Builder for Response elements. */
68     protected XMLObjectBuilder responseBuilder;
69
70     /** Builder for Status elements. */
71     private XMLObjectBuilder statusBuilder;
72
73     /** Builder for StatusCode elements. */
74     private XMLObjectBuilder statusCodeBuilder;
75
76     /** Builder for StatusMessage elements. */
77     private XMLObjectBuilder statusMessageBuilder;
78
79     /** Builder for Issuer elements. */
80     protected XMLObjectBuilder issuerBuilder;
81
82     /**
83      * Default constructor.
84      */
85     public AbstractProfileHandler() {
86         builderFactory = Configuration.getBuilderFactory();
87         idGenerator = new SecureRandomIdentifierGenerator();
88
89         responseBuilder = builderFactory.getBuilder(Response.DEFAULT_ELEMENT_NAME);
90         statusBuilder = builderFactory.getBuilder(Status.DEFAULT_ELEMENT_NAME);
91         statusCodeBuilder = builderFactory.getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
92         statusMessageBuilder = builderFactory.getBuilder(StatusMessage.DEFAULT_ELEMENT_NAME);
93         issuerBuilder = builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
94     }
95
96     /**
97      * Returns the XML builder factory.
98      * 
99      * @return Returns the builderFactory.
100      */
101     public XMLObjectBuilderFactory getBuilderFactory() {
102         return builderFactory;
103     }
104
105     /**
106      * Returns the id generator.
107      * 
108      * @return Returns the idGenerator.
109      */
110     public IdentifierGenerator getIdGenerator() {
111         return idGenerator;
112     }
113
114     /**
115      * This decodes the attribute query message from the supplied request.
116      * 
117      * @param decoder <code>MessageDecoder</code>
118      * @param request <code>ServletRequest</code>
119      * @return <code>SAMLObject</code>
120      * @throws BindingException if the request cannot be decoded
121      */
122     public static SAMLObject decodeMessage(MessageDecoder<ServletRequest> decoder, ServletRequest request)
123             throws BindingException {
124
125         decoder.setRequest(request);
126         decoder.decode();
127         if (log.isDebugEnabled()) {
128             log.debug("decoded servlet request");
129         }
130
131         return decoder.getSAMLMessage();
132     }
133
134     /**
135      * This encodes the supplied response.
136      * 
137      * @param encoder <code>MessageEncoder</code>
138      * @param response <code>SAMLObject</code>
139      * @throws BindingException if the response cannot be encoded
140      */
141     public static void encodeResponse(MessageEncoder<ServletResponse> encoder, SAMLObject response)
142             throws BindingException {
143
144         encoder.setSAMLMessage(response);
145         encoder.encode();
146     }
147
148     /**
149      * Build a status message, with an optional second-level failure message.
150      * 
151      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
152      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
153      *            null, no second-level Status element will be set.
154      * @param secondLevelFailureMessage An optional second-level failure message.
155      * 
156      * @return a Status object.
157      */
158     protected Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
159
160         Status status = (Status) statusBuilder.buildObject(Status.DEFAULT_ELEMENT_NAME);
161         StatusCode statusCode = (StatusCode) statusCodeBuilder.buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
162
163         statusCode.setValue(topLevelCode);
164         if (secondLevelCode != null) {
165             StatusCode secondLevelStatusCode = (StatusCode) statusCodeBuilder
166                     .buildObject(StatusCode.DEFAULT_ELEMENT_NAME);
167             secondLevelStatusCode.setValue(secondLevelCode);
168             statusCode.setStatusCode(secondLevelStatusCode);
169         }
170
171         if (secondLevelFailureMessage != null) {
172             StatusMessage msg = (StatusMessage) statusMessageBuilder.buildObject(StatusMessage.DEFAULT_ELEMENT_NAME);
173             msg.setMessage(secondLevelFailureMessage);
174             status.setMessage(msg);
175         }
176
177         return status;
178     }
179
180     /**
181      * Build a SAML 2 Response element with basic fields populated.
182      * 
183      * Failure handlers can send the returned response element to the RP. Success handlers should add the assertions
184      * before sending it.
185      * 
186      * @param inResponseTo The ID of the request this is in response to.
187      * @param issuer The URI of the RP issuing the response.
188      * @param status The response's status code.
189      * 
190      * @return The populated Response object.
191      */
192     protected Response buildResponse(String inResponseTo, String issuer, final Status status) {
193
194         Response response = (Response) responseBuilder.buildObject(Response.DEFAULT_ELEMENT_NAME);
195
196         Issuer i = (Issuer) issuerBuilder.buildObject(Issuer.DEFAULT_ELEMENT_NAME);
197         i.setValue(issuer);
198
199         response.setVersion(SAML_VERSION);
200         response.setId(getIdGenerator().generateIdentifier());
201         response.setInResponseto(inResponseTo);
202         response.setIssueInstance(new DateTime());
203         response.setIssuer(i);
204         response.setStatus(status);
205
206         return response;
207     }
208
209     protected Assertion buildAssertion(final Subject subject, final Conditions conditions, String issuer,
210             final String[] audiences) {
211
212         Assertion assertion = (Assertion) assertionBuilder.buildObject(Assertion.DEFAULT_ELEMENT_NAME);
213         assertion.setID(getIdGenerator().generateIdentifier());
214         assertion.setVersion(SAML_VERSION);
215         assertion.setIssueInstant(new DateTime());
216         assertion.setConditions(conditions);
217         assertion.setSubject(subject);
218
219         Issuer i = (Issuer) issuerBuilder.buildObject(Issuer.DEFAULT_ELEMENT_NAME);
220         i.setValue(issuer);
221         assertion.setIssuer(i);
222
223         // if audiences were specified, set an AudienceRestriction condition
224         if (audiences != null && audiences.length > 0) {
225
226             Conditions conditions = assertion.getConditions();
227             List<AudienceRestriction> audienceRestrictionConditions = conditions.getAudienceRestrictions();
228
229             for (String audienceURI : audiences) {
230
231                 Audience audience = (Audience) audienceBuilder.buildObject(Audience.DEFAULT_ELEMENT_NAME);
232                 audience.setAudienceURI(audienceURI);
233
234                 AudienceRestriction audienceRestriction = (AudienceRestriction) audienceRestrictionBuilder
235                         .buildObject(AudienceRestriction.DEFAULT_ELEMENT_NAME);
236                 List<Audience> audienceList = audienceRestriction.getAudiences();
237                 audienceList.add(audience);
238
239                 audienceRestrictionConditions.add(audienceRestriction);
240             }
241         }
242
243         return assertion;
244     }
245 }