Skip to content

Commit d6296ac

Browse files
authored
Merge pull request #5749 from BookStackApp/admin_command_updates
Create Admin Command: New Flags
2 parents 481f356 + f36e6fb commit d6296ac

File tree

3 files changed

+287
-33
lines changed

3 files changed

+287
-33
lines changed

app/Console/Commands/CreateAdminCommand.php

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Illuminate\Support\Facades\Validator;
99
use Illuminate\Support\Str;
1010
use Illuminate\Validation\Rules\Password;
11-
use Illuminate\Validation\Rules\Unique;
1211

1312
class CreateAdminCommand extends Command
1413
{
@@ -21,7 +20,9 @@ class CreateAdminCommand extends Command
2120
{--email= : The email address for the new admin user}
2221
{--name= : The name of the new admin user}
2322
{--password= : The password to assign to the new admin user}
24-
{--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}';
23+
{--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}
24+
{--generate-password : Generate a random password for the new admin user}
25+
{--initial : Indicate if this should set/update the details of the initial admin user}';
2526

2627
/**
2728
* The console command description.
@@ -35,26 +36,12 @@ class CreateAdminCommand extends Command
3536
*/
3637
public function handle(UserRepo $userRepo): int
3738
{
38-
$details = $this->snakeCaseOptions();
39-
40-
if (empty($details['email'])) {
41-
$details['email'] = $this->ask('Please specify an email address for the new admin user');
42-
}
43-
44-
if (empty($details['name'])) {
45-
$details['name'] = $this->ask('Please specify a name for the new admin user');
46-
}
47-
48-
if (empty($details['password'])) {
49-
if (empty($details['external_auth_id'])) {
50-
$details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)');
51-
} else {
52-
$details['password'] = Str::random(32);
53-
}
54-
}
39+
$initialAdminOnly = $this->option('initial');
40+
$shouldGeneratePassword = $this->option('generate-password');
41+
$details = $this->gatherDetails($shouldGeneratePassword, $initialAdminOnly);
5542

5643
$validator = Validator::make($details, [
57-
'email' => ['required', 'email', 'min:5', new Unique('users', 'email')],
44+
'email' => ['required', 'email', 'min:5'],
5845
'name' => ['required', 'min:2'],
5946
'password' => ['required_without:external_auth_id', Password::default()],
6047
'external_auth_id' => ['required_without:password'],
@@ -68,16 +55,101 @@ public function handle(UserRepo $userRepo): int
6855
return 1;
6956
}
7057

58+
$adminRole = Role::getSystemRole('admin');
59+
60+
if ($initialAdminOnly) {
61+
$handled = $this->handleInitialAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole);
62+
if ($handled !== null) {
63+
return $handled;
64+
}
65+
}
66+
67+
$emailUsed = $userRepo->getByEmail($details['email']) !== null;
68+
if ($emailUsed) {
69+
$this->error("Could not create admin account.");
70+
$this->error("An account with the email address \"{$details['email']}\" already exists.");
71+
return 1;
72+
}
73+
7174
$user = $userRepo->createWithoutActivity($validator->validated());
72-
$user->attachRole(Role::getSystemRole('admin'));
75+
$user->attachRole($adminRole);
7376
$user->email_confirmed = true;
7477
$user->save();
7578

76-
$this->info("Admin account with email \"{$user->email}\" successfully created!");
79+
if ($shouldGeneratePassword) {
80+
$this->line($details['password']);
81+
} else {
82+
$this->info("Admin account with email \"{$user->email}\" successfully created!");
83+
}
7784

7885
return 0;
7986
}
8087

88+
/**
89+
* Handle updates to the original admin account if it exists.
90+
* Returns an int return status if handled, otherwise returns null if not handled (new user to be created).
91+
*/
92+
protected function handleInitialAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): int|null
93+
{
94+
$defaultAdmin = $userRepo->getByEmail('[email protected]');
95+
if ($defaultAdmin && $defaultAdmin->hasSystemRole('admin')) {
96+
if ($defaultAdmin->email !== $data['email'] && $userRepo->getByEmail($data['email']) !== null) {
97+
$this->error("Could not create admin account.");
98+
$this->error("An account with the email address \"{$data['email']}\" already exists.");
99+
return 1;
100+
}
101+
102+
$userRepo->updateWithoutActivity($defaultAdmin, $data, true);
103+
if ($generatePassword) {
104+
$this->line($data['password']);
105+
} else {
106+
$this->info("The default admin user has been updated with the provided details!");
107+
}
108+
109+
return 0;
110+
} else if ($adminRole->users()->count() > 0) {
111+
$this->warn('Non-default admin user already exists. Skipping creation of new admin user.');
112+
return 2;
113+
}
114+
115+
return null;
116+
}
117+
118+
protected function gatherDetails(bool $generatePassword, bool $initialAdmin): array
119+
{
120+
$details = $this->snakeCaseOptions();
121+
122+
if (empty($details['email'])) {
123+
if ($initialAdmin) {
124+
$details['email'] = '[email protected]';
125+
} else {
126+
$details['email'] = $this->ask('Please specify an email address for the new admin user');
127+
}
128+
}
129+
130+
if (empty($details['name'])) {
131+
if ($initialAdmin) {
132+
$details['name'] = 'Admin';
133+
} else {
134+
$details['name'] = $this->ask('Please specify a name for the new admin user');
135+
}
136+
}
137+
138+
if (empty($details['password'])) {
139+
if (empty($details['external_auth_id'])) {
140+
if ($generatePassword) {
141+
$details['password'] = Str::random(32);
142+
} else {
143+
$details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)');
144+
}
145+
} else {
146+
$details['password'] = Str::random(32);
147+
}
148+
}
149+
150+
return $details;
151+
}
152+
81153
protected function snakeCaseOptions(): array
82154
{
83155
$returnOpts = [];

app/Users/UserRepo.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ public function create(array $data, bool $sendInvite = false): User
100100
}
101101

102102
/**
103-
* Update the given user with the given data.
103+
* Update the given user with the given data, but do not create an activity.
104104
*
105105
* @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array<int>, language: ?string} $data
106106
*
107107
* @throws UserUpdateException
108108
*/
109-
public function update(User $user, array $data, bool $manageUsersAllowed): User
109+
public function updateWithoutActivity(User $user, array $data, bool $manageUsersAllowed): User
110110
{
111111
if (!empty($data['name'])) {
112112
$user->name = $data['name'];
@@ -134,6 +134,21 @@ public function update(User $user, array $data, bool $manageUsersAllowed): User
134134
}
135135

136136
$user->save();
137+
138+
return $user;
139+
}
140+
141+
/**
142+
* Update the given user with the given data.
143+
*
144+
* @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array<int>, language: ?string} $data
145+
*
146+
* @throws UserUpdateException
147+
*/
148+
public function update(User $user, array $data, bool $manageUsersAllowed): User
149+
{
150+
$user = $this->updateWithoutActivity($user, $data, $manageUsersAllowed);
151+
137152
Activity::add(ActivityType::USER_UPDATE, $user);
138153

139154
return $user;

0 commit comments

Comments
 (0)