• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / auth / 10060982055

23 Jul 2024 02:35PM UTC coverage: 59.361% (+0.09%) from 59.271%
10060982055

push

github

web-flow
Merge pull request #17 from daycry/development

Fixes

33 of 86 new or added lines in 29 files covered. (38.37%)

2 existing lines in 2 files now uncovered.

1858 of 3130 relevant lines covered (59.36%)

22.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.92
/src/Commands/UserCommand.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Auth.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Auth\Commands;
15

16
use Config\Services;
17
use Daycry\Auth\Authentication\Authenticators\Session;
18
use Daycry\Auth\Config\Auth;
19
use Daycry\Auth\Entities\User as UserEntity;
20
use Daycry\Auth\Exceptions\BadInputException;
21
use Daycry\Auth\Exceptions\CancelException;
22
use Daycry\Auth\Exceptions\UserNotFoundException;
23
use Daycry\Auth\Models\UserModel;
24
use Daycry\Auth\Validation\ValidationRules;
25

26
class UserCommand extends BaseCommand
27
{
28
    private array $validActions = [
29
        'create', 'activate', 'deactivate', 'changename', 'changeemail',
30
        'delete', 'password', 'list', 'addgroup', 'removegroup',
31
    ];
32

33
    /**
34
     * Command's name
35
     *
36
     * @var string
37
     */
38
    protected $name = 'auth:user';
39

40
    /**
41
     * Command's short description
42
     *
43
     * @var string
44
     */
45
    protected $description = 'Manage Auth users.';
46

47
    /**
48
     * Command's usage
49
     *
50
     * @var string
51
     */
52
    protected $usage = <<<'EOL'
53
        auth:user <action> options
54

55
            auth:user create -n newusername -e newuser@example.com
56

57
            auth:user activate -n username
58
            auth:user activate -e user@example.com
59

60
            auth:user deactivate -n username
61
            auth:user deactivate -e user@example.com
62

63
            auth:user changename -n username --new-name newusername
64
            auth:user changename -e user@example.com --new-name newusername
65

66
            auth:user changeemail -n username --new-email newuseremail@example.com
67
            auth:user changeemail -e user@example.com --new-email newuseremail@example.com
68

69
            auth:user delete -i 123
70
            auth:user delete -n username
71
            auth:user delete -e user@example.com
72

73
            auth:user password -n username
74
            auth:user password -e user@example.com
75

76
            auth:user list
77
            auth:user list -n username -e user@example.com
78

79
            auth:user addgroup -n username -g mygroup
80
            auth:user addgroup -e user@example.com -g mygroup
81

82
            auth:user removegroup -n username -g mygroup
83
            auth:user removegroup -e user@example.com -g mygroup
84
        EOL;
85

86
    /**
87
     * Command's Arguments
88
     *
89
     * @var array<string, string>
90
     */
91
    protected $arguments = [
92
        'action' => <<<'EOL'
93

94
                create:      Create a new user
95
                activate:    Activate a user
96
                deactivate:  Deactivate a user
97
                changename:  Change user name
98
                changeemail: Change user email
99
                delete:      Delete a user
100
                password:    Change a user password
101
                list:        List users
102
                addgroup:    Add a user to a group
103
                removegroup: Remove a user from a group
104
            EOL,
105
    ];
106

107
    /**
108
     * Command's Options
109
     *
110
     * @var array<string, string>
111
     */
112
    protected $options = [
113
        '-i'          => 'User id',
114
        '-n'          => 'User name',
115
        '-e'          => 'User email',
116
        '--new-name'  => 'New username',
117
        '--new-email' => 'New email',
118
        '-g'          => 'Group name',
119
    ];
120

121
    /**
122
     * Validation rules for user fields
123
     */
124
    private array $validationRules = [];
125

126
    /**
127
     * Auth Table names
128
     *
129
     * @var array<string, string>
130
     */
131
    private array $tables = [];
132

133
    /**
134
     * Displays the help for the spark cli script itself.
135
     */
136
    public function run(array $params): int
137
    {
138
        $this->setTables();
22✔
139
        $this->setValidationRules();
22✔
140

141
        $action = $params[0] ?? null;
22✔
142

143
        if ($action === null || ! in_array($action, $this->validActions, true)) {
22✔
144
            $this->write(
1✔
145
                'Specify a valid action: ' . implode(',', $this->validActions),
1✔
146
                'red'
1✔
147
            );
1✔
148

149
            return EXIT_ERROR;
1✔
150
        }
151

152
        $userid      = (int) ($params['i'] ?? 0);
21✔
153
        $username    = $params['n'] ?? null;
21✔
154
        $email       = $params['e'] ?? null;
21✔
155
        $newUsername = $params['new-name'] ?? null;
21✔
156
        $newEmail    = $params['new-email'] ?? null;
21✔
157
        $group       = $params['g'] ?? null;
21✔
158

159
        try {
160
            switch ($action) {
161
                case 'create':
21✔
162
                    $this->create($username, $email);
3✔
163
                    break;
1✔
164

165
                case 'activate':
18✔
166
                    $this->activate($username, $email);
1✔
167
                    break;
1✔
168

169
                case 'deactivate':
17✔
170
                    $this->deactivate($username, $email);
1✔
171
                    break;
1✔
172

173
                case 'changename':
16✔
174
                    $this->changename($username, $email, $newUsername);
2✔
175
                    break;
1✔
176

177
                case 'changeemail':
14✔
178
                    $this->changeemail($username, $email, $newEmail);
2✔
179
                    break;
1✔
180

181
                case 'delete':
12✔
182
                    $this->delete($userid, $username, $email);
3✔
183
                    break;
2✔
184

185
                case 'password':
9✔
186
                    $this->password($username, $email);
3✔
187
                    break;
2✔
188

189
                case 'list':
6✔
190
                    $this->list($username, $email);
2✔
191
                    break;
2✔
192

193
                case 'addgroup':
4✔
194
                    $this->addgroup($group, $username, $email);
2✔
195
                    break;
2✔
196

197
                case 'removegroup':
2✔
198
                    $this->removegroup($group, $username, $email);
2✔
199
                    break;
15✔
200
            }
201
        } catch (BadInputException|CancelException|UserNotFoundException $e) {
6✔
202
            $this->write($e->getMessage(), 'red');
6✔
203

204
            return EXIT_ERROR;
6✔
205
        }
206

207
        return EXIT_SUCCESS;
15✔
208
    }
209

210
    private function setTables(): void
211
    {
212
        /** @var Auth $config */
213
        $config       = config('Auth');
22✔
214
        $this->tables = $config->tables;
22✔
215
    }
216

217
    private function setValidationRules(): void
218
    {
219
        $validationRules = new ValidationRules();
22✔
220

221
        $rules = $validationRules->getRegistrationRules();
22✔
222

223
        // Remove `strong_password` rule because it only supports use cases
224
        // to check the user's own password.
225
        $passwordRules = $rules['password']['rules'];
22✔
226
        if (is_string($passwordRules)) {
22✔
227
            $passwordRules = explode('|', $passwordRules);
×
228
        }
229
        if (($key = array_search('strong_password[]', $passwordRules, true)) !== false) {
22✔
230
            unset($passwordRules[$key]);
22✔
231
        }
232
        if (($key = array_search('strong_password', $passwordRules, true)) !== false) {
22✔
233
            unset($passwordRules[$key]);
×
234
        }
235

236
        /** @var Auth $config */
237
        $config = config('Auth');
22✔
238

239
        // Add `min_length`
240
        $passwordRules[] = 'min_length[' . $config->minimumPasswordLength . ']';
22✔
241

242
        $rules['password']['rules'] = $passwordRules;
22✔
243

244
        // Remove `password_confirm` field.
245
        unset($rules['password_confirm']);
22✔
246

247
        $this->validationRules = $rules;
22✔
248
    }
249

250
    /**
251
     * Create a new user
252
     *
253
     * @param string|null $username User name to create (optional)
254
     * @param string|null $email    User email to create (optional)
255
     */
256
    private function create(?string $username = null, ?string $email = null): void
257
    {
258
        $data = [];
3✔
259

260
        // If you don't use `username`, remove the validation rules for it.
261
        if ($username === null && isset($this->validationRules['username'])) {
3✔
NEW
262
            $username = $this->prompt('Username', null, $this->validationRules['username']['rules']);
×
263
        }
264
        $data['username'] = $username;
3✔
265
        if ($username === null) {
3✔
NEW
266
            unset($data['username']);
×
267
        }
268

269
        if ($email === null) {
3✔
270
            $email = $this->prompt('Email', null, $this->validationRules['email']['rules'], config('Auth')->DBGroup);
×
271
        }
272
        $data['email'] = $email;
3✔
273

274
        $password = $this->prompt(
3✔
275
            'Password',
3✔
276
            null,
3✔
277
            $this->validationRules['password']['rules'],
3✔
278
            config('Auth')->DBGroup
3✔
279
        );
3✔
280
        $passwordConfirm = $this->prompt(
3✔
281
            'Password confirmation',
3✔
282
            null,
3✔
283
            $this->validationRules['password']['rules'],
3✔
284
            config('Auth')->DBGroup
3✔
285
        );
3✔
286

287
        if ($password !== $passwordConfirm) {
3✔
288
            throw new BadInputException("The passwords don't match");
1✔
289
        }
290
        $data['password'] = $password;
2✔
291

292
        // Run validation if the user has passed username and/or email via command line
293
        $validation = Services::validation();
2✔
294
        $validation->setRules($this->validationRules);
2✔
295

296
        if (! $validation->run($data, null, config('Auth')->DBGroup)) {
2✔
297
            foreach ($validation->getErrors() as $message) {
1✔
298
                $this->write($message, 'red');
1✔
299
            }
300

301
            throw new CancelException('User creation aborted');
1✔
302
        }
303

304
        $userModel = model(UserModel::class);
1✔
305

306
        $user = new UserEntity($data);
1✔
307

308
        if ($username === null) {
1✔
NEW
309
            $userModel->allowEmptyInserts()->save($user);
×
NEW
310
            $this->write('New User created', 'green');
×
311
        } else {
312
            $userModel->save($user);
1✔
313
            $this->write('User "' . $username . '" created', 'green');
1✔
314
        }
315
    }
316

317
    /**
318
     * Activate an existing user by username or email
319
     *
320
     * @param string|null $username User name to search for (optional)
321
     * @param string|null $email    User email to search for (optional)
322
     */
323
    private function activate(?string $username = null, ?string $email = null): void
324
    {
325
        $user = $this->findUser('Activate user', $username, $email);
1✔
326

327
        $confirm = $this->prompt('Activate the user ' . $user->username . ' ?', ['y', 'n']);
1✔
328

329
        if ($confirm === 'y') {
1✔
330
            $userModel = model(UserModel::class);
1✔
331

332
            $user->active = 1;
1✔
333
            $userModel->save($user);
1✔
334

335
            $this->write('User "' . $user->username . '" activated', 'green');
1✔
336
        } else {
337
            $this->write('User "' . $user->username . '" activation cancelled', 'yellow');
×
338
        }
339
    }
340

341
    /**
342
     * Deactivate an existing user by username or email
343
     *
344
     * @param string|null $username User name to search for (optional)
345
     * @param string|null $email    User email to search for (optional)
346
     */
347
    private function deactivate(?string $username = null, ?string $email = null): void
348
    {
349
        $user = $this->findUser('Deactivate user', $username, $email);
1✔
350

351
        $confirm = $this->prompt('Deactivate the user "' . $username . '" ?', ['y', 'n']);
1✔
352

353
        if ($confirm === 'y') {
1✔
354
            $userModel = model(UserModel::class);
1✔
355

356
            $user->active = 0;
1✔
357
            $userModel->save($user);
1✔
358

359
            $this->write('User "' . $user->username . '" deactivated', 'green');
1✔
360
        } else {
361
            $this->write('User "' . $user->username . '" deactivation cancelled', 'yellow');
×
362
        }
363
    }
364

365
    /**
366
     * Change the name of an existing user by username or email
367
     *
368
     * @param string|null $username    User name to search for (optional)
369
     * @param string|null $email       User email to search for (optional)
370
     * @param string|null $newUsername User new name (optional)
371
     */
372
    private function changename(
373
        ?string $username = null,
374
        ?string $email = null,
375
        ?string $newUsername = null
376
    ): void {
377
        $user = $this->findUser('Change username', $username, $email);
2✔
378

379
        if ($newUsername === null) {
2✔
380
            $newUsername = $this->prompt('New username', null, $this->validationRules['username']['rules'], config('Auth')->DBGroup);
×
381
        } else {
382
            // Run validation if the user has passed username and/or email via command line
383
            $validation = Services::validation();
2✔
384
            $validation->setRules([
2✔
385
                'username' => $this->validationRules['username'],
2✔
386
            ]);
2✔
387

388
            if (! $validation->run(['username' => $newUsername])) {
2✔
389
                foreach ($validation->getErrors() as $message) {
1✔
390
                    $this->write($message, 'red');
1✔
391
                }
392

393
                throw new CancelException('User name change aborted');
1✔
394
            }
395
        }
396

397
        $userModel = model(UserModel::class);
1✔
398

399
        $oldUsername    = $user->username;
1✔
400
        $user->username = $newUsername;
1✔
401
        $userModel->save($user);
1✔
402

403
        $this->write('Username "' . $oldUsername . '" changed to "' . $newUsername . '"', 'green');
1✔
404
    }
405

406
    /**
407
     * Change the email of an existing user by username or email
408
     *
409
     * @param string|null $username User name to search for (optional)
410
     * @param string|null $email    User email to search for (optional)
411
     * @param string|null $newEmail User new email (optional)
412
     */
413
    private function changeemail(
414
        ?string $username = null,
415
        ?string $email = null,
416
        ?string $newEmail = null
417
    ): void {
418
        $user = $this->findUser('Change email', $username, $email);
2✔
419

420
        if ($newEmail === null) {
2✔
421
            $newEmail = $this->prompt('New email', null, $this->validationRules['email']['rules'], config('Auth')->DBGroup);
×
422
        } else {
423
            // Run validation if the user has passed username and/or email via command line
424
            $validation = Services::validation();
2✔
425
            $validation->setRules([
2✔
426
                'email' => $this->validationRules['email'],
2✔
427
            ]);
2✔
428

429
            if (! $validation->run(['email' => $newEmail])) {
2✔
430
                foreach ($validation->getErrors() as $message) {
1✔
431
                    $this->write($message, 'red');
1✔
432
                }
433

434
                throw new CancelException('User email change aborted');
1✔
435
            }
436
        }
437

438
        $userModel = model(UserModel::class);
1✔
439

440
        $user->email = $newEmail;
1✔
441
        $userModel->save($user);
1✔
442

443
        $this->write('Email for "' . $user->username . '" changed to ' . $newEmail, 'green');
1✔
444
    }
445

446
    /**
447
     * Delete an existing user by username or email
448
     *
449
     * @param int         $userid   User id to delete (optional)
450
     * @param string|null $username User name to search for (optional)
451
     * @param string|null $email    User email to search for (optional)
452
     */
453
    private function delete(int $userid = 0, ?string $username = null, ?string $email = null): void
454
    {
455
        $userModel = model(UserModel::class);
3✔
456

457
        if ($userid !== 0) {
3✔
458
            $user = $userModel->findById($userid);
1✔
459

460
            $this->checkUserExists($user);
1✔
461
        } else {
462
            $user = $this->findUser('Delete user', $username, $email);
2✔
463
        }
464

465
        $confirm = $this->prompt(
2✔
466
            'Delete the user "' . $user->username . '" (' . $user->email . ') ?',
2✔
467
            ['y', 'n']
2✔
468
        );
2✔
469

470
        if ($confirm === 'y') {
2✔
471
            $userModel->delete($user->id, true);
2✔
472

473
            $this->write('User "' . $user->username . '" deleted', 'green');
2✔
474
        } else {
475
            $this->write('User "' . $user->username . '" deletion cancelled', 'yellow');
×
476
        }
477
    }
478

479
    /**
480
     * @param UserEntity|null $user
481
     */
482
    private function checkUserExists($user): void
483
    {
484
        if ($user === null) {
16✔
485
            throw new UserNotFoundException("User doesn't exist");
1✔
486
        }
487
    }
488

489
    /**
490
     * Change the password of an existing user by username or email
491
     *
492
     * @param string|null $username User name to search for (optional)
493
     * @param string|null $email    User email to search for (optional)
494
     */
495
    private function password($username = null, $email = null): void
496
    {
497
        $user = $this->findUser('Change user password', $username, $email);
3✔
498

499
        $confirm = $this->prompt('Set the password for "' . $user->username . '" ?', ['y', 'n']);
3✔
500

501
        if ($confirm === 'y') {
3✔
502
            $password = $this->prompt(
3✔
503
                'Password',
3✔
504
                null,
3✔
505
                $this->validationRules['password']['rules']
3✔
506
            );
3✔
507
            $passwordConfirm = $this->prompt(
3✔
508
                'Password confirmation',
3✔
509
                null,
3✔
510
                $this->validationRules['password']['rules']
3✔
511
            );
3✔
512

513
            if ($password !== $passwordConfirm) {
3✔
514
                throw new BadInputException("The passwords don't match");
1✔
515
            }
516

517
            $userModel = model(UserModel::class);
2✔
518

519
            $user->password = $password;
2✔
520
            $userModel->save($user);
2✔
521

522
            $this->write('Password for "' . $user->username . '" set', 'green');
2✔
523
        } else {
524
            $this->write('Password setting for "' . $user->username . '" cancelled', 'yellow');
×
525
        }
526
    }
527

528
    /**
529
     * List users searching by username or email
530
     *
531
     * @param string|null $username User name to search for (optional)
532
     * @param string|null $email    User email to search for (optional)
533
     */
534
    private function list(?string $username = null, ?string $email = null): void
535
    {
536
        $userModel = model(UserModel::class);
2✔
537
        $userModel
2✔
538
            ->select($this->tables['users'] . '.id as id, username, secret as email')
2✔
539
            ->join(
2✔
540
                $this->tables['identities'],
2✔
541
                $this->tables['users'] . '.id = ' . $this->tables['identities'] . '.user_id',
2✔
542
                'LEFT'
2✔
543
            )
2✔
544
            ->groupStart()
2✔
545
            ->where($this->tables['identities'] . '.type', Session::ID_TYPE_EMAIL_PASSWORD)
2✔
546
            ->orGroupStart()
2✔
547
            ->where($this->tables['identities'] . '.type', null)
2✔
548
            ->groupEnd()
2✔
549
            ->groupEnd()
2✔
550
            ->asArray();
2✔
551

552
        if ($username !== null) {
2✔
553
            $userModel->like('username', $username);
×
554
        }
555
        if ($email !== null) {
2✔
556
            $userModel->like('secret', $email);
1✔
557
        }
558

559
        $this->write("Id\tUser");
2✔
560

561
        foreach ($userModel->findAll() as $user) {
2✔
562
            $this->write($user['id'] . "\t" . $user['username'] . ' (' . $user['email'] . ')');
2✔
563
        }
564
    }
565

566
    /**
567
     * Add a user by username or email to a group
568
     *
569
     * @param string|null $group    Group to add user to
570
     * @param string|null $username User name to search for (optional)
571
     * @param string|null $email    User email to search for (optional)
572
     */
573
    private function addgroup($group = null, $username = null, $email = null): void
574
    {
575
        if ($group === null) {
2✔
576
            $group = $this->prompt('Group', null, 'required');
×
577
        }
578

579
        $user = $this->findUser('Add user to group', $username, $email);
2✔
580

581
        $confirm = $this->prompt(
2✔
582
            'Add the user "' . $user->username . '" to the group "' . $group . '" ?',
2✔
583
            ['y', 'n']
2✔
584
        );
2✔
585

586
        if ($confirm === 'y') {
2✔
587
            $user->addGroup($group);
1✔
588

589
            $this->write('User "' . $user->username . '" added to group "' . $group . '"', 'green');
1✔
590
        } else {
591
            $this->write(
1✔
592
                'Addition of the user "' . $user->username . '" to the group "' . $group . '" cancelled',
1✔
593
                'yellow'
1✔
594
            );
1✔
595
        }
596
    }
597

598
    /**
599
     * Remove a user by username or email from a group
600
     *
601
     * @param string|null $group    Group to remove user from
602
     * @param string|null $username User name to search for (optional)
603
     * @param string|null $email    User email to search for (optional)
604
     */
605
    private function removegroup($group = null, $username = null, $email = null): void
606
    {
607
        if ($group === null) {
2✔
608
            $group = $this->prompt('Group', null, 'required');
×
609
        }
610

611
        $user = $this->findUser('Remove user from group', $username, $email);
2✔
612

613
        $confirm = $this->prompt(
2✔
614
            'Remove the user "' . $user->username . '" from the group "' . $group . '" ?',
2✔
615
            ['y', 'n']
2✔
616
        );
2✔
617

618
        if ($confirm === 'y') {
2✔
619
            $user->removeGroup($group);
1✔
620

621
            $this->write('User "' . $user->username . '" removed from group "' . $group . '"', 'green');
1✔
622
        } else {
623
            $this->write('Removal of the user "' . $user->username . '" from the group "' . $group . '" cancelled', 'yellow');
1✔
624
        }
625
    }
626

627
    /**
628
     * Find an existing user by username or email.
629
     *
630
     * @param string      $question Initial question at user prompt
631
     * @param string|null $username User name to search for (optional)
632
     * @param string|null $email    User email to search for (optional)
633
     */
634
    private function findUser($question = '', $username = null, $email = null): UserEntity
635
    {
636
        if ($username === null && $email === null) {
15✔
637
            $choice = $this->prompt($question . ' by username or email ?', ['u', 'e']);
2✔
638

639
            if ($choice === 'u') {
2✔
640
                $username = $this->prompt('Username', null, 'required');
1✔
641
            } elseif ($choice === 'e') {
1✔
642
                $email = $this->prompt(
1✔
643
                    'Email',
1✔
644
                    null,
1✔
645
                    'required'
1✔
646
                );
1✔
647
            }
648
        }
649

650
        $userModel = model(UserModel::class);
15✔
651
        $userModel
15✔
652
            ->select($this->tables['users'] . '.id as id, username, secret')
15✔
653
            ->join(
15✔
654
                $this->tables['identities'],
15✔
655
                $this->tables['users'] . '.id = ' . $this->tables['identities'] . '.user_id',
15✔
656
                'LEFT'
15✔
657
            )
15✔
658
            ->groupStart()
15✔
659
            ->where($this->tables['identities'] . '.type', Session::ID_TYPE_EMAIL_PASSWORD)
15✔
660
            ->orGroupStart()
15✔
661
            ->where($this->tables['identities'] . '.type', null)
15✔
662
            ->groupEnd()
15✔
663
            ->groupEnd()
15✔
664
            ->asArray();
15✔
665

666
        $user = null;
15✔
667
        if ($username !== null) {
15✔
668
            $user = $userModel->where('username', $username)->first();
14✔
669
        } elseif ($email !== null) {
1✔
670
            $user = $userModel->where('secret', $email)->first();
1✔
671
        }
672

673
        $this->checkUserExists($user);
15✔
674

675
        return $userModel->findById($user['id']);
14✔
676
    }
677
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc