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

TokenManager::getToken()   B

Complexity

Conditions 3
Paths 15

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 0
cts 19
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 20
nc 15
nop 3
crap 12
1
<?php
2
declare(strict_types=1);
3
4
namespace SlayerBirden\DataFlowServer\Authentication\Service;
5
6
use Doctrine\Common\Collections\Criteria;
7
use Doctrine\ORM\EntityManager;
8
use Doctrine\ORM\ORMException;
9
use Psr\Log\LoggerInterface;
10
use SlayerBirden\DataFlowServer\Authentication\Entities\Grant;
11
use SlayerBirden\DataFlowServer\Authentication\Entities\Token;
12
use SlayerBirden\DataFlowServer\Authentication\Exception\InvalidCredentialsException;
13
use SlayerBirden\DataFlowServer\Authentication\Exception\PermissionDeniedException;
14
use SlayerBirden\DataFlowServer\Authentication\PasswordManagerInterface;
15
use SlayerBirden\DataFlowServer\Authentication\TokenManagerInterface;
16
use SlayerBirden\DataFlowServer\Authorization\PermissionManagerInterface;
17
use SlayerBirden\DataFlowServer\Domain\Entities\User;
18
19
class TokenManager implements TokenManagerInterface
20
{
21
    /**
22
     * @var PasswordManagerInterface
23
     */
24
    private $passwordManager;
25
    /**
26
     * @var EntityManager
27
     */
28
    private $entityManager;
29
    /**
30
     * @var LoggerInterface
31
     */
32
    private $logger;
33
    /**
34
     * @var PermissionManagerInterface
35
     */
36
    private $permissionManager;
37
38 4
    public function __construct(
39
        PasswordManagerInterface $passwordManager,
40
        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...
41
        LoggerInterface $logger,
42
        PermissionManagerInterface $permissionManager
43
    ) {
44 4
        $this->passwordManager = $passwordManager;
45 4
        $this->entityManager = $entityManager;
46 4
        $this->logger = $logger;
47 4
        $this->permissionManager = $permissionManager;
48 4
    }
49
50
    /**
51
     * @inheritdoc
52
     * @throws \Exception
53
     */
54
    public function getToken(string $user, string $password, array $resources): Token
55
    {
56
        try {
57
            $user = $this->getByUsername($user);
58
            if ($this->passwordManager->isValid($password, $user)) {
59
                $token = new Token();
60
                $now = new \DateTime();
61
                // default token for 1 month
62
                $due = (new \DateTime())->add(new \DateInterval('P1M'));
63
64
                $token->setActive(true);
65
                $token->setCreatedAt($now);
66
                $token->setOwner($user);
67
                $token->setDue($due);
68
                $token->setGrants($this->getGrants($token, $resources, $user));
69
                $token->setToken($this->generateToken());
70
71
                $this->entityManager->persist($token);
72
                $this->entityManager->flush();
73
74
                return $token;
75
            }
76
        } catch (ORMException $exception) {
77
            $this->logger->error((string)$exception);
78
            throw new InvalidCredentialsException('Unknown ORM error.', $exception);
79
        }
80
81
        throw new InvalidCredentialsException('Could not validate credentials.');
82
    }
83
84
    private function getByUsername(string $user): User
85
    {
86
        $criteria = Criteria::create();
87
        $criteria->where(Criteria::expr()->eq('email', $user));
88
        $users = $this->entityManager->getRepository(User::class)->matching($criteria);
89
90
        if ($users->count()) {
91
            return $users->first();
92
        } else {
93
            throw new InvalidCredentialsException('Could not find user by provided email.');
94
        }
95
    }
96
97
    /**
98
     * UUIDv4
99
     * https://stackoverflow.com/a/15875555/927404
100
     *
101
     * @return string
102
     */
103 4
    private function generateToken(): string
104
    {
105 4
        $data = random_bytes(16);
106
107 4
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
108 4
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
109
110 4
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
111
    }
112
113
    /**
114
     * @param Token $token
115
     * @param array $resources
116
     * @param User $user
117
     * @return Grant[]
118
     * @throws PermissionDeniedException
119
     * @throws ORMException
120
     */
121 4
    private function getGrants(Token $token, array $resources, User $user): array
122
    {
123 4
        $grants = [];
124 4
        foreach ($resources as $resource) {
125 4
            if (!$this->permissionManager->isAllowed($resource, $user)) {
126
                throw new PermissionDeniedException(sprintf('%s is not allowed for provided user.', $resource));
127
            }
128 4
            $grant = new Grant();
129 4
            $grant->setToken($token);
130 4
            $grant->setResource($resource);
131 4
            $this->entityManager->persist($grant);
132 4
            $grants[] = $grant;
133
        }
134
135 4
        return $grants;
136
    }
137
138
    /**
139
     * @param User $user
140
     * @param array $resources
141
     * @return Token
142
     * @throws ORMException
143
     * @throws \Doctrine\ORM\OptimisticLockException
144
     * @throws \Exception
145
     */
146 4
    public function getTmpToken(User $user, array $resources): Token
147
    {
148 4
        $token = new Token();
149 4
        $now = new \DateTime();
150
        // tmp token for 1 hour
151 4
        $due = (new \DateTime())->add(new \DateInterval('PT1H'));
152
153 4
        $token->setActive(true);
154 4
        $token->setCreatedAt($now);
155 4
        $token->setOwner($user);
156 4
        $token->setDue($due);
157 4
        $token->setGrants($this->getGrants($token, $resources, $user));
158 4
        $token->setToken($this->generateToken());
159
160 4
        $this->entityManager->persist($token);
161 4
        $this->entityManager->flush();
162
163 4
        return $token;
164
    }
165
}
166