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.io.IOException;
21 import java.io.StringReader;
22 import java.util.ArrayList;
23
24 import javax.servlet.ServletContext;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.joda.time.DateTime;
29 import org.joda.time.DateTimeZone;
30 import org.opensaml.Configuration;
31 import org.opensaml.common.SAMLObjectBuilder;
32 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
33 import org.opensaml.common.xml.SAMLConstants;
34 import org.opensaml.saml2.binding.AuthnResponseEndpointSelector;
35 import org.opensaml.saml2.core.Assertion;
36 import org.opensaml.saml2.core.AttributeStatement;
37 import org.opensaml.saml2.core.AuthnContext;
38 import org.opensaml.saml2.core.AuthnContextClassRef;
39 import org.opensaml.saml2.core.AuthnContextDeclRef;
40 import org.opensaml.saml2.core.AuthnRequest;
41 import org.opensaml.saml2.core.AuthnStatement;
42 import org.opensaml.saml2.core.NameID;
43 import org.opensaml.saml2.core.NameIDPolicy;
44 import org.opensaml.saml2.core.RequestedAuthnContext;
45 import org.opensaml.saml2.core.Response;
46 import org.opensaml.saml2.core.Statement;
47 import org.opensaml.saml2.core.StatusCode;
48 import org.opensaml.saml2.core.Subject;
49 import org.opensaml.saml2.core.SubjectConfirmation;
50 import org.opensaml.saml2.core.SubjectConfirmationData;
51 import org.opensaml.saml2.core.SubjectLocality;
52 import org.opensaml.saml2.metadata.AffiliateMember;
53 import org.opensaml.saml2.metadata.AffiliationDescriptor;
54 import org.opensaml.saml2.metadata.AssertionConsumerService;
55 import org.opensaml.saml2.metadata.Endpoint;
56 import org.opensaml.saml2.metadata.EntityDescriptor;
57 import org.opensaml.saml2.metadata.IDPSSODescriptor;
58 import org.opensaml.saml2.metadata.SPSSODescriptor;
59 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
60 import org.opensaml.ws.message.decoder.MessageDecodingException;
61 import org.opensaml.ws.transport.http.HTTPInTransport;
62 import org.opensaml.ws.transport.http.HTTPOutTransport;
63 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
64 import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
65 import org.opensaml.xml.io.MarshallingException;
66 import org.opensaml.xml.io.Unmarshaller;
67 import org.opensaml.xml.io.UnmarshallingException;
68 import org.opensaml.xml.security.SecurityException;
69 import org.opensaml.xml.util.DatatypeHelper;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72 import org.w3c.dom.Element;
73
74 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
75 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
76 import edu.internet2.middleware.shibboleth.common.relyingparty.ProfileConfiguration;
77 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
78 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
79 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.SSOConfiguration;
80 import edu.internet2.middleware.shibboleth.common.util.HttpHelper;
81 import edu.internet2.middleware.shibboleth.idp.authn.PassiveAuthenticationException;
82 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
83 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
84 import edu.internet2.middleware.shibboleth.idp.session.Session;
85 import edu.internet2.middleware.shibboleth.idp.util.HttpServletHelper;
86
87
88 public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
89
90
91 private final Logger log = LoggerFactory.getLogger(SSOProfileHandler.class);
92
93
94 private SAMLObjectBuilder<AuthnStatement> authnStatementBuilder;
95
96
97 private SAMLObjectBuilder<AuthnContext> authnContextBuilder;
98
99
100 private SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
101
102
103 private SAMLObjectBuilder<AuthnContextDeclRef> authnContextDeclRefBuilder;
104
105
106 private SAMLObjectBuilder<SubjectLocality> subjectLocalityBuilder;
107
108
109 private SAMLObjectBuilder<Endpoint> endpointBuilder;
110
111
112 private String authenticationManagerPath;
113
114
115
116
117
118
119 @SuppressWarnings("unchecked")
120 public SSOProfileHandler(String authnManagerPath) {
121 super();
122
123 if (DatatypeHelper.isEmpty(authnManagerPath)) {
124 throw new IllegalArgumentException("Authentication manager path may not be null");
125 }
126 if (authnManagerPath.startsWith("/")) {
127 authenticationManagerPath = authnManagerPath;
128 } else {
129 authenticationManagerPath = "/" + authnManagerPath;
130 }
131
132 authnStatementBuilder = (SAMLObjectBuilder<AuthnStatement>) getBuilderFactory().getBuilder(
133 AuthnStatement.DEFAULT_ELEMENT_NAME);
134 authnContextBuilder = (SAMLObjectBuilder<AuthnContext>) getBuilderFactory().getBuilder(
135 AuthnContext.DEFAULT_ELEMENT_NAME);
136 authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) getBuilderFactory().getBuilder(
137 AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
138 authnContextDeclRefBuilder = (SAMLObjectBuilder<AuthnContextDeclRef>) getBuilderFactory().getBuilder(
139 AuthnContextDeclRef.DEFAULT_ELEMENT_NAME);
140 subjectLocalityBuilder = (SAMLObjectBuilder<SubjectLocality>) getBuilderFactory().getBuilder(
141 SubjectLocality.DEFAULT_ELEMENT_NAME);
142 endpointBuilder = (SAMLObjectBuilder<Endpoint>) getBuilderFactory().getBuilder(
143 AssertionConsumerService.DEFAULT_ELEMENT_NAME);
144 }
145
146
147 public String getProfileId() {
148 return SSOConfiguration.PROFILE_ID;
149 }
150
151
152 public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
153 HttpServletRequest httpRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
154 HttpServletResponse httpResponse = ((HttpServletResponseAdapter) outTransport).getWrappedResponse();
155 ServletContext servletContext = httpRequest.getSession().getServletContext();
156
157 LoginContext loginContext = HttpServletHelper.getLoginContext(getStorageService(),
158 servletContext, httpRequest);
159 if (loginContext == null || !(loginContext instanceof Saml2LoginContext)) {
160 log.debug("Incoming request does not contain a login context, processing as first leg of request");
161 performAuthentication(inTransport, outTransport);
162 } else if (loginContext.isPrincipalAuthenticated() || loginContext.getAuthenticationFailure() != null) {
163 log.debug("Incoming request contains a login context, processing as second leg of request");
164 HttpServletHelper.unbindLoginContext(getStorageService(), servletContext, httpRequest, httpResponse);
165 completeAuthenticationRequest((Saml2LoginContext)loginContext, inTransport, outTransport);
166 } else {
167 log.debug("Incoming request contained a login context but principal was not authenticated, processing as first leg of request");
168 performAuthentication(inTransport, outTransport);
169 }
170 }
171
172
173
174
175
176
177
178
179
180
181
182 protected void performAuthentication(HTTPInTransport inTransport, HTTPOutTransport outTransport)
183 throws ProfileException {
184 HttpServletRequest httpRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
185 HttpServletResponse httpResponse = ((HttpServletResponseAdapter) outTransport).getWrappedResponse();
186
187 SSORequestContext requestContext = new SSORequestContext();
188
189 try {
190 decodeRequest(requestContext, inTransport, outTransport);
191
192 String relyingPartyId = requestContext.getInboundMessageIssuer();
193 requestContext.setPeerEntityId(relyingPartyId);
194 RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
195 ProfileConfiguration ssoConfig = rpConfig.getProfileConfiguration(getProfileId());
196 if (ssoConfig == null) {
197 String msg = "SAML 2 SSO profile is not configured for relying party "
198 + requestContext.getInboundMessageIssuer();
199 log.warn(msg);
200 throw new ProfileException(msg);
201 }
202
203 log.debug("Creating login context and transferring control to authentication engine");
204 Saml2LoginContext loginContext = new Saml2LoginContext(relyingPartyId, requestContext.getRelayState(),
205 requestContext.getInboundSAMLMessage());
206 loginContext.setUnsolicited(requestContext.isUnsolicited());
207 loginContext.setAuthenticationEngineURL(authenticationManagerPath);
208 loginContext.setProfileHandlerURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
209 loginContext.setDefaultAuthenticationMethod(rpConfig.getDefaultAuthenticationMethod());
210
211 HttpServletHelper.bindLoginContext(loginContext, getStorageService(), httpRequest.getSession()
212 .getServletContext(), httpRequest, httpResponse);
213
214 String authnEngineUrl = HttpServletHelper.getContextRelativeUrl(httpRequest, authenticationManagerPath)
215 .buildURL();
216 log.debug("Redirecting user to authentication engine at {}", authnEngineUrl);
217 httpResponse.sendRedirect(authnEngineUrl);
218 } catch (MarshallingException e) {
219 log.error("Unable to marshall authentication request context");
220 throw new ProfileException("Unable to marshall authentication request context", e);
221 } catch (IOException ex) {
222 log.error("Error forwarding SAML 2 AuthnRequest to AuthenticationManager", ex);
223 throw new ProfileException("Error forwarding SAML 2 AuthnRequest to AuthenticationManager", ex);
224 }
225 }
226
227
228
229
230
231
232
233
234
235
236
237 protected void completeAuthenticationRequest(Saml2LoginContext loginContext, HTTPInTransport inTransport,
238 HTTPOutTransport outTransport) throws ProfileException {
239 SSORequestContext requestContext = buildRequestContext(loginContext, inTransport, outTransport);
240
241 Response samlResponse;
242 try {
243 checkSamlVersion(requestContext);
244 checkNameIDPolicy(requestContext);
245
246 if (loginContext.getAuthenticationFailure() != null) {
247 if (loginContext.getAuthenticationFailure() instanceof PassiveAuthenticationException) {
248 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.NO_PASSIVE_URI,
249 null));
250 } else {
251 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI,
252 null));
253 }
254 throw new ProfileException("Authentication failure", loginContext.getAuthenticationFailure());
255 }
256
257 if (requestContext.getSubjectNameIdentifier() != null) {
258 log.debug("Authentication request contained a subject with a name identifier, resolving principal from NameID");
259 resolvePrincipal(requestContext);
260 String requestedPrincipalName = requestContext.getPrincipalName();
261 if (!DatatypeHelper.safeEquals(loginContext.getPrincipalName(), requestedPrincipalName)) {
262 log.warn(
263 "Authentication request identified principal {} but authentication mechanism identified principal {}",
264 requestedPrincipalName, loginContext.getPrincipalName());
265 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI,
266 null));
267 throw new ProfileException("User failed authentication");
268 }
269 }
270
271 resolveAttributes(requestContext);
272
273 ArrayList<Statement> statements = new ArrayList<Statement>();
274 statements.add(buildAuthnStatement(requestContext));
275 if (requestContext.getProfileConfiguration().includeAttributeStatement()) {
276 AttributeStatement attributeStatement = buildAttributeStatement(requestContext);
277 if (attributeStatement != null) {
278 requestContext.setReleasedAttributes(requestContext.getAttributes().keySet());
279 statements.add(attributeStatement);
280 }
281 }
282
283 samlResponse = buildResponse(requestContext, "urn:oasis:names:tc:SAML:2.0:cm:bearer", statements);
284 } catch (ProfileException e) {
285 if (requestContext.isUnsolicited()) {
286
287 log.warn("Unsolicited response generation failed: {}", e.getMessage());
288 throw e;
289 }
290 samlResponse = buildErrorResponse(requestContext);
291 }
292
293 requestContext.setOutboundSAMLMessage(samlResponse);
294 requestContext.setOutboundSAMLMessageId(samlResponse.getID());
295 requestContext.setOutboundSAMLMessageIssueInstant(samlResponse.getIssueInstant());
296 encodeResponse(requestContext);
297 writeAuditLogEntry(requestContext);
298 }
299
300
301
302
303
304
305
306
307
308
309 protected void decodeRequest(SSORequestContext requestContext, HTTPInTransport inTransport,
310 HTTPOutTransport outTransport) throws ProfileException {
311 if (log.isDebugEnabled()) {
312 log.debug("Decoding message with decoder binding '{}'", getInboundMessageDecoder(requestContext)
313 .getBindingURI());
314 }
315
316 requestContext.setCommunicationProfileId(getProfileId());
317
318 requestContext.setMetadataProvider(getMetadataProvider());
319 requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
320
321 requestContext.setCommunicationProfileId(getProfileId());
322 requestContext.setInboundMessageTransport(inTransport);
323 requestContext.setInboundSAMLProtocol(SAMLConstants.SAML20P_NS);
324 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
325
326 requestContext.setOutboundMessageTransport(outTransport);
327 requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML20P_NS);
328
329 try {
330 SAMLMessageDecoder decoder = getInboundMessageDecoder(requestContext);
331 requestContext.setMessageDecoder(decoder);
332 decoder.decode(requestContext);
333 log.debug("Decoded request from relying party '{}'", requestContext.getInboundMessageIssuer());
334
335 if (!(requestContext.getInboundSAMLMessage() instanceof AuthnRequest)) {
336 log.warn("Incomming message was not a AuthnRequest, it was a '{}'", requestContext
337 .getInboundSAMLMessage().getClass().getName());
338 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null,
339 "Invalid SAML AuthnRequest message."));
340 throw new ProfileException("Invalid SAML AuthnRequest message.");
341 }
342 } catch (MessageDecodingException e) {
343 String msg = "Error decoding authentication request message";
344 log.warn(msg, e);
345 throw new ProfileException(msg, e);
346 } catch (SecurityException e) {
347 String msg = "Message did not meet security requirements";
348 log.warn(msg, e);
349 throw new ProfileException(msg, e);
350 }
351 }
352
353
354
355
356
357
358
359
360
361
362 protected void checkNameIDPolicy(SSORequestContext requestContext) throws ProfileException {
363 AuthnRequest request = requestContext.getInboundSAMLMessage();
364
365 NameIDPolicy nameIdPolcy = request.getNameIDPolicy();
366 if (nameIdPolcy == null) {
367 return;
368 }
369
370 String spNameQualifier = DatatypeHelper.safeTrimOrNullString(nameIdPolcy.getSPNameQualifier());
371 if (spNameQualifier == null) {
372 return;
373 }
374
375 if (DatatypeHelper.safeEquals(spNameQualifier, requestContext.getInboundMessageIssuer())) {
376 log.debug("SPNameQualifier '{}' matches message issuer.", spNameQualifier);
377 return;
378 }
379
380 log.debug("Checking if message issuer is a member of affiliation '{}'", spNameQualifier);
381 try {
382 EntityDescriptor affiliation = getMetadataProvider().getEntityDescriptor(spNameQualifier);
383 if (affiliation != null) {
384 AffiliationDescriptor affiliationDescriptor = affiliation.getAffiliationDescriptor();
385 if (affiliationDescriptor != null && affiliationDescriptor.getMembers() != null) {
386 for (AffiliateMember member : affiliationDescriptor.getMembers()) {
387 if (DatatypeHelper.safeEquals(member.getID(), requestContext.getInboundMessageIssuer())) {
388 return;
389 }
390 }
391 }
392 }
393
394 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
395 "Invalid SPNameQualifier for this request"));
396 throw new ProfileException("Relying party '" + requestContext.getInboundMessageIssuer()
397 + "' is not a member of the affiliation " + spNameQualifier);
398 } catch (MetadataProviderException e) {
399 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Internal service error"));
400 log.error("Error looking up metadata for affiliation", e);
401 throw new ProfileException("Relying party '" + requestContext.getInboundMessageIssuer()
402 + "' is not a member of the affiliation " + spNameQualifier);
403 }
404 }
405
406
407
408
409
410
411
412
413
414
415
416
417 protected SSORequestContext buildRequestContext(Saml2LoginContext loginContext, HTTPInTransport in,
418 HTTPOutTransport out) throws ProfileException {
419 SSORequestContext requestContext = new SSORequestContext();
420 requestContext.setCommunicationProfileId(getProfileId());
421
422 requestContext.setMessageDecoder(getInboundMessageDecoder(requestContext));
423
424 requestContext.setLoginContext(loginContext);
425 requestContext.setUnsolicited(loginContext.isUnsolicited());
426
427 requestContext.setInboundMessageTransport(in);
428 requestContext.setInboundSAMLProtocol(SAMLConstants.SAML20P_NS);
429
430 requestContext.setOutboundMessageTransport(out);
431 requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML20P_NS);
432
433 requestContext.setMetadataProvider(getMetadataProvider());
434
435 String relyingPartyId = loginContext.getRelyingPartyId();
436 requestContext.setPeerEntityId(relyingPartyId);
437 requestContext.setInboundMessageIssuer(relyingPartyId);
438
439 populateRequestContext(requestContext);
440
441 return requestContext;
442 }
443
444
445 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
446 throws ProfileException {
447 super.populateRelyingPartyInformation(requestContext);
448
449 EntityDescriptor relyingPartyMetadata = requestContext.getPeerEntityMetadata();
450 if (relyingPartyMetadata != null) {
451 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
452 requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS));
453 }
454 }
455
456
457 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
458 throws ProfileException {
459 super.populateAssertingPartyInformation(requestContext);
460
461 EntityDescriptor localEntityDescriptor = requestContext.getLocalEntityMetadata();
462 if (localEntityDescriptor != null) {
463 requestContext.setLocalEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
464 requestContext.setLocalEntityRoleMetadata(localEntityDescriptor
465 .getIDPSSODescriptor(SAMLConstants.SAML20P_NS));
466 }
467 }
468
469
470
471
472
473
474
475
476
477
478
479
480
481 protected void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
482 SSORequestContext ssoRequestContext = (SSORequestContext) requestContext;
483 try {
484 Saml2LoginContext loginContext = ssoRequestContext.getLoginContext();
485 requestContext.setRelayState(loginContext.getRelayState());
486
487 AuthnRequest authnRequest = deserializeRequest(loginContext.getAuthenticationRequest());
488 requestContext.setInboundMessage(authnRequest);
489 requestContext.setInboundSAMLMessage(authnRequest);
490 requestContext.setInboundSAMLMessageId(authnRequest.getID());
491
492 Subject authnSubject = authnRequest.getSubject();
493 if (authnSubject != null) {
494 requestContext.setSubjectNameIdentifier(authnSubject.getNameID());
495 }
496 } catch (UnmarshallingException e) {
497 log.error("Unable to unmarshall authentication request context");
498 ssoRequestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
499 "Error recovering request state"));
500 throw new ProfileException("Error recovering request state", e);
501 }
502 }
503
504
505
506
507
508
509
510
511 protected AuthnStatement buildAuthnStatement(SSORequestContext requestContext) {
512 Saml2LoginContext loginContext = requestContext.getLoginContext();
513
514 AuthnContext authnContext = buildAuthnContext(requestContext);
515
516 AuthnStatement statement = authnStatementBuilder.buildObject();
517 statement.setAuthnContext(authnContext);
518 statement.setAuthnInstant(loginContext != null ? loginContext.getAuthenticationInstant() : null);
519
520 Session session = getUserSession(requestContext.getInboundMessageTransport());
521 if (session != null) {
522 statement.setSessionIndex(session.getSessionID());
523 }
524
525 long maxSPSessionLifetime = requestContext.getProfileConfiguration().getMaximumSPSessionLifetime();
526 if (maxSPSessionLifetime > 0) {
527 DateTime lifetime = new DateTime(DateTimeZone.UTC).plus(maxSPSessionLifetime);
528 log.debug("Explicitly setting SP session expiration time to '{}'", lifetime.toString());
529 statement.setSessionNotOnOrAfter(lifetime);
530 }
531
532 statement.setSubjectLocality(buildSubjectLocality(requestContext));
533
534 return statement;
535 }
536
537
538
539
540
541
542
543
544 protected AuthnContext buildAuthnContext(SSORequestContext requestContext) {
545 AuthnContext authnContext = authnContextBuilder.buildObject();
546
547 Saml2LoginContext loginContext = requestContext.getLoginContext();
548 AuthnRequest authnRequest = requestContext.getInboundSAMLMessage();
549 RequestedAuthnContext requestedAuthnContext = authnRequest.getRequestedAuthnContext();
550 if (requestedAuthnContext != null) {
551 if (requestedAuthnContext.getAuthnContextClassRefs() != null) {
552 for (AuthnContextClassRef classRef : requestedAuthnContext.getAuthnContextClassRefs()) {
553 if (DatatypeHelper.safeEquals(classRef.getAuthnContextClassRef(),
554 loginContext.getAuthenticationMethod())) {
555 AuthnContextClassRef ref = authnContextClassRefBuilder.buildObject();
556 ref.setAuthnContextClassRef(loginContext.getAuthenticationMethod());
557 authnContext.setAuthnContextClassRef(ref);
558 }
559 }
560 } else if (requestedAuthnContext.getAuthnContextDeclRefs() != null) {
561 for (AuthnContextDeclRef declRef : requestedAuthnContext.getAuthnContextDeclRefs()) {
562 if (DatatypeHelper.safeEquals(declRef.getAuthnContextDeclRef(),
563 loginContext.getAuthenticationMethod())) {
564 AuthnContextDeclRef ref = authnContextDeclRefBuilder.buildObject();
565 ref.setAuthnContextDeclRef(loginContext.getAuthenticationMethod());
566 authnContext.setAuthnContextDeclRef(ref);
567 }
568 }
569 }
570 }
571
572 if (authnContext.getAuthnContextClassRef() == null || authnContext.getAuthnContextDeclRef() == null) {
573 AuthnContextClassRef ref = authnContextClassRefBuilder.buildObject();
574 ref.setAuthnContextClassRef(loginContext.getAuthenticationMethod());
575 authnContext.setAuthnContextClassRef(ref);
576 }
577
578 return authnContext;
579 }
580
581
582
583
584
585
586
587
588 protected SubjectLocality buildSubjectLocality(SSORequestContext requestContext) {
589 HTTPInTransport transport = (HTTPInTransport) requestContext.getInboundMessageTransport();
590 SubjectLocality subjectLocality = subjectLocalityBuilder.buildObject();
591 subjectLocality.setAddress(transport.getPeerAddress());
592
593 return subjectLocality;
594 }
595
596
597 protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
598 String requiredNameFormat = null;
599 AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
600 NameIDPolicy nameIdPolicy = authnRequest.getNameIDPolicy();
601 if (nameIdPolicy != null) {
602 requiredNameFormat = DatatypeHelper.safeTrimOrNullString(nameIdPolicy.getFormat());
603
604 if (requiredNameFormat != null
605 && (NameID.ENCRYPTED.equals(requiredNameFormat) || NameID.UNSPECIFIED.equals(requiredNameFormat))) {
606 requiredNameFormat = null;
607 }
608 }
609
610 return requiredNameFormat;
611 }
612
613
614 protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
615 NameID nameId = super.buildNameId(requestContext);
616 if (nameId != null) {
617 AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
618 NameIDPolicy nameIdPolicy = authnRequest.getNameIDPolicy();
619 if (nameIdPolicy != null) {
620 String spNameQualifier = DatatypeHelper.safeTrimOrNullString(nameIdPolicy.getSPNameQualifier());
621 if (spNameQualifier != null) {
622
623
624 if (nameId.getSPNameQualifier() != null) {
625 if (!nameId.getSPNameQualifier().equals(spNameQualifier)) {
626
627 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI,
628 StatusCode.INVALID_NAMEID_POLICY_URI,
629 "Invalid SPNameQualifier for this request"));
630 throw new ProfileException("Requested SPNameQualifier '{" + spNameQualifier
631 + "}' conflicts with generated value '{" + nameId.getSPNameQualifier() + "}'");
632 }
633 } else {
634
635 nameId.setSPNameQualifier(spNameQualifier);
636 }
637 } else {
638 nameId.setSPNameQualifier(requestContext.getInboundMessageIssuer());
639 }
640 }
641 }
642
643 return nameId;
644 }
645
646
647
648
649
650
651
652
653 protected Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) {
654 AuthnRequest authnRequest = ((SSORequestContext) requestContext).getInboundSAMLMessage();
655
656 Endpoint endpoint = null;
657 if (requestContext.getRelyingPartyConfiguration().getRelyingPartyId() == SAMLMDRelyingPartyConfigurationManager.ANONYMOUS_RP_NAME) {
658 if (authnRequest.getAssertionConsumerServiceURL() != null) {
659 endpoint = endpointBuilder.buildObject();
660 endpoint.setLocation(authnRequest.getAssertionConsumerServiceURL());
661 if (authnRequest.getProtocolBinding() != null) {
662 endpoint.setBinding(authnRequest.getProtocolBinding());
663 } else {
664 endpoint.setBinding(getSupportedOutboundBindings().get(0));
665 }
666 log.warn(
667 "Generating endpoint for anonymous relying party self-identified as '{}', ACS url '{}' and binding '{}'",
668 new Object[] { requestContext.getInboundMessageIssuer(), endpoint.getLocation(),
669 endpoint.getBinding(), });
670 } else {
671 log.warn("Unable to generate endpoint for anonymous party. No ACS URL provided.");
672 }
673 } else {
674 AuthnResponseEndpointSelector endpointSelector = new AuthnResponseEndpointSelector();
675 endpointSelector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
676 endpointSelector.setMetadataProvider(getMetadataProvider());
677 endpointSelector.setEntityMetadata(requestContext.getPeerEntityMetadata());
678 endpointSelector.setEntityRoleMetadata(requestContext.getPeerEntityRoleMetadata());
679 endpointSelector.setSamlRequest(requestContext.getInboundSAMLMessage());
680 endpointSelector.getSupportedIssuerBindings().addAll(getSupportedOutboundBindings());
681 endpoint = endpointSelector.selectEndpoint();
682 }
683
684 return endpoint;
685 }
686
687
688
689
690
691
692
693
694
695
696 protected AuthnRequest deserializeRequest(String request) throws UnmarshallingException {
697 try {
698 Element requestElem = getParserPool().parse(new StringReader(request)).getDocumentElement();
699 Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory().getUnmarshaller(requestElem);
700 return (AuthnRequest) unmarshaller.unmarshall(requestElem);
701 } catch (Exception e) {
702 throw new UnmarshallingException("Unable to read serialized authentication request");
703 }
704 }
705
706
707 protected void postProcessAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
708 throws ProfileException {
709 SSORequestContext ctx = (SSORequestContext) requestContext;
710 if (ctx.isUnsolicited()) {
711 Subject subject = assertion.getSubject();
712 if (subject != null) {
713 for (SubjectConfirmation sc : subject.getSubjectConfirmations()) {
714 if (sc != null) {
715 SubjectConfirmationData scd = sc.getSubjectConfirmationData();
716 if (scd != null) {
717 scd.setInResponseTo(null);
718 }
719 }
720 }
721 }
722 }
723 }
724
725
726 protected void postProcessResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Response samlResponse)
727 throws ProfileException {
728 SSORequestContext ctx = (SSORequestContext) requestContext;
729 if (ctx.isUnsolicited()) {
730 samlResponse.setInResponseTo(null);
731 }
732 }
733
734
735 protected class SSORequestContext extends BaseSAML2ProfileRequestContext<AuthnRequest, Response, SSOConfiguration> {
736
737
738 private boolean unsolicited;
739
740
741 private Saml2LoginContext loginContext;
742
743
744
745
746
747
748 public boolean isUnsolicited() {
749 return unsolicited;
750 }
751
752
753
754
755
756
757 public Saml2LoginContext getLoginContext() {
758 return loginContext;
759 }
760
761
762
763
764
765
766 public void setUnsolicited(boolean unsolicited) {
767 this.unsolicited = unsolicited;
768 }
769
770
771
772
773
774
775 public void setLoginContext(Saml2LoginContext context) {
776 loginContext = context;
777 }
778
779 }
780 }