From: Dan Brown Date: Tue, 5 Aug 2025 12:39:30 +0000 (+0100) Subject: Commands: Updated create admin comment to accept extra flags X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/a961552c23101bcf260aaf930a8362277852f67b Commands: Updated create admin comment to accept extra flags Added flags to target changes to the first default admin user, and to generate a password. This is related to #4575. --- diff --git a/app/Console/Commands/CreateAdminCommand.php b/app/Console/Commands/CreateAdminCommand.php index 82b6e50aa..ce619e05d 100644 --- a/app/Console/Commands/CreateAdminCommand.php +++ b/app/Console/Commands/CreateAdminCommand.php @@ -21,7 +21,9 @@ class CreateAdminCommand extends Command {--email= : The email address for the new admin user} {--name= : The name of the new admin user} {--password= : The password to assign to the new admin user} - {--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}'; + {--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)} + {--generate-password : Generate a random password for the new admin user} + {--first-admin : Indicate if this should set/update the details of the initial admin user}'; /** * The console command description. @@ -35,23 +37,9 @@ class CreateAdminCommand extends Command */ public function handle(UserRepo $userRepo): int { - $details = $this->snakeCaseOptions(); - - if (empty($details['email'])) { - $details['email'] = $this->ask('Please specify an email address for the new admin user'); - } - - if (empty($details['name'])) { - $details['name'] = $this->ask('Please specify a name for the new admin user'); - } - - if (empty($details['password'])) { - if (empty($details['external_auth_id'])) { - $details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)'); - } else { - $details['password'] = Str::random(32); - } - } + $firstAdminOnly = $this->option('first-admin'); + $shouldGeneratePassword = $this->option('generate-password'); + $details = $this->gatherDetails($shouldGeneratePassword); $validator = Validator::make($details, [ 'email' => ['required', 'email', 'min:5', new Unique('users', 'email')], @@ -68,16 +56,81 @@ class CreateAdminCommand extends Command return 1; } + $adminRole = Role::getSystemRole('admin'); + + if ($firstAdminOnly) { + $handled = $this->handleFirstAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole); + if ($handled) { + return 0; + } + } + $user = $userRepo->createWithoutActivity($validator->validated()); - $user->attachRole(Role::getSystemRole('admin')); + $user->attachRole($adminRole); $user->email_confirmed = true; $user->save(); - $this->info("Admin account with email \"{$user->email}\" successfully created!"); + if ($shouldGeneratePassword) { + $this->line($details['password']); + } else { + $this->info("Admin account with email \"{$user->email}\" successfully created!"); + } return 0; } + /** + * Handle updates to the first admin if exists. + * Returns true if the action has been handled (user updated or already a non-default admin user) otherwise + * returns false if no action has been taken, and we therefore need to proceed with a normal account creation. + */ + protected function handleFirstAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): bool + { + $defaultAdmin = $userRepo->getByEmail('admin@admin.com'); + if ($defaultAdmin && $defaultAdmin->hasSystemRole('admin')) { + $userRepo->updateWithoutActivity($defaultAdmin, $data, true); + if ($generatePassword) { + $this->line($data['password']); + } else { + $this->info("The default admin user has been updated with the provided details!"); + } + + return true; + } else if ($adminRole->users()->count() > 0) { + $this->warn('Non-default admin user already exists. Skipping creation of new admin user.'); + return true; + } + + return false; + } + + protected function gatherDetails(bool $generatePassword): array + { + $details = $this->snakeCaseOptions(); + + if (empty($details['email'])) { + $details['email'] = $this->ask('Please specify an email address for the new admin user'); + } + + if (empty($details['name'])) { + $details['name'] = $this->ask('Please specify a name for the new admin user'); + } + + if (empty($details['password'])) { + if (empty($details['external_auth_id'])) { + if ($generatePassword) { + $details['password'] = Str::random(32); + } else { + $details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)'); + } + } else { + $details['password'] = Str::random(32); + } + } + + return $details; + } + protected function snakeCaseOptions(): array { $returnOpts = []; diff --git a/app/Users/UserRepo.php b/app/Users/UserRepo.php index 5c8ace8fa..d24f7002e 100644 --- a/app/Users/UserRepo.php +++ b/app/Users/UserRepo.php @@ -100,13 +100,13 @@ class UserRepo } /** - * Update the given user with the given data. + * Update the given user with the given data, but do not create an activity. * * @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array, language: ?string} $data * * @throws UserUpdateException */ - public function update(User $user, array $data, bool $manageUsersAllowed): User + public function updateWithoutActivity(User $user, array $data, bool $manageUsersAllowed): User { if (!empty($data['name'])) { $user->name = $data['name']; @@ -134,6 +134,21 @@ class UserRepo } $user->save(); + + return $user; + } + + /** + * Update the given user with the given data. + * + * @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array, language: ?string} $data + * + * @throws UserUpdateException + */ + public function update(User $user, array $data, bool $manageUsersAllowed): User + { + $user = $this->updateWithoutActivity($user, $data, $manageUsersAllowed); + Activity::add(ActivityType::USER_UPDATE, $user); return $user;