Completed
Push — master ( 142578...5f4f14 )
by Igor
06:03
created

AuthHandler::parseProfileFacebook()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
crap 1
1
<?php
2
3
namespace app\components\auth;
4
5
use Yii;
6
use yii\authclient\ClientInterface;
7
use yii\helpers\ArrayHelper;
8
use app\models\User;
9
use app\models\UserProvider;
10
11
/**
12
 * AuthHandler handles successful authentication
13
 */
14
class AuthHandler
15
{
16
    /**
17
     * @var int
18
     */
19
    private $type;
20
    /**
21
     * @var User
22
     */
23
    private $user;
24
    /**
25
     * @var string
26
     */
27
    private $email;
28
    /**
29
     * @var bool
30
     */
31
    private $verified = false;
32
    /**
33
     * @var bool
34
     */
35
    private $exist = false;
36
    /**
37
     * @var ClientInterface
38
     */
39
    private $client;
40
41 8
    public function __construct(ClientInterface $client)
42
    {
43 8
        $this->client = $client;
44 8
        $this->type = UserProvider::getTypeByName($client->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface yii\authclient\ClientInterface 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...
Documentation Bug introduced by
The property $type was declared of type integer, but \app\models\UserProvider...TypeByName($client->id) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
45 8
    }
46
47
    /**
48
     * @SuppressWarnings(PHPMD.ElseExpression)
49
     */
50 8
    public function handle()
51
    {
52 8
        $user = $this->findUserByProvider();
53 8
        if ($user) {
54 2
            $this->exist = true;
55
        } else {
56 8
            $profile = $this->client->getUserAttributes();
57 8
            $this->email = ArrayHelper::getValue($profile, 'email');
58 8
            $this->verified = ArrayHelper::getValue($profile, 'verified');
59
60 8
            if ($this->verified && !empty($this->email)) {
61 1
                $user = User::findByEmail($this->email);
62
            }
63
64 8
            if (!$user) {
65 8
                $user = new User();
66 8
                $user->setProfile($this->parseProfile());
67
            }
68
        }
69
70 8
        $user->setProviders($this->parseProvider());
71 8
        $this->user = $user;
72
73 8
        return $this;
74
    }
75
76 8
    public function getUser()
77
    {
78 8
        return $this->user;
79
    }
80
81 8
    public function getEmail()
82
    {
83 8
        return $this->email;
84
    }
85
86 8
    public function isExist()
87
    {
88 8
        return $this->exist;
89
    }
90
91 8
    public function isVerified()
92
    {
93 8
        return $this->verified;
94
    }
95
96
    /**
97
     * Find user by provider
98
     *
99
     * @param string $token password reset token
0 ignored issues
show
Bug introduced by
There is no parameter named $token. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
100
     * @return app\models\User|null
101
     */
102 8
    private function findUserByProvider()
103
    {
104 8
        $profile = $this->client->getUserAttributes();
105 8
        $id = ArrayHelper::getValue($profile, 'id');
106 8
        if ($provider = UserProvider::findByProvider($this->type, $id)) {
107 2
            $user = $provider->user;
108 2
            $provider->setAttributes($this->parseProvider());
0 ignored issues
show
Bug introduced by
The method setAttributes cannot be called on $provider (of type array|boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
109 2
            $provider->save();
0 ignored issues
show
Bug introduced by
The method save cannot be called on $provider (of type array|boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
110
111 2
            return $user;
112
        }
113 8
        return null;
114
    }
115
116
    /**
117
     * Parse provider
118
     *
119
     * @return array
120
     */
121 8
    private function parseProvider()
122
    {
123 8
        $profile = $this->client->getUserAttributes();
124 8
        $token = $this->client->getAccessToken()->getParams();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface yii\authclient\ClientInterface as the method getAccessToken() does only exist in the following implementations of said interface: yii\authclient\BaseOAuth, yii\authclient\OAuth1, yii\authclient\OAuth2, yii\authclient\clients\Facebook, yii\authclient\clients\GitHub, yii\authclient\clients\Google, yii\authclient\clients\GoogleHybrid, yii\authclient\clients\LinkedIn, yii\authclient\clients\Live, yii\authclient\clients\Twitter, yii\authclient\clients\VKontakte, yii\authclient\clients\Yandex.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
125
126 8
        $data = [];
127 8
        switch ($this->type) {
128 8
            case UserProvider::TYPE_FACEBOOK:
129 1
                $data = $this->parseProviderFacebook($profile, $token);
130 1
                break;
131
132 7
            case UserProvider::TYPE_VKONTAKTE:
133 6
                $data = $this->parseProviderVkontakte($profile, $token);
134 6
                break;
135
136 1
            case UserProvider::TYPE_TWITTER:
137 1
                $data = $this->parseProviderTwitter($profile, $token);
138 1
                break;
139
        }
140 8
        $data['type'] = $this->type;
141 8
        return $data;
142
    }
143
144
    /**
145
     * Parse profile
146
     *
147
     * @return array
148
     */
149 8
    private function parseProfile()
150
    {
151 8
        $profile = $this->client->getUserAttributes();
152
153 8
        $data = [];
154 8
        switch ($this->type) {
155 8
            case UserProvider::TYPE_FACEBOOK:
156 1
                $data = $this->parseProfileFacebook($profile);
157 1
                break;
158
159 7
            case UserProvider::TYPE_VKONTAKTE:
160 6
                $data = $this->parseProfileVkontakte($profile);
161 6
                break;
162
163 1
            case UserProvider::TYPE_TWITTER:
164 1
                $data = $this->parseProfileTwitter($profile);
165 1
                break;
166
        }
167 8
        return $data;
168
    }
169
170
    /**
171
     * Prepare provider attributes for facebook
172
     *
173
     * @return array
174
     */
175 1
    private function parseProviderFacebook($profile, $token)
176
    {
177
        return [
178 1
            'profile_id' => ArrayHelper::getValue($profile, 'id'),
179 1
            'profile_url' => ArrayHelper::getValue($profile, 'link'),
180 1
            'access_token' => ArrayHelper::getValue($token, 'access_token'),
181 1
            'access_token_secret' => ''
182
        ];
183
    }
184
185
    /**
186
     * Prepare provider attributes for vkontakte
187
     *
188
     * @return array
189
     */
190 6
    private function parseProviderVkontakte($profile, $token)
191
    {
192
        return [
193 6
            'profile_id' => ArrayHelper::getValue($profile, 'id'),
194 6
            'profile_url' => 'https://vk.com/id' . ArrayHelper::getValue($profile, 'id'),
195 6
            'access_token' => ArrayHelper::getValue($token, 'access_token'),
196 6
            'access_token_secret' => ''
197
        ];
198
    }
199
200
    /**
201
     * Prepare provider attributes for twitter
202
     *
203
     * @return array
204
     */
205 1
    private function parseProviderTwitter($profile, $token)
206
    {
207
        return [
208 1
            'profile_id' => ArrayHelper::getValue($profile, 'id'),
209 1
            'profile_url' => 'https://twitter.com/' . ArrayHelper::getValue($profile, 'screen_name'),
210 1
            'access_token' => ArrayHelper::getValue($token, 'oauth_token'),
211 1
            'access_token_secret' => ArrayHelper::getValue($token, 'oauth_token_secret')
212
        ];
213
    }
214
215
    /**
216
     * Prepare profile attributes for facebook
217
     *
218
     * @return array
219
     */
220 1
    private function parseProfileFacebook($profile)
221
    {
222
        return [
223 1
            'full_name' => trim(ArrayHelper::getValue($profile, 'name')),
224 1
            'birth_day' => '—',
225 1
            'photo' => ArrayHelper::getValue($profile, 'picture.data.url', '')
226
        ];
227
    }
228
229
    /**
230
     * Prepare profile attributes for vkontakte
231
     *
232
     * @return array
233
     */
234 6
    private function parseProfileVkontakte($profile)
235
    {
236 6
        $firstName = ArrayHelper::getValue($profile, 'first_name');
237 6
        $lastName = ArrayHelper::getValue($profile, 'last_name');
238 6
        $birthDay = date_create_from_format('d.m.Y', ArrayHelper::getValue($profile, 'bdate'));
239
        return [
240 6
            'full_name' => trim($firstName . ' ' . $lastName),
241 6
            'birth_day' => date_format($birthDay, 'Y-m-d'),
242 6
            'photo' => str_replace('_50', '_400', ArrayHelper::getValue($profile, 'photo'))
243
        ];
244
    }
245
246
    /**
247
     * Prepare profile attributes for twitter
248
     *
249
     * @param array $data Data from social network
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
250
     * @return array
251
     */
252 1
    private function parseProfileTwitter($profile)
253
    {
254 1
        $photo = ArrayHelper::getValue($profile, 'profile_image_url');
255
        return [
256 1
            'full_name' => $profile['name'],
257 1
            'photo' => str_replace('_normal', '_400x400', $photo)
258
        ];
259
    }
260
}
261