Example #1
0
        /// <summary>
        /// Helper function.
        /// </summary>
        private async Task <EmbeddedIdentityServerUser> GetOrCreateUser(string email, bool emailConfirmed)
        {
            var idUser = await _userManager.FindByNameAsync(email) ??
                         await _userManager.FindByEmailAsync(email);

            if (idUser == null)
            {
                // Create the identity user if it doesn't exist
                idUser = new EmbeddedIdentityServerUser
                {
                    UserName       = email,
                    Email          = email,
                    EmailConfirmed = emailConfirmed
                };

                var result = await _userManager.CreateAsync(idUser);

                if (!result.Succeeded)
                {
                    string msg = string.Join(", ", result.Errors.Select(e => e.Description));
                    throw new InvalidOperationException(msg);
                }
            }
            return(idUser);
        }
Example #2
0
        private async Task LoadSharedKeyAndQrCodeUriAsync(EmbeddedIdentityServerUser user)
        {
            // Load the authenticator key & QR code URI to display on the form
            var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);

            if (string.IsNullOrEmpty(unformattedKey))
            {
                await _userManager.ResetAuthenticatorKeyAsync(user);

                unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
            }

            SharedKey = FormatKey(unformattedKey);

            var email = await _userManager.GetEmailAsync(user);

            AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
        }
Example #3
0
        private async Task <(string Subject, string Body)> MakeInvitationEmailAsync(EmbeddedIdentityServerUser identityRecipient, string name, string name2, string name3, string preferredLang)
        {
            // Load the info
            var info = await _appRepo.GetTenantInfoAsync();

            // Use the recipient's preferred Language
            CultureInfo culture = string.IsNullOrWhiteSpace(preferredLang) ?
                                  CultureInfo.CurrentUICulture : new CultureInfo(preferredLang);
            var localizer = _localizer.WithCulture(culture);

            // Prepare the parameters
            string userId     = identityRecipient.Id;
            string emailToken = await _userManager.GenerateEmailConfirmationTokenAsync(identityRecipient);

            string passwordToken = await _userManager.GeneratePasswordResetTokenAsync(identityRecipient);

            string nameOfInvitor =
                info.SecondaryLanguageId == culture.Name ? info.ShortCompanyName2 ?? info.ShortCompanyName :
                info.TernaryLanguageId == culture.Name ? info.ShortCompanyName3 ?? info.ShortCompanyName :
                info.ShortCompanyName;

            string nameOfRecipient =
                info.SecondaryLanguageId == name ? name2 ?? name :
                info.TernaryLanguageId == name ? name3 ?? name :
                name;

            string callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { userId, code = emailToken, passwordCode = passwordToken, area = "Identity" },
                protocol: Request.Scheme);

            // Prepare the email
            string emailSubject = localizer["InvitationEmailSubject0", localizer["AppName"]];
            string emailBody    = _emailTemplates.MakeInvitationEmail(
                nameOfRecipient: nameOfRecipient,
                nameOfInvitor: nameOfInvitor,
                validityInDays: Constants.TokenExpiryInDays,
                userId: userId,
                callbackUrl: callbackUrl,
                culture: culture);

            return(emailSubject, emailBody);
        }
Example #4
0
        /// <summary>
        /// Database initialization is performed here, after the web host is configured but before it is run
        /// this way the initialization has access to environment variables in configuration providers, but it
        /// only runs once when the web app loads
        /// </summary>
        public static void InitDatabase(IServiceProvider provider)
        {
            // If missing, the default admin user is added here
            using var scope = provider.CreateScope();

            // (1) Retrieve the admin credentials from configurations
            var    opt      = scope.ServiceProvider.GetRequiredService <IOptions <GlobalOptions> >().Value;
            string email    = opt?.Admin?.Email ?? "*****@*****.**";
            string fullName = opt?.Admin?.FullName ?? "Administrator";
            string password = opt?.Admin?.Password ?? "Admin@123";

            // (2) Create the user in the admin database
            var adminRepo = scope.ServiceProvider.GetRequiredService <AdminRepository>();

            adminRepo.AdminUsers__CreateAdmin(email, fullName, password).Wait();

            // (3) Create the user in the embedded identity server (if enabled)
            if (opt.EmbeddedIdentityServerEnabled)
            {
                var userManager = scope.ServiceProvider.GetRequiredService <UserManager <EmbeddedIdentityServerUser> >();
                var admin       = userManager.FindByEmailAsync(email).GetAwaiter().GetResult();

                if (admin == null)
                {
                    admin = new EmbeddedIdentityServerUser
                    {
                        UserName       = email,
                        Email          = email,
                        EmailConfirmed = true
                    };

                    var result = userManager.CreateAsync(admin, password).GetAwaiter().GetResult();
                    if (!result.Succeeded)
                    {
                        string msg = string.Join(", ", result.Errors.Select(e => e.Description));
                        throw new Exception($"Failed to create the administrator account. Message: {msg}");
                    }
                }
            }
        }
Example #5
0
        protected override async Task <List <int> > SaveExecuteAsync(List <UserForSave> entities, ExpandExpression expand, bool returnIds)
        {
            // NOTE: this method is not optimized for massive bulk (e.g. 1,000+ users), since it relies
            // on querying identity through UserManager one email at a time but it should be acceptable
            // with the usual workloads, customers with more than 200 users are rare anyways

            // Step (1) enlist the app repo
            _appRepo.EnlistTransaction(Transaction.Current); // So that it is not affected by admin trx scope later

            // Step (2): If Embedded Identity Server is enabled, create any emails that don't already exist there
            var usersToInvite = new List <(EmbeddedIdentityServerUser IdUser, UserForSave User)>();

            if (_options.EmbeddedIdentityServerEnabled)
            {
                _identityTrxScope = ControllerUtilities.CreateTransaction(TransactionScopeOption.RequiresNew);

                foreach (var entity in entities)
                {
                    var email = entity.Email;

                    // In case the user was added in a previous failed transaction
                    // or something, we always try to be forgiving in the code
                    var identityUser = await _userManager.FindByNameAsync(email) ??
                                       await _userManager.FindByEmailAsync(email);

                    // This is truly a new user, create it
                    if (identityUser == null)
                    {
                        // Create the identity user
                        identityUser = new EmbeddedIdentityServerUser
                        {
                            UserName       = email,
                            Email          = email,
                            EmailConfirmed = !_options.EmailEnabled

                                             // Note: If the system is integrated with an email service, user emails
                                             // are automatically confirmed, otherwise users must confirm their
                        };

                        var result = await _userManager.CreateAsync(identityUser);

                        if (!result.Succeeded)
                        {
                            string msg = string.Join(", ", result.Errors.Select(e => e.Description));
                            _logger.LogError(msg);

                            throw new BadRequestException($"An unexpected error occurred while creating an account for '{email}'");
                        }
                    }

                    // Mark for invitation later
                    if (!identityUser.EmailConfirmed)
                    {
                        usersToInvite.Add((identityUser, entity));
                    }
                }
            }

            // Step (3): Extract the images
            var(blobsToDelete, blobsToSave, imageIds) = await ImageUtilities.ExtractImages <User, UserForSave>(_appRepo, entities, BlobName);

            // Step (4): Save the users in the app database
            var ids = await _appRepo.Users__Save(entities, imageIds, returnIds);

            // Step (5): Delete old images from the blob storage
            if (blobsToDelete.Any())
            {
                await _blobService.DeleteBlobsAsync(blobsToDelete);
            }

            // Step (6): Save new images to the blob storage
            if (blobsToSave.Any())
            {
                await _blobService.SaveBlobsAsync(blobsToSave);
            }

            // Step (7) Same the emails in the admin database
            var tenantId = _tenantIdAccessor.GetTenantId();

            _adminTrxScope = ControllerUtilities.CreateTransaction(TransactionScopeOption.RequiresNew);
            _adminRepo.EnlistTransaction(Transaction.Current);
            var oldEmails = new List <string>(); // Emails are readonly after the first save
            var newEmails = entities.Where(e => e.Id == 0).Select(e => e.Email);
            await _adminRepo.GlobalUsers__Save(newEmails, oldEmails, tenantId);

            // Step (8): Send the invitation emails
            if (usersToInvite.Any()) // This will be empty if embedded identity is disabled or if email is disabled
            {
                var userIds       = usersToInvite.Select(e => e.User.Id).ToArray();
                var tos           = new List <string>();
                var subjects      = new List <string>();
                var substitutions = new List <Dictionary <string, string> >();
                foreach (var(idUser, user) in usersToInvite)
                {
                    // Add the email sender parameters
                    var(subject, body) = await MakeInvitationEmailAsync(idUser, user.Name, user.Name2, user.Name3, user.PreferredLanguage);

                    tos.Add(idUser.Email);
                    subjects.Add(subject);
                    substitutions.Add(new Dictionary <string, string> {
                        { "-message-", body }
                    });
                }

                await _emailSender.SendEmailBulkAsync(
                    tos : tos,
                    subjects : subjects,
                    htmlMessage : $"-message-",
                    substitutions : substitutions.ToList()
                    );
            }

            // Return the new Ids
            return(ids);
        }