using System; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using System.Security.Authentication; using Serilog; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc; using OnlineBankingAuth.Tokens; using Landmark.OLA.Models.OnlineBanking; using Landmark.OLA.Application.Common.Security; using Landmark.OLA.Models.Core; using OnlineBankingAuth.Parsers; using OnlineBankingAuth.Services; namespace Landmark.OLA.Application.Controllers { [Route("api/[controller]")] [ApiController] public class SamlAssertionConsumerController : ControllerBase { private readonly IDigitalInsightVendorClient _diVendor; private readonly ITokenService _tokenService; public SamlAssertionConsumerController ( IDigitalInsightVendorClient diVendor, ITokenService tokenService ) { _diVendor = diVendor; _tokenService = tokenService; } /// /// Use to refer a user to the application with a valid SAML assertion /// /// /// Returns an application redirect, adding in a SAML assertion via a JWT in a secure http-only cookie. /// Cookies: Set a standard pair of authentication and authorization cookies. /// Security: Passed document must include a valid, signed SAML assertion, provides member authentication. /// // POST: api/SAMLAssertionConsumer [HttpPost] [ProducesResponseType(403)] [ProducesResponseType(401)] [ProducesResponseType(201)] [ProducesResponseType(typeof(ModelStateDictionary), 401)] public async Task Post([FromForm] SAMLMessaging document) { try { var decodedString = Encoding.UTF8.GetString(Convert.FromBase64String(document.SAMLResponse)); var parsedXml = XElement.Parse(decodedString); var xmlDocument = new XmlDocument { PreserveWhitespace = true }; xmlDocument.Load(parsedXml.CreateReader()); // @TODO: Should catch error here, move element query to static service #1400b var x509Payload = (XmlElement)xmlDocument.GetElementsByTagName("ds:X509Certificate")[0]; var vendorCertMatches = _diVendor.ValidateDIPublicKey(x509Payload); if (!vendorCertMatches) { throw new AuthenticationException("The vendor's public certificate did not match! "); } var assertion = _diVendor.GetSAMLPayload(xmlDocument); // Check that assertion is still valid var datesValid = _diVendor.ValidateTimeStamp(assertion); if (!datesValid) { throw new AuthenticationException("The Authentication has expired!"); } var obToken = new OnlineBankingToken(assertion, null, true) { IsTwoFactor = true }; obToken.IndividualID = obToken.MemberID; AssignTokens(obToken); HttpContext.Response.Redirect(_diVendor.GetSuccessRedirectUrl()); } catch (Exception ex) { Log.Error(ex, $"Failure to validate SAML in {GetType().Name}"); HttpContext.Response.Redirect(_diVendor.GetFailureRedirectUrl(ex.Message)); } } private void AssignTokens(OnlineBankingToken obToken) { var meta = new MemberMeta { IsTwoFactor = obToken.IsTwoFactor, IsMember = (obToken.MemberID != null), IsIndividual = (obToken.IndividualID != null) }; var privateToken = _tokenService.CreateOnlineBankingJWT(obToken); var publicToken = _tokenService.CreateAuthMetaToken(meta); CookieService.SetAuthenticationCookie(HttpContext.Response.Cookies, privateToken); CookieService.SetAuthorizationCookie(HttpContext.Response.Cookies, publicToken); } /// /// Use to create an empty cookie session /// /// /// Returns a pair of empty authorization and authentication cookies. /// Cookies: Sets empty Authentication and Authorization cookies. /// Security: None required or provided. /// [HttpGet("InitSession")] [ProducesResponseType(200)] public IActionResult Get() { var obToken = new OnlineBankingToken { MemberID = null, IndividualID = null, FI_ID = "0", IsTwoFactor = false }; AssignTokens(obToken); return Ok(); } /// /// Use to create a cookie session with credentials /// /// /// Returns a pair of authorization and authentication cookies when provided with valid credentials. /// Cookies: Sets Authentication and Authorization cookies. /// Security: Require. /// [HttpPost("Authenticate")] [ProducesResponseType(403)] [ProducesResponseType(201)] [ProducesResponseType(typeof(ModelStateDictionary), 401)] public async Task PostAsync([FromBody] OnlineBankingFormData document) { try { var diResponse = await _diVendor.AuthenticateOnlineBankingAccount(document); if (diResponse.Token == null) { Log.Error($"Member failed non-two factor login, {@document.Username}"); return Ok(diResponse.Status); } var assertion = new DISAMLAssertion { MemberID = diResponse.Token.DiMemberNumber, FI_ID = diResponse.Token.DiFiid }; var obToken = new OnlineBankingToken(assertion, null, false) { IsTwoFactor = false }; obToken.IndividualID = obToken.MemberID; AssignTokens(obToken); return Ok(diResponse.Status); } catch (Exception ex) { Log.Error(ex, $"Failure to authenticate user in {GetType().Name}"); return StatusCode((int)System.Net.HttpStatusCode.InternalServerError); } } /// /// Use to identify a unique individual on the member account. /// /// /// Returns OK if an individual ID was added to the cookie. /// Cookies: Sets Individual ID in cookie. /// Security: Requires existing cookie, with member. /// [HttpPost("Identify")] [ProducesResponseType(403)] [ProducesResponseType(400)] [ProducesResponseType(200)] [ProducesResponseType(typeof(ModelStateDictionary), 401)] public async Task PostIdentify([FromBody] OnlineIdentityFormData document) { try { var token = _tokenService.GetAuthenticatedDataFromCookie(HttpContext); if (token == null) { Log.Error($"Non-authenticated attempt at individual confirmation! Using info {@document}, Refusing."); return Unauthorized(); } token.IndividualID = token.MemberID; AssignTokens(token); return Ok(); } catch (Exception ex) { Log.Error(ex, $"Failure to identify user in {GetType().Name}"); return StatusCode((int)System.Net.HttpStatusCode.InternalServerError); } } } }