#[SOLVED] How do I programmatically create a user with a password?

1 messages ยท Page 1 of 1 (latest)

stable light
#

I am trying to programmatically create a user but I'm running into problems hashing the password. I have no idea if it's Umbraco or standard .NET core that should handle this.

My code looks like this:

private void CreateUserIfNotExist(string email, string password, string? name)
    {
      var user = _userService.GetByEmail(email);
      if (user != null)
      {
        return;
      }

      var newUser = _userService.CreateUserWithIdentity(name ?? email, email);
      var userGroup = _userService.GetUserGroupByAlias("editors") as IReadOnlyUserGroup;

      if (userGroup != null)
      {
        newUser.AddGroup(userGroup);
        newUser.RawPasswordValue = ???? // how to hash password?
        _userService.Save(newUser);
      }

    }

How can I figure out the RawPasswordValue? I've tried 5 different ways of dependency inject a password hasher, but I can't find a way to type it to <IUser> which is what CreateUserWithIdentity returns.

The documentation has a page that brings up my hope:
https://docs.umbraco.com/umbraco-cms/reference/management/services/userservice/create-a-new-user

But it has no example shown at all, broken documentation perhaps?

This will show you how to create a new user using the UserService in Umbraco.

slim field
#

Something like this should do it..
Inject _userManager and _globalSettings for creating like so:

            IdentityResult userCreated = await _userManager.CreateAsync(
                BackOfficeIdentityUser.CreateNew(
                    _globalSettings,
                    username: "username",
                    email: "[email protected]",
                    culture: "en-US",
                    name: "Person's Name"),
                password: "password"); // remember to meet password requirements

usings:

    private readonly IBackOfficeUserManager _userManager;
    private readonly GlobalSettings _globalSettings;
stable light
#

@slim field This looks great!
I am doing this in a startupcomponent which is not async. I can probably with trial and error make that async somehow, but I wonder should that instead be a scheduled task then by definition?

The usermanager does not seem to have sync methods, only async.

slim field
#

Make it async! But otherwise.. cheat

            IdentityResult userCreated = _userManager.CreateAsync(
                BackOfficeIdentityUser.CreateNew(
                    _globalSettings,
                    username: "username",
                    email: "[email protected]",
                    culture: "en-US",
                    name: "Person's Name"),
                password: "password").Result; 
#

if you're doing this on startup and do NOT make it async, you'll delay your startup by however many users the app needs to wait for to be created.

stable light
#

Double checking if you mistyped there? My intuition says it should not delay if it's async, and should delay if it's synchronous?
(I'm probably wrong tho ๐Ÿ˜„ )

slim field
#

that's what I typed, note the NOT there ๐Ÿ˜‰

stable light
#

aaah yes! I did note that, but my eyes skipped the "and" haha

#

Many thanks for this!

#

Hmm, I'm hitting an issue with lifetimes:

Cannot consume scoped service 'Umbraco.Cms.Core.Security.IBackOfficeUserManager' from singleton 'cms.PrepopulateUsersComponent'

Can I only consume IBackOfficeUserManager from non singletons, or is there a workaround?

#

I can perhaps create a scope with _serviceScopeFactory

slim field
#

yeah try that.. you need to get to GetRequiredService so you can do GetRequiredService<IBackOfficeUserManager> and one for globalsettings.. it's not very easy, I'm just googling this too lol

#

does it absolutely need to happen on startup?

stable light
#

No, but it's an ephemeral environment that will have a small set of specific test users which needs to be created on startup or close to startup.

I think I have that part working correctly, with the service scoping, but I ran into an issue with the .Result cheat:

#

or actually.. seems this might be specifically related to the scoping

slim field
#

yeah, make sure to add a using

#

not sure how you got there though, I don't think you have a lot of access to anything while umbraco is booting

stable light
#

Should I not have access to the IBackOfficeUserManager at that point?

#

My constructor is doing:

using IServiceScope scope = _serviceScopeFactory.CreateScope();
_userManager = scope.ServiceProvider.GetRequiredService<IBackOfficeUserManager>();
#

should perhaps create that inside the Initialize method instead in the startup component

slim field
#

Are you in an IComposer or what?

stable light
#

IComponent

#

It worked!

#

I just create a new scope inside Initialize and keep reusing it for the user creation. Seems to work just fine from what I can tell. ๐Ÿ’ƒ

๐Ÿ™

slim field
#

make sure to complete it!

using IScope scope = _scopeProvider.CreateScope();

// Existing code

scope.Complete();
#

but if you're in a Component you can inject, you don't need to do GetRequiredService right?

#

I can't get mine to work.. lol

stable light
#

This is a bit above my skill currently so I have no idea how the scoping works to be honest, but it did complain about the scoping as if the component is a singleton.

I'll make sure to complete the scope as well!

All the users I create are Disabled for some reason ๐Ÿ˜ฌ

#

I am using an IServiceScope instead of an IScope currently, I suppose the .Dispose() method is equivalent

#

u.IsApproved = true; ๐Ÿ‘

#

[SOLVED] How do I programmatically create a user with a password?

slim field
#

Great that it's solved! Yeah you don't need to dispose any more in later C# versions when adding a using.

Could you do me a favor and show me a a very condensed version of what you've done please? I couldn't figure out yesterday how to make it run, so now I'm curious ๐Ÿ˜

stable light
#

@slim field sorry I missed your question, I'm on paternal leave 80% so I'm very in-and-out ๐Ÿ™‚

In appsettings I have a key PrepopulateUsers with an array of Name, Email and Passwords.

In a RegisterStartupComponents I check if this key is populated, in that case a PrepopulateUsersComponent is ran.

Initialize method of that component:

public void Initialize()
    {
      var userListRequest = _config.GetSection("PrepopulateUsers").GetChildren();


      using IServiceScope scope = _serviceScopeFactory.CreateScope();
      var userManager = scope.ServiceProvider.GetRequiredService<IBackOfficeUserManager>();

      foreach (var userRequest in userListRequest)
      {
        var name = userRequest["name"];
        var email = userRequest["email"];
        var password = userRequest["password"];

        if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
        {
          continue;
        }

        CreateUserIfNotExist(userManager, email, password, name);
      }

      scope.Dispose();
    }
#

And that method, Create if not exists:

private void CreateUserIfNotExist(IBackOfficeUserManager userManager, string email, string password, string? name)
    {
      var userGroup = _userService.GetUserGroupByAlias("editor") as IReadOnlyUserGroup;

      if (userGroup != null)
      {
        var existingUser = _userService.GetByEmail(email);
        if (existingUser != null)
        {
          return;
        }

        var identity = BackOfficeIdentityUser.CreateNew(
            _globalSettings,
            username: email,
            email: email,
            culture: "sv-SE",
            name: name
        );

        var userCreated = userManager.CreateAsync(identity, password: password).Result;

        var user = _userService.GetByEmail(email);

        if (user != null)
        {
          // Add the user to the group
          user.AddGroup(userGroup);
          user.IsApproved = true;
          _userService.Save(user);
        }
      }
    }