1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package edu.internet2.middleware.shibboleth.idp.profile;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import javax.servlet.http.HttpServletRequest;
24
25 import org.opensaml.common.IdentifierGenerator;
26 import org.opensaml.common.binding.SAMLMessageContext;
27 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
28 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
29 import org.opensaml.saml1.core.NameIdentifier;
30 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
31 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
32 import org.opensaml.saml2.metadata.Endpoint;
33 import org.opensaml.saml2.metadata.EntityDescriptor;
34 import org.opensaml.saml2.metadata.NameIDFormat;
35 import org.opensaml.saml2.metadata.PDPDescriptor;
36 import org.opensaml.saml2.metadata.RoleDescriptor;
37 import org.opensaml.saml2.metadata.SSODescriptor;
38 import org.opensaml.saml2.metadata.provider.MetadataProvider;
39 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
40 import org.opensaml.ws.message.encoder.MessageEncodingException;
41 import org.opensaml.ws.security.SecurityPolicyResolver;
42 import org.opensaml.ws.transport.InTransport;
43 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
44 import org.opensaml.xml.security.credential.Credential;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
49 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
50 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
51 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
52 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
53 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
54 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
55 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
56 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
57 import edu.internet2.middleware.shibboleth.idp.session.Session;
58
59
60
61
62 public abstract class AbstractSAMLProfileHandler extends
63 AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
64
65
66 private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
67
68
69 private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
70
71
72 private IdentifierGenerator idGenerator;
73
74
75 private Map<String, SAMLMessageDecoder> messageDecoders;
76
77
78 private Map<String, SAMLMessageEncoder> messageEncoders;
79
80
81 private String inboundBinding;
82
83
84 private List<String> supportedOutboundBindings;
85
86
87 private SecurityPolicyResolver securityPolicyResolver;
88
89
90 protected AbstractSAMLProfileHandler() {
91 super();
92 }
93
94
95
96
97
98
99 public SecurityPolicyResolver getSecurityPolicyResolver() {
100 if (securityPolicyResolver == null) {
101 setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
102 }
103
104 return securityPolicyResolver;
105 }
106
107
108
109
110
111
112 public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
113 securityPolicyResolver = resolver;
114 }
115
116
117
118
119
120
121 protected Logger getAduitLog() {
122 return auditLog;
123 }
124
125
126
127
128
129
130 public IdentifierGenerator getIdGenerator() {
131 return idGenerator;
132 }
133
134
135
136
137
138
139 public String getInboundBinding() {
140 return inboundBinding;
141 }
142
143
144
145
146
147
148 public Map<String, SAMLMessageDecoder> getMessageDecoders() {
149 return messageDecoders;
150 }
151
152
153
154
155
156
157 public Map<String, SAMLMessageEncoder> getMessageEncoders() {
158 return messageEncoders;
159 }
160
161
162
163
164
165
166 public MetadataProvider getMetadataProvider() {
167 SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
168 if (rpcManager != null) {
169 return rpcManager.getMetadataProvider();
170 }
171
172 return null;
173 }
174
175
176
177
178
179
180 public List<String> getSupportedOutboundBindings() {
181 return supportedOutboundBindings;
182 }
183
184
185
186
187
188
189
190
191 protected Session getUserSession(InTransport inTransport) {
192 HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
193 return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
194 }
195
196
197
198
199
200
201
202
203 protected Session getUserSession(String principalName) {
204 return getSessionManager().getSession(principalName);
205 }
206
207
208
209
210
211
212 public void setIdGenerator(IdentifierGenerator generator) {
213 idGenerator = generator;
214 }
215
216
217
218
219
220
221 public void setInboundBinding(String binding) {
222 inboundBinding = binding;
223 }
224
225
226
227
228
229
230 public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
231 messageDecoders = decoders;
232 }
233
234
235
236
237
238
239 public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
240 messageEncoders = encoders;
241 }
242
243
244
245
246
247
248 public void setSupportedOutboundBindings(List<String> bindings) {
249 supportedOutboundBindings = bindings;
250 }
251
252
253 public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
254 try {
255 if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
256 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
257 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
258 }
259 } catch (MetadataProviderException e) {
260 log.error("Unable to look up relying party metadata", e);
261 return null;
262 }
263
264 return super.getRelyingPartyConfiguration(relyingPartyId);
265 }
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
281 populateRelyingPartyInformation(requestContext);
282 populateAssertingPartyInformation(requestContext);
283 populateSAMLMessageInformation(requestContext);
284 populateProfileInformation(requestContext);
285 populateUserInformation(requestContext);
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
300 throws ProfileException {
301 MetadataProvider metadataProvider = requestContext.getMetadataProvider();
302 String relyingPartyId = requestContext.getInboundMessageIssuer();
303
304 EntityDescriptor relyingPartyMetadata;
305 try {
306 relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
307 requestContext.setPeerEntityMetadata(relyingPartyMetadata);
308 } catch (MetadataProviderException e) {
309 log.error("Error looking up metadata for relying party " + relyingPartyId, e);
310 throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
311 }
312
313 RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
314 if (rpConfig == null) {
315 log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
316 throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
317 + relyingPartyId);
318 }
319 requestContext.setRelyingPartyConfiguration(rpConfig);
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
338 throws ProfileException {
339 String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
340 requestContext.setLocalEntityId(assertingPartyId);
341 requestContext.setOutboundMessageIssuer(assertingPartyId);
342
343 try {
344 EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
345 assertingPartyId);
346 if (localEntityDescriptor != null) {
347 requestContext.setLocalEntityMetadata(localEntityDescriptor);
348 }
349 } catch (MetadataProviderException e) {
350 log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
351 throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
352 }
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367 protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
368 throws ProfileException;
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387 protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
388 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
389 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
390 if (profileConfig != null) {
391 requestContext.setProfileConfiguration(profileConfig);
392 requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
393 }
394
395 Endpoint endpoint = selectEndpoint(requestContext);
396 if (endpoint == null) {
397 log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
398 throw new ProfileException("No peer endpoint available to which to send SAML response");
399 }
400 requestContext.setPeerEntityEndpoint(endpoint);
401 }
402
403
404
405
406
407
408
409
410
411
412 protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
413 ArrayList<String> nameFormats = new ArrayList<String>();
414
415 RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
416 if (relyingPartyRole != null) {
417 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
418 if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
419 nameFormats.addAll(relyingPartySupportedFormats);
420 }
421 }
422
423
424 if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
425 nameFormats.clear();
426 }
427
428 return nameFormats;
429 }
430
431
432
433
434
435
436
437
438 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
439 List<NameIDFormat> nameIDFormats = null;
440
441 if (role instanceof SSODescriptor) {
442 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
443 } else if (role instanceof AuthnAuthorityDescriptor) {
444 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
445 } else if (role instanceof PDPDescriptor) {
446 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
447 } else if (role instanceof AttributeAuthorityDescriptor) {
448 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
449 }
450
451 ArrayList<String> supportedFormats = new ArrayList<String>();
452 if (nameIDFormats != null) {
453 for (NameIDFormat format : nameIDFormats) {
454 supportedFormats.add(format.getFormat());
455 }
456 }
457
458 return supportedFormats;
459 }
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476 protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
477 throws ProfileException;
478
479
480
481
482
483
484
485
486
487
488 protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
489
490
491
492
493
494
495
496
497 protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
498 try {
499 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
500
501 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
502 .getProfileConfiguration();
503 if (profileConfig != null) {
504 if (isSignResponse(requestContext)) {
505 Credential signingCredential = profileConfig.getSigningCredential();
506 if (signingCredential == null) {
507 signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
508 }
509
510 if (signingCredential == null) {
511 throw new ProfileException(
512 "Signing of responses is required but no signing credential is available");
513 }
514
515 if (signingCredential.getPrivateKey() == null) {
516 throw new ProfileException(
517 "Signing of response is required but signing credential does not have a private key");
518 }
519
520 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
521 }
522 }
523
524 log.debug("Encoding response to SAML request {} from relying party {}", requestContext
525 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
526
527 requestContext.setMessageEncoder(encoder);
528 encoder.encode(requestContext);
529 } catch (MessageEncodingException e) {
530 throw new ProfileException("Unable to encode response to relying party: "
531 + requestContext.getInboundMessageIssuer(), e);
532 }
533 }
534
535
536
537
538
539
540
541
542 protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
543
544 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
545
546 AbstractSAMLProfileConfiguration profileConfig =
547 (AbstractSAMLProfileConfiguration) requestContext.getProfileConfiguration();
548
549 if (profileConfig != null) {
550 try {
551 return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
552 || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional
553 && !encoder.providesMessageIntegrity(requestContext));
554 } catch (MessageEncodingException e) {
555 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
556 encoder.getBindingURI());
557 throw new ProfileException("Unable to determine if outbound response should be signed");
558 }
559 } else {
560 return false;
561 }
562
563 }
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583 protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
584 throws ProfileException {
585 SAMLMessageEncoder encoder = null;
586
587 Endpoint endpoint = requestContext.getPeerEntityEndpoint();
588 if (endpoint == null) {
589 log.warn("No peer endpoint available for peer. Unable to send response.");
590 throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
591 }
592
593 if (endpoint != null) {
594 encoder = getMessageEncoders().get(endpoint.getBinding());
595 if (encoder == null) {
596 log.error("No outbound message encoder configured for binding: {}", requestContext
597 .getPeerEntityEndpoint().getBinding());
598 throw new ProfileException("No outbound message encoder configured for binding: "
599 + requestContext.getPeerEntityEndpoint().getBinding());
600 }
601 }
602 return encoder;
603 }
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622 protected SAMLMessageDecoder getInboundMessageDecoder(BaseSAMLProfileRequestContext requestContext)
623 throws ProfileException {
624 SAMLMessageDecoder decoder = null;
625
626 decoder = getMessageDecoders().get(getInboundBinding());
627 if (decoder == null) {
628 log.error("No inbound message decoder configured for binding: {}", getInboundBinding());
629 throw new ProfileException("No inbound message decoder configured for binding: " + getInboundBinding());
630 }
631 return decoder;
632 }
633
634
635
636
637
638
639 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
640 AuditLogEntry auditLogEntry = new AuditLogEntry();
641 auditLogEntry.setMessageProfile(getProfileId());
642 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
643 auditLogEntry.setPrincipalName(context.getPrincipalName());
644 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
645 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
646 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
647 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
648 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
649 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
650 if (context.getReleasedAttributes() != null) {
651 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
652 }
653
654 getAduitLog().info(auditLogEntry.toString());
655 }
656 }