Allow more than one artifact in the list
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / SPArtifactMapper.java
1 /*
2  * Copyright [2005] [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 /*
18  * SPArtifactMapper is a callback routine implementing a SAML 
19  * interface. It is based on the shib-target/ArtifactMapper.cpp
20  * in the C++ code and shares a lot of logic with the 
21  * AttributeRequestor class with a little bit of ShibBinding thrown
22  * in for good measure. This callback routine helps build and then
23  * directs a request that presents an Artifact and
24  * receives the Assertion that the Artifact represents.
25  */
26 package edu.internet2.middleware.shibboleth.serviceprovider;
27
28 import java.util.Iterator;
29
30 import org.apache.log4j.Logger;
31 import org.opensaml.NoSuchProviderException;
32 import org.opensaml.ProfileException;
33 import org.opensaml.SAMLBinding;
34 import org.opensaml.SAMLBindingFactory;
35 import org.opensaml.SAMLException;
36 import org.opensaml.SAMLRequest;
37 import org.opensaml.SAMLResponse;
38 import org.opensaml.SAMLSOAPHTTPBinding;
39 import org.opensaml.UnsupportedExtensionException;
40 import org.opensaml.SAMLBrowserProfile.ArtifactMapper;
41 import org.opensaml.artifact.SAMLArtifact;
42 import org.opensaml.artifact.SAMLArtifactType0001;
43 import org.opensaml.artifact.SAMLArtifactType0002;
44
45 import edu.internet2.middleware.shibboleth.common.Trust;
46 import edu.internet2.middleware.shibboleth.metadata.Endpoint;
47 import edu.internet2.middleware.shibboleth.metadata.EndpointManager;
48 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
49 import edu.internet2.middleware.shibboleth.metadata.IDPSSODescriptor;
50 import edu.internet2.middleware.shibboleth.metadata.Metadata;
51 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
52 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
53
54 /**
55  * A callback class that SAML calls when processing an Artifact.
56  * The Artifact references an Assertion being held by an IdP, but 
57  * SAML requires access to the Metadata and Trust services of Shibboleth
58  * to locate the IdP Endpoint and generate a valid request to fetch it.
59  * This class contains much in common with AttributeRequestor, but while
60  * that class is static this must be an instance that captures state 
61  * (mostly the ApplicationInfo block of the Application to which the
62  * Artifact was directed).
63  * @author Howard Gilbert
64  *
65  */
66 public class SPArtifactMapper implements ArtifactMapper {
67         
68         private static Logger log = Logger.getLogger(SPArtifactMapper.class.getName());
69         
70         // State variables set by constructor used by callback
71         ApplicationInfo appinfo = null;
72         ServiceProviderConfig config = null;
73         
74
75         /**
76          * To create an instance of this object you must provide a snapshot
77          * of the ServiceProviderConfig and the ApplicationInfo from the 
78          * Application element to which the Artifact is directed. We keep
79          * this around as state until they are needed by the callback.
80          * @param appinfo ApplicationInfo
81          * @param config ServiceProviderConfig
82          */
83         public SPArtifactMapper(
84                         ApplicationInfo appinfo,
85                         ServiceProviderConfig config) {
86                 this.appinfo = appinfo;
87                 this.config = config;
88         }
89
90
91         /**
92          * The Callback routine from SAML to direct a Request containing 
93          * the Artifact to the IdP.
94          * @param request A SAMLRequest to resolve the Artifact
95          * @return The SAMLResponse from the IdP
96          * @throws SAMLException
97          */
98         public SAMLResponse resolve(SAMLRequest request) 
99                 throws SAMLException {
100                 SAMLResponse response = null;
101                 
102                 // Ok, so what is this Artifact anyway
103                 Iterator artifacts = request.getArtifacts();
104                 if (!artifacts.hasNext())
105                         throw new SAMLException("SPArtifactMapper was passed no artifact.");
106                 EntityDescriptor entity = null;
107                 SAMLArtifact artifact = null;
108                 while (artifacts.hasNext()) {
109                         artifact = (SAMLArtifact)artifacts.next();
110                         entity = ((Metadata)appinfo).lookup(artifact);
111                         if (entity!=null)
112                                 break;
113                 }
114                 if (entity==null) {
115                         throw new MetadataException("Unable to find Artifact issuer in Metadata.");
116                 }
117                 String entityId = entity.getId();
118                 log.info("Processing Artifact issued by "+entityId);
119
120         IDPSSODescriptor idp = entity.getIDPSSODescriptor(
121                 request.getMinorVersion()==1?
122                     org.opensaml.XML.SAML11_PROTOCOL_ENUM :
123                     org.opensaml.XML.SAML10_PROTOCOL_ENUM
124                 );
125         if (idp==null) {
126                 throw new MetadataException("Entity "+entityId+" has no usable IDPSSODescriptor.");
127         }
128                 
129                 
130                 // Sign the Request if so configured
131         String credentialId = appinfo.getCredentialIdForEntity(entity);
132         if (credentialId!=null)
133             AttributeRequestor.possiblySignRequest(config.getCredentials(), request, credentialId);
134         
135         //TODO: C++ code determines if the IdP is authenticated
136         //boolean authenticated=false; 
137         
138         if (artifact instanceof SAMLArtifactType0001) {
139                 // A Type1 Artifact takes any usable SOAP Endpoint
140             EndpointManager endpointManager = idp.getArtifactResolutionServiceManager();
141             Iterator endpoints = endpointManager.getEndpoints();
142             while (endpoints.hasNext()) {
143                 //  Search for an Endpoint with a SOAP Binding
144                 Endpoint endpoint = (Endpoint)endpoints.next();
145                 String binding = endpoint.getBinding();
146                 if (!binding.equals(SAMLBinding.SOAP))
147                         continue; // The C++ code is more elaborate here
148                 
149                 response = resolveArtifact(request, idp, endpoint);
150                 break; // Got response, stop scanning endpoints
151             }
152         } else if (artifact instanceof SAMLArtifactType0002) {
153             // A Type2 Artifact carries an Endpoint location
154                 SAMLArtifactType0002 type2 = (SAMLArtifactType0002) artifact;
155             EndpointManager endpointManager = idp.getArtifactResolutionServiceManager();
156             Iterator endpoints = endpointManager.getEndpoints();
157             while (endpoints.hasNext()) {
158                 // Search for an Endpoint matching the Artifact
159                 Endpoint endpoint = (Endpoint)endpoints.next();
160                 String binding = endpoint.getBinding();
161                 if (!binding.equals(SAMLBinding.SOAP))
162                         continue; // The C++ code is more elaborate here
163                 String location = endpoint.getLocation();
164                 if (!location.equals(type2.getSourceLocation()))
165                         continue;
166                 
167                 response = resolveArtifact(request, idp, endpoint);
168                 break; // Got response, stop scanning endpoints
169             }
170         } else {
171                 throw new UnsupportedExtensionException("Unrecognized Artifact type.");
172         }
173         if (response == null) {
174             throw new MetadataException("Unable to locate acceptable binding/endpoint to resolve artifact.");
175         }
176         return response;
177         }
178
179         /**
180      * Call back into SAML to transmit the Request to the IdP Enpoint
181      * and get back the Response represented by the Artifact.
182      * @param request A SAMLRequest containing the Artifact
183      * @param idp The IdP entity
184      * @param endpoint The IdP Endpoint
185      * @return The SAMLResponse returned by the IdP
186      * @throws NoSuchProviderException
187      * @throws SAMLException
188      * @throws ProfileException if the response has no assertions
189          */
190     private SAMLResponse resolveArtifact(SAMLRequest request, IDPSSODescriptor idp, Endpoint endpoint) throws NoSuchProviderException, SAMLException, ProfileException {
191         SAMLResponse response;
192         SAMLBinding sbinding = SAMLBindingFactory.getInstance(endpoint.getBinding());                   
193         if (sbinding instanceof SAMLSOAPHTTPBinding) { // I shure hope so
194             SAMLSOAPHTTPBinding httpbind = (SAMLSOAPHTTPBinding)sbinding;
195             httpbind.addHook(new ShibHttpHook(idp,(Trust)appinfo));
196         }
197         response=sbinding.send(endpoint.getLocation(),request);
198         if (!response.getAssertions().hasNext()) {
199                 throw new ProfileException("No SAML assertions returned in response to artifact profile request.");
200         }
201         return response;
202     }
203
204 }