Example #1
0
        private bool UserExistsInLdapDirectory(LdapSettings ldapSettings, LoginInfo loginInfo)
        {
            var userName = loginInfo.UserName != null?loginInfo.UserName.Trim() : loginInfo.Login;

            var filter = I18NHelper.FormatInvariant("(&(objectCategory={0})({1}={2}))", ldapSettings.GetEffectiveUserObjectCategoryAttribute(), ldapSettings.GetEffectiveAccountNameAttribute(), LdapHelper.EscapeLdapSearchFilter(userName));

            try
            {
                var found = _authenticator.SearchLdap(ldapSettings, filter);

                if (found)
                {
                    return(true);
                }

                _log.LogInformation(LogSourceLdap, I18NHelper.FormatInvariant("User '{0}' is not found in LDAP directory {1}", userName, ldapSettings.LdapAuthenticationUrl));

                return(false);
            }
            catch (Exception ex)
            {
                _log.LogInformation(LogSourceLdap, I18NHelper.FormatInvariant("Error while searching a user in LDAP directory {0}", ldapSettings.LdapAuthenticationUrl));
                _log.LogError(LogSourceLdap, ex);

                return(false);
            }
        }
Example #2
0
        public async Task <AuthenticationUser> AuthenticateUserAsync(string login, string password, bool ignoreInvalidLoginAttempts)
        {
            if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password))
            {
                throw new AuthenticationException("Username and password cannot be empty", ErrorCodes.EmptyCredentials);
            }

            var user = await _userRepository.GetUserByLoginAsync(login);

            if (user == null)
            {
                await _log.LogInformation(WebApiConfig.LogSourceSessions, I18NHelper.FormatInvariant("Could not get user with login '{0}'", login));

                throw new AuthenticationException("Invalid username or password", ErrorCodes.InvalidCredentials);
            }

            var instanceSettings = await _settingsRepository.GetInstanceSettingsAsync();

            if (instanceSettings.IsSamlEnabled.GetValueOrDefault())
            {
                // Fallback is allowed by default (value is null)
                if (!user.IsFallbackAllowed.GetValueOrDefault(true))
                {
                    throw new AuthenticationException("User must be authenticated via Federated Authentication mechanism", ErrorCodes.FallbackIsDisabled);
                }
            }

            AuthenticationStatus authenticationStatus;

            switch (user.Source)
            {
            case UserGroupSource.Database:
                authenticationStatus = AuthenticateDatabaseUser(user, password, instanceSettings.PasswordExpirationInDays);
                break;

            case UserGroupSource.Windows:
                if (!instanceSettings.IsLdapIntegrationEnabled)
                {
                    throw new AuthenticationException(string.Format("To authenticate user with login: {0}, ldap integration must be enabled", login), ErrorCodes.LdapIsDisabled);
                }
                authenticationStatus = await _ldapRepository.AuthenticateLdapUserAsync(login, password, instanceSettings.UseDefaultConnection);

                break;

            default:
                throw new AuthenticationException(string.Format("Authentication provider could not be found for login: {0}", login));
            }

            await ProcessAuthenticationStatus(authenticationStatus, user, instanceSettings, ignoreInvalidLoginAttempts);

            user.LicenseType = await _userRepository.GetEffectiveUserLicenseAsync(user.Id);

            return(user);
        }
Example #3
0
        internal Task LoadAsync()
        {
            return(Task.Run(async() =>
            {
                try
                {
                    await _log.LogInformation(WebApiConfig.LogSourceSessions, "Service starting...");

                    var ps = 1000;
                    var pn = 1;
                    int count;
                    var expiredSessions = new List <Session>();

                    do
                    {
                        count = 0;
                        var sessions = await _repo.SelectSessions(ps, pn);

                        foreach (var session in sessions)
                        {
                            ++count;

                            if (session.IsExpired())
                            {
                                expiredSessions.Add(session);
                            }
                            else
                            {
                                InsertSession(session);
                            }
                        }

                        ++pn;
                    }while (count == ps);

                    // Mark expired sessions as trully expired in the DB
                    foreach (var session in expiredSessions)
                    {
                        await _repo.EndSession(session.SessionId, session.EndTime);
                    }
                    var logExpiredSessionsCount = expiredSessions.Count > 0 ? $" {expiredSessions.Count} sessions expired" : string.Empty;

                    await _log.LogInformation(WebApiConfig.LogSourceSessions, $"Service started.{logExpiredSessionsCount}");
                }
                catch (Exception ex)
                {
                    await _log.LogError(WebApiConfig.LogSourceSessions,
                                        new Exception("Error loading sessions from database.", ex));
                }
            }));
        }
