Completed
Push — master ( 298ac7...0024da )
by Oleg
12:58
created

UpdatePasswordAction::process()   B

Complexity

Conditions 5
Paths 14

Size

Total Lines 44
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 0
cts 42
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 37
nc 14
nop 2
crap 30
1
<?php
2
declare(strict_types=1);
3
4
namespace SlayerBirden\DataFlowServer\Authentication\Controller;
5
6
use Doctrine\Common\Collections\Criteria;
7
use Doctrine\ORM\EntityManager;
8
use Doctrine\ORM\ORMException;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Psr\Log\LoggerInterface;
14
use SlayerBirden\DataFlowServer\Authentication\Entities\Password;
15
use SlayerBirden\DataFlowServer\Authentication\Exception\PasswordRestrictionsException;
16
use SlayerBirden\DataFlowServer\Authentication\Hydrator\PasswordHydrator;
17
use SlayerBirden\DataFlowServer\Authentication\PasswordManagerInterface;
18
use SlayerBirden\DataFlowServer\Notification\DangerMessage;
19
use SlayerBirden\DataFlowServer\Notification\SuccessMessage;
20
use SlayerBirden\DataFlowServer\Stdlib\Validation\ValidationResponseFactory;
21
use Zend\Diactoros\Response\JsonResponse;
22
use Zend\Hydrator\ExtractionInterface;
23
use Zend\InputFilter\InputFilterInterface;
24
25
class UpdatePasswordAction implements MiddlewareInterface
26
{
27
    /**
28
     * @var EntityManager
29
     */
30
    private $entityManager;
31
    /**
32
     * @var LoggerInterface
33
     */
34
    private $logger;
35
    /**
36
     * @var PasswordManagerInterface
37
     */
38
    private $passwordManager;
39
    /**
40
     * @var ExtractionInterface
41
     */
42
    private $extraction;
43
    /**
44
     * @var InputFilterInterface
45
     */
46
    private $inputFilter;
47
    /**
48
     * @var PasswordHydrator
49
     */
50
    private $hydration;
51
52
    public function __construct(
53
        EntityManager $entityManager,
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
54
        InputFilterInterface $inputFilter,
55
        LoggerInterface $logger,
56
        PasswordManagerInterface $passwordManager,
57
        ExtractionInterface $extraction,
58
        PasswordHydrator $hydration
59
    ) {
60
        $this->entityManager = $entityManager;
61
        $this->logger = $logger;
62
        $this->passwordManager = $passwordManager;
63
        $this->extraction = $extraction;
64
        $this->inputFilter = $inputFilter;
65
        $this->hydration = $hydration;
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
72
    {
73
        $data = $request->getParsedBody();
74
        $this->inputFilter->setData($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $request->getParsedBody() on line 73 can also be of type null or object; however, Zend\InputFilter\InputFilterInterface::setData() does only seem to accept array|object<Traversable>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
75
76
        if ($this->inputFilter->isValid()) {
77
            $this->entityManager->beginTransaction();
78
            try {
79
                $pw = $this->updatePassword($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $request->getParsedBody() on line 73 can also be of type null or object; however, SlayerBirden\DataFlowSer...ction::updatePassword() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
80
                $this->entityManager->flush();
81
                $this->entityManager->commit();
82
83
                return new JsonResponse([
84
                    'data' => [
85
                        'password' => $this->extraction->extract($pw),
86
                        'validation' => [],
87
                    ],
88
                    'success' => true,
89
                    'msg' => new SuccessMessage('Password successfully updated.'),
90
                ], 200);
91
            } catch (PasswordRestrictionsException | \InvalidArgumentException $exception) {
92
                $this->entityManager->rollback();
93
                $msg = new DangerMessage($exception->getMessage());
94
                $status = 400;
95
            } catch (ORMException $exception) {
96
                $this->logger->error((string)$exception);
97
                $this->entityManager->rollback();
98
                $msg = new DangerMessage('There was an error while updating password.');
99
                $status = 400;
100
            } catch (\Throwable $exception) {
101
                $this->logger->error((string)$exception);
102
                $this->entityManager->rollback();
103
                $msg = new DangerMessage('Unknown app error.');
104
                $status = 500;
105
            }
106
            return new JsonResponse([
107
                'data' => [],
108
                'success' => false,
109
                'msg' => $msg,
110
            ], $status);
111
        } else {
112
            return (new ValidationResponseFactory())('password', $this->inputFilter);
113
        }
114
    }
115
116
    /**
117
     * @param array $data
118
     * @return Password
119
     * @throws PasswordRestrictionsException
120
     * @throws ORMException
121
     * @throws \Exception
122
     */
123
    private function updatePassword(array $data): Password
124
    {
125
        $hash = $this->passwordManager->getHash($data['password']);
126
        $userPasswords = $this->entityManager
127
            ->getRepository(Password::class)
128
            ->matching(
129
                Criteria::create()->where(Criteria::expr()->eq('owner', $data['owner']))
130
            );
131
        # check if mentioned
132
        /** @var Password $item */
133
        foreach ($userPasswords as $item) {
134
            if ($hash === $item->getHash()) {
135
                throw new PasswordRestrictionsException(
136
                    'You have already used this password before. Please use a new one.'
137
                );
138
            }
139
            if ($item->isActive()) {
140
                $item->setActive(false);
141
                $this->entityManager->persist($item);
142
            }
143
        }
144
145
        return $this->hydration->hydrate($data, new Password());
146
    }
147
}
148