Completed
Push — master ( 178a08...5c0b6f )
by Oleg
05:22
created

TokenManager::getToken()   A

Complexity

Conditions 3
Paths 15

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3.0105

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 17
cts 19
cp 0.8947
rs 9.456
c 0
b 0
f 0
cc 3
nc 15
nop 3
crap 3.0105
1
<?php
2
declare(strict_types=1);
3
4
namespace SlayerBirden\DataFlowServer\Authentication\Service;
5
6
use Doctrine\Common\Collections\ArrayCollection;
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\ORM\EntityManager;
9
use Doctrine\ORM\ORMException;
10
use Psr\Log\LoggerInterface;
11
use SlayerBirden\DataFlowServer\Authentication\Entities\Grant;
12
use SlayerBirden\DataFlowServer\Authentication\Entities\Token;
13
use SlayerBirden\DataFlowServer\Authentication\Exception\InvalidCredentialsException;
14
use SlayerBirden\DataFlowServer\Authentication\Exception\PermissionDeniedException;
15
use SlayerBirden\DataFlowServer\Authentication\PasswordManagerInterface;
16
use SlayerBirden\DataFlowServer\Authentication\TokenManagerInterface;
17
use SlayerBirden\DataFlowServer\Authorization\PermissionManagerInterface;
18
use SlayerBirden\DataFlowServer\Domain\Entities\User;
19
20
class TokenManager implements TokenManagerInterface
21
{
22
    /**
23
     * @var PasswordManagerInterface
24
     */
25
    private $passwordManager;
26
    /**
27
     * @var EntityManager
28
     */
29
    private $entityManager;
30
    /**
31
     * @var LoggerInterface
32
     */
33
    private $logger;
34
    /**
35
     * @var PermissionManagerInterface
36
     */
37
    private $permissionManager;
38
39 12
    public function __construct(
40
        PasswordManagerInterface $passwordManager,
41
        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...
42
        LoggerInterface $logger,
43
        PermissionManagerInterface $permissionManager
44
    ) {
45 12
        $this->passwordManager = $passwordManager;
46 12
        $this->entityManager = $entityManager;
47 12
        $this->logger = $logger;
48 12
        $this->permissionManager = $permissionManager;
49 12
    }
50
51
    /**
52
     * @inheritdoc
53
     * @throws \Exception
54
     */
55 3
    public function getToken(string $user, string $password, array $resources): Token
56
    {
57
        try {
58 3
            $user = $this->getByUsername($user);
59 3
            if ($this->passwordManager->isValidForUser($password, $user)) {
60 2
                $token = new Token();
61 2
                $now = new \DateTime();
62
                // default token for 1 month
63 2
                $due = (new \DateTime())->add(new \DateInterval('P1M'));
64
65 2
                $token->setActive(true);
66 2
                $token->setCreatedAt($now);
67 2
                $token->setOwner($user);
68 2
                $token->setDue($due);
69 2
                $token->setGrants($this->getGrants($token, $resources, $user));
0 ignored issues
show
Bug introduced by
It seems like $this->getGrants($token, $resources, $user) targeting SlayerBirden\DataFlowSer...kenManager::getGrants() can also be of type array<integer,object<Sla...cation\Entities\Grant>>; however, SlayerBirden\DataFlowSer...ties\Token::setGrants() does only seem to accept object<Doctrine\Common\Collections\Collection>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
70 1
                $token->setToken($this->generateToken());
71
72 1
                $this->entityManager->persist($token);
73 1
                $this->entityManager->flush();
74
75 1
                return $token;
76
            }
77 1
        } catch (ORMException $exception) {
78
            $this->logger->error((string)$exception);
79
            throw new InvalidCredentialsException('Unknown ORM error.', $exception);
80
        }
81
82 1
        throw new InvalidCredentialsException('Could not validate credentials.');
83
    }
84
85 3
    private function getByUsername(string $user): User
86
    {
87 3
        $criteria = Criteria::create();
88 3
        $criteria->where(Criteria::expr()->eq('email', $user));
89 3
        $users = $this->entityManager->getRepository(User::class)->matching($criteria);
90
91 3
        if ($users->count()) {
92 3
            return $users->first();
93
        } else {
94
            throw new InvalidCredentialsException('Could not find user by provided email.');
95
        }
96
    }
97
98
    /**
99
     * UUIDv4
100
     * https://stackoverflow.com/a/15875555/927404
101
     *
102
     * @return string
103
     */
104 6
    private function generateToken(): string
105
    {
106 6
        $data = random_bytes(16);
107
108 6
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
109 6
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
110
111 6
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
112
    }
113
114
    /**
115
     * @param Token $token
116
     * @param array $resources
117
     * @param User $user
118
     * @return ArrayCollection|Grant[]
119
     * @throws PermissionDeniedException
120
     * @throws ORMException
121
     */
122 9
    private function getGrants(Token $token, array $resources, User $user): ArrayCollection
123
    {
124 9
        $grants = [];
125 9
        foreach ($resources as $resource) {
126 9
            if (!$this->permissionManager->isAllowed($resource, $user)) {
127 3
                throw new PermissionDeniedException(sprintf('%s is not allowed for provided user.', $resource));
128
            }
129 6
            $grant = new Grant();
130 6
            $grant->setToken($token);
131 6
            $grant->setResource($resource);
132 6
            $this->entityManager->persist($grant);
133 6
            $grants[] = $grant;
134
        }
135
136 6
        return new ArrayCollection($grants);
137
    }
138
139
    /**
140
     * @param User $user
141
     * @param array $resources
142
     * @return Token
143
     * @throws ORMException
144
     * @throws \Doctrine\ORM\OptimisticLockException
145
     * @throws \Exception
146
     */
147 7
    public function getTmpToken(User $user, array $resources): Token
148
    {
149 7
        $token = new Token();
150 7
        $now = new \DateTime();
151
        // tmp token for 1 hour
152 7
        $due = (new \DateTime())->add(new \DateInterval('PT1H'));
153
154 7
        $token->setActive(true);
155 7
        $token->setCreatedAt($now);
156 7
        $token->setOwner($user);
157 7
        $token->setDue($due);
158 7
        $token->setGrants($this->getGrants($token, $resources, $user));
0 ignored issues
show
Bug introduced by
It seems like $this->getGrants($token, $resources, $user) targeting SlayerBirden\DataFlowSer...kenManager::getGrants() can also be of type array<integer,object<Sla...cation\Entities\Grant>>; however, SlayerBirden\DataFlowSer...ties\Token::setGrants() does only seem to accept object<Doctrine\Common\Collections\Collection>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
159 5
        $token->setToken($this->generateToken());
160
161 5
        $this->entityManager->persist($token);
162 5
        $this->entityManager->flush();
163
164 5
        return $token;
165
    }
166
}
167