Passed
Push — master ( 105307...650254 )
by vistart
04:03
created

UserController::getUser()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
ccs 0
cts 9
cp 0
rs 8.8571
cc 6
eloc 9
nc 6
nop 1
crap 42
1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link https://vistart.me/
9
 * @copyright Copyright (c) 2016 - 2017 vistart
10
 * @license https://vistart.me/license/
11
 */
12
13
namespace rhosocial\user\console\controllers;
14
15
use Faker\Factory;
16
use rhosocial\user\User;
17
use rhosocial\user\Profile;
18
use yii\console\Controller;
19
use yii\console\Exception;
20
use Yii;
21
use yii\helpers\Console;
22
23
/**
24
 * The simple operations associated with User.
25
 *
26
 * @version 1.0
27
 * @author vistart <[email protected]>
28
 */
29
class UserController extends Controller
30
{
31
    public $userClass;
32
    
33
    public $defaultAction = 'show';
34
    
35
    /**
36
     * Check and get valid User.
37
     * @return User
38
     * @throws Exception throw if User is not an instance inherited from `\rhosocial\user\User`.
39
     */
40
    protected function checkUserClass()
41
    {
42
        $userClass = $this->userClass;
43
        if (!class_exists($userClass)) {
44
            throw new Exception('User Class Invalid.');
45
        }
46
        if (!((new $userClass()) instanceof User)) {
47
            throw new Exception('User Class(' . $userClass . ') does not inherited from `\rhosocial\user\User`.');
48
        }
49
        return $userClass;
50
    }
51
    
52
    /**
53
     * Get user from database.
54
     * @param User|string|integer $user User ID.
55
     * @return User
56
     * @throws Exception
57
     */
58
    protected function getUser($user)
59
    {
60
        $userClass = $this->checkUserClass();
61
        if (is_numeric($user)) {
62
            $user = $userClass::find()->id($user)->one();
63
        } elseif (is_string($user) && strlen($user)) {
64
            $user = $userClass::find()->guid($user)->one();
65
        }
66
        if (!$user || $user->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
It seems like $user is not always an object, but can also be of type array|string. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
67
            throw new Exception('User Not Registered.');
68
        }
69
        return $user;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $user; (yii\db\ActiveRecord|array|string) is incompatible with the return type documented by rhosocial\user\console\c...UserController::getUser of type rhosocial\user\User.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
70
    }
71
    
72
    /**
73
     * Register new User.
74
     * @param string $password Password.
75
     * @param string $nickname If profile contains this property, this parameter is required.
76
     * @param string $firstName If profile contains this property, this parameter is required.
77
     * @param string $lastName If profile contains this propery, this parameter is required.
78
     * @return int
79
     * @throws Exception
80
     */
81
    public function actionRegister($password, $nickname = null, $firstName = null, $lastName = null)
82
    {
83
        $userClass = $this->checkUserClass();
84
        
85
        $user = new $userClass(['password' => $password]);
86
        /* @var $user User */
87
        $profile = $user->createProfile([
88
            'nickname' => $nickname,
89
            'first_name' => $firstName,
90
            'last_name' => $lastName,
91
        ]);
92
        /* @var $profile Profile */
93
        try {
94
            is_null($profile) ? $user->register(): $user->register([$profile]);
95
        } catch (\Exception $ex) {
96
            throw new Exception($ex->getMessage());
97
        }
98
        echo "User Registered:\n";
99
        return $this->actionShow($user);
100
    }
101
    
102
    /**
103
     * Deregister user.
104
     * @param User|string|integer $user The user to be deregistered.
105
     * @return int
106
     */
107
    public function actionDeregister($user)
108
    {
109
        $user = $this->getUser($user);
110
        if ($user->deregister()) {
111
            echo "User (" . $user->getID() . ") Deregistered.\n";
112
            return static::EXIT_CODE_NORMAL;
113
        }
114
        return static::EXIT_CODE_ERROR;
115
    }
116
    
117
    /**
118
     * Show User Information.
119
     * @param User|string|integer $user User ID.
120
     * @param boolean $guid Show GUID?
121
     * @param boolean $passHash Show PasswordH Hash?
122
     * @param boolean $accessToken Show Access Token?
123
     * @param boolean $authKey Show Authentication Key?
124
     * @return int
125
     */
126
    public function actionShow($user, $guid = false, $passHash = false, $accessToken = false, $authKey = false)
127
    {
128
        $user = $this->getUser($user);
129
        echo Yii::t('app', 'User') . " (" . $user->getID() . "), " . Yii::t('app', 'registered at') . " (" . $user->getCreatedAt() . ")"
130
                . ($user->getCreatedAt() == $user->getUpdatedAt() ? "" : ", " . Yii::t('app', 'last updated at') . " (" . $user->getUpdatedAt() . ")") .".\n";
131
        if ($guid) {
132
            echo "GUID: " . $user->getGUID() . "\n";
133
        }
134
        if ($passHash) {
135
            echo "Password Hash: " . $user->{$user->passwordHashAttribute} . "\n";
136
        }
137
        if ($accessToken) {
138
            echo "Access Token: " . $user->getAccessToken() . "\n";
139
        }
140
        if ($authKey) {
141
            echo "Authentication Key: " . $user->getAuthKey() . "\n";
142
        }
143
        return static::EXIT_CODE_NORMAL;
144
    }
145
    
146
    /**
147
     * Show statistics.
148
     * @param User|string|integer $user User ID.
149
     * @return int
150
     */
151
    public function actionStat($user = null)
152
    {
153
        if ($user === null) {
154
            $count = User::find()->count();
155
            echo "Total number of user(s): " . $count . "\n";
156
            if ($count == 0) {
157
                return static::EXIT_CODE_NORMAL;
158
            }
159
            $last = User::find()->orderByCreatedAt(SORT_DESC)->one();
160
            /* @var $last User */
161
            echo "Latest user (" . $last->getID() . ") registered at " . $last->getCreatedAt() . "\n";
162
            return static::EXIT_CODE_NORMAL;
163
        }
164
        $user = $this->getUser($user);
0 ignored issues
show
Unused Code introduced by
$user is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
165
        return static::EXIT_CODE_NORMAL;
166
    }
167
    
168
    /**
169
     * Assign a role to user or revoke a role.
170
     * @param User|string|integer $user User ID.
171
     * @param string $operation Only `assign` and `revoke` are acceptable.
172
     * @param string $role Role name.
173
     * @return int
174
     */
175
    public function actionRole($user, $operation, $role)
176
    {
177
        $user = $this->getUser($user);
178
        $role = Yii::$app->authManager->getRole($role);
179
        if ($operation == 'assign') {
180
            try {
181
                $assignment = Yii::$app->authManager->assign($role, $user);
182
            } catch (\yii\db\IntegrityException $ex) {
183
                echo "Failed to assign `" . $role->name . "`.\n";
184
                echo "Maybe the role has been assigned.\n";
185
                return static::EXIT_CODE_ERROR;
186
            }
187
            if ($assignment) {
188
                echo "`$role->name`" . " assigned to User (" . $user->getID() . ") successfully.\n";
189
            } else {
190
                echo "Failed to assign `" . $role->name . "`.\n";
191
            }
192
            return static::EXIT_CODE_NORMAL;
193
        }
194
        if ($operation == 'revoke') {
195
            $assignment = Yii::$app->authManager->revoke($role, $user);
196
            if ($assignment) {
197
                echo "`$role->name`" . " revoked from User (" . $user->getID() . ").\n";
198
            } else {
199
                echo "Failed to revoke `" . $role->name . "`.\n";
200
                echo "Maybe the role has not been assigned yet.\n";
201
            }
202
            return static::EXIT_CODE_NORMAL;
203
        }
204
        echo "Unrecognized operation: $operation.\n";
205
        echo "The accepted operations are `assign` and `revoke`.\n";
206
        return static::EXIT_CODE_ERROR;
207
    }
208
    
209
    /**
210
     * Assign a permission to user or revoke a permission.
211
     * @param User|string|integer $user User ID.
212
     * @param string $operation Only `assign` and `revoke` are acceptable.
213
     * @param string $permission Permission name.
214
     * @return int
215
     */
216
    public function actionPermission($user, $operation, $permission)
217
    {
218
        $user = $this->getUser($user);
219
        $permission = Yii::$app->authManager->getPermission($permission);
220
        if ($operation == 'assign') {
221
            try {
222
                $assignment = Yii::$app->authManager->assign($permission, $user);
223
            } catch (\yii\db\IntegrityException $ex) {
224
                echo "Failed to assign `" . $permission->name . "`.\n";
225
                echo "Maybe the permission has been assigned.\n";
226
                return static::EXIT_CODE_ERROR;
227
            }
228
            if ($assignment) {
229
                echo "`$permission->name`" . " assigned to User (" . $user->getID() . ") successfully.\n";
230
            } else {
231
                echo "Failed to assign `" . $permission->name . "`.\n";
232
            }
233
            return static::EXIT_CODE_NORMAL;
234
        }
235
        if ($operation == 'revoke') {
236
            $assignment = Yii::$app->authManager->revoke($permission, $user);
237
            if ($assignment) {
238
                echo "`$permission->name`" . " revoked from User (" . $user->getID() . ").\n";
239
            } else {
240
                echo "Failed to revoke `" . $permission->name . "`.\n";
241
                echo "Maybe the permission has not been assigned yet.\n";
242
            }
243
            return static::EXIT_CODE_NORMAL;
244
        }
245
        echo "Unrecognized operation: $operation.\n";
246
        echo "The accepted operations are `assign` and `revoke`.\n";
247
        return static::EXIT_CODE_ERROR;
248
    }
249
250
    /**
251
     * Validate password.
252
     * @param User|string|integer $user User ID.
253
     * @param password $password Password.
254
     * @return int
255
     */
256
    public function actionValidatePassword($user, $password)
257
    {
258
        $user = $this->getUser($user);
259
        $result = $user->validatePassword($password);
260
        if ($result) {
261
            echo "Correct.\n";
262
        } else {
263
            echo "Incorrect.\n";
264
        }
265
        return static::EXIT_CODE_NORMAL;
266
    }
267
268
    /**
269
     * Change password directly.
270
     * @param User|string|integer $user User ID.
271
     * @param string $password Password.
272
     * @return int
273
     */
274
    public function actionPassword($user, $password)
275
    {
276
        $user = $this->getUser($user);
277
        $user->applyForNewPassword();
278
        $result = $user->resetPassword($password, $user->getPasswordResetToken());
279
        if ($result) {
280
            echo "Password changed.\n";
281
        } else {
282
            echo "Password not changed.\n";
283
        }
284
        return static::EXIT_CODE_NORMAL;
285
    }
286
287
    /**
288
     * Confirm password in history.
289
     * This command will list all matching passwords in reverse order.
290
     * @param User|string|integer $user User ID.
291
     * @param string $password Password.
292
     * @return int
293
     */
294
    public function actionConfirmPasswordHistory($user, $password)
295
    {
296
        $user = $this->getUser($user);
297
        $passwordHistory = $user->passwordHistories;
298
        $passwordInHistory = 0;
299
        foreach ($passwordHistory as $pass) {
300
            if ($pass->validatePassword($password)) {
301
                $passwordInHistory++;
302
                echo "This password was created at " . $pass->getCreatedAt() . ".\n";
303
            }
304
        }
305
        if ($passwordInHistory) {
306
            echo "$passwordInHistory matched.\n";
307
            return static::EXIT_CODE_NORMAL;
308
        }
309
        echo "No password matched.\n";
310
        return static::EXIT_CODE_ERROR;
311
    }
312
313
    /**
314
     * Register users for testing.
315
     * @param int $total
316
     * @param string password
317
     * @return int
318
     * @throws Exception
319
     */
320
    public function actionAddTestUsers($total = 1000, $password = '123456')
321
    {
322
        echo "Registration Start...\n";
323
        $userClass = $this->checkUserClass();
324
325
        $faker = Factory::create(str_replace('-', '_', Yii::$app->language));
326
        $total = (int)$total;
327
        $acc = 0;
328
        $time = time();
329
        $genders = [Profile::GENDER_MALE, Profile::GENDER_FEMALE, Profile::GENDER_UNSPECIFIED];
330
        for ($i = 1; $i <= $total; $i++) {
331
            $user = new $userClass(['password' => $password]);
332
            $user->source = 'console_test';
333
            /* @var $user User */
334
            $gender = $faker->randomElement($genders);
335
            $profile = null;
0 ignored issues
show
Unused Code introduced by
$profile is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
336
            if ($gender == Profile::GENDER_MALE) {
337
                $profile = $user->createProfile([
338
                    'nickname' => $faker->titleMale,
339
                    'first_name' => $faker->firstNameMale,
340
                    'last_name' => $faker->lastName,
341
                ]);
342
            } elseif ($gender == Profile::GENDER_FEMALE) {
343
                $profile = $user->createProfile([
344
                    'nickname' => $faker->titleFemale,
345
                    'first_name' => $faker->firstNameFemale,
346
                    'last_name' => $faker->lastName,
347
                ]);
348
            } else {
349
                $profile = $user->createProfile([
350
                    'nickname' => $faker->title,
351
                    'first_name' => $faker->firstName,
352
                    'last_name' => $faker->lastName,
353
                ]);
354
            }
355
            $profile->gender = $gender;
356
            /* @var $profile Profile */
357
            try {
358
                $result = (is_null($profile) ? $user->register() : $user->register([$profile]));
359
                if ($result !== true) {
360
                    var_dump($user->getErrors());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($user->getErrors()); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
361
                    throw new Exception("User Not Registered.");
362
                }
363
            } catch (\Exception $ex) {
364
                echo $ex->getMessage() . "\n";
365
                continue;
366
            }
367
            $acc++;
368
            if ($acc % 10 == 0) {
369
                $percent = (float)$i / $total * 100;
370
                echo "10 users registered($percent% finished).\n";
371
            }
372
        }
373
        $consumed = time() - $time;
374
        echo "Totally $acc users registered.\n";
375
        echo "Registration finished($consumed seconds consumed).\n";
376
        return static::EXIT_CODE_NORMAL;
377
    }
378
379
    /**
380
     * Deregister all users for testing.
381
     * @return int
382
     */
383
    public function actionRemoveAllTestUsers()
384
    {
385
        echo "Deregistration Start...\n";
386
387
        $userClass = $this->checkUserClass();
388
        $acc = 0;
389
        $time = time();
390
        foreach ($userClass::find()->andWhere(['source' => 'console_test'])->each() as $user) {
391
            try {
392
                $user->deregister();
393
            } catch (\Exception $ex) {
394
                echo $ex->getMessage() . "\n";
395
                continue;
396
            }
397
            $acc++;
398
            if ($acc % 10 == 0) {
399
                echo "10 users deregistered.\n";
400
            }
401
        }
402
        $consumed = time() - $time;
403
        echo "Totally $acc users deregistered.\n";
404
        echo "Deregistration finished($consumed seconds consumed).\n";
405
        return static::EXIT_CODE_NORMAL;
406
    }
407
}
408