Example #4
0
        public static async Task ProcessMessages(string logSource,
                                                 TenantInformation tenant,
                                                 IServiceLogRepository serviceLogRepository,
                                                 IList <IWorkflowMessage> messages,
                                                 string exceptionMessagePrepender,
                                                 IWorkflowMessagingProcessor workflowMessagingProcessor)
        {
            if (messages == null || messages.Count <= 0)
            {
                return;
            }

            var processor = workflowMessagingProcessor ?? WorkflowMessagingProcessor.Instance;

            foreach (var actionMessage in messages.Where(a => a != null))
            {
                try
                {
                    await ActionMessageSender.Send((ActionMessage)actionMessage, tenant, processor);

                    string message = $"Sent {actionMessage.ActionType} message: {actionMessage.ToJSON()} with tenant id: {tenant.TenantId} to the Message queue";
                    await
                    serviceLogRepository.LogInformation(logSource, message);
                }
                catch (Exception ex)
                {
                    string message =
                        $"Error while sending {actionMessage.ActionType} message with content {actionMessage.ToJSON()}. {exceptionMessagePrepender}. Exception: {ex.Message}. StackTrace: {ex.StackTrace ?? string.Empty}";
                    await
                    serviceLogRepository.LogError(logSource, message);

                    throw;
                }
            }
        }
Example #5
0
        public async Task <IHttpActionResult> PostSession(string login, [FromBody] string password, bool force = false)
        {
            try
            {
                var decodedLogin    = SystemEncryptions.Decode(login);
                var decodedPassword = SystemEncryptions.Decode(password);
                var user            = await _authenticationRepository.AuthenticateUserAsync(decodedLogin, decodedPassword, false);

                return(await RequestSessionTokenAsync(user, force));
            }
            catch (AuthenticationException ex)
            {
                await _log.LogInformation(WebApiConfig.LogSourceSessions, ex.Message);

                throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.CreateHttpError()));
            }
            catch (ApplicationException ex)
            {
                await _log.LogInformation(WebApiConfig.LogSourceSessions, ex.Message);

                return(Conflict());
            }
            catch (ArgumentNullException ex)
            {
                await _log.LogInformation(WebApiConfig.LogSourceSessions, ex.Message);

                return(BadRequest());
            }
            catch (FormatException ex)
            {
                await _log.LogInformation(WebApiConfig.LogSourceSessions, ex.Message);

                throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message));
            }
            catch (Exception ex)
            {
                await _log.LogError(WebApiConfig.LogSourceSessions, ex);

                return(InternalServerError());
            }
        }
        public async Task ProcessMessages(string logSource,
                                          IApplicationSettingsRepository applicationSettingsRepository,
                                          IServiceLogRepository serviceLogRepository,
                                          IList <IWorkflowMessage> messages,
                                          string exceptionMessagePrepender,
                                          IDbTransaction transaction = null)
        {
            var tenantInfo = await applicationSettingsRepository.GetTenantInfo(transaction);

            if (string.IsNullOrWhiteSpace(tenantInfo?.TenantId))
            {
                throw new TenantInfoNotFoundException("No tenant information found. Please contact your administrator.");
            }

            if (messages == null || messages.Count <= 0)
            {
                return;
            }
            foreach (var actionMessage in messages.Where(a => a != null))
            {
                try
                {
                    await WorkflowMessagingProcessor.Instance.SendMessageAsync(tenantInfo.TenantId, actionMessage);

                    string message = $"Sent {actionMessage.ActionType} message: {actionMessage.ToJSON()} with tenant id: {tenantInfo.TenantId} to the Message queue";
                    await
                    serviceLogRepository.LogInformation(logSource, message);
                }
                catch (Exception ex)
                {
                    string message =
                        $"Error while sending {actionMessage.ActionType} message with content {actionMessage.ToJSON()}. {exceptionMessagePrepender}. Exception: {ex.Message}. StackTrace: {ex.StackTrace ?? string.Empty}";
                    await
                    serviceLogRepository.LogError(logSource, message);

                    throw;
                }
            }
        }
        public async Task Execute(IApplicationSettingsRepository applicationSettingsRepository, IServiceLogRepository serviceLogRepository, ActionMessage message, IDbTransaction transaction)
        {
            var tenantInfo = await applicationSettingsRepository.GetTenantInfo(transaction);

            var tenantId = tenantInfo?.TenantId;

            try
            {
                if (string.IsNullOrWhiteSpace(tenantId))
                {
                    throw new TenantInfoNotFoundException("No tenant information found. Please contact your administrator.");
                }
                await WorkflowMessagingProcessor.Instance.SendMessageAsync(tenantId, message);

                await serviceLogRepository.LogInformation("SendMessageExecutor", $"Sent {message.ActionType} message for tenant {tenantId}: {message.ToJSON()}");
            }
            catch (Exception ex)
            {
                await serviceLogRepository.LogError("SendMessageExecutor", $"Failed to send {message.ActionType} message for tenant {tenantId}: {message.ToJSON()}. Exception: {ex.Message}");

                throw;
            }
        }
Example #8
0
        public static async Task <IList <IWorkflowMessage> > GenerateMessages(int userId,
                                                                              int revisionId,
                                                                              string userName,
                                                                              long transactionId,
                                                                              WorkflowEventTriggers postOpTriggers,
                                                                              IBaseArtifactVersionControlInfo artifactInfo,
                                                                              string projectName,
                                                                              IDictionary <int, IList <Property> > modifiedProperties,
                                                                              WorkflowState currentState,
                                                                              string artifactUrl,
                                                                              string baseUrl,
                                                                              IEnumerable <int> ancestorArtifactTypeIds,
                                                                              IUsersRepository usersRepository,
                                                                              IServiceLogRepository serviceLogRepository,
                                                                              IWebhooksRepository webhooksRepository,
                                                                              IProjectMetaRepository projectMetaRepository)
        {
            var resultMessages = new List <IWorkflowMessage>();
            var baseHostUri    = baseUrl ?? ServerUriHelper.GetBaseHostUri()?.ToString();

            foreach (var workflowEventTrigger in postOpTriggers)
            {
                if (workflowEventTrigger?.Action == null)
                {
                    continue;
                }
                switch (workflowEventTrigger.ActionType)
                {
                case MessageActionType.Notification:
                    var notificationAction = workflowEventTrigger.Action as EmailNotificationAction;
                    if (notificationAction == null)
                    {
                        continue;
                    }
                    var notificationMessage = await GetNotificationMessage(userId,
                                                                           revisionId,
                                                                           transactionId,
                                                                           artifactInfo,
                                                                           projectName,
                                                                           notificationAction,
                                                                           artifactUrl,
                                                                           baseHostUri,
                                                                           usersRepository);

                    if (notificationMessage == null)
                    {
                        await serviceLogRepository.LogInformation(LogSource, $"Skipping Email notification action for artifact {artifactInfo.Id}");

                        Log.Debug($" Skipping Email notification action for artifact {artifactInfo.Id}. Message: Notification.");
                        continue;
                    }
                    resultMessages.Add(notificationMessage);
                    break;

                case MessageActionType.GenerateChildren:
                    var generateChildrenAction = workflowEventTrigger.Action as GenerateChildrenAction;
                    if (generateChildrenAction == null)
                    {
                        continue;
                    }
                    var ancestors = new List <int>(ancestorArtifactTypeIds ?? new int[0]);
                    ancestors.Add(artifactInfo.ItemTypeId);
                    var generateChildrenMessage = new GenerateDescendantsMessage
                    {
                        TransactionId           = transactionId,
                        ChildCount              = generateChildrenAction.ChildCount.GetValueOrDefault(10),
                        DesiredArtifactTypeId   = generateChildrenAction.ArtifactTypeId,
                        ArtifactId              = artifactInfo.Id,
                        AncestorArtifactTypeIds = ancestors,
                        RevisionId              = revisionId,
                        UserId         = userId,
                        ProjectId      = artifactInfo.ProjectId,
                        UserName       = userName,
                        BaseHostUri    = baseHostUri,
                        ProjectName    = projectName,
                        TypePredefined = (int)artifactInfo.PredefinedType
                    };
                    resultMessages.Add(generateChildrenMessage);
                    break;

                case MessageActionType.GenerateTests:
                    var generateTestsAction = workflowEventTrigger.Action as GenerateTestCasesAction;
                    if (generateTestsAction == null || artifactInfo.PredefinedType != ItemTypePredefined.Process)
                    {
                        await serviceLogRepository.LogInformation(LogSource, $"Skipping GenerateTestCasesAction for artifact {artifactInfo.Id} as it is not a process");

                        Log.Debug($"Skipping GenerateTestCasesAction for artifact {artifactInfo.Id} as it is not a process. Message: Notification.");
                        continue;
                    }
                    var generateTestsMessage = new GenerateTestsMessage
                    {
                        TransactionId = transactionId,
                        ArtifactId    = artifactInfo.Id,
                        RevisionId    = revisionId,
                        UserId        = userId,
                        ProjectId     = artifactInfo.ProjectId,
                        UserName      = userName,
                        BaseHostUri   = baseHostUri,
                        ProjectName   = projectName
                    };
                    resultMessages.Add(generateTestsMessage);
                    break;

                case MessageActionType.GenerateUserStories:
                    var generateUserStories = workflowEventTrigger.Action as GenerateUserStoriesAction;
                    if (generateUserStories == null || artifactInfo.PredefinedType != ItemTypePredefined.Process)
                    {
                        await serviceLogRepository.LogInformation(LogSource, $"Skipping GenerateUserStories for artifact {artifactInfo.Id} as it is not a process");

                        Log.Debug($"Skipping GenerateUserStories for artifact {artifactInfo.Id} as it is not a process. Message: Notification.");
                        continue;
                    }
                    var generateUserStoriesMessage = new GenerateUserStoriesMessage
                    {
                        TransactionId = transactionId,
                        ArtifactId    = artifactInfo.Id,
                        RevisionId    = revisionId,
                        UserId        = userId,
                        ProjectId     = artifactInfo.ProjectId,
                        UserName      = userName,
                        BaseHostUri   = baseHostUri,
                        ProjectName   = projectName
                    };
                    resultMessages.Add(generateUserStoriesMessage);
                    break;

                case MessageActionType.Webhooks:
                    var webhookAction = workflowEventTrigger.Action as WebhookAction;
                    if (webhookAction == null)
                    {
                        continue;
                    }

                    var customTypes = await projectMetaRepository.GetCustomProjectTypesAsync(artifactInfo.ProjectId, userId);

                    var artifactType = customTypes.ArtifactTypes.FirstOrDefault(at => at.Id == artifactInfo.ItemTypeId);

                    var artifactPropertyInfos = await webhooksRepository.GetArtifactsWithPropertyValuesAsync(
                        userId,
                        new List <int> {
                        artifactInfo.Id
                    },
                        new List <int>
                    {
                        (int)PropertyTypePredefined.Name,
                        (int)PropertyTypePredefined.Description,
                        (int)PropertyTypePredefined.ID,
                        (int)PropertyTypePredefined.CreatedBy,
                        (int)PropertyTypePredefined.LastEditedOn,
                        (int)PropertyTypePredefined.LastEditedBy,
                        (int)PropertyTypePredefined.CreatedOn
                    },
                        artifactType.CustomPropertyTypeIds);

                    var webhookArtifactInfo = new WebhookArtifactInfo
                    {
                        Id          = Guid.NewGuid().ToString(),
                        EventType   = WebhookEventType,
                        PublisherId = WebhookPublisherId,
                        Scope       = new WebhookArtifactInfoScope
                        {
                            Type       = WebhookType,
                            WorkflowId = currentState.WorkflowId
                        },
                        Resource = new WebhookResource
                        {
                            Name                 = artifactInfo.Name,
                            ProjectId            = artifactInfo.ProjectId,
                            ParentId             = ((WorkflowMessageArtifactInfo)artifactInfo).ParentId,
                            ArtifactTypeId       = artifactInfo.ItemTypeId,
                            ArtifactTypeName     = artifactType?.Name,
                            BaseArtifactType     = artifactType?.PredefinedType?.ToString(),
                            ArtifactPropertyInfo =
                                await ConvertToWebhookPropertyInfo(artifactPropertyInfos, customTypes.PropertyTypes, usersRepository),
                            State = new WebhookStateInfo
                            {
                                Id         = currentState.Id,
                                Name       = currentState.Name,
                                WorkflowId = currentState.WorkflowId
                            },
                            Revision     = revisionId,
                            Version      = WebhookArtifactVersion,
                            Id           = artifactInfo.Id,
                            BlueprintUrl = string.Format($"{baseHostUri}?ArtifactId={artifactInfo.Id}"),
                            Link         = string.Format($"{baseHostUri}api/v1/projects/{artifactInfo.ProjectId}/artifacts/{artifactInfo.Id}")
                        }
                    };
                    var webhookMessage = await GetWebhookMessage(userId, revisionId, transactionId, webhookAction, webhooksRepository, webhookArtifactInfo);

                    if (webhookMessage == null)
                    {
                        await serviceLogRepository.LogInformation(LogSource, $"Skipping Webhook action for artifact {artifactInfo.Id}: {artifactInfo.Name}.");

                        continue;
                    }
                    resultMessages.Add(webhookMessage);
                    break;
                }
            }
            return(resultMessages);
        }
        internal static async Task <bool> ProcessCreatedArtifacts(TenantInformation tenant,
                                                                  ArtifactsPublishedMessage message,
                                                                  IArtifactsPublishedRepository repository,
                                                                  IServiceLogRepository serviceLogRepository,
                                                                  IWorkflowMessagingProcessor messageProcessor,
                                                                  int transactionCommitWaitTimeInMilliSeconds = 60000)
        {
            var createdArtifacts = message?.Artifacts?.Where(p => p.IsFirstTimePublished && repository.WorkflowRepository.IsWorkflowSupported((ItemTypePredefined)p.Predefined)).ToList();

            if (createdArtifacts == null || createdArtifacts.Count <= 0)
            {
                Logger.Log("No created artifacts found", message, tenant);
                return(false);
            }
            Logger.Log($"{createdArtifacts.Count} created artifacts found", message, tenant);

            var artifactIds   = createdArtifacts.Select(a => a.Id).ToHashSet();
            var artifactInfos = (await repository.WorkflowRepository.GetWorkflowMessageArtifactInfoAsync(message.UserId, artifactIds, message.RevisionId)).ToDictionary(k => k.Id);

            Logger.Log($"{artifactInfos.Count} artifact infos found", message, tenant);
            var notificationMessages = new Dictionary <int, List <IWorkflowMessage> >();

            foreach (var createdArtifact in createdArtifacts)
            {
                WorkflowMessageArtifactInfo artifactInfo;
                if (!artifactInfos.TryGetValue(createdArtifact.Id, out artifactInfo))
                {
                    await serviceLogRepository.LogInformation(LogSource, $"Could not recover information for artifact Id: {createdArtifact.Id} and Name: {createdArtifact.Name} and Project Id: {createdArtifact.ProjectId}");

                    Logger.Log($"Could not recover information for artifact Id: {createdArtifact.Id} and Name: {createdArtifact.Name} and Project Id: {createdArtifact.ProjectId}", message, tenant);
                    continue;
                }

                var eventTriggers = await repository.WorkflowRepository.GetWorkflowEventTriggersForNewArtifactEvent(message.UserId,
                                                                                                                    new[] { createdArtifact.Id },
                                                                                                                    message.RevisionId, true);

                if (eventTriggers?.AsynchronousTriggers == null ||
                    eventTriggers.AsynchronousTriggers.Count == 0)
                {
                    Logger.Log($"Found no async triggers for artifact with ID {createdArtifact.Id}", message, tenant);
                    continue;
                }
                Logger.Log($"Found {eventTriggers.AsynchronousTriggers.Count} async triggers for artifact with ID {createdArtifact.Id}", message, tenant);

                int artifactId = createdArtifact.Id;

                var currentState = await repository.WorkflowRepository.GetStateForArtifactAsync(message.UserId, createdArtifact.Id, int.MaxValue, true);

                var actionMessages = await WorkflowEventsMessagesHelper.GenerateMessages(message.UserId,
                                                                                         message.RevisionId,
                                                                                         message.UserName,
                                                                                         message.TransactionId,
                                                                                         eventTriggers.AsynchronousTriggers,
                                                                                         artifactInfo,
                                                                                         artifactInfo.ProjectName,
                                                                                         new Dictionary <int, IList <Property> >(),
                                                                                         currentState,
                                                                                         createdArtifact.Url,
                                                                                         createdArtifact.BaseUrl,
                                                                                         createdArtifact.AncestorArtifactTypeIds,
                                                                                         repository.UsersRepository,
                                                                                         serviceLogRepository,
                                                                                         repository.WebhooksRepository,
                                                                                         repository.ProjectMetaRepository);

                if (actionMessages == null || actionMessages.Count == 0)
                {
                    continue;
                }

                if (!notificationMessages.ContainsKey(artifactId))
                {
                    notificationMessages.Add(artifactId, new List <IWorkflowMessage>());
                }

                notificationMessages[artifactId].AddRange(actionMessages);
            }

            if (notificationMessages.Count == 0)
            {
                Logger.Log("None of the created artifacts have async triggers", message, tenant);
                return(false);
            }
            Logger.Log($"Sending async trigger messages for artifacts: {string.Join(", ", notificationMessages.Select(kvp => kvp.Key))}", message, tenant);

            foreach (var notificationMessage in notificationMessages.Where(m => m.Value != null))
            {
                await WorkflowEventsMessagesHelper.ProcessMessages(LogSource,
                                                                   tenant,
                                                                   serviceLogRepository,
                                                                   notificationMessage.Value,
                                                                   $"Error on new artifact creation with Id: {notificationMessage.Key}",
                                                                   messageProcessor);
            }

            return(true);
        }
