2 * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3 * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4 * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5 * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6 * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7 * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8 * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2 Project.
9 * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10 * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11 * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12 * products derived from this software without specific prior written permission. For written permission, please contact
13 * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14 * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15 * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16 * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18 * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19 * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 package edu.internet2.middleware.shibboleth.idp.provider;
28 import java.io.IOException;
30 import java.net.URISyntaxException;
31 import java.security.Principal;
32 import java.security.cert.X509Certificate;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.Date;
37 import java.util.Iterator;
39 import javax.security.auth.x500.X500Principal;
40 import javax.servlet.ServletException;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
44 import org.apache.log4j.Logger;
45 import org.opensaml.SAMLAssertion;
46 import org.opensaml.SAMLAttribute;
47 import org.opensaml.SAMLAttributeDesignator;
48 import org.opensaml.SAMLAttributeQuery;
49 import org.opensaml.SAMLAttributeStatement;
50 import org.opensaml.SAMLAudienceRestrictionCondition;
51 import org.opensaml.SAMLCondition;
52 import org.opensaml.SAMLException;
53 import org.opensaml.SAMLRequest;
54 import org.opensaml.SAMLResponse;
55 import org.opensaml.SAMLStatement;
56 import org.opensaml.SAMLSubject;
58 import sun.misc.BASE64Decoder;
59 import edu.internet2.middleware.shibboleth.aa.AAException;
60 import edu.internet2.middleware.shibboleth.common.InvalidNameIdentifierException;
61 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
62 import edu.internet2.middleware.shibboleth.common.RelyingParty;
63 import edu.internet2.middleware.shibboleth.common.ShibBrowserProfile;
64 import edu.internet2.middleware.shibboleth.idp.IdPProtocolHandler;
65 import edu.internet2.middleware.shibboleth.idp.IdPProtocolSupport;
66 import edu.internet2.middleware.shibboleth.idp.InvalidClientDataException;
67 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
70 * @author Walter Hoehn
72 public class SAMLv1_AttributeQueryHandler extends BaseServiceHandler implements IdPProtocolHandler {
74 private static Logger log = Logger.getLogger(SAMLv1_AttributeQueryHandler.class.getName());
77 * @see edu.internet2.middleware.shibboleth.idp.ProtocolHandler#getHandlerName()
79 public String getHandlerName() {
81 return "SAML v1.1 Attribute Query";
84 private String getEffectiveName(HttpServletRequest req, RelyingParty relyingParty, IdPProtocolSupport support)
85 throws InvalidProviderCredentialException {
87 X509Certificate credential = getCredentialFromProvider(req);
89 if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
90 log.info("Request is from an unauthenticated service provider.");
94 log.info("Request contains credential: ("
95 + credential.getSubjectX500Principal().getName(X500Principal.RFC2253) + ").");
96 // Mockup old requester name for requests from < 1.2 targets
97 if (fromLegacyProvider(req)) {
98 String legacyName = ShibBrowserProfile.getHostNameFromDN(credential.getSubjectX500Principal());
99 if (legacyName == null) {
100 log.error("Unable to extract legacy requester name from certificate subject.");
103 log.info("Request from legacy service provider: (" + legacyName + ").");
108 // See if we have metadata for this provider
109 EntityDescriptor provider = support.lookup(relyingParty.getProviderId());
110 if (provider == null) {
111 log.info("No metadata found for provider: (" + relyingParty.getProviderId() + ").");
112 log.info("Treating remote provider as unauthenticated.");
116 // Make sure that the suppplied credential is valid for the
117 // selected relying party
118 if (isValidCredential(provider, credential)) {
119 log.info("Supplied credential validated for this provider.");
120 log.info("Request from service provider: (" + relyingParty.getProviderId() + ").");
121 return relyingParty.getProviderId();
123 log.error("Supplied credential ("
124 + credential.getSubjectX500Principal().getName(X500Principal.RFC2253)
125 + ") is NOT valid for provider (" + relyingParty.getProviderId() + ").");
126 throw new InvalidProviderCredentialException("Invalid credential.");
133 * @see edu.internet2.middleware.shibboleth.idp.ProtocolHandler#processRequest(javax.servlet.http.HttpServletRequest,
134 * javax.servlet.http.HttpServletResponse, org.opensaml.SAMLRequest,
135 * edu.internet2.middleware.shibboleth.idp.ProtocolSupport)
137 public SAMLResponse processRequest(HttpServletRequest request, HttpServletResponse response,
138 SAMLRequest samlRequest, IdPProtocolSupport support) throws SAMLException, InvalidClientDataException,
139 IOException, ServletException {
141 // TODO negate this and throw an error if it isn't
142 if (samlRequest.getQuery() != null && (samlRequest.getQuery() instanceof SAMLAttributeQuery)) {
143 log.info("Recieved an attribute query.");
144 // processAttributeQuery(samlRequest, request, response);
148 RelyingParty relyingParty = null;
150 SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
152 if (!fromLegacyProvider(request)) {
153 log.info("Remote provider has identified itself as: (" + attributeQuery.getResource() + ").");
156 // This is the requester name that will be passed to subsystems
157 String effectiveName = null;
159 X509Certificate credential = getCredentialFromProvider(request);
160 if (credential == null || credential.getSubjectX500Principal().getName(X500Principal.RFC2253).equals("")) {
161 log.info("Request is from an unauthenticated service provider.");
164 // Identify a Relying Party
165 relyingParty = support.getServiceProviderMapper().getRelyingParty(attributeQuery.getResource());
168 effectiveName = getEffectiveName(request, relyingParty, support);
169 } catch (InvalidProviderCredentialException ipc) {
170 //TODO no, throw an exception
171 //sendSAMLFailureResponse(response, samlRequest, new SAMLException(SAMLException.RESPONDER,
172 // "Invalid credentials for request."));
177 if (effectiveName == null) {
178 log.debug("Using default Relying Party for unauthenticated provider.");
179 relyingParty = support.getServiceProviderMapper().getRelyingParty(null);
182 // Fail if we can't honor SAML Subject Confirmation
183 if (!fromLegacyProvider(request)) {
184 Iterator iterator = attributeQuery.getSubject().getConfirmationMethods();
185 boolean hasConfirmationMethod = false;
186 while (iterator.hasNext()) {
187 log.info("Request contains SAML Subject Confirmation method: (" + (String) iterator.next() + ").");
189 if (hasConfirmationMethod) { throw new SAMLException(SAMLException.REQUESTER,
190 "This SAML authority cannot honor requests containing the supplied SAML Subject Confirmation Method."); }
193 // Map Subject to local principal
196 principal = support.getNameMapper().getPrincipal(attributeQuery.getSubject().getName(), relyingParty,
197 relyingParty.getIdentityProvider());
199 log.info("Request is for principal (" + principal.getName() + ").");
201 SAMLAttribute[] attrs;
202 Iterator requestedAttrsIterator = attributeQuery.getDesignators();
203 if (requestedAttrsIterator.hasNext()) {
204 log.info("Request designates specific attributes, resolving this set.");
205 ArrayList requestedAttrs = new ArrayList();
206 while (requestedAttrsIterator.hasNext()) {
207 SAMLAttributeDesignator attribute = (SAMLAttributeDesignator) requestedAttrsIterator.next();
209 log.debug("Designated attribute: (" + attribute.getName() + ")");
210 requestedAttrs.add(new URI(attribute.getName()));
211 } catch (URISyntaxException use) {
213 .error("Request designated an attribute name that does not conform to the required URI syntax ("
214 + attribute.getName() + "). Ignoring this attribute");
218 attrs = support.getReleaseAttributes(principal, effectiveName, null, (URI[]) requestedAttrs
219 .toArray(new URI[0]));
221 log.info("Request does not designate specific attributes, resolving all available.");
222 attrs = support.getReleaseAttributes(principal, effectiveName, null);
225 log.info("Found " + attrs.length + " attribute(s) for " + principal.getName());
227 SAMLResponse samlResponse = null;
229 if (attrs == null || attrs.length == 0) {
230 // No attribute found
231 samlResponse = new SAMLResponse(samlRequest.getId(), null, null, null);
234 // SAMLAttributeQuery attributeQuery = (SAMLAttributeQuery) samlRequest.getQuery();
236 //TODO catch clonenotsupportedexception
237 //TODO no, put the use inside this blcok so we don't have to init
238 // Reference requested subject
239 SAMLSubject rSubject = null;
241 rSubject = (SAMLSubject) attributeQuery.getSubject().clone();
242 } catch (CloneNotSupportedException e1) {
243 // TODO Auto-generated catch block
244 e1.printStackTrace();
246 ArrayList audiences = new ArrayList();
247 if (relyingParty.getProviderId() != null) {
248 audiences.add(relyingParty.getProviderId());
250 if (relyingParty.getName() != null && !relyingParty.getName().equals(relyingParty.getProviderId())) {
251 audiences.add(relyingParty.getName());
253 SAMLCondition condition = new SAMLAudienceRestrictionCondition(audiences);
255 // Put all attributes into an assertion
256 SAMLStatement statement = new SAMLAttributeStatement(rSubject, Arrays.asList(attrs));
258 // Set assertion expiration to longest attribute expiration
260 for (int i = 0; i < attrs.length; i++) {
261 if (max < attrs[i].getLifetime()) {
262 max = attrs[i].getLifetime();
265 Date now = new Date();
266 Date then = new Date(now.getTime() + (max * 1000)); // max is in
269 SAMLAssertion sAssertion = new SAMLAssertion(relyingParty.getIdentityProvider().getProviderId(), now,
270 then, Collections.singleton(condition), null, Collections.singleton(statement));
272 samlResponse = new SAMLResponse(samlRequest.getId(), null, Collections.singleton(sAssertion), null);
273 IdPProtocolSupport.addSignatures(samlResponse, relyingParty, support.lookup(relyingParty
274 .getProviderId()), false);
277 if (log.isDebugEnabled()) { // This takes some processing, so only do it if we need to
279 log.debug("Dumping generated SAML Response:"
280 + System.getProperty("line.separator")
282 new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
284 } catch (SAMLException e) {
285 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
286 } catch (IOException e) {
287 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
291 log.info("Successfully responded about " + principal.getName());
293 if (effectiveName == null) {
294 if (fromLegacyProvider(request)) {
295 support.getTransactionLog().info(
296 "Attribute assertion issued to anonymous legacy provider at (" + request.getRemoteAddr()
297 + ") on behalf of principal (" + principal.getName() + ").");
299 support.getTransactionLog().info(
300 "Attribute assertion issued to anonymous provider at (" + request.getRemoteAddr()
301 + ") on behalf of principal (" + principal.getName() + ").");
304 if (fromLegacyProvider(request)) {
305 support.getTransactionLog().info(
306 "Attribute assertion issued to legacy provider (" + effectiveName
307 + ") on behalf of principal (" + principal.getName() + ").");
309 support.getTransactionLog().info(
310 "Attribute assertion issued to provider (" + effectiveName + ") on behalf of principal ("
311 + principal.getName() + ").");
314 } catch (InvalidNameIdentifierException e) {
315 // TODO Auto-generated catch block
317 } catch (NameIdentifierMappingException e) {
318 // TODO Auto-generated catch block
320 } catch (AAException e) {
321 //TODO get rid of AAException, I think
322 // TODO Auto-generated catch block
330 * throw new SAMLException(SAMLException.REQUESTER, "Identity Provider unable to respond to this SAML Request
331 * type."); } catch (InvalidNameIdentifierException invalidNameE) { log.info("Could not associate the request
332 * subject with a principal: " + invalidNameE); try { // TODO once again, ifgure out passThruErrors if (false) { //
333 * if (relyingParty.passThruErrors()) { sendSAMLFailureResponse(response, samlRequest, new
334 * SAMLException(Arrays.asList(invalidNameE .getSAMLErrorCodes()), "The supplied Subject was unrecognized.",
335 * invalidNameE)); } else { sendSAMLFailureResponse(response, samlRequest, new
336 * SAMLException(Arrays.asList(invalidNameE .getSAMLErrorCodes()), "The supplied Subject was unrecognized.")); }
337 * return; } catch (Exception ee) { log.fatal("Could not construct a SAML error response: " + ee); throw new
338 * ServletException("Identity Provider response failure."); }
343 private static boolean fromLegacyProvider(HttpServletRequest request) {
345 String version = request.getHeader("Shibboleth");
346 if (version != null) {
347 log.debug("Request from Shibboleth version: " + version);
350 log.debug("No version header found.");