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