public async Task <(string returnUrl, string requestId)> LoginAsync(EAuthCallbackModel model) { EAuthService service = new EAuthService(); EAuthResponseModel eAuthResponseModel = service.Parse(model); string requestId = eAuthResponseModel?.RequestId; string returnUrl = _applicationStoreService.GetApplicationBaseUrl(requestId) + _loginReturnPath; (string personIdentifier, string holderName, string email) = ExtractPersonData(eAuthResponseModel); ApplicationUser user = _userManager.Users.SingleOrDefault(u => u.Certificate_UniqueIdentifier.Trim() == personIdentifier && !u.Deleted); if (user == null) { returnUrl += $"?error=true&message=userNotFound"; return(returnUrl, requestId); } else { /* * Ако потребителя е сменил своето име в подписа, го променяме и в базата * т.е. ако жена се омъжи и смени фамилията си и т.н. * Ако подписа е променен също запазваме данните */ if (user.Certificate_Name == null || user.Certificate_Name.ToLower().Trim() != (holderName ?? "").ToLower().Trim()) { //Check if certificate is the same and update user info if needed user.Certificate_Name = holderName; await _userManager.UpdateAsync(user); } } if (!user.EmailConfirmed) { await SendConfirmEmailAsync(user, _applicationStoreService.GetApplicationBaseUrl(requestId)); returnUrl += $"?error=true&message=EmailNotConfirmed,ConfirmationEmailResend"; return(returnUrl, requestId); } if (!user.ConfirmedByAdmin) { returnUrl += $"?error=true&message=NotConfirmedByAdmin"; return(returnUrl, requestId); } //sign in existing user returnUrl = await SignInExistingUser(user, returnUrl); return(returnUrl, requestId); }
public ActionResult LoginCallback(EAuthCallbackModel model) { // Записване на отговора в журнал. EAuthService service = new EAuthService(); EAuthResponseModel eAuthResponseModel = service.Parse(model); // Изходът от този action не се използва. Целта е само да се зареди локален адрес в callback iframe-а. // Това се интерпретира от Java Script-а като успешен край на автентикацията и следва пренасочване към друг адрес. // // До февруари 2019 г. този метод връщаше празен резултат и това вършеше работа. //return new EmptyResult(); // Chrome версия 72+ обаче започна да интерпретира security header-а X-Content-Type-Options=nosniff по друг начин. // Когато резултатът е празен, вече не зарежда резултата на екрана(или в iframe-а), а изтегля празен файл. // Така не се задейства никакъв Java Script, iframe-ът не разбира, че е зареден локален адрес, и автентикацията // не завършва в Chrome 72+. Tрябва се върне някакъв HTML, колкото да се задейства onload handler-ът на iframe-а. //return PartialView(); return(View(eAuthResponseModel)); }
public static EAuthResponseModel ParseSamlResponse(string encodedSamlResponse, string encodedRelayState) { EAuthResponseModel model = new EAuthResponseModel { Errors = new List <string>() }; if (encodedSamlResponse != null) { string stepName = "декодиране на base64 текста"; try { model.SamlResponse = DecodeSamlParameter(encodedSamlResponse); stepName = "десериализиране на отговора"; ResponseType response = XmlUtil.Deserialize <ResponseType>(model.SamlResponse); stepName = "четене на полетата от отговора"; InterpretResponse(response, model); } catch (Exception ex) { model.Errors.Add($"Грешка при {stepName}: {ex.Message}"); } } else { model.Errors.Add("Отговорът е празен"); } if (encodedRelayState != null) { try { model.RelayState = DecodeSamlParameter(encodedRelayState); } catch (Exception ex) { model.Errors.Add($"Грешка при декодиране на base64 relayState: {ex.Message}"); } } return(model); }
private (string personIdentifier, string holderName, string email) ExtractPersonData(EAuthResponseModel eAuthResponseModel) { if (eAuthResponseModel == null) { throw new ArgumentNullException(nameof(EAuthResponseModel)); } string holderName = eAuthResponseModel?.PersonNamesLatin; string email = eAuthResponseModel?.Email; string personIdentifier = null; if (!string.IsNullOrWhiteSpace(eAuthResponseModel?.PersonIdentifier)) { string[] split = eAuthResponseModel.PersonIdentifier.Split(" ", System.StringSplitOptions.RemoveEmptyEntries); personIdentifier = split.Length == 0 ? "" : (split.Length == 1 ? split[0].Trim() : split.LastOrDefault()?.Trim()); } return(personIdentifier, holderName, email); }
public async Task <(string returnUrl, string requestId)> RegisterAsync(EAuthCallbackModel model) { string returnUrl = ""; string requestId = ""; try { if (model == null) { throw new ArgumentNullException(nameof(EAuthCallbackModel)); } EAuthService service = new EAuthService(); EAuthResponseModel eAuthResponseModel = service.Parse(model); requestId = eAuthResponseModel?.RequestId; returnUrl = _applicationStoreService.GetApplicationBaseUrl(requestId) + _registerReturnPath; string userType = _applicationStoreService.GetUserType(requestId); string chsiNumber = _applicationStoreService.GetChsiNumber(requestId); string lang = _applicationStoreService.GetLang(requestId); string email = _applicationStoreService.GetEmail(requestId); (string personIdentifier, string holderName, string certEmail) = ExtractPersonData(eAuthResponseModel); Thread.CurrentThread.CurrentCulture = new CultureInfo(lang); Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang); if ((userType == UserType.CHSI.ToString() || userType == UserType.CHSIHelper.ToString()) && chsiNumber == default) { returnUrl += $"?error=true"; return(returnUrl, requestId); } //check egn bool isDuplicateEgn = _userManager.Users.Any(u => u.Certificate_UniqueIdentifier == (personIdentifier ?? "").ToUpper().Trim() && !u.Deleted); if (isDuplicateEgn) { returnUrl += $"?error=true&message=duplicateEgn"; return(returnUrl, requestId); } if (string.IsNullOrWhiteSpace(email)) { email = certEmail; } //check email if (string.IsNullOrWhiteSpace(email)) { //Redirect to register page with parameter to gather additional info returnUrl += "?requireEmail=true"; return(returnUrl, requestId); } //Create new user ApplicationUser user = new ApplicationUser() { Certificate = string.IsNullOrWhiteSpace(eAuthResponseModel.SamlResponse) ? null : Encoding.ASCII.GetBytes(eAuthResponseModel.SamlResponse), Certificate_Thumbprint = eAuthResponseModel.RequestId, Email = email, EmailConfirmed = false, UserName = GenerateUserName(holderName), Certificate_Name = holderName.Trim(), Certificate_UniqueIdentifier = personIdentifier?.Trim(), AuthType = AuthType.EAUTH.ToString(), UserType = userType, ConfirmedByAdmin = false, CreatedOn = DateTime.UtcNow }; string userRole = Constants.Role_AuctionOrgaziner; if (userType == UserType.AUCPAR.ToString()) { user.ConfirmedByAdmin = true; userRole = Constants.Role_AuctionParticipant; } else if (userType == UserType.CHSI.ToString()) { user.CHSINumber = chsiNumber; //TODO Check CHSI in CHSI register by number //Return error if chsi is not found bool ChsiRegisterCheck = true; user.CheckedInCHSIRegister = ChsiRegisterCheck; user.ConfirmedByAdmin = ChsiRegisterCheck; } else if (userType == UserType.CHSIHelper.ToString()) { user.CHSINumber = chsiNumber; //TODO Check CHSI helpers in CHSI register by number //Return error if chsi is not found bool ChsiRegisterCheck = true; user.CheckedInCHSIRegister = ChsiRegisterCheck; user.ConfirmedByAdmin = ChsiRegisterCheck; } IdentityResult ir = await _userManager.CreateAsync(user); if (!ir.Succeeded) { //create new user fail Log.Error("Error creating user with ESign", string.Join(',', ir.Errors.Select(x => x.Description))); returnUrl += $"?error=true"; return(returnUrl, requestId); } //Assign to role var roleAddResult = await _userManager.AddToRoleAsync(user, userRole); if (!roleAddResult.Succeeded) { Log.Error($"Error adding user ID= {user.Id} to role {userRole}", string.Join(',', roleAddResult.Errors.Select(x => x.Description))); } try { //create person await CreatePerson(user.Id, holderName, email); //send confirmation email await SendConfirmEmailAsync(user, _applicationStoreService.GetApplicationBaseUrl(requestId)); return(returnUrl, requestId); } catch (Exception x) { //await AuditService.EditContentAndUserAsync(AuditId, AuditContentType.Activity, // AuditMessages.Failure.ToString(), user.Id, user.UserName); Log.Error("Error creating person with EAuth", x); IdentityResult deleteResult = await _userManager.DeleteAsync(user); returnUrl += $"?error=true"; return(returnUrl, requestId); } } catch (Exception x) { if (x is ArgumentNullException) { Log.Error("Empty EAuth response " + x.Message); returnUrl += $"?error=true"; return(returnUrl, requestId); } Log.Error(x, "Error parsing EAuth response on register"); //cannot parse EAuth response returnUrl += $"?error=true"; return(returnUrl, requestId); } }
private static void InterpretResponse(ResponseType response, EAuthResponseModel model) { model.RequestId = response.InResponseTo; // Пример за грешка: // <samlp:Status> // <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder"><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:AuthnFailed" /></samlp:StatusCode> // <samlp:StatusMessage>NOT_DETECTED_QES ***ИЛИ*** Некоректни данни</samlp:StatusMessage> // </samlp:Status> // Пример за успех: // <samlp:Status> // <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /> // <samlp:StatusMessage>Успешен отговор на заявката</samlp:StatusMessage> // </samlp:Status> StatusType status = response.Status; if (status != null) { bool isSuccessful = status.StatusCode?.Value.EndsWith("Success") ?? false; string statusMessage = status.StatusMessage; // Ако в заявката към еАвт не е подаден сертификат, системата вместо съобщение за грешка връща код NOT_DETECTED_QES // ИЛИ съобщение "STS Exception: STSToken exception: bg.egov.mtits.eauthn.delegate.exceptions.STSDelegateException : null". if (statusMessage == "NOT_DETECTED_QES" || statusMessage != null && statusMessage.Contains("STSDelegateException")) { model.NotDetectedQes = true; model.Errors.Add(ClientCertNotSelected); } else if (!isSuccessful) // Съобщението за успех не се пази. { if (!string.IsNullOrEmpty(statusMessage)) { model.Errors.Add(statusMessage); } XmlElement[] details = status.StatusDetail?.Any; if (details != null) { model.Errors.AddRange(details.Select(e => e.OuterXml)); } } } if (response.Items?[0] is AssertionType assertion) { ConditionsType conditions = assertion.Conditions; if (conditions != null && conditions.NotOnOrAfterSpecified) { model.ExpirationDateTime = conditions.NotOnOrAfter; } // Пример за ЕГН: // <saml2:Subject> // <saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" NameQualifier="urn:egov:bg:eauth:1.0:attributes:eIdentifier:EGN">8012311234</saml2:NameID> // <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:sender-vouches"><saml2:SubjectConfirmationData /></saml2:SubjectConfirmation> // </saml2:Subject> NameIDType nameId = assertion.Subject?.Items?.OfType <NameIDType>().FirstOrDefault(); if (nameId != null) { model.PidTypeCode = nameId.NameQualifier?.Split(':').Last(); // Към 2018-02 е известен само вид "EGN". model.PersonIdentifier = nameId.Value; } // Пример за име и контакти: // <saml2:AttributeStatement> // <saml2:Attribute Name="urn:egov:bg:eauth:1.0:attributes:personNamesLatin" NameFormat="urn:egov:bg:eauth:1.0:attributes:personNamesLatin"> // <saml2:AttributeValue xsi:type="xs:string">Ivan Dilyanov Dilov</saml2:AttributeValue> // </saml2:Attribute> // <saml2:Attribute Name="urn:egov:bg:eauth:1.0:attributes:eMail" NameFormat="urn:egov:bg:eauth:1.0:attributes:eMail"> // <saml2:AttributeValue xsi:type="xs:string">[email protected]</saml2:AttributeValue> // </saml2:Attribute> // <saml2:Attribute Name="urn:egov:bg:eauth:1.0:attributes:phone" NameFormat="urn:egov:bg:eauth:1.0:attributes:phone"> // <saml2:AttributeValue xsi:type="xs:string">+359 888476663</saml2:AttributeValue> // </saml2:Attribute> // </saml2:AttributeStatement> if (assertion.Items != null) { foreach (AttributeStatementType attributes in assertion.Items.OfType <AttributeStatementType>()) { if (attributes.Items != null) { foreach (AttributeType attribute in attributes.Items.OfType <AttributeType>()) { string type = attribute.Name?.Split(':').Last().ToLower(); string value = attribute.AttributeValue != null?string.Join(Environment.NewLine, attribute.AttributeValue.Where(v => v != null)) : null; if (type == "personnameslatin") { model.PersonNamesLatin = value; } else if (type == "email") { model.Email = value; } else if (type == "phone") { model.Phone = value; } } } } } } }