Completed
Push — issue#767 ( 20b3b1...32deeb )
by Guilherme
06:59
created

CompleteUserInfoTaskValidator::checkScopes()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 23
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 1
nop 1
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
ccs 15
cts 15
cp 1
crap 3
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\OpenIDBundle\Task;
12
13
use FOS\OAuthServerBundle\Event\OAuthEvent;
14
use LoginCidadao\CoreBundle\Entity\City;
15
use LoginCidadao\CoreBundle\Entity\Country;
16
use LoginCidadao\CoreBundle\Entity\State;
17
use LoginCidadao\CoreBundle\Model\PersonInterface;
18
use LoginCidadao\OAuthBundle\Model\ClientInterface;
19
use LoginCidadao\ValidationBundle\Validator\Constraints\CPFValidator;
20
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21
use Symfony\Component\HttpFoundation\Request;
22
23
class CompleteUserInfoTaskValidator
24
{
25
    /** @var EventDispatcherInterface */
26
    private $dispatcher;
27
28
    /**
29
     * Allow the CompleteUserInfoTask to be skipped if the user has already authorized the RP and the RP didn't ask
30
     * for the Task explicitly.
31
     * @var bool
32
     */
33
    private $skipIfAuthorized;
34
35
    /**
36
     * CompleteUserInfoTaskValidator constructor.
37
     * @param EventDispatcherInterface $dispatcher
38
     * @param bool $skipIfAuthorized
39
     */
40 9
    public function __construct(EventDispatcherInterface $dispatcher, $skipIfAuthorized)
41
    {
42 9
        $this->dispatcher = $dispatcher;
43 9
        $this->skipIfAuthorized = $skipIfAuthorized;
44 9
    }
45
46
    /**
47
     * Check if prompt=consent was requested (also checks the Nonce)
48
     *
49
     * @param Request $request
50
     * @return bool
51
     */
52 4
    public function shouldPromptConsent(Request $request)
53
    {
54 4
        return $request->get('prompt', null) === 'consent' && $this->isNonceValid($request);
55
    }
56
57
    /**
58
     * Checks if the Client/RP already has the user's authorization.
59
     * @param PersonInterface $person
60
     * @param ClientInterface $client
61
     * @return bool
62
     */
63 2
    public function isClientAuthorized(PersonInterface $person, ClientInterface $client)
64
    {
65
        /** @var OAuthEvent $event */
66 2
        $event = $this->dispatcher->dispatch(
67 2
            OAuthEvent::PRE_AUTHORIZATION_PROCESS,
68 2
            new OAuthEvent($person, $client)
69
        );
70
71 2
        return $event->isAuthorizedClient();
72
    }
73
74
    /**
75
     * Check if the Request's route is valid for this Task
76
     * @param Request $request
77
     * @return bool
78
     */
79 5
    public function isRouteValid(Request $request)
80
    {
81 5
        $route = $request->get('_route');
82 5
        $scopes = $request->get('scope', false);
83
84 5
        return $route === '_authorize_validate' && false !== $scopes;
85
    }
86
87
    /**
88
     * Get the CompleteUserInfoTask for the $user's missing data
89
     *
90
     * @param PersonInterface $user
91
     * @param ClientInterface $client
92
     * @param Request $request
93
     * @return CompleteUserInfoTask|null
94
     */
95 3
    public function getCompleteUserInfoTask(PersonInterface $user, ClientInterface $client, Request $request)
96
    {
97 3
        if (!$this->isRouteValid($request) || $this->canSkipTask($request, $user, $client)) {
98 1
            return null;
99
        }
100
101 2
        $scopes = explode(' ', $request->get('scope', false));
0 ignored issues
show
Bug introduced by
It seems like $request->get('scope', false) can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

101
        $scopes = explode(' ', /** @scrutinizer ignore-type */ $request->get('scope', false));
Loading history...
102 2
        $emptyClaims = array_intersect($scopes, $this->checkScopes($user));
103
104 2
        if (empty($emptyClaims) > 0) {
105 1
            return null;
106
        }
107
108 1
        return new CompleteUserInfoTask($client->getPublicId(), $emptyClaims, $request->get('nonce'));
109
    }
110
111
    /**
112
     * @param Request $request
113
     * @return bool
114
     */
115 2
    private function isNonceValid(Request $request)
116
    {
117 2
        $nonce = $request->get('nonce', null);
118
119
        // TODO: check is nonce was already used
120
121 2
        return $nonce !== null;
122
    }
123
124 2
    private function checkScopes(PersonInterface $user)
125
    {
126 2
        $fullName = $user->getFullName();
127 2
        $missingScope = $this->setSynonyms([
128 2
            'full_name' => $this->isFilled($fullName) && $this->isFilled($user->getSurname()),
129 2
            'phone_number' => $this->isFilled($user->getMobile()),
130 2
            'country' => $user->getCountry() instanceof Country,
131 2
            'state' => $user->getState() instanceof State,
132 2
            'city' => $user->getCity() instanceof City,
133 2
            'birthday' => $user->getBirthdate() instanceof \DateTime,
134 2
            'email_verified' => $user->getEmailConfirmedAt() instanceof \DateTime,
135 2
            'cpf' => $this->isFilled($user->getCpf()) && CPFValidator::isCPFValid($user->getCpf()),
136
        ], [
137 2
            'name' => 'full_name',
138
            'birthdate' => 'birthday',
139
            'surname' => 'full_name',
140
            'mobile' => 'phone_number',
141
            'email' => 'email_verified',
142
        ]);
143
144 2
        return array_keys(
145 2
            array_filter($missingScope, function ($value) {
146 2
                return !$value;
147 2
            })
148
        );
149
    }
150
151 2
    private function isFilled($value)
152
    {
153 2
        return $value && strlen($value) > 0;
154
    }
155
156 2
    private function setSynonyms(array $data, array $synonyms)
157
    {
158 2
        foreach ($synonyms as $synonym => $original) {
159 2
            $data[$synonym] = $data[$original];
160
        }
161
162 2
        return $data;
163
    }
164
165
    /**
166
     * Check if this Task can be skipped based on an existing Authorization
167
     *
168
     * @param Request $request
169
     * @param PersonInterface $user
170
     * @param ClientInterface $client
171
     * @return bool
172
     */
173 3
    private function canSkipTask(Request $request, PersonInterface $user, ClientInterface $client)
174
    {
175
        // Can this Task be skipped if the RP is already Authorized?
176 3
        if ($this->skipIfAuthorized) {
177
            // To force this task's execution, the RP MUST send prompt=consent and a nonce value.
178 1
            $shouldPromptConsent = $this->shouldPromptConsent($request);
179 1
            $isAuthorized = $this->isClientAuthorized($user, $client);
180
181
            // Skip if the RP is authorized and the Task wasn't explicitly requested
182 1
            if ($isAuthorized && !$shouldPromptConsent) {
183 1
                return true;
184
            }
185
        }
186
187 2
        return false;
188
    }
189
}
190