Update to use new profile handler code.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AttributeQuery.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.saml2;
18
19 import javax.servlet.ServletException;
20
21 import org.apache.log4j.Logger;
22 import org.joda.time.DateTime;
23 import org.opensaml.common.SAMLObjectBuilder;
24 import org.opensaml.common.binding.BindingException;
25 import org.opensaml.saml2.core.Advice;
26 import org.opensaml.saml2.core.Assertion;
27 import org.opensaml.saml2.core.AttributeStatement;
28 import org.opensaml.saml2.core.Audience;
29 import org.opensaml.saml2.core.AudienceRestriction;
30 import org.opensaml.saml2.core.Conditions;
31 import org.opensaml.saml2.core.Issuer;
32 import org.opensaml.saml2.core.ProxyRestriction;
33 import org.opensaml.saml2.core.Response;
34 import org.opensaml.saml2.core.Status;
35 import org.opensaml.saml2.core.StatusCode;
36 import org.opensaml.saml2.core.Subject;
37 import org.opensaml.saml2.encryption.Encrypter;
38 import org.opensaml.saml2.metadata.provider.MetadataProvider;
39 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
40 import org.opensaml.xml.encryption.EncryptionException;
41 import org.opensaml.xml.security.credential.Credential;
42
43 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
44 import edu.internet2.middleware.shibboleth.common.attribute.SAML2AttributeAuthority;
45 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethAttributeRequestContext;
46 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAML2AttributeAuthority;
47 import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolver;
48 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
49 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
50 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
51 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.AttributeQueryConfiguration;
52 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
53
54 /**
55  * SAML 2.0 Attribute Query profile handler.
56  */
57 public class AttributeQuery extends AbstractSAML2ProfileHandler {
58
59     /** Class logger. */
60     private static Logger log = Logger.getLogger(AttributeQuery.class);
61
62     /** For building response. */
63     private SAMLObjectBuilder<Response> responseBuilder;
64
65     /** For building status. */
66     private SAMLObjectBuilder<Status> statusBuilder;
67
68     /** For building statuscode. */
69     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
70
71     /** For building assertion. */
72     private SAMLObjectBuilder<Assertion> assertionBuilder;
73
74     /** For building issuer. */
75     private SAMLObjectBuilder<Issuer> issuerBuilder;
76
77     /** For building subject. */
78     private SAMLObjectBuilder<Subject> subjectBuilder;
79
80     /** For building conditions. */
81     private SAMLObjectBuilder<Conditions> conditionsBuilder;
82
83     /** For building audience restriction. */
84     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
85
86     /** For building audience. */
87     private SAMLObjectBuilder<Audience> audienceBuilder;
88
89     /** For building advice. */
90     private SAMLObjectBuilder<Advice> adviceBuilder;
91
92     /** Provider id. */
93     private String providerId;
94
95     /** Attribute authority. */
96     private SAML2AttributeAuthority attributeAuthority;
97
98     /** Attribute query configuration. */
99     private AttributeQueryConfiguration config;
100
101     /**
102      * This creates a new attribute query.
103      * 
104      * @param ar <code>AttributeResolver</code>
105      */
106     public AttributeQuery(AttributeResolver<ShibbolethAttributeRequestContext> ar) {
107         // instantiate configuration
108         config = new AttributeQueryConfiguration();
109         providerId = config.getProfileId();
110
111         // instantiate attribute authority
112         attributeAuthority = new ShibbolethSAML2AttributeAuthority(ar);
113
114         // instantiate XML builders
115         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
116         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
117         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
118                 StatusCode.DEFAULT_ELEMENT_NAME);
119         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
120                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
121         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
122         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
123         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
124                 Conditions.DEFAULT_ELEMENT_NAME);
125         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
126                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
127         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
128         adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
129     }
130
131     /** {@inheritDoc} */
132     public boolean processRequest(ProfileRequest request, ProfileResponse response) throws ServletException {
133         if (log.isDebugEnabled()) {
134             log.debug("begin processRequest");
135         }
136
137         // get message from the decoder
138         org.opensaml.saml2.core.AttributeQuery message = null;
139         try {
140             message = (org.opensaml.saml2.core.AttributeQuery) decodeMessage(request.getMessageDecoder(), request
141                     .getRequest());
142         } catch (BindingException e) {
143             log.error("Error decoding attribute query message", e);
144             throw new ServletException("Error decoding attribute query message");
145         }
146
147         // TODO get user data from the session
148         ServiceInformation serviceInformation = null;
149         String principalName = serviceInformation.getSubjectNameID().getSPProvidedID();
150         String authenticationMethod = serviceInformation.getAuthenticationMethod().getAuthenticationMethod();
151
152         // create attribute request for the attribute authority
153         ShibbolethAttributeRequestContext requestContext = null;
154         try {
155             MetadataProvider metadataProvider = getRelyingPartyManager().getMetadataProvider();
156             RelyingPartyConfiguration relyingPartyConfiguration = getRelyingPartyManager()
157                     .getRelyingPartyConfiguration(providerId);
158             requestContext = new ShibbolethAttributeRequestContext(metadataProvider, relyingPartyConfiguration);
159             requestContext.setPrincipalName(principalName);
160             requestContext.setPrincipalAuthenticationMethod(authenticationMethod);
161             requestContext.setRequest(request.getRequest());
162         } catch (MetadataProviderException e) {
163             log.error("Error creating ShibbolethAttributeRequestContext", e);
164             throw new ServletException("Error retrieving metadata", e);
165         }
166
167         // resolve attributes with the attribute authority
168         AttributeStatement statement = null;
169         try {
170             statement = attributeAuthority.performAttributeQuery(requestContext);
171         } catch (AttributeRequestException e) {
172             log.error("Error resolving attributes", e);
173             throw new ServletException("Error resolving attributes", e);
174         }
175
176         // construct attribute response
177         Response samlResponse = null;
178         try {
179             ProfileResponseContext profileResponse = new ProfileResponseContext(request, message);
180             profileResponse.setAttributeStatement(statement);
181             samlResponse = buildResponse(profileResponse);
182         } catch (EncryptionException e) {
183             log.error("Error encrypting SAML response", e);
184             throw new ServletException("Error encrypting SAML response", e);
185         }
186         if (log.isDebugEnabled()) {
187             log.debug("built saml2 response: " + samlResponse);
188         }
189
190         // encode response
191         try {
192             encodeResponse(response.getMessageEncoder(), samlResponse);
193         } catch (BindingException e) {
194             log.error("Error encoding attribute query response", e);
195             throw new ServletException("Error encoding attribute query response", e);
196         }
197
198         return true;
199     }
200
201     /**
202      * This builds the response for this SAML request.
203      * 
204      * @param responseContext <code>ProfileResponseContext</code>
205      * @return <code>Response</code>
206      * @throws EncryptionException if an error occurs attempting to encrypt data
207      */
208     private Response buildResponse(ProfileResponseContext responseContext) throws EncryptionException {
209         /*
210          * required: samlp:Status, ID, Version, IssueInstant
211          */
212         Response response = responseBuilder.buildObject();
213         response.setVersion(SAML_VERSION);
214         response.setID(getIdGenerator().generateIdentifier());
215         response.setInResponseTo(responseContext.getRequest().getMessageDecoder().getSecurityPolicy().getIssuer()
216                 .toString());
217         response.setIssueInstant(responseContext.getIssueInstant());
218         response.setDestination(responseContext.getRequest().getRequest().getRemoteHost());
219
220         response.setIssuer(buildIssuer());
221
222         // TODO get consent configuration
223         /*
224          * if (consent != null) { response.setConsent(consent); }
225          */
226
227         // TODO get extension configuration
228         /*
229          * if (extensions != null) { response.setExtensions(extensions); }
230          */
231
232         if (config.getSignAssertions()) {
233             // TODO sign assertion: Credential credential = config.getSigningCredential();
234             if (config.getEncryptAssertion()) {
235                 // TODO load encryption parameters
236                 Encrypter encrypter = null;
237                 response.getEncryptedAssertions().add(encrypter.encrypt(buildAssertion(responseContext)));
238             } else {
239                 response.getAssertions().add(buildAssertion(responseContext));
240             }
241         } else {
242             if (config.getEncryptAssertion()) {
243                 // TODO load encryption parameters
244                 Encrypter encrypter = null;
245                 response.getEncryptedAssertions().add(encrypter.encrypt(buildAssertion(responseContext)));
246             } else {
247                 response.getAssertions().add(buildAssertion(responseContext));
248             }
249         }
250         response.setStatus(buildStatus(StatusCode.SUCCESS_URI));
251         return response;
252     }
253
254     /**
255      * This builds the status response for this SAML request.
256      * 
257      * @param statusCodeUri <code>String</code> to set
258      * @return <code>Status</code>
259      */
260     private Status buildStatus(String statusCodeUri) {
261         Status status = statusBuilder.buildObject();
262         StatusCode statusCode = statusCodeBuilder.buildObject();
263         statusCode.setValue(statusCodeUri);
264         status.setStatusCode(statusCode);
265         return status;
266     }
267
268     /**
269      * This builds the assertion for this SAML request.
270      * 
271      * @param responseContext <code>ProfileResponseContext</code>
272      * @return <code>Assertion</code>
273      * @throws EncryptionException if an error occurs attempting to encrypt data
274      */
275     private Assertion buildAssertion(ProfileResponseContext responseContext) throws EncryptionException {
276         /*
277          * required: saml:Issuer, ID, Version, IssueInstant
278          */
279         Assertion assertion = assertionBuilder.buildObject();
280         assertion.setID(getIdGenerator().generateIdentifier());
281         assertion.setIssueInstant(responseContext.getIssueInstant());
282         assertion.setVersion(SAML_VERSION);
283         assertion.setIssuer(buildIssuer());
284
285         // build subject
286         assertion.setSubject(buildSubject(responseContext.getMessage().getSubject()));
287         // build conditions
288         assertion.setConditions(buildConditions(responseContext.getIssueInstant()));
289         // build advice
290         assertion.setAdvice(buildAdvice());
291         // add attribute statement
292         assertion.getAttributeStatements().add(responseContext.getAttributeStatement());
293         return assertion;
294     }
295
296     /**
297      * This builds the issuer response for this SAML request.
298      * 
299      * @return <code>Issuer</code>
300      */
301     private Issuer buildIssuer() {
302         RelyingPartyConfiguration relyingPartyConfiguration = getRelyingPartyManager().getRelyingPartyConfiguration(
303                 providerId);
304         Issuer issuer = issuerBuilder.buildObject();
305         issuer.setValue(relyingPartyConfiguration.getProviderID());
306         return issuer;
307     }
308
309     /**
310      * This builds the subject for this SAML request.
311      * 
312      * @param messageSubject <code>Subject</code>
313      * @return <code>Subject</code>
314      * @throws EncryptionException if encryption of the name id fails
315      */
316     private Subject buildSubject(Subject messageSubject) throws EncryptionException {
317         Subject subject = subjectBuilder.buildObject();
318         if (config.getEncryptNameID()) {
319             // TODO load encryption parameters
320             Encrypter encrypter = null;
321             subject.setEncryptedID(encrypter.encrypt(messageSubject.getNameID()));
322         } else {
323             subject.setNameID(messageSubject.getNameID());
324             // TODO when is subject.setBaseID(newBaseID) called, if ever?
325         }
326         return subject;
327     }
328
329     /**
330      * This builds the conditions for this SAML request.
331      * 
332      * @param issueInstant <code>DateTime</code>
333      * @return <code>Conditions</code>
334      */
335     private Conditions buildConditions(DateTime issueInstant) {
336         Conditions conditions = conditionsBuilder.buildObject();
337         conditions.setNotBefore(issueInstant);
338         conditions.setNotOnOrAfter(issueInstant.plus(config.getAssertionLifetime()));
339
340         // add audience restrictions
341         AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
342         for (String s : config.getAssertionAudiences()) {
343             Audience audience = audienceBuilder.buildObject();
344             audience.setAudienceURI(s);
345             audienceRestriction.getAudiences().add(audience);
346         }
347         conditions.getAudienceRestrictions().add(audienceRestriction);
348
349         // add proxy restrictions
350         ProxyRestriction proxyRestriction = conditions.getProxyRestriction();
351         for (String s : config.getProxyAudiences()) {
352             Audience audience = audienceBuilder.buildObject();
353             audience.setAudienceURI(s);
354             proxyRestriction.getAudiences().add(audience);
355         }
356         proxyRestriction.setProxyCount(new Integer(config.getProxyCount()));
357
358         // TODO add additional conditions : conditions.getConditions().add(Condition);
359         // TODO what about OneTimeUse?
360         return conditions;
361     }
362
363     /**
364      * This builds the advice for this SAML request.
365      * 
366      * @return <code>Advice</code>
367      */
368     private Advice buildAdvice() {
369         Advice advice = adviceBuilder.buildObject();
370         // TODO set advice
371         // advice.getAssertionIDReferences().add();
372         // advice.getAssertionURIReferences().add();
373         // advice.getAssertions().add();
374         // advice.getEncryptedAssertions().add();
375         // advice.addNamespace(namespace);
376         return advice;
377     }
378 }