Issues (25)

src/Client/Facebook.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\AuthClient\Client;
6
7
use Psr\Http\Message\RequestInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Yiisoft\Yii\AuthClient\OAuth2;
10
use Yiisoft\Yii\AuthClient\OAuthToken;
11
use Yiisoft\Yii\AuthClient\RequestUtil;
12
13
/**
14
 * Facebook allows authentication via Facebook OAuth.
15
 *
16
 * In order to use Facebook OAuth you must register your application at <https://developers.facebook.com/apps>.
17
 *
18
 * Example application configuration:
19
 *
20
 * ```php
21
 * 'components' => [
22
 *     'authClientCollection' => [
23
 *         'class' => Yiisoft\Yii\AuthClient\Collection::class,
24
 *         'clients' => [
25
 *             'facebook' => [
26
 *                 'class' => Yiisoft\Yii\AuthClient\Clients\Facebook::class,
27
 *                 'clientId' => 'facebook_client_id',
28
 *                 'clientSecret' => 'facebook_client_secret',
29
 *             ],
30
 *         ],
31
 *     ]
32
 *     // ...
33
 * ]
34
 * ```
35
 *
36
 * @link https://developers.facebook.com/apps
37
 * @link https://developers.facebook.com/docs/reference/api
38
 */
39
final class Facebook extends OAuth2
40
{
41
    protected string $authUrl = 'https://www.facebook.com/dialog/oauth';
42
    protected string $tokenUrl = 'https://graph.facebook.com/oauth/access_token';
43
    protected string $endpoint = 'https://graph.facebook.com';
44
    /**
45
     * @var array list of attribute names, which should be requested from API to initialize user attributes.
46
     */
47
    private array $attributeNames = [
48
        'name',
49
        'email',
50
    ];
51
    protected bool $autoRefreshAccessToken = false; // Facebook does not provide access token refreshment
52
    /**
53
     * @var bool whether to automatically upgrade short-live (2 hours) access token to long-live (60 days) one, after fetching it.
54
     *
55
     * @see exchangeToken()
56
     */
57
    private bool $autoExchangeAccessToken = false;
58
    /**
59
     * @var string URL endpoint for the client auth code generation.
60
     *
61
     * @link https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension
62
     * @see fetchClientAuthCode()
63
     * @see fetchClientAccessToken()
64
     */
65
    private string $clientAuthCodeUrl = 'https://graph.facebook.com/oauth/client_code';
66
67
    public function applyAccessTokenToRequest(RequestInterface $request, OAuthToken $accessToken): RequestInterface
68
    {
69
        $request = parent::applyAccessTokenToRequest($request, $accessToken);
70
71
        $params = [];
72
        if (($machineId = $accessToken->getParam('machine_id')) !== null) {
73
            $params['machine_id'] = $machineId;
74
        }
75
        $params['appsecret_proof'] = hash_hmac('sha256', $accessToken->getToken(), $this->clientSecret);
0 ignored issues
show
It seems like $accessToken->getToken() can also be of type null; however, parameter $data of hash_hmac() 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

75
        $params['appsecret_proof'] = hash_hmac('sha256', /** @scrutinizer ignore-type */ $accessToken->getToken(), $this->clientSecret);
Loading history...
76
        return RequestUtil::addParams($request, $params);
77
    }
78
79
    public function fetchAccessToken(ServerRequestInterface $request, $authCode, array $params = []): OAuthToken
80
    {
81
        $token = parent::fetchAccessToken($request, $authCode, $params);
82
        if ($this->autoExchangeAccessToken) {
83
            $token = $this->exchangeAccessToken($token);
84
        }
85
        return $token;
86
    }
87
88
    /**
89
     * Exchanges short-live (2 hours) access token to long-live (60 days) one.
90
     * Note that this method will success for already long-live token, but will not actually prolong it any further.
91
     * Pay attention, that this method will fail on already expired access token.
92
     *
93
     * @link https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension
94
     *
95
     * @param OAuthToken $token short-live access token.
96
     *
97
     * @return OAuthToken long-live access token.
98
     */
99
    public function exchangeAccessToken(OAuthToken $token): OAuthToken
100
    {
101
        $params = [
0 ignored issues
show
The assignment to $params is dead and can be removed.
Loading history...
102
            'grant_type' => 'fb_exchange_token',
103
            'fb_exchange_token' => $token->getToken(),
104
        ];
105
106
        $request = $this->createRequest('POST', $this->getTokenUrl());
107
        //->setParams($params);
108
        $this->applyClientCredentialsToRequest($request);
109
        $response = $this->sendRequest($request);
110
111
        $token = $this->createToken(['params' => $response]);
112
        $this->setAccessToken($token);
113
114
        return $token;
115
    }
116
117
    /**
118
     * Requests the authorization code for the client-specific access token.
119
     * This make sense for the distributed applications, which provides several Auth clients (web and mobile)
120
     * to avoid triggering Facebook's automated spam systems.
121
     *
122
     * @link https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension
123
     * @see fetchClientAccessToken()
124
     *
125
     * @param ServerRequestInterface $incomingRequest
126
     * @param OAuthToken|null $token access token, if not set {@see accessToken} will be used.
127
     * @param array $params additional request params.
128
     *
129
     * @return string client auth code.
130
     */
131
    public function fetchClientAuthCode(
132
        ServerRequestInterface $incomingRequest,
133
        OAuthToken $token = null,
134
        $params = []
135
    ): string {
136
        if ($token === null) {
137
            $token = $this->getAccessToken();
138
        }
139
140
        $params = array_merge(
141
            [
142
                'access_token' => $token->getToken(),
143
                'redirect_uri' => $this->getReturnUrl($incomingRequest),
144
            ],
145
            $params
146
        );
147
148
        $request = $this->createRequest('POST', $this->clientAuthCodeUrl);
149
        $request = RequestUtil::addParams($request, $params);
150
151
        $request = $this->applyClientCredentialsToRequest($request);
152
153
        $response = $this->sendRequest($request);
154
155
        // TODO: parse response!
156
157
        return $response['code'];
158
    }
159
160
    /**
161
     * Fetches access token from client-specific authorization code.
162
     * This make sense for the distributed applications, which provides several Auth clients (web and mobile)
163
     * to avoid triggering Facebook's automated spam systems.
164
     *
165
     * @link https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension
166
     * @see fetchClientAuthCode()
167
     *
168
     * @param ServerRequestInterface $incomingRequest
169
     * @param string $authCode client auth code.
170
     * @param array $params
171
     *
172
     * @return OAuthToken long-live client-specific access token.
173
     */
174
    public function fetchClientAccessToken(
175
        ServerRequestInterface $incomingRequest,
176
        string $authCode,
177
        array $params = []
178
    ): OAuthToken {
179
        $params = array_merge(
180
            [
181
                'code' => $authCode,
182
                'redirect_uri' => $this->getReturnUrl($incomingRequest),
183
                'client_id' => $this->clientId,
184
            ],
185
            $params
186
        );
187
188
        $request = $this->createRequest('POST', $this->getTokenUrl());
189
        $request = RequestUtil::addParams($request, $params);
190
191
        $response = $this->sendRequest($request);
192
193
        $token = $this->createToken(['params' => $response]);
194
        $this->setAccessToken($token);
195
196
        return $token;
197
    }
198
199
    /**
200
     * @return string service name.
201
     */
202
    public function getName(): string
203
    {
204
        return 'facebook';
205
    }
206
207
    /**
208
     * @return string service title.
209
     */
210
    public function getTitle(): string
211
    {
212
        return 'Facebook';
213
    }
214
215
    protected function initUserAttributes(): array
216
    {
217
        return $this->api(
218
            'me',
219
            'GET',
220
            [
221
                'fields' => implode(',', $this->attributeNames),
222
            ]
223
        );
224
    }
225
226
    protected function defaultViewOptions(): array
227
    {
228
        return [
229
            'popupWidth' => 860,
230
            'popupHeight' => 480,
231
        ];
232
    }
233
234
    protected function getDefaultScope(): string
235
    {
236
        return 'email';
237
    }
238
}
239