Example #10
0
        public async Task <IHttpActionResult> PostRequestPasswordResetAsync([FromBody] string login)
        {
            try
            {
                var matchingSetting = await _applicationSettingsRepository.GetValue(IsPasswordRecoveryEnabledKey, false);

                if (!matchingSetting)
                {
                    await _log.LogInformation(WebApiConfig.LogSourceUsersPasswordReset, "Password recovery is disabled");

                    return(Conflict());
                }

                var instanceSettings = await _settingsRepository.GetInstanceSettingsAsync();

                if (instanceSettings?.EmailSettingsDeserialized?.HostName == null)
                {
                    await _log.LogInformation(WebApiConfig.LogSourceUsersPasswordReset, "Invalid instance email settings");

                    return(Conflict());
                }

                var user = await _userRepository.GetUserByLoginAsync(login);

                if (user == null)
                {
                    await _log.LogInformation(WebApiConfig.LogSourceUsersPasswordReset, "The user doesn't exist");

                    return(Conflict());
                }

                bool passwordResetAllowed = await _userRepository.CanUserResetPasswordAsync(login);

                if (!passwordResetAllowed)
                {
                    await _log.LogInformation(WebApiConfig.LogSourceUsersPasswordReset, "The user isn't allowed to reset the password");

                    return(Conflict());
                }

                bool passwordRequestLimitExceeded = await _userRepository.HasUserExceededPasswordRequestLimitAsync(login);

                if (passwordRequestLimitExceeded)
                {
                    await _log.LogInformation(WebApiConfig.LogSourceUsersPasswordReset, "Exceeded requests limit");

                    return(Conflict());
                }

                bool passwordResetCooldownInEffect = await _authenticationRepository.IsChangePasswordCooldownInEffect(user);

                if (passwordResetCooldownInEffect)
                {
                    await _log.LogInformation(WebApiConfig.LogSourceUsersPasswordReset, "Cooldown is in effect");

                    return(Conflict());
                }

                var recoveryToken = SystemEncryptions.CreateCryptographicallySecureGuid();
                var recoveryUrl   = new Uri(Request.RequestUri, ServiceConstants.ForgotPasswordResetUrl + "/" + recoveryToken).AbsoluteUri;

                // decrypt the password to be set in mailBee
                instanceSettings.EmailSettingsDeserialized.DecryptPassword();

                _emailHelper.Initialize(instanceSettings.EmailSettingsDeserialized);

                _emailHelper.SendEmail(user.Email, "Reset Password",
                                       $@"
                        <html>
                            <div>Hello {user.DisplayName}.</div>
                            <br>
                            <div>We have received a request to reset your password.</div>
                            <br>
                            <div>To confirm this password reset, visit the following link:</div>
                            <a href='{recoveryUrl}'>Reset password</a>
                            <br><br>
                            <div>If you did not make this request, you can ignore this email, and no changes will be made.</div>
                            <br>
                            <div>If you have any questions, please contact your administrator. </div>
                        </html>");

                await _userRepository.UpdatePasswordRecoveryTokensAsync(login, recoveryToken);

                return(Ok());
            }
            catch (EmailException ex)
            {
                await _log.LogError(WebApiConfig.LogSourceUsersPasswordReset, ex);

                return(Conflict());
            }
            catch (Exception ex)
            {
                await _log.LogError(WebApiConfig.LogSourceUsersPasswordReset, ex);

                return(InternalServerError());
            }
        }