Completed
Pull Request — master (#251)
by
unknown
02:45
created

SettingsController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 6
cts 6
cp 1
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 6
crap 1
1
<?php
2
3
/*
4
 * This file is part of the 2amigos/yii2-usuario project.
5
 *
6
 * (c) 2amigOS! <http://2amigos.us/>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Da\User\Controller;
13
14
15
use Da\User\Contracts\MailChangeStrategyInterface;
16
use Da\User\Event\GdprEvent;
17
use Da\User\Event\ProfileEvent;
18
use Da\User\Event\SocialNetworkConnectEvent;
19
use Da\User\Event\UserEvent;
20
use Da\User\Form\GdprDeleteForm;
21
use Da\User\Form\SettingsForm;
22
use Da\User\Helper\SecurityHelper;
23
use Da\User\Model\Profile;
24
use Da\User\Model\SocialNetworkAccount;
25
use Da\User\Model\User;
26
use Da\User\Module;
27
use Da\User\Query\ProfileQuery;
28
use Da\User\Query\SocialNetworkAccountQuery;
29
use Da\User\Query\UserQuery;
30
use Da\User\Service\EmailChangeService;
31
use Da\User\Service\TwoFactorQrCodeUriGeneratorService;
32
use Da\User\Traits\ContainerAwareTrait;
33
use Da\User\Traits\ModuleAwareTrait;
34
use Da\User\Validator\AjaxRequestModelValidator;
35
use Da\User\Validator\TwoFactorCodeValidator;
36
use Yii;
37
use yii\filters\AccessControl;
38
use yii\filters\VerbFilter;
39
use yii\helpers\ArrayHelper;
40
use yii\web\Controller;
41
use yii\web\ForbiddenHttpException;
42
use yii\web\NotFoundHttpException;
43
use yii\web\Response;
44
45
class SettingsController extends Controller
46
{
47
    use ContainerAwareTrait;
48
    use ModuleAwareTrait;
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public $defaultAction = 'profile';
54
55
    protected $profileQuery;
56
    protected $userQuery;
57
    protected $socialNetworkAccountQuery;
58
59
60
    /**
61
     * SettingsController constructor.
62
     *
63
     * @param string $id
64
     * @param Module $module
65
     * @param ProfileQuery $profileQuery
66
     * @param UserQuery $userQuery
67
     * @param SocialNetworkAccountQuery $socialNetworkAccountQuery
68
     * @param array $config
69
     */
70 3
    public function __construct(
71
        $id,
72
        Module $module,
73
        ProfileQuery $profileQuery,
74
        UserQuery $userQuery,
75
        SocialNetworkAccountQuery $socialNetworkAccountQuery,
76
        array $config = []
77
    )
78
    {
79 3
        $this->profileQuery = $profileQuery;
80 3
        $this->userQuery = $userQuery;
81 3
        $this->socialNetworkAccountQuery = $socialNetworkAccountQuery;
82 3
        parent::__construct($id, $module, $config);
83 3
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88 3
    public function behaviors()
89
    {
90
        return [
91 3
            'verbs' => [
92
                'class' => VerbFilter::class,
93
                'actions' => [
94
                    'disconnect' => ['post'],
95
                    'delete' => ['post'],
96
                    'two-factor-disable' => ['post']
97
                ],
98
            ],
99
            'access' => [
100
                'class' => AccessControl::class,
101
                'rules' => [
102
                    [
103
                        'allow' => true,
104
                        'actions' => [
105
                            'profile',
106
                            'account',
107
                            'export',
108
                            'networks',
109
                            'privacy',
110
                            'gdprdelete',
111
                            'disconnect',
112
                            'delete',
113
                            'two-factor',
114
                            'two-factor-enable',
115
                            'two-factor-disable'
116
                        ],
117
                        'roles' => ['@'],
118
                    ],
119
                    [
120
                        'allow' => true,
121
                        'actions' => ['confirm'],
122
                        'roles' => ['?', '@'],
123
                    ],
124
                ],
125
            ],
126
        ];
127
    }
128
129
    public function actionProfile()
130
    {
131
        $profile = $this->profileQuery->whereUserId(Yii::$app->user->identity->getId())->one();
132
133
        if ($profile === null) {
134
            $profile = $this->make(Profile::class);
135
            $profile->link('user', Yii::$app->user->identity);
136
        }
137
138
        $event = $this->make(ProfileEvent::class, [$profile]);
139
140
        $this->make(AjaxRequestModelValidator::class, [$profile])->validate();
141
142
        if ($profile->load(Yii::$app->request->post())) {
143
            $this->trigger(UserEvent::EVENT_BEFORE_PROFILE_UPDATE, $event);
144
            if ($profile->save()) {
145
                Yii::$app->getSession()->setFlash('success', Yii::t('usuario', 'Your profile has been updated'));
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
146
                $this->trigger(UserEvent::EVENT_AFTER_PROFILE_UPDATE, $event);
147
148
                return $this->refresh();
149
            }
150
        }
151
152
        return $this->render(
153
            'profile',
154
            [
155
                'model' => $profile,
156
            ]
157
        );
158
    }
159
160 2
    public function actionPrivacy()
161
    {
162 2
        if (!$this->module->enableGDPRcompliance)
163 1
            throw new NotFoundHttpException();
164
165 1
        return $this->render('privacy', [
166 1
            'module' => $this->module
167
        ]);
168
    }
169
170 1
    public function actionGdprdelete()
171
    {
172 1
        if (!$this->module->enableGDPRcompliance)
173
            throw new NotFoundHttpException();
174
175
        /** @var GdprDeleteForm $form */
176 1
        $form = $this->make(GdprDeleteForm::class);
177
178 1
        $user = $form->getUser();
179
        /* @var $event GdprEvent */
180 1
        $event = $this->make(GdprEvent::class, [$user]);
181
182 1
        if ($form->load(Yii::$app->request->post()) && $form->validate()) {
183 1
            $this->trigger(GdprEvent::EVENT_BEFORE_DELETE, $event);
184
185 1
            if ($event->isValid) {
186 1
                Yii::$app->user->logout();
187
                //Disconnect social networks
188 1
                $networks = $this->socialNetworkAccountQuery->where(['user_id' => $user->id])->all();
0 ignored issues
show
Bug introduced by
Accessing id on the interface yii\web\IdentityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
189 1
                foreach ($networks as $network) {
190
                    $this->disconnectSocialNetwork($network->id);
191
                }
192
193
                /* @var $security SecurityHelper */
194 1
                $security = $this->make(SecurityHelper::class);
195 1
                $anonymReplacement = $this->module->GDPRanonymPrefix . $user->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface yii\web\IdentityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
196
197 1
                $user->updateAttributes([
198 1
                    'email' => $anonymReplacement . "@example.com",
199 1
                    'username' => $anonymReplacement,
200 1
                    'gdpr_deleted' => 1,
201 1
                    'blocked_at' => time(),
202 1
                    'auth_key' => $security->generateRandomString()
203
                ]);
204 1
                $user->profile->updateAttributes([
0 ignored issues
show
Bug introduced by
Accessing profile on the interface yii\web\IdentityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
205 1
                    'public_email' => $anonymReplacement . "@example.com",
206 1
                    'name' => $anonymReplacement,
207 1
                    'gravatar_email' => $anonymReplacement . "@example.com",
208 1
                    'location' => $anonymReplacement,
209 1
                    'website' => $anonymReplacement . ".tld",
210 1
                    'bio' => Yii::t('usuario', 'Deleted by GDPR request')
211
                ]);
212
213
214
            }
215 1
            $this->trigger(GdprEvent::EVENT_AFTER_DELETE, $event);
216
217 1
            Yii::$app->session->setFlash('info', Yii::t('usuario', 'Your personal information has been removed'));
218
219 1
            return $this->goHome();
220
221
        }
222
223 1
        return $this->render('gdprdelete', [
224 1
            'model' => $form,
225
        ]);
226
    }
227
228
    /**
229
     * @param $id
230
     * @throws ForbiddenHttpException
231
     * @throws NotFoundHttpException
232
     * @throws \Exception
233
     * @throws \Throwable
234
     * @throws \yii\db\StaleObjectException
235
     */
236
    protected function disconnectSocialNetwork($id)
237
    {
238
        /** @var SocialNetworkAccount $account */
239
        $account = $this->socialNetworkAccountQuery->whereId($id)->one();
240
241
        if ($account === null) {
242
            throw new NotFoundHttpException();
243
        }
244
        if ($account->user_id !== Yii::$app->user->id) {
245
            throw new ForbiddenHttpException();
246
        }
247
        $event = $this->make(SocialNetworkConnectEvent::class, [Yii::$app->user->identity, $account]);
248
249
        $this->trigger(SocialNetworkConnectEvent::EVENT_BEFORE_DISCONNECT, $event);
250
        $account->delete();
251
        $this->trigger(SocialNetworkConnectEvent::EVENT_AFTER_DISCONNECT, $event);
252
    }
253
254
    /**
255
     * Exports the data from the current user in a mechanical readable format (csv). Properties exported can be defined
256
     * in the module configuration.
257
     * @throws NotFoundHttpException if gdpr compliance is not enabled
258
     * @throws \Exception
259
     * @throws \Throwable
260
     */
261
    public function actionExport()
262
    {
263
        if (!$this->module->enableGDPRcompliance)
264
            throw new NotFoundHttpException();
265
266
        try {
267
            $properties = $this->module->GDPRexportProperties;
268
            $user = Yii::$app->user->identity;
269
            $data = [$properties, []];
270
271
            $formatter = Yii::$app->formatter;
272
            // override the default html-specific format for nulls
273
            $formatter->nullDisplay = "";
274
275
            foreach ($properties as $property) {
276
                $data[1][] = $formatter->asText(ArrayHelper::getValue($user, $property));
277
            }
278
279
            array_walk($data[0], function (&$value, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
280
                $splitted = explode('.', $value);
281
                $value = array_pop($splitted);
282
            });
283
284
            Yii::$app->response->headers->removeAll();
285
            Yii::$app->response->headers->add('Content-type', 'text/csv');
286
            Yii::$app->response->headers->add('Content-Disposition', 'attachment;filename=gdpr-data.csv');
287
            Yii::$app->response->send();
288
            $f = fopen('php://output', 'w');
289
            foreach ($data as $line) {
290
                fputcsv($f, $line);
291
            }
292
        } catch (\Exception $e) {
293
            throw $e;
294
        } catch (\Throwable $e) {
295
            throw $e;
296
        }
297
298
    }
299
300 1
    public function actionAccount()
301
    {
302
        /** @var SettingsForm $form */
303 1
        $form = $this->make(SettingsForm::class);
304 1
        $event = $this->make(UserEvent::class, [$form->getUser()]);
305
306 1
        $this->make(AjaxRequestModelValidator::class, [$form])->validate();
307
308 1
        if ($form->load(Yii::$app->request->post())) {
309 1
            $this->trigger(UserEvent::EVENT_BEFORE_ACCOUNT_UPDATE, $event);
310
311 1
            if ($form->save()) {
312 1
                Yii::$app->getSession()->setFlash(
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
313 1
                    'success',
314 1
                    Yii::t('usuario', 'Your account details have been updated')
315
                );
316 1
                $this->trigger(UserEvent::EVENT_AFTER_ACCOUNT_UPDATE, $event);
317
318 1
                return $this->refresh();
319
            }
320
        }
321
322 1
        return $this->render(
323 1
            'account',
324
            [
325 1
                'model' => $form,
326
            ]
327
        );
328
    }
329
330
    public function actionConfirm($id, $code)
331
    {
332
        $user = $this->userQuery->whereId($id)->one();
333
334
        if ($user === null || MailChangeStrategyInterface::TYPE_INSECURE === $this->module->emailChangeStrategy) {
335
            throw new NotFoundHttpException();
336
        }
337
        $event = $this->make(UserEvent::class, [$user]);
338
339
        $this->trigger(UserEvent::EVENT_BEFORE_CONFIRMATION, $event);
340
        if ($this->make(EmailChangeService::class, [$code, $user])->run()) {
341
            $this->trigger(UserEvent::EVENT_AFTER_CONFIRMATION, $event);
342
        }
343
344
        return $this->redirect(['account']);
345
    }
346
347
    public function actionNetworks()
348
    {
349
        return $this->render(
350
            'networks',
351
            [
352
                'user' => Yii::$app->user->identity,
353
            ]
354
        );
355
    }
356
357
    public function actionDisconnect($id)
358
    {
359
        $this->disconnectSocialNetwork($id);
360
        return $this->redirect(['networks']);
361
    }
362
363
    public function actionDelete()
364
    {
365
        if (!$this->module->allowAccountDelete) {
366
            throw new NotFoundHttpException(Yii::t('usuario', 'Not found'));
367
        }
368
369
        /** @var User $user */
370
        $user = Yii::$app->user->identity;
371
        $event = $this->make(UserEvent::class, [$user]);
372
        Yii::$app->user->logout();
373
374
        $this->trigger(UserEvent::EVENT_BEFORE_DELETE, $event);
375
        $user->delete();
376
        $this->trigger(UserEvent::EVENT_AFTER_DELETE, $event);
377
378
        Yii::$app->session->setFlash('info', Yii::t('usuario', 'Your account has been completely deleted'));
379
380
        return $this->goHome();
381
    }
382
383
    public function actionTwoFactor($id)
384
    {
385
        /** @var User $user */
386
        $user = $this->userQuery->whereId($id)->one();
387
388
        if (null === $user) {
389
            throw new NotFoundHttpException();
390
        }
391
392
        $uri = $this->make(TwoFactorQrCodeUriGeneratorService::class, [$user])->run();
393
394
        return $this->renderAjax('two-factor', ['id' => $id, 'uri' => $uri]);
395
    }
396
397
    public function actionTwoFactorEnable($id)
398
    {
399
        Yii::$app->response->format = Response::FORMAT_JSON;
400
401
        /** @var User $user */
402
        $user = $this->userQuery->whereId($id)->one();
403
404
        if (null === $user) {
405
            return [
406
                'success' => false,
407
                'message' => Yii::t('usuario', 'User not found.')
408
            ];
409
        }
410
        $code = Yii::$app->request->get('code');
411
412
        $success = $this
413
            ->make(TwoFactorCodeValidator::class, [$user, $code, $this->module->twoFactorAuthenticationCycles])
414
            ->validate();
415
416
        $success = $success && $user->updateAttributes(['auth_tf_enabled' => '1']);
417
418
        return [
419
            'success' => $success,
420
            'message' => $success
421
                ? Yii::t('usuario', 'Two factor authentication successfully enabled.')
422
                : Yii::t('usuario', 'Verification failed. Please, enter new code.')
423
        ];
424
    }
425
426
    public function actionTwoFactorDisable($id)
427
    {
428
        /** @var User $user */
429
        $user = $this->userQuery->whereId($id)->one();
430
431
        if (null === $user) {
432
            throw new NotFoundHttpException();
433
        }
434
435
        if ($user->updateAttributes(['auth_tf_enabled' => '0'])) {
436
            Yii::$app
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
437
                ->getSession()
438
                ->setFlash('success', Yii::t('usuario', 'Two factor authentication has been disabled.'));
439
        } else {
440
            Yii::$app
441
                ->getSession()
442
                ->setFlash('danger', Yii::t('usuario', 'Unable to disable Two factor authentication.'));
443
        }
444
445
        $this->redirect(['account']);
446
    }
447
}
448