[SOLVED] How do I programmatically create a user w...
# help-with-umbraco
t
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:
Copy code
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?
s
Something like this should do it.. Inject
_userManager
and
_globalSettings
for creating like so:
Copy code
csharp
            IdentityResult userCreated = await _userManager.CreateAsync(
                BackOfficeIdentityUser.CreateNew(
                    _globalSettings,
                    username: "username",
                    email: "email@provider.ext",
                    culture: "en-US",
                    name: "Person's Name"),
                password: "password"); // remember to meet password requirements
usings:
Copy code
csharp
    private readonly IBackOfficeUserManager _userManager;
    private readonly GlobalSettings _globalSettings;
t
@Sebastiaan 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.
s
Make it async! But otherwise.. cheat
Copy code
csharp
            IdentityResult userCreated = _userManager.CreateAsync(
                BackOfficeIdentityUser.CreateNew(
                    _globalSettings,
                    username: "username",
                    email: "email@provider.ext",
                    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.
t
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 😄 )
s
that's what I typed, note the NOT there 😉
t
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:
Copy code
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
s
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?
t
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: https://cdn.discordapp.com/attachments/1220389358566379603/1220409940263440456/image.png?ex=660ed653&is=65fc6153&hm=0a51e5a85d8b57c44607c97981464ae70fd76f4e9ef133fa8fff6470ddc2397d&
or actually.. seems this might be specifically related to the scoping
s
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
t
Should I not have access to the
IBackOfficeUserManager
at that point?
My constructor is doing:
Copy code
using IServiceScope scope = _serviceScopeFactory.CreateScope();
_userManager = scope.ServiceProvider.GetRequiredService<IBackOfficeUserManager>();
should perhaps create that inside the
Initialize
method instead in the startup component
s
Are you in an
IComposer
or what?
t
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. 💃 🙏
s
make sure to complete it!
Copy code
csharp
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
t
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;
👍
s
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 😁
t
@Sebastiaan 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:
Copy code
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:
Copy code
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);
        }
      }
    }
So in essence, if appsettings demands it we create a set of default users. The point is to have this set of users always exist on a certain type of deploys.
97 Views