예제 #1
0
        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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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;
                                }
                            }
                        }
                    }
                }
            }
        }