c7643d3927eac6bd03cba81effd9d58642a35f18
[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                 SAMLArtifact artifact = (SAMLArtifact)artifacts.next();
107                 EntityDescriptor entity = ((Metadata)appinfo).lookup(artifact);
108                 if (entity==null) {
109                         throw new MetadataException("Unable to find Artifact issuer in Metadata.");
110                 }
111                 String entityId = entity.getId();
112                 log.info("Processing Artifact issued by "+entityId);
113
114         IDPSSODescriptor idp = entity.getIDPSSODescriptor(
115                 request.getMinorVersion()==1?
116                     org.opensaml.XML.SAML11_PROTOCOL_ENUM :
117                     org.opensaml.XML.SAML10_PROTOCOL_ENUM
118                 );
119         if (idp==null) {
120                 throw new MetadataException("Entity "+entityId+" has no usable IDPSSODescriptor.");
121         }
122                 
123                 
124                 // Sign the Request if so configured
125         String credentialId = appinfo.getCredentialIdForEntity(entity);
126         if (credentialId!=null)
127             AttributeRequestor.possiblySignRequest(config.getCredentials(), request, credentialId);
128         
129         //TODO: C++ code determines if the IdP is authenticated
130         //boolean authenticated=false; 
131         
132         if (artifact instanceof SAMLArtifactType0001) {
133                 // A Type1 Artifact takes any usable SOAP Endpoint
134             EndpointManager endpointManager = idp.getArtifactResolutionServiceManager();
135             Iterator endpoints = endpointManager.getEndpoints();
136             while (endpoints.hasNext()) {
137                 //  Search for an Endpoint with a SOAP Binding
138                 Endpoint endpoint = (Endpoint)endpoints.next();
139                 String binding = endpoint.getBinding();
140                 if (!binding.equals(SAMLBinding.SOAP))
141                         continue; // The C++ code is more elaborate here
142                 
143                 response = resolveArtifact(request, idp, endpoint);
144                 break; // Got response, stop scanning endpoints
145             }
146         } else if (artifact instanceof SAMLArtifactType0002) {
147             // A Type2 Artifact carries an Endpoint location
148                 SAMLArtifactType0002 type2 = (SAMLArtifactType0002) artifact;
149             EndpointManager endpointManager = idp.getArtifactResolutionServiceManager();
150             Iterator endpoints = endpointManager.getEndpoints();
151             while (endpoints.hasNext()) {
152                 // Search for an Endpoint matching the Artifact
153                 Endpoint endpoint = (Endpoint)endpoints.next();
154                 String binding = endpoint.getBinding();
155                 if (!binding.equals(SAMLBinding.SOAP))
156                         continue; // The C++ code is more elaborate here
157                 String location = endpoint.getLocation();
158                 if (!location.equals(type2.getSourceLocation()))
159                         continue;
160                 
161                 response = resolveArtifact(request, idp, endpoint);
162                 break; // Got response, stop scanning endpoints
163             }
164         } else {
165                 throw new UnsupportedExtensionException("Unrecognized Artifact type.");
166         }
167         if (response == null) {
168             throw new MetadataException("Unable to locate acceptable binding/endpoint to resolve artifact.");
169         }
170         return response;
171         }
172
173         /**
174      * Call back into SAML to transmit the Request to the IdP Enpoint
175      * and get back the Response represented by the Artifact.
176      * @param request A SAMLRequest containing the Artifact
177      * @param idp The IdP entity
178      * @param endpoint The IdP Endpoint
179      * @return The SAMLResponse returned by the IdP
180      * @throws NoSuchProviderException
181      * @throws SAMLException
182      * @throws ProfileException if the response has no assertions
183          */
184     private SAMLResponse resolveArtifact(SAMLRequest request, IDPSSODescriptor idp, Endpoint endpoint) throws NoSuchProviderException, SAMLException, ProfileException {
185         SAMLResponse response;
186         SAMLBinding sbinding = SAMLBindingFactory.getInstance(endpoint.getBinding());                   
187         if (sbinding instanceof SAMLSOAPHTTPBinding) { // I shure hope so
188             SAMLSOAPHTTPBinding httpbind = (SAMLSOAPHTTPBinding)sbinding;
189             httpbind.addHook(new ShibHttpHook(idp,(Trust)appinfo));
190         }
191         response=sbinding.send(endpoint.getLocation(),request);
192         if (!response.getAssertions().hasNext()) {
193                 throw new ProfileException("No SAML assertions returned in response to artifact profile request.");
194         }
195         return response;
196     }
197
198 }