/[Softwareborsen]/oiosaml.net/trunk/src/dk.nita.saml20/dk.nita.saml20/Protocol/Saml20SignonHandler.cs
Softwarebørsen

Contents of /oiosaml.net/trunk/src/dk.nita.saml20/dk.nita.saml20/Protocol/Saml20SignonHandler.cs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 34990 - (show annotations) (download)
Tue Apr 17 11:14:08 2018 UTC (13 months ago) by mollekas
File size: 32858 byte(s)
Removed sha algorithm state from HttpSOAPBindingBuilder.
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.IO;
5 using System.Linq;
6 using System.Security.Cryptography;
7 using System.Security.Cryptography.X509Certificates;
8 using System.Security.Cryptography.Xml;
9 using System.Text;
10 using System.Web;
11 using System.Web.Caching;
12 using System.Xml;
13 using dk.nita.saml20.Actions;
14 using dk.nita.saml20.Bindings;
15 using dk.nita.saml20.Bindings.SignatureProviders;
16 using dk.nita.saml20.Profiles.DKSaml20.Attributes;
17 using dk.nita.saml20.Session;
18 using dk.nita.saml20.session;
19 using dk.nita.saml20.config;
20 using dk.nita.saml20.Identity;
21 using dk.nita.saml20.Logging;
22 using dk.nita.saml20.Properties;
23 using dk.nita.saml20.protocol.pages;
24 using dk.nita.saml20.Schema.Core;
25 using dk.nita.saml20.Schema.Metadata;
26 using dk.nita.saml20.Schema.Protocol;
27 using dk.nita.saml20.Specification;
28 using dk.nita.saml20.Utils;
29 using Saml2.Properties;
30 using Trace=dk.nita.saml20.Utils.Trace;
31
32 namespace dk.nita.saml20.protocol
33 {
34 /// <summary>
35 /// Implements a Saml 2.0 protocol sign-on endpoint. Handles all SAML bindings.
36 /// </summary>
37 public class Saml20SignonHandler : Saml20AbstractEndpointHandler
38 {
39 private readonly X509Certificate2 _certificate;
40
41 /// <summary>
42 /// Initializes a new instance of the <see cref="Saml20SignonHandler"/> class.
43 /// </summary>
44 public Saml20SignonHandler()
45 {
46 _certificate = FederationConfig.GetConfig().SigningCertificate.GetCertificate();
47
48 // Read the proper redirect url from config
49 try
50 {
51 RedirectUrl = SAML20FederationConfig.GetConfig().ServiceProvider.SignOnEndpoint.RedirectUrl;
52 ErrorBehaviour = SAML20FederationConfig.GetConfig().ServiceProvider.SignOnEndpoint.ErrorBehaviour.ToString();
53 }
54 catch(Exception e)
55 {
56 if (Trace.ShouldTrace(TraceEventType.Error))
57 Trace.TraceData(TraceEventType.Error, e.ToString());
58 }
59 }
60
61 #region IHttpHandler Members
62
63 /// <summary>
64 /// Handles a request.
65 /// </summary>
66 /// <param name="context">The context.</param>
67 protected override void Handle(HttpContext context)
68 {
69 Trace.TraceMethodCalled(GetType(), "Handle()");
70
71 //Some IdP's are known to fail to set an actual value in the SOAPAction header
72 //so we just check for the existence of the header field.
73 if (Array.Exists(context.Request.Headers.AllKeys, delegate(string s) { return s == SOAPConstants.SOAPAction; }))
74 {
75 SessionStore.AssertSessionExists();
76
77 HandleSOAP(context, context.Request.InputStream);
78 return;
79 }
80
81 if (!string.IsNullOrEmpty(context.Request.Params["SAMLart"]))
82 {
83 SessionStore.AssertSessionExists();
84
85 HandleArtifact(context);
86 }
87
88 if (!string.IsNullOrEmpty(context.Request.Params["SamlResponse"]))
89 {
90 SessionStore.AssertSessionExists();
91
92 HandleResponse(context);
93 }
94 else
95 {
96 if (SAML20FederationConfig.GetConfig().CommonDomain.Enabled && context.Request.QueryString["r"] == null
97 && context.Request.Params["cidp"] == null)
98 {
99 AuditLogging.logEntry(Direction.OUT, Operation.DISCOVER, "Redirecting to Common Domain for IDP discovery");
100 context.Response.Redirect(SAML20FederationConfig.GetConfig().CommonDomain.LocalReaderEndpoint);
101 }
102 else
103 {
104 AuditLogging.logEntry(Direction.IN, Operation.ACCESS,
105 "User accessing resource: " + context.Request.RawUrl +
106 " without authentication.");
107
108 SessionStore.CreateSessionIfNotExists();
109
110 SendRequest(context);
111 }
112 }
113 }
114
115 #endregion
116
117 private void HandleArtifact(HttpContext context)
118 {
119 HttpArtifactBindingBuilder builder = new HttpArtifactBindingBuilder(context);
120 Stream inputStream = builder.ResolveArtifact();
121 HandleSOAP(context, inputStream);
122 }
123
124 private void HandleSOAP(HttpContext context, Stream inputStream)
125 {
126 Trace.TraceMethodCalled(GetType(), "HandleSOAP");
127 HttpArtifactBindingParser parser = new HttpArtifactBindingParser(inputStream);
128 HttpArtifactBindingBuilder builder = new HttpArtifactBindingBuilder(context);
129
130 if (parser.IsArtifactResolve())
131 {
132 Trace.TraceData(TraceEventType.Information, Tracing.ArtifactResolveIn);
133
134 IDPEndPoint idp = RetrieveIDPConfiguration(parser.Issuer);
135 AuditLogging.IdpId = idp.Id;
136 AuditLogging.AssertionId = parser.ArtifactResolve.ID;
137 if (!parser.CheckSamlMessageSignature(idp.metadata.Keys))
138 {
139 HandleError(context, "Invalid Saml message signature");
140 AuditLogging.logEntry(Direction.IN, Operation.ARTIFACTRESOLVE, "Could not verify signature", parser.SamlMessage);
141 }
142 builder.RespondToArtifactResolve(idp, parser.ArtifactResolve);
143 }else if(parser.IsArtifactResponse())
144 {
145 Trace.TraceData(TraceEventType.Information, Tracing.ArtifactResponseIn);
146
147 Status status = parser.ArtifactResponse.Status;
148 if (status.StatusCode.Value != Saml20Constants.StatusCodes.Success)
149 {
150 HandleError(context, status);
151 AuditLogging.logEntry(Direction.IN, Operation.ARTIFACTRESOLVE, string.Format("Illegal status for ArtifactResponse {0} expected 'Success', msg: {1}", status.StatusCode.Value, parser.SamlMessage));
152 return;
153 }
154 if(parser.ArtifactResponse.Any.LocalName == Response.ELEMENT_NAME)
155 {
156 bool isEncrypted;
157 XmlElement assertion = GetAssertion(parser.ArtifactResponse.Any, out isEncrypted);
158 if (assertion == null)
159 HandleError(context, "Missing assertion");
160 if(isEncrypted)
161 {
162 HandleEncryptedAssertion(context, assertion);
163 }else
164 {
165 HandleAssertion(context, assertion);
166 }
167
168 }else
169 {
170 AuditLogging.logEntry(Direction.IN, Operation.ARTIFACTRESOLVE, string.Format("Unsupported payload message in ArtifactResponse: {0}, msg: {1}", parser.ArtifactResponse.Any.LocalName, parser.SamlMessage));
171 HandleError(context,
172 string.Format("Unsupported payload message in ArtifactResponse: {0}",
173 parser.ArtifactResponse.Any.LocalName));
174 }
175 }else
176 {
177 Status s = parser.GetStatus();
178 if (s != null)
179 {
180 HandleError(context, s);
181 }
182 else
183 {
184 AuditLogging.logEntry(Direction.IN, Operation.ARTIFACTRESOLVE, string.Format("Unsupported SamlMessage element: {0}, msg: {1}", parser.SamlMessageName, parser.SamlMessage));
185 HandleError(context, string.Format("Unsupported SamlMessage element: {0}", parser.SamlMessageName));
186 }
187 }
188 }
189
190 /// <summary>
191 /// Send an authentication request to the IDP.
192 /// </summary>
193 private void SendRequest(HttpContext context)
194 {
195 Trace.TraceMethodCalled(GetType(), "SendRequest()");
196
197 // See if the "ReturnUrl" - parameter is set.
198 string returnUrl = context.Request.QueryString["ReturnUrl"];
199 // If PreventOpenRedirectAttack has been enabled ... the return URL is only set if the URL is local.
200 if (!string.IsNullOrEmpty(returnUrl) && (!FederationConfig.GetConfig().PreventOpenRedirectAttack || IsLocalUrl(returnUrl)))
201 SessionStore.CurrentSession[SessionConstants.RedirectUrl] = returnUrl;
202
203 IDPEndPoint idpEndpoint = RetrieveIDP(context);
204
205 if (idpEndpoint == null)
206 {
207 //Display a page to the user where she can pick the IDP
208 SelectSaml20IDP page = new SelectSaml20IDP();
209 page.ProcessRequest(context);
210 return;
211 }
212
213 Saml20AuthnRequest authnRequest = Saml20AuthnRequest.GetDefault();
214 TransferClient(idpEndpoint, authnRequest, context);
215 }
216
217 /// <summary>
218 /// This method is used for preventing open redirect attacks.
219 /// </summary>
220 /// <param name="url">URL that is checked for being local or not.</param>
221 /// <returns>Returns true if URL is local. Empty or null strings are not considered as local URL's</returns>
222 private bool IsLocalUrl(string url)
223 {
224 if (string.IsNullOrEmpty(url))
225 {
226 return false;
227 }
228 else
229 {
230 return ((url[0] == '/' && (url.Length == 1 ||
231 (url[1] != '/' && url[1] != '\\'))) || // "/" or "/foo" but not "//" or "/\"
232 (url.Length > 1 &&
233 url[0] == '~' && url[1] == '/')); // "~/" or "~/foo"
234 }
235 }
236
237 private Status GetStatusElement(XmlDocument doc)
238 {
239 XmlElement statElem =
240 (XmlElement)doc.GetElementsByTagName(Status.ELEMENT_NAME, Saml20Constants.PROTOCOL)[0];
241
242 return Serialization.DeserializeFromXmlString<Status>(statElem.OuterXml);
243 }
244
245 internal static XmlElement GetAssertion(XmlElement el, out bool isEncrypted)
246 {
247
248 XmlNodeList encryptedList =
249 el.GetElementsByTagName(EncryptedAssertion.ELEMENT_NAME, Saml20Constants.ASSERTION);
250
251 if (encryptedList.Count == 1)
252 {
253 isEncrypted = true;
254 return (XmlElement)encryptedList[0];
255 }
256
257 XmlNodeList assertionList =
258 el.GetElementsByTagName(Assertion.ELEMENT_NAME, Saml20Constants.ASSERTION);
259
260 if (assertionList.Count == 1)
261 {
262 isEncrypted = false;
263 return (XmlElement)assertionList[0];
264 }
265
266 isEncrypted = false;
267 return null;
268 }
269
270 /// <summary>
271 /// Handle the authentication response from the IDP.
272 /// </summary>
273 private void HandleResponse(HttpContext context)
274 {
275 Encoding defaultEncoding = Encoding.UTF8;
276 XmlDocument doc = GetDecodedSamlResponse(context, defaultEncoding);
277
278 AuditLogging.logEntry(Direction.IN, Operation.LOGIN, "Received SAMLResponse: " + doc.OuterXml);
279
280 try
281 {
282
283 XmlAttribute inResponseToAttribute =
284 doc.DocumentElement.Attributes["InResponseTo"];
285
286 if(inResponseToAttribute == null)
287 throw new Saml20Exception("Received a response message that did not contain an InResponseTo attribute");
288
289 string inResponseTo = inResponseToAttribute.Value;
290
291 CheckReplayAttack(context, inResponseTo);
292
293 Status status = GetStatusElement(doc);
294
295 if (status.StatusCode.Value != Saml20Constants.StatusCodes.Success)
296 {
297 if (status.StatusCode.Value == Saml20Constants.StatusCodes.Responder && status.StatusCode.SubStatusCode != null && Saml20Constants.StatusCodes.NoPassive == status.StatusCode.SubStatusCode.Value)
298 HandleError(context, "IdP responded with statuscode NoPassive. A user cannot be signed in with the IsPassiveFlag set when the user does not have a session with the IdP.");
299
300 HandleError(context, status);
301 return;
302 }
303
304 // Determine whether the assertion should be decrypted before being validated.
305
306 bool isEncrypted;
307 XmlElement assertion = GetAssertion(doc.DocumentElement, out isEncrypted);
308 if (isEncrypted)
309 {
310 assertion = GetDecryptedAssertion(assertion).Assertion.DocumentElement;
311 }
312
313 // Check if an encoding-override exists for the IdP endpoint in question
314 string issuer = GetIssuer(assertion);
315 IDPEndPoint endpoint = RetrieveIDPConfiguration(issuer);
316 if (!string.IsNullOrEmpty(endpoint.ResponseEncoding))
317 {
318 Encoding encodingOverride = null;
319 try
320 {
321 encodingOverride = System.Text.Encoding.GetEncoding(endpoint.ResponseEncoding);
322 }
323 catch (ArgumentException ex)
324 {
325 HandleError(context, ex);
326 return;
327 }
328
329 if (encodingOverride.CodePage != defaultEncoding.CodePage)
330 {
331 XmlDocument doc1 = GetDecodedSamlResponse(context, encodingOverride);
332 assertion = GetAssertion(doc1.DocumentElement, out isEncrypted);
333 }
334 }
335
336 HandleAssertion(context, assertion);
337 return;
338 }
339 catch (Exception e)
340 {
341 HandleError(context, e);
342 return;
343 }
344 }
345
346 private static void CheckReplayAttack(HttpContext context, string inResponseTo)
347 {
348 var expectedInResponseToSessionState = SessionStore.CurrentSession[SessionConstants.ExpectedInResponseTo];
349
350 SessionStore.CurrentSession[SessionConstants.ExpectedInResponseTo] = null; // Ensure that no more responses can be received.
351
352 string expectedInResponseTo = expectedInResponseToSessionState.ToString();
353 if (string.IsNullOrEmpty(expectedInResponseTo) || string.IsNullOrEmpty(inResponseTo))
354 throw new Saml20Exception("Empty protocol message id is not allowed.");
355
356 if (inResponseTo != expectedInResponseTo)
357 {
358 AuditLogging.logEntry(Direction.IN, Operation.LOGIN, string.Format("Unexpected value {0} for InResponseTo, expected {1}, possible replay attack!", inResponseTo, expectedInResponseTo));
359 throw new Saml20Exception("Replay attack.");
360 }
361 }
362
363 private static XmlDocument GetDecodedSamlResponse(HttpContext context, Encoding encoding)
364 {
365 string base64 = context.Request.Params["SAMLResponse"];
366
367 XmlDocument doc = new XmlDocument();
368 doc.XmlResolver = null;
369 doc.PreserveWhitespace = true;
370 string samlResponse = encoding.GetString(Convert.FromBase64String(base64));
371 if (Trace.ShouldTrace(TraceEventType.Information))
372 Trace.TraceData(TraceEventType.Information, "Decoded SAMLResponse", samlResponse);
373
374 doc.LoadXml(samlResponse);
375 return doc;
376 }
377
378 /// <summary>
379 /// Decrypts an encrypted assertion, and sends the result to the HandleAssertion method.
380 /// </summary>
381 private void HandleEncryptedAssertion(HttpContext context, XmlElement elem)
382 {
383 Trace.TraceMethodCalled(GetType(), "HandleEncryptedAssertion()");
384 Saml20EncryptedAssertion decryptedAssertion = GetDecryptedAssertion(elem);
385 HandleAssertion(context, decryptedAssertion.Assertion.DocumentElement);
386 }
387
388 private static Saml20EncryptedAssertion GetDecryptedAssertion(XmlElement elem)
389 {
390 Saml20EncryptedAssertion decryptedAssertion = new Saml20EncryptedAssertion((RSA)FederationConfig.GetConfig().SigningCertificate.GetCertificate().PrivateKey);
391 decryptedAssertion.LoadXml(elem);
392 decryptedAssertion.Decrypt();
393 return decryptedAssertion;
394 }
395
396 /// <summary>
397 /// Retrieves the name of the issuer from an XmlElement containing an assertion.
398 /// </summary>
399 /// <param name="assertion">An XmlElement containing an assertion</param>
400 /// <returns>The identifier of the Issuer</returns>
401 private string GetIssuer(XmlElement assertion)
402 {
403 string result = string.Empty;
404 XmlNodeList list = assertion.GetElementsByTagName("Issuer", Saml20Constants.ASSERTION);
405 if (list.Count > 0)
406 {
407 XmlElement issuer = (XmlElement) list[0];
408 result = issuer.InnerText;
409 }
410
411 return result;
412 }
413
414 /// <summary>
415 /// Is called before the assertion is made into a strongly typed representation
416 /// </summary>
417 /// <param name="context">The httpcontext.</param>
418 /// <param name="elem">The assertion element.</param>
419 /// <param name="endpoint">The endpoint.</param>
420 protected virtual void PreHandleAssertion(HttpContext context, XmlElement elem, IDPEndPoint endpoint)
421 {
422 Trace.TraceMethodCalled(GetType(), "PreHandleAssertion");
423
424 if (endpoint != null && endpoint.SLOEndpoint != null && !String.IsNullOrEmpty(endpoint.SLOEndpoint.IdpTokenAccessor))
425 {
426 ISaml20IdpTokenAccessor idpTokenAccessor =
427 Activator.CreateInstance(Type.GetType(endpoint.SLOEndpoint.IdpTokenAccessor, false)) as ISaml20IdpTokenAccessor;
428 if (idpTokenAccessor != null)
429 idpTokenAccessor.ReadToken(elem);
430 }
431
432 Trace.TraceMethodDone(GetType(), "PreHandleAssertion");
433 }
434
435 /// <summary>
436 /// Deserializes an assertion, verifies its signature and logs in the user if the assertion is valid.
437 /// </summary>
438 private void HandleAssertion(HttpContext context, XmlElement elem)
439 {
440 Trace.TraceMethodCalled(GetType(), "HandleAssertion");
441
442 string issuer = GetIssuer(elem);
443
444 IDPEndPoint endp = RetrieveIDPConfiguration(issuer);
445
446 AuditLogging.IdpId = endp.Id;
447
448 PreHandleAssertion(context, elem, endp);
449
450 bool quirksMode = false;
451
452 if (endp != null)
453 {
454 quirksMode = endp.QuirksMode;
455 }
456
457 Saml20Assertion assertion = new Saml20Assertion(elem, null, quirksMode);
458
459 if (endp == null || endp.metadata == null)
460 {
461 AuditLogging.logEntry(Direction.IN, Operation.AUTHNREQUEST_POST,
462 "Unknown login IDP, assertion: " + elem);
463
464 HandleError(context, Resources.UnknownLoginIDP);
465 return;
466 }
467
468 if (!endp.OmitAssertionSignatureCheck)
469 {
470 IEnumerable<string> validationFailures;
471 if (!assertion.CheckSignature(GetTrustedSigners(endp.metadata.GetKeys(KeyTypes.signing), endp, out validationFailures)))
472 {
473 AuditLogging.logEntry(Direction.IN, Operation.AUTHNREQUEST_POST,
474 "Invalid signature, assertion: " + elem);
475
476 string errorMessage = Resources.SignatureInvalid;
477
478 validationFailures = validationFailures.ToArray();
479 if (validationFailures.Any())
480 {
481 errorMessage += $"\nVerification of IDP certificate used for signature failed from the following certificate checks:\n{string.Join("\n", validationFailures)}";
482 }
483
484 HandleError(context, errorMessage);
485 return;
486 }
487 }
488
489 if (assertion.IsExpired())
490 {
491 AuditLogging.logEntry(Direction.IN, Operation.AUTHNREQUEST_POST,
492 "Assertion expired, assertion: " + elem.OuterXml);
493
494 HandleError(context, Resources.AssertionExpired);
495 return;
496 }
497
498 // Only check if assertion has the required assurancelevel if it is present.
499 string assuranceLevel = GetAssuranceLevel(assertion);
500 string minimumAssuranceLevel = SAML20FederationConfig.GetConfig().MinimumAssuranceLevel;
501 if (assuranceLevel != null)
502 {
503 // Assurance level is ok if the string matches the configured minimum assurance level. This is in order to support the value "Test". However, normally the value will be an integer
504 if (assuranceLevel != minimumAssuranceLevel)
505 {
506 // If strings are different it is still ok if the assertion has stronger assurance level than the minimum required.
507 int assuranceLevelAsInt;
508 int minimumAssuranceLevelAsInt;
509 if (!int.TryParse(assuranceLevel, out assuranceLevelAsInt) ||
510 !int.TryParse(minimumAssuranceLevel, out minimumAssuranceLevelAsInt) ||
511 assuranceLevelAsInt < minimumAssuranceLevelAsInt)
512 {
513 string errorMessage = string.Format(Resources.AssuranceLevelTooLow, assuranceLevel,
514 minimumAssuranceLevel);
515 AuditLogging.logEntry(Direction.IN, Operation.AUTHNREQUEST_POST,
516 errorMessage + " Assertion: " + elem.OuterXml);
517
518 HandleError(context,
519 string.Format(Resources.AssuranceLevelTooLow, assuranceLevel, minimumAssuranceLevel));
520 return;
521 }
522 }
523 }
524
525 CheckConditions(context, assertion);
526 AuditLogging.AssertionId = assertion.Id;
527 AuditLogging.logEntry(Direction.IN, Operation.AUTHNREQUEST_POST,
528 "Assertion validated succesfully");
529
530 DoLogin(context, assertion);
531 }
532
533 internal static IEnumerable<AsymmetricAlgorithm> GetTrustedSigners(ICollection<KeyDescriptor> keys, IDPEndPoint ep, out IEnumerable<string> validationFailureReasons)
534 {
535 if (keys == null)
536 throw new ArgumentNullException("keys");
537
538 var failures = new List<string>();
539 List<AsymmetricAlgorithm> result = new List<AsymmetricAlgorithm>(keys.Count);
540 foreach (KeyDescriptor keyDescriptor in keys)
541 {
542 KeyInfo ki = (KeyInfo) keyDescriptor.KeyInfo;
543
544 foreach (KeyInfoClause clause in ki)
545 {
546 if(clause is KeyInfoX509Data)
547 {
548 X509Certificate2 cert = XmlSignatureUtils.GetCertificateFromKeyInfo((KeyInfoX509Data) clause);
549
550 string failureReason;
551 if (!IsSatisfiedByAllSpecifications(ep, cert, out failureReason))
552 {
553 failures.Add(failureReason);
554 continue;
555 }
556 }
557
558 AsymmetricAlgorithm key = XmlSignatureUtils.ExtractKey(clause);
559 result.Add(key);
560 }
561
562 }
563
564 validationFailureReasons = failures;
565 return result;
566 }
567
568 private static bool IsSatisfiedByAllSpecifications(IDPEndPoint ep, X509Certificate2 cert, out string failureReason)
569 {
570 foreach(ICertificateSpecification spec in SpecificationFactory.GetCertificateSpecifications(ep))
571 {
572 string r;
573 if (!spec.IsSatisfiedBy(cert, out r))
574 {
575 failureReason = $"{spec.GetType().Name}: {r}";
576 return false;
577
578 }
579 }
580
581 failureReason = null;
582 return true;
583 }
584
585
586 private void CheckConditions(HttpContext context, Saml20Assertion assertion)
587 {
588 if(assertion.IsOneTimeUse)
589 {
590 if (context.Cache[assertion.Id] != null)
591 {
592 HandleError(context, Resources.OneTimeUseReplay);
593 }else
594 {
595 context.Cache.Insert(assertion.Id, string.Empty, null, assertion.NotOnOrAfter, Cache.NoSlidingExpiration);
596 }
597 }
598 }
599
600 private void DoLogin(HttpContext context, Saml20Assertion assertion)
601 {
602 SessionStore.AssociateUserIdWithCurrentSession(assertion.Subject.Value);
603
604 // The assertion is what keeps the session alive. If it is ever removed ... the session will appear as removed in the SessionStoreProvider because Saml20AssertionLite is the only thing kept in session store when login flow is completed..
605 SessionStore.CurrentSession[SessionConstants.Saml20AssertionLite] = Saml20AssertionLite.ToLite(assertion);
606
607 if(Trace.ShouldTrace(TraceEventType.Information))
608 {
609 Trace.TraceData(TraceEventType.Information, string.Format(Tracing.Login, assertion.Subject.Value, assertion.SessionIndex, assertion.Subject.Format));
610 }
611
612 string assuranceLevel = GetAssuranceLevel(assertion) ?? "(Unknown)";
613
614 AuditLogging.logEntry(Direction.IN, Operation.LOGIN, string.Format("Subject: {0} NameIDFormat: {1} Level of authentication: {2} Session timeout in minutes: {3}", assertion.Subject.Value, assertion.Subject.Format, assuranceLevel, FederationConfig.GetConfig().SessionTimeout));
615
616
617 foreach(IAction action in Actions.Actions.GetActions())
618 {
619 Trace.TraceMethodCalled(action.GetType(), "LoginAction()");
620
621 action.LoginAction(this, context, assertion);
622
623 Trace.TraceMethodDone(action.GetType(), "LoginAction()");
624 }
625 }
626
627 /// <summary>
628 /// Retrieves the assurance level from the assertion.
629 /// </summary>
630 /// <returns>Returns the assurance level or null if it has not been defined.</returns>
631 private string GetAssuranceLevel(Saml20Assertion assertion)
632 {
633 foreach (var attribute in assertion.Attributes)
634 {
635 if (attribute.Name == DKSaml20AssuranceLevelAttribute.NAME
636 && attribute.AttributeValue != null
637 && attribute.AttributeValue.Length > 0)
638 return attribute.AttributeValue[0];
639 }
640
641 return null;
642 }
643
644 private void TransferClient(IDPEndPoint idpEndpoint, Saml20AuthnRequest request, HttpContext context)
645 {
646 AuditLogging.AssertionId = request.ID;
647 AuditLogging.IdpId = idpEndpoint.Id;
648
649 // Determine which endpoint to use from the configuration file or the endpoint metadata.
650 IDPEndPointElement destination =
651 DetermineEndpointConfiguration(SAMLBinding.REDIRECT, idpEndpoint.SSOEndpoint, idpEndpoint.metadata.SSOEndpoints());
652
653
654
655 request.Destination = destination.Url;
656
657 bool isPassive;
658 string isPassiveAsString = context.Request.Params[IDPIsPassive];
659 if (bool.TryParse(isPassiveAsString, out isPassive))
660 {
661 request.IsPassive = isPassive;
662 }
663
664 if (idpEndpoint.IsPassive)
665 request.IsPassive = true;
666
667 bool forceAuthn;
668 string forceAuthnAsString = context.Request.Params[IDPForceAuthn];
669 if (bool.TryParse(forceAuthnAsString, out forceAuthn))
670 {
671 request.ForceAuthn = forceAuthn;
672 }
673
674 if (idpEndpoint.ForceAuthn)
675 request.ForceAuthn = true;
676
677 if (idpEndpoint.SSOEndpoint != null)
678 {
679 if (!string.IsNullOrEmpty(idpEndpoint.SSOEndpoint.ForceProtocolBinding))
680 {
681 request.ProtocolBinding = idpEndpoint.SSOEndpoint.ForceProtocolBinding;
682 }
683 }
684
685 //Save request message id to session
686 SessionStore.CurrentSession[SessionConstants.ExpectedInResponseTo] = request.ID;
687
688 var shaHashingAlgorithm = SignatureProviderFactory.ValidateShaHashingAlgorithm(idpEndpoint.ShaHashingAlgorithm);
689 if (destination.Binding == SAMLBinding.REDIRECT)
690 {
691 Trace.TraceData(TraceEventType.Information, string.Format(Tracing.SendAuthnRequest, Saml20Constants.ProtocolBindings.HTTP_Redirect, idpEndpoint.Id));
692
693 HttpRedirectBindingBuilder builder = new HttpRedirectBindingBuilder();
694 builder.signingKey = _certificate.PrivateKey;
695 builder.Request = request.GetXml().OuterXml;
696 builder.ShaHashingAlgorithm = shaHashingAlgorithm;
697 string s = request.Destination + "?" + builder.ToQuery();
698
699 AuditLogging.logEntry(Direction.OUT, Operation.AUTHNREQUEST_REDIRECT, "Redirecting user to IdP for authentication", builder.Request);
700
701 context.Response.Redirect(s, true);
702 return;
703 }
704
705 if (destination.Binding == SAMLBinding.POST)
706 {
707 Trace.TraceData(TraceEventType.Information, string.Format(Tracing.SendAuthnRequest, Saml20Constants.ProtocolBindings.HTTP_Post, idpEndpoint.Id));
708
709 HttpPostBindingBuilder builder = new HttpPostBindingBuilder(destination);
710 //Honor the ForceProtocolBinding and only set this if it's not already set
711 if (string.IsNullOrEmpty(request.ProtocolBinding))
712 request.ProtocolBinding = Saml20Constants.ProtocolBindings.HTTP_Post;
713 XmlDocument req = request.GetXml();
714 var signingCertificate = FederationConfig.GetConfig().SigningCertificate.GetCertificate();
715 var signatureProvider = SignatureProviderFactory.CreateFromShaHashingAlgorithmName(shaHashingAlgorithm);
716 signatureProvider.SignAssertion(req, request.ID, signingCertificate);
717 builder.Request = req.OuterXml;
718 AuditLogging.logEntry(Direction.OUT, Operation.AUTHNREQUEST_POST);
719
720 builder.GetPage().ProcessRequest(context);
721 return;
722 }
723
724 if(destination.Binding == SAMLBinding.ARTIFACT)
725 {
726 Trace.TraceData(TraceEventType.Information, string.Format(Tracing.SendAuthnRequest, Saml20Constants.ProtocolBindings.HTTP_Artifact, idpEndpoint.Id));
727
728 HttpArtifactBindingBuilder builder = new HttpArtifactBindingBuilder(context);
729
730 //Honor the ForceProtocolBinding and only set this if it's not already set
731 if (string.IsNullOrEmpty(request.ProtocolBinding))
732 request.ProtocolBinding = Saml20Constants.ProtocolBindings.HTTP_Artifact;
733 AuditLogging.logEntry(Direction.OUT, Operation.AUTHNREQUEST_REDIRECT_ARTIFACT);
734
735 builder.RedirectFromLogin(idpEndpoint, destination, request);
736 }
737
738 HandleError(context, Resources.BindingError);
739 }
740
741 }
742 }

softwareborsen@digst.dk
ViewVC Help
Powered by ViewVC 1.1.20 RSS 2.0 feed