1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
19
20 import java.text.MessageFormat;
21
22 import org.joda.time.DateTime;
23 import org.opensaml.common.SAMLObject;
24 import org.opensaml.common.SAMLObjectBuilder;
25 import org.opensaml.common.binding.BasicEndpointSelector;
26 import org.opensaml.common.binding.artifact.SAMLArtifactMap;
27 import org.opensaml.common.binding.artifact.SAMLArtifactMap.SAMLArtifactMapEntry;
28 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
29 import org.opensaml.common.xml.SAMLConstants;
30 import org.opensaml.saml2.binding.SAML2ArtifactMessageContext;
31 import org.opensaml.saml2.core.ArtifactResolve;
32 import org.opensaml.saml2.core.ArtifactResponse;
33 import org.opensaml.saml2.core.NameID;
34 import org.opensaml.saml2.core.Status;
35 import org.opensaml.saml2.core.StatusCode;
36 import org.opensaml.saml2.metadata.AssertionConsumerService;
37 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
38 import org.opensaml.saml2.metadata.Endpoint;
39 import org.opensaml.saml2.metadata.EntityDescriptor;
40 import org.opensaml.saml2.metadata.SPSSODescriptor;
41 import org.opensaml.saml2.metadata.provider.MetadataProvider;
42 import org.opensaml.ws.message.decoder.MessageDecodingException;
43 import org.opensaml.ws.transport.http.HTTPInTransport;
44 import org.opensaml.ws.transport.http.HTTPOutTransport;
45 import org.opensaml.xml.security.SecurityException;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
50 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
51 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.ArtifactResolutionConfiguration;
52
53
54
55
56 public class ArtifactResolution extends AbstractSAML2ProfileHandler {
57
58
59 private final Logger log = LoggerFactory.getLogger(ArtifactResolution.class);
60
61
62 private SAMLArtifactMap artifactMap;
63
64
65 private SAMLObjectBuilder<ArtifactResponse> responseBuilder;
66
67
68 private SAMLObjectBuilder<AssertionConsumerService> acsEndpointBuilder;
69
70
71
72
73
74
75 public ArtifactResolution(SAMLArtifactMap map) {
76 super();
77
78 artifactMap = map;
79
80 responseBuilder = (SAMLObjectBuilder<ArtifactResponse>) getBuilderFactory().getBuilder(
81 ArtifactResponse.DEFAULT_ELEMENT_NAME);
82 acsEndpointBuilder = (SAMLObjectBuilder<AssertionConsumerService>) getBuilderFactory().getBuilder(
83 AssertionConsumerService.DEFAULT_ELEMENT_NAME);
84 }
85
86
87 public String getProfileId() {
88 return ArtifactResolutionConfiguration.PROFILE_ID;
89 }
90
91
92 public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
93 ArtifactResponse samlResponse;
94
95 ArtifactResolutionRequestContext requestContext = new ArtifactResolutionRequestContext();
96 try {
97 decodeRequest(requestContext, inTransport, outTransport);
98
99 if (requestContext.getProfileConfiguration() == null) {
100 String msg = MessageFormat.format(
101 "SAML 2 Artifact Resolve profile is not configured for relying party ''{0}''", requestContext
102 .getInboundMessageIssuer());
103 requestContext
104 .setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI, msg));
105 log.warn(msg);
106 throw new ProfileException(msg);
107 }
108
109 checkSamlVersion(requestContext);
110
111 SAMLArtifactMapEntry artifactEntry = artifactMap.get(requestContext.getArtifact());
112 if (artifactEntry == null || artifactEntry.isExpired()) {
113 String msg = MessageFormat.format("Unknown artifact ''{0}'' from relying party ''{1}''", requestContext
114 .getArtifact(), requestContext.getInboundMessageIssuer());
115 log.error(msg);
116 requestContext
117 .setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI, msg));
118 }
119
120 if (!artifactEntry.getIssuerId().equals(requestContext.getLocalEntityId())) {
121 String msg = MessageFormat.format(
122 "Artifact issuer mismatch. Artifact issued by ''{0}'' but IdP has entity ID of ''{1}''",
123 artifactEntry.getIssuerId(), requestContext.getLocalEntityId());
124 log.warn(msg);
125 requestContext
126 .setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI, msg));
127 return;
128 }
129
130 if (!artifactEntry.getRelyingPartyId().equals(requestContext.getInboundMessageIssuer())) {
131 String msg = MessageFormat
132 .format(
133 "Artifact requester mismatch. Artifact was issued to ''{0}'' but the resolve request came from ''{1}''",
134 artifactEntry.getRelyingPartyId(), requestContext.getInboundMessageIssuer());
135 log.warn(msg);
136 requestContext
137 .setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI, msg));
138 return;
139 }
140
141
142 requestContext.setReferencedMessage(artifactEntry.getSamlMessage());
143 samlResponse = buildArtifactResponse(requestContext);
144 } catch (ProfileException e) {
145 samlResponse = buildArtifactErrorResponse(requestContext);
146 }
147
148 requestContext.setOutboundSAMLMessage(samlResponse);
149 requestContext.setOutboundSAMLMessageId(samlResponse.getID());
150 requestContext.setOutboundSAMLMessageIssueInstant(samlResponse.getIssueInstant());
151
152 encodeResponse(requestContext);
153 writeAuditLogEntry(requestContext);
154 }
155
156
157
158
159
160
161
162
163
164
165 protected void decodeRequest(ArtifactResolutionRequestContext requestContext, HTTPInTransport inTransport,
166 HTTPOutTransport outTransport) throws ProfileException {
167 if (log.isDebugEnabled()) {
168 log.debug("Decoding message with decoder binding '{}'",
169 getInboundMessageDecoder(requestContext).getBindingURI());
170 }
171
172 requestContext.setCommunicationProfileId(getProfileId());
173
174 MetadataProvider metadataProvider = getMetadataProvider();
175 requestContext.setMetadataProvider(metadataProvider);
176
177 requestContext.setInboundMessageTransport(inTransport);
178 requestContext.setInboundSAMLProtocol(SAMLConstants.SAML20P_NS);
179 requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
180 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
181
182 requestContext.setOutboundMessageTransport(outTransport);
183 requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML20P_NS);
184
185 try {
186 SAMLMessageDecoder decoder = getInboundMessageDecoder(requestContext);
187 requestContext.setMessageDecoder(decoder);
188 decoder.decode(requestContext);
189 log.debug("Decoded request from relying party '{}'", requestContext.getInboundMessageIssuer());
190 } catch (MessageDecodingException e) {
191 String msg = "Error decoding artifact resolve message";
192 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, msg));
193 log.warn(msg, e);
194 throw new ProfileException(msg);
195 } catch (SecurityException e) {
196 String msg = "Message did not meet security requirements";
197 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI, msg));
198 log.warn(msg, e);
199 throw new ProfileException(msg, e);
200 } finally {
201 populateRequestContext(requestContext);
202 }
203 }
204
205
206 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
207 throws ProfileException {
208 super.populateRelyingPartyInformation(requestContext);
209
210 EntityDescriptor relyingPartyMetadata = requestContext.getPeerEntityMetadata();
211 if (relyingPartyMetadata != null) {
212 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
213 requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS));
214 }
215 }
216
217
218 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
219 throws ProfileException {
220 super.populateAssertingPartyInformation(requestContext);
221
222 EntityDescriptor localEntityDescriptor = requestContext.getLocalEntityMetadata();
223 if (localEntityDescriptor != null) {
224 requestContext.setLocalEntityRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
225 requestContext.setLocalEntityRoleMetadata(localEntityDescriptor
226 .getAttributeAuthorityDescriptor(SAMLConstants.SAML20P_NS));
227 }
228 }
229
230
231
232
233
234
235
236
237
238
239
240
241 protected void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
242 ArtifactResolve samlMessage = (ArtifactResolve) requestContext.getInboundSAMLMessage();
243 if (samlMessage != null && samlMessage.getArtifact() != null) {
244 ((ArtifactResolutionRequestContext) requestContext).setArtifact(samlMessage.getArtifact().getArtifact());
245 }
246 }
247
248
249
250
251
252
253
254
255 protected Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) {
256 Endpoint endpoint;
257
258 if (getInboundBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) {
259 endpoint = acsEndpointBuilder.buildObject();
260 endpoint.setBinding(SAMLConstants.SAML2_SOAP11_BINDING_URI);
261 } else {
262 BasicEndpointSelector endpointSelector = new BasicEndpointSelector();
263 endpointSelector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
264 endpointSelector.setMetadataProvider(getMetadataProvider());
265 endpointSelector.setEntityMetadata(requestContext.getPeerEntityMetadata());
266 endpointSelector.setEntityRoleMetadata(requestContext.getPeerEntityRoleMetadata());
267 endpointSelector.setSamlRequest(requestContext.getInboundSAMLMessage());
268 endpointSelector.getSupportedIssuerBindings().addAll(getSupportedOutboundBindings());
269 endpoint = endpointSelector.selectEndpoint();
270 }
271
272 return endpoint;
273 }
274
275
276
277
278
279
280
281
282 protected ArtifactResponse buildArtifactResponse(ArtifactResolutionRequestContext requestContext) {
283 DateTime issueInstant = new DateTime();
284
285
286 ArtifactResponse samlResponse = responseBuilder.buildObject();
287 samlResponse.setIssueInstant(issueInstant);
288 populateStatusResponse(requestContext, samlResponse);
289
290 if (requestContext.getFailureStatus() == null) {
291 Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
292 samlResponse.setStatus(status);
293 samlResponse.setMessage(requestContext.getReferencedMessage());
294 } else {
295 samlResponse.setStatus(requestContext.getFailureStatus());
296 }
297
298 return samlResponse;
299 }
300
301
302
303
304
305
306
307
308 protected ArtifactResponse buildArtifactErrorResponse(ArtifactResolutionRequestContext requestContext) {
309 ArtifactResponse samlResponse = responseBuilder.buildObject();
310 samlResponse.setIssueInstant(new DateTime());
311 populateStatusResponse(requestContext, samlResponse);
312
313 samlResponse.setStatus(requestContext.getFailureStatus());
314
315 return samlResponse;
316 }
317
318
319 public class ArtifactResolutionRequestContext extends
320 BaseSAML2ProfileRequestContext<ArtifactResolve, ArtifactResponse, ArtifactResolutionConfiguration>
321 implements SAML2ArtifactMessageContext<ArtifactResolve, ArtifactResponse, NameID> {
322
323
324 private String artifact;
325
326
327 private SAMLObject referencedMessage;
328
329
330 public String getArtifact() {
331 return artifact;
332 }
333
334
335 public void setArtifact(String saml2Artifact) {
336 this.artifact = saml2Artifact;
337 }
338
339
340 public SAMLObject getReferencedMessage() {
341 return referencedMessage;
342 }
343
344
345 public void setReferencedMessage(SAMLObject message) {
346 referencedMessage = message;
347 }
348 }
349 }