@@ -40,225 +40,225 @@ |
||
40 | 40 | */ |
41 | 41 | class Twitter extends OAuth1 |
42 | 42 | { |
43 | - /** |
|
44 | - * {@inheritdoc} |
|
45 | - */ |
|
46 | - protected $apiBaseUrl = 'https://api.twitter.com/1.1/'; |
|
47 | - |
|
48 | - /** |
|
49 | - * {@inheritdoc} |
|
50 | - */ |
|
51 | - protected $authorizeUrl = 'https://api.twitter.com/oauth/authenticate'; |
|
52 | - |
|
53 | - /** |
|
54 | - * {@inheritdoc} |
|
55 | - */ |
|
56 | - protected $requestTokenUrl = 'https://api.twitter.com/oauth/request_token'; |
|
57 | - |
|
58 | - /** |
|
59 | - * {@inheritdoc} |
|
60 | - */ |
|
61 | - protected $accessTokenUrl = 'https://api.twitter.com/oauth/access_token'; |
|
62 | - |
|
63 | - /** |
|
64 | - * {@inheritdoc} |
|
65 | - */ |
|
66 | - protected $apiDocumentation = 'https://dev.twitter.com/web/sign-in/implementing'; |
|
67 | - |
|
68 | - /** |
|
69 | - * {@inheritdoc} |
|
70 | - */ |
|
71 | - protected function getAuthorizeUrl($parameters = []) |
|
72 | - { |
|
73 | - if ($this->config->get('authorize') === true) { |
|
74 | - $this->authorizeUrl = 'https://api.twitter.com/oauth/authorize'; |
|
75 | - } |
|
76 | - |
|
77 | - return parent::getAuthorizeUrl($parameters); |
|
78 | - } |
|
79 | - |
|
80 | - /** |
|
81 | - * {@inheritdoc} |
|
82 | - */ |
|
83 | - public function getUserProfile() |
|
84 | - { |
|
85 | - $response = $this->apiRequest('account/verify_credentials.json', 'GET', [ |
|
86 | - 'include_email' => $this->config->get('include_email') === false ? 'false' : 'true', |
|
87 | - ]); |
|
88 | - |
|
89 | - $data = new Data\Collection($response); |
|
90 | - |
|
91 | - if (!$data->exists('id_str')) { |
|
92 | - throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
93 | - } |
|
94 | - |
|
95 | - $userProfile = new User\Profile(); |
|
96 | - |
|
97 | - $userProfile->identifier = $data->get('id_str'); |
|
98 | - $userProfile->displayName = $data->get('screen_name'); |
|
99 | - $userProfile->description = $data->get('description'); |
|
100 | - $userProfile->firstName = $data->get('name'); |
|
101 | - $userProfile->email = $data->get('email'); |
|
102 | - $userProfile->emailVerified = $data->get('email'); |
|
103 | - $userProfile->webSiteURL = $data->get('url'); |
|
104 | - $userProfile->region = $data->get('location'); |
|
105 | - |
|
106 | - $userProfile->profileURL = $data->exists('screen_name') |
|
107 | - ? ('https://twitter.com/' . $data->get('screen_name')) |
|
108 | - : ''; |
|
109 | - |
|
110 | - $photoSize = $this->config->get('photo_size') ?: 'original'; |
|
111 | - $photoSize = $photoSize === 'original' ? '' : "_{$photoSize}"; |
|
112 | - $userProfile->photoURL = $data->exists('profile_image_url_https') |
|
113 | - ? str_replace('_normal', $photoSize, $data->get('profile_image_url_https')) |
|
114 | - : ''; |
|
115 | - |
|
116 | - $userProfile->data = [ |
|
117 | - 'followed_by' => $data->get('followers_count'), |
|
118 | - 'follows' => $data->get('friends_count'), |
|
119 | - ]; |
|
120 | - |
|
121 | - return $userProfile; |
|
122 | - } |
|
123 | - |
|
124 | - /** |
|
125 | - * {@inheritdoc} |
|
126 | - */ |
|
127 | - public function getUserContacts($parameters = []) |
|
128 | - { |
|
129 | - $parameters = ['cursor' => '-1'] + $parameters; |
|
130 | - |
|
131 | - $response = $this->apiRequest('friends/ids.json', 'GET', $parameters); |
|
132 | - |
|
133 | - $data = new Data\Collection($response); |
|
134 | - |
|
135 | - if (!$data->exists('ids')) { |
|
136 | - throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
137 | - } |
|
138 | - |
|
139 | - if ($data->filter('ids')->isEmpty()) { |
|
140 | - return []; |
|
141 | - } |
|
142 | - |
|
143 | - $contacts = []; |
|
144 | - |
|
145 | - // 75 id per time should be okey |
|
146 | - $contactsIds = array_chunk((array)$data->get('ids'), 75); |
|
147 | - |
|
148 | - foreach ($contactsIds as $chunk) { |
|
149 | - $parameters = ['user_id' => implode(',', $chunk)]; |
|
150 | - |
|
151 | - try { |
|
152 | - $response = $this->apiRequest('users/lookup.json', 'GET', $parameters); |
|
153 | - |
|
154 | - if ($response && count($response)) { |
|
155 | - foreach ($response as $item) { |
|
156 | - $contacts[] = $this->fetchUserContact($item); |
|
157 | - } |
|
158 | - } |
|
159 | - } catch (\Exception $e) { |
|
160 | - continue; |
|
161 | - } |
|
162 | - } |
|
163 | - |
|
164 | - return $contacts; |
|
165 | - } |
|
166 | - |
|
167 | - /** |
|
168 | - * @param $item |
|
169 | - * |
|
170 | - * @return User\Contact |
|
171 | - */ |
|
172 | - protected function fetchUserContact($item) |
|
173 | - { |
|
174 | - $item = new Data\Collection($item); |
|
175 | - |
|
176 | - $userContact = new User\Contact(); |
|
177 | - |
|
178 | - $userContact->identifier = $item->get('id_str'); |
|
179 | - $userContact->displayName = $item->get('name'); |
|
180 | - $userContact->photoURL = $item->get('profile_image_url'); |
|
181 | - $userContact->description = $item->get('description'); |
|
182 | - |
|
183 | - $userContact->profileURL = $item->exists('screen_name') |
|
184 | - ? ('https://twitter.com/' . $item->get('screen_name')) |
|
185 | - : ''; |
|
186 | - |
|
187 | - return $userContact; |
|
188 | - } |
|
189 | - |
|
190 | - /** |
|
191 | - * {@inheritdoc} |
|
192 | - */ |
|
193 | - public function setUserStatus($status) |
|
194 | - { |
|
195 | - if (is_string($status)) { |
|
196 | - $status = ['status' => $status]; |
|
197 | - } |
|
198 | - |
|
199 | - // Prepare request parameters. |
|
200 | - $params = []; |
|
201 | - if (isset($status['status'])) { |
|
202 | - $params['status'] = $status['status']; |
|
203 | - } |
|
204 | - if (isset($status['picture'])) { |
|
205 | - $media = $this->apiRequest('https://upload.twitter.com/1.1/media/upload.json', 'POST', [ |
|
206 | - 'media' => base64_encode(file_get_contents($status['picture'])), |
|
207 | - ]); |
|
208 | - $params['media_ids'] = $media->media_id; |
|
209 | - } |
|
210 | - |
|
211 | - $response = $this->apiRequest('statuses/update.json', 'POST', $params); |
|
212 | - |
|
213 | - return $response; |
|
214 | - } |
|
215 | - |
|
216 | - /** |
|
217 | - * {@inheritdoc} |
|
218 | - */ |
|
219 | - public function getUserActivity($stream = 'me') |
|
220 | - { |
|
221 | - $apiUrl = ($stream == 'me') |
|
222 | - ? 'statuses/user_timeline.json' |
|
223 | - : 'statuses/home_timeline.json'; |
|
224 | - |
|
225 | - $response = $this->apiRequest($apiUrl); |
|
226 | - |
|
227 | - if (!$response) { |
|
228 | - return []; |
|
229 | - } |
|
230 | - |
|
231 | - $activities = []; |
|
232 | - |
|
233 | - foreach ($response as $item) { |
|
234 | - $activities[] = $this->fetchUserActivity($item); |
|
235 | - } |
|
236 | - |
|
237 | - return $activities; |
|
238 | - } |
|
239 | - |
|
240 | - /** |
|
241 | - * @param $item |
|
242 | - * @return User\Activity |
|
243 | - */ |
|
244 | - protected function fetchUserActivity($item) |
|
245 | - { |
|
246 | - $item = new Data\Collection($item); |
|
247 | - |
|
248 | - $userActivity = new User\Activity(); |
|
249 | - |
|
250 | - $userActivity->id = $item->get('id_str'); |
|
251 | - $userActivity->date = $item->get('created_at'); |
|
252 | - $userActivity->text = $item->get('text'); |
|
253 | - |
|
254 | - $userActivity->user->identifier = $item->filter('user')->get('id_str'); |
|
255 | - $userActivity->user->displayName = $item->filter('user')->get('name'); |
|
256 | - $userActivity->user->photoURL = $item->filter('user')->get('profile_image_url'); |
|
257 | - |
|
258 | - $userActivity->user->profileURL = $item->filter('user')->get('screen_name') |
|
259 | - ? ('https://twitter.com/' . $item->filter('user')->get('screen_name')) |
|
260 | - : ''; |
|
261 | - |
|
262 | - return $userActivity; |
|
263 | - } |
|
43 | + /** |
|
44 | + * {@inheritdoc} |
|
45 | + */ |
|
46 | + protected $apiBaseUrl = 'https://api.twitter.com/1.1/'; |
|
47 | + |
|
48 | + /** |
|
49 | + * {@inheritdoc} |
|
50 | + */ |
|
51 | + protected $authorizeUrl = 'https://api.twitter.com/oauth/authenticate'; |
|
52 | + |
|
53 | + /** |
|
54 | + * {@inheritdoc} |
|
55 | + */ |
|
56 | + protected $requestTokenUrl = 'https://api.twitter.com/oauth/request_token'; |
|
57 | + |
|
58 | + /** |
|
59 | + * {@inheritdoc} |
|
60 | + */ |
|
61 | + protected $accessTokenUrl = 'https://api.twitter.com/oauth/access_token'; |
|
62 | + |
|
63 | + /** |
|
64 | + * {@inheritdoc} |
|
65 | + */ |
|
66 | + protected $apiDocumentation = 'https://dev.twitter.com/web/sign-in/implementing'; |
|
67 | + |
|
68 | + /** |
|
69 | + * {@inheritdoc} |
|
70 | + */ |
|
71 | + protected function getAuthorizeUrl($parameters = []) |
|
72 | + { |
|
73 | + if ($this->config->get('authorize') === true) { |
|
74 | + $this->authorizeUrl = 'https://api.twitter.com/oauth/authorize'; |
|
75 | + } |
|
76 | + |
|
77 | + return parent::getAuthorizeUrl($parameters); |
|
78 | + } |
|
79 | + |
|
80 | + /** |
|
81 | + * {@inheritdoc} |
|
82 | + */ |
|
83 | + public function getUserProfile() |
|
84 | + { |
|
85 | + $response = $this->apiRequest('account/verify_credentials.json', 'GET', [ |
|
86 | + 'include_email' => $this->config->get('include_email') === false ? 'false' : 'true', |
|
87 | + ]); |
|
88 | + |
|
89 | + $data = new Data\Collection($response); |
|
90 | + |
|
91 | + if (!$data->exists('id_str')) { |
|
92 | + throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
93 | + } |
|
94 | + |
|
95 | + $userProfile = new User\Profile(); |
|
96 | + |
|
97 | + $userProfile->identifier = $data->get('id_str'); |
|
98 | + $userProfile->displayName = $data->get('screen_name'); |
|
99 | + $userProfile->description = $data->get('description'); |
|
100 | + $userProfile->firstName = $data->get('name'); |
|
101 | + $userProfile->email = $data->get('email'); |
|
102 | + $userProfile->emailVerified = $data->get('email'); |
|
103 | + $userProfile->webSiteURL = $data->get('url'); |
|
104 | + $userProfile->region = $data->get('location'); |
|
105 | + |
|
106 | + $userProfile->profileURL = $data->exists('screen_name') |
|
107 | + ? ('https://twitter.com/' . $data->get('screen_name')) |
|
108 | + : ''; |
|
109 | + |
|
110 | + $photoSize = $this->config->get('photo_size') ?: 'original'; |
|
111 | + $photoSize = $photoSize === 'original' ? '' : "_{$photoSize}"; |
|
112 | + $userProfile->photoURL = $data->exists('profile_image_url_https') |
|
113 | + ? str_replace('_normal', $photoSize, $data->get('profile_image_url_https')) |
|
114 | + : ''; |
|
115 | + |
|
116 | + $userProfile->data = [ |
|
117 | + 'followed_by' => $data->get('followers_count'), |
|
118 | + 'follows' => $data->get('friends_count'), |
|
119 | + ]; |
|
120 | + |
|
121 | + return $userProfile; |
|
122 | + } |
|
123 | + |
|
124 | + /** |
|
125 | + * {@inheritdoc} |
|
126 | + */ |
|
127 | + public function getUserContacts($parameters = []) |
|
128 | + { |
|
129 | + $parameters = ['cursor' => '-1'] + $parameters; |
|
130 | + |
|
131 | + $response = $this->apiRequest('friends/ids.json', 'GET', $parameters); |
|
132 | + |
|
133 | + $data = new Data\Collection($response); |
|
134 | + |
|
135 | + if (!$data->exists('ids')) { |
|
136 | + throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
137 | + } |
|
138 | + |
|
139 | + if ($data->filter('ids')->isEmpty()) { |
|
140 | + return []; |
|
141 | + } |
|
142 | + |
|
143 | + $contacts = []; |
|
144 | + |
|
145 | + // 75 id per time should be okey |
|
146 | + $contactsIds = array_chunk((array)$data->get('ids'), 75); |
|
147 | + |
|
148 | + foreach ($contactsIds as $chunk) { |
|
149 | + $parameters = ['user_id' => implode(',', $chunk)]; |
|
150 | + |
|
151 | + try { |
|
152 | + $response = $this->apiRequest('users/lookup.json', 'GET', $parameters); |
|
153 | + |
|
154 | + if ($response && count($response)) { |
|
155 | + foreach ($response as $item) { |
|
156 | + $contacts[] = $this->fetchUserContact($item); |
|
157 | + } |
|
158 | + } |
|
159 | + } catch (\Exception $e) { |
|
160 | + continue; |
|
161 | + } |
|
162 | + } |
|
163 | + |
|
164 | + return $contacts; |
|
165 | + } |
|
166 | + |
|
167 | + /** |
|
168 | + * @param $item |
|
169 | + * |
|
170 | + * @return User\Contact |
|
171 | + */ |
|
172 | + protected function fetchUserContact($item) |
|
173 | + { |
|
174 | + $item = new Data\Collection($item); |
|
175 | + |
|
176 | + $userContact = new User\Contact(); |
|
177 | + |
|
178 | + $userContact->identifier = $item->get('id_str'); |
|
179 | + $userContact->displayName = $item->get('name'); |
|
180 | + $userContact->photoURL = $item->get('profile_image_url'); |
|
181 | + $userContact->description = $item->get('description'); |
|
182 | + |
|
183 | + $userContact->profileURL = $item->exists('screen_name') |
|
184 | + ? ('https://twitter.com/' . $item->get('screen_name')) |
|
185 | + : ''; |
|
186 | + |
|
187 | + return $userContact; |
|
188 | + } |
|
189 | + |
|
190 | + /** |
|
191 | + * {@inheritdoc} |
|
192 | + */ |
|
193 | + public function setUserStatus($status) |
|
194 | + { |
|
195 | + if (is_string($status)) { |
|
196 | + $status = ['status' => $status]; |
|
197 | + } |
|
198 | + |
|
199 | + // Prepare request parameters. |
|
200 | + $params = []; |
|
201 | + if (isset($status['status'])) { |
|
202 | + $params['status'] = $status['status']; |
|
203 | + } |
|
204 | + if (isset($status['picture'])) { |
|
205 | + $media = $this->apiRequest('https://upload.twitter.com/1.1/media/upload.json', 'POST', [ |
|
206 | + 'media' => base64_encode(file_get_contents($status['picture'])), |
|
207 | + ]); |
|
208 | + $params['media_ids'] = $media->media_id; |
|
209 | + } |
|
210 | + |
|
211 | + $response = $this->apiRequest('statuses/update.json', 'POST', $params); |
|
212 | + |
|
213 | + return $response; |
|
214 | + } |
|
215 | + |
|
216 | + /** |
|
217 | + * {@inheritdoc} |
|
218 | + */ |
|
219 | + public function getUserActivity($stream = 'me') |
|
220 | + { |
|
221 | + $apiUrl = ($stream == 'me') |
|
222 | + ? 'statuses/user_timeline.json' |
|
223 | + : 'statuses/home_timeline.json'; |
|
224 | + |
|
225 | + $response = $this->apiRequest($apiUrl); |
|
226 | + |
|
227 | + if (!$response) { |
|
228 | + return []; |
|
229 | + } |
|
230 | + |
|
231 | + $activities = []; |
|
232 | + |
|
233 | + foreach ($response as $item) { |
|
234 | + $activities[] = $this->fetchUserActivity($item); |
|
235 | + } |
|
236 | + |
|
237 | + return $activities; |
|
238 | + } |
|
239 | + |
|
240 | + /** |
|
241 | + * @param $item |
|
242 | + * @return User\Activity |
|
243 | + */ |
|
244 | + protected function fetchUserActivity($item) |
|
245 | + { |
|
246 | + $item = new Data\Collection($item); |
|
247 | + |
|
248 | + $userActivity = new User\Activity(); |
|
249 | + |
|
250 | + $userActivity->id = $item->get('id_str'); |
|
251 | + $userActivity->date = $item->get('created_at'); |
|
252 | + $userActivity->text = $item->get('text'); |
|
253 | + |
|
254 | + $userActivity->user->identifier = $item->filter('user')->get('id_str'); |
|
255 | + $userActivity->user->displayName = $item->filter('user')->get('name'); |
|
256 | + $userActivity->user->photoURL = $item->filter('user')->get('profile_image_url'); |
|
257 | + |
|
258 | + $userActivity->user->profileURL = $item->filter('user')->get('screen_name') |
|
259 | + ? ('https://twitter.com/' . $item->filter('user')->get('screen_name')) |
|
260 | + : ''; |
|
261 | + |
|
262 | + return $userActivity; |
|
263 | + } |
|
264 | 264 | } |
@@ -88,7 +88,7 @@ discard block |
||
88 | 88 | |
89 | 89 | $data = new Data\Collection($response); |
90 | 90 | |
91 | - if (!$data->exists('id_str')) { |
|
91 | + if ( ! $data->exists('id_str')) { |
|
92 | 92 | throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
93 | 93 | } |
94 | 94 | |
@@ -104,7 +104,7 @@ discard block |
||
104 | 104 | $userProfile->region = $data->get('location'); |
105 | 105 | |
106 | 106 | $userProfile->profileURL = $data->exists('screen_name') |
107 | - ? ('https://twitter.com/' . $data->get('screen_name')) |
|
107 | + ? ('https://twitter.com/'.$data->get('screen_name')) |
|
108 | 108 | : ''; |
109 | 109 | |
110 | 110 | $photoSize = $this->config->get('photo_size') ?: 'original'; |
@@ -132,7 +132,7 @@ discard block |
||
132 | 132 | |
133 | 133 | $data = new Data\Collection($response); |
134 | 134 | |
135 | - if (!$data->exists('ids')) { |
|
135 | + if ( ! $data->exists('ids')) { |
|
136 | 136 | throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
137 | 137 | } |
138 | 138 | |
@@ -143,7 +143,7 @@ discard block |
||
143 | 143 | $contacts = []; |
144 | 144 | |
145 | 145 | // 75 id per time should be okey |
146 | - $contactsIds = array_chunk((array)$data->get('ids'), 75); |
|
146 | + $contactsIds = array_chunk((array) $data->get('ids'), 75); |
|
147 | 147 | |
148 | 148 | foreach ($contactsIds as $chunk) { |
149 | 149 | $parameters = ['user_id' => implode(',', $chunk)]; |
@@ -181,7 +181,7 @@ discard block |
||
181 | 181 | $userContact->description = $item->get('description'); |
182 | 182 | |
183 | 183 | $userContact->profileURL = $item->exists('screen_name') |
184 | - ? ('https://twitter.com/' . $item->get('screen_name')) |
|
184 | + ? ('https://twitter.com/'.$item->get('screen_name')) |
|
185 | 185 | : ''; |
186 | 186 | |
187 | 187 | return $userContact; |
@@ -224,7 +224,7 @@ discard block |
||
224 | 224 | |
225 | 225 | $response = $this->apiRequest($apiUrl); |
226 | 226 | |
227 | - if (!$response) { |
|
227 | + if ( ! $response) { |
|
228 | 228 | return []; |
229 | 229 | } |
230 | 230 | |
@@ -256,7 +256,7 @@ discard block |
||
256 | 256 | $userActivity->user->photoURL = $item->filter('user')->get('profile_image_url'); |
257 | 257 | |
258 | 258 | $userActivity->user->profileURL = $item->filter('user')->get('screen_name') |
259 | - ? ('https://twitter.com/' . $item->filter('user')->get('screen_name')) |
|
259 | + ? ('https://twitter.com/'.$item->filter('user')->get('screen_name')) |
|
260 | 260 | : ''; |
261 | 261 | |
262 | 262 | return $userActivity; |
@@ -48,24 +48,24 @@ discard block |
||
48 | 48 | { |
49 | 49 | parent::configure(); |
50 | 50 | |
51 | - if (!$this->config->exists('url')) { |
|
51 | + if ( ! $this->config->exists('url')) { |
|
52 | 52 | throw new InvalidApplicationCredentialsException( |
53 | 53 | 'You must define a provider url' |
54 | 54 | ); |
55 | 55 | } |
56 | 56 | $url = $this->config->get('url'); |
57 | 57 | |
58 | - if (!$this->config->exists('realm')) { |
|
58 | + if ( ! $this->config->exists('realm')) { |
|
59 | 59 | throw new InvalidApplicationCredentialsException( |
60 | 60 | 'You must define a realm' |
61 | 61 | ); |
62 | 62 | } |
63 | 63 | $realm = $this->config->get('realm'); |
64 | 64 | |
65 | - $this->apiBaseUrl = $url . '/realms/' . $realm . '/protocol/openid-connect/'; |
|
65 | + $this->apiBaseUrl = $url.'/realms/'.$realm.'/protocol/openid-connect/'; |
|
66 | 66 | |
67 | - $this->authorizeUrl = $this->apiBaseUrl . 'auth'; |
|
68 | - $this->accessTokenUrl = $this->apiBaseUrl . 'token'; |
|
67 | + $this->authorizeUrl = $this->apiBaseUrl.'auth'; |
|
68 | + $this->accessTokenUrl = $this->apiBaseUrl.'token'; |
|
69 | 69 | |
70 | 70 | } |
71 | 71 | |
@@ -78,7 +78,7 @@ discard block |
||
78 | 78 | |
79 | 79 | $data = new Data\Collection($response); |
80 | 80 | |
81 | - if (!$data->exists('sub')) { |
|
81 | + if ( ! $data->exists('sub')) { |
|
82 | 82 | throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
83 | 83 | } |
84 | 84 |
@@ -31,72 +31,72 @@ |
||
31 | 31 | class Keycloak extends OAuth2 |
32 | 32 | { |
33 | 33 | |
34 | - /** |
|
35 | - * {@inheritdoc} |
|
36 | - */ |
|
37 | - public $scope = 'openid profile email'; |
|
38 | - |
|
39 | - /** |
|
40 | - * {@inheritdoc} |
|
41 | - */ |
|
42 | - protected $apiDocumentation = 'https://www.keycloak.org/docs/latest/securing_apps/#_oidc'; |
|
43 | - |
|
44 | - /** |
|
45 | - * {@inheritdoc} |
|
46 | - */ |
|
47 | - protected function configure() |
|
48 | - { |
|
49 | - parent::configure(); |
|
50 | - |
|
51 | - if (!$this->config->exists('url')) { |
|
52 | - throw new InvalidApplicationCredentialsException( |
|
53 | - 'You must define a provider url' |
|
54 | - ); |
|
55 | - } |
|
56 | - $url = $this->config->get('url'); |
|
57 | - |
|
58 | - if (!$this->config->exists('realm')) { |
|
59 | - throw new InvalidApplicationCredentialsException( |
|
60 | - 'You must define a realm' |
|
61 | - ); |
|
62 | - } |
|
63 | - $realm = $this->config->get('realm'); |
|
64 | - |
|
65 | - $this->apiBaseUrl = $url . '/realms/' . $realm . '/protocol/openid-connect/'; |
|
66 | - |
|
67 | - $this->authorizeUrl = $this->apiBaseUrl . 'auth'; |
|
68 | - $this->accessTokenUrl = $this->apiBaseUrl . 'token'; |
|
69 | - |
|
70 | - } |
|
71 | - |
|
72 | - /** |
|
73 | - * {@inheritdoc} |
|
74 | - */ |
|
75 | - public function getUserProfile() |
|
76 | - { |
|
77 | - $response = $this->apiRequest('userinfo'); |
|
78 | - |
|
79 | - $data = new Data\Collection($response); |
|
80 | - |
|
81 | - if (!$data->exists('sub')) { |
|
82 | - throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
83 | - } |
|
84 | - |
|
85 | - $userProfile = new User\Profile(); |
|
86 | - |
|
87 | - $userProfile->identifier = $data->get('sub'); |
|
88 | - $userProfile->displayName = $data->get('preferred_username'); |
|
89 | - $userProfile->email = $data->get('email'); |
|
90 | - $userProfile->firstName = $data->get('given_name'); |
|
91 | - $userProfile->lastName = $data->get('family_name'); |
|
92 | - $userProfile->emailVerified = $data->get('email_verified'); |
|
93 | - |
|
94 | - // Collect organization claim if provided in the IDToken |
|
95 | - if ($data->exists('organization')) { |
|
96 | - $kc_orgs = array_keys((array) $data->get('organization')); |
|
97 | - $userProfile->data['organization'] = array_shift($kc_orgs); //Get the first key |
|
98 | - } |
|
99 | - |
|
100 | - return $userProfile; |
|
101 | - } |
|
34 | + /** |
|
35 | + * {@inheritdoc} |
|
36 | + */ |
|
37 | + public $scope = 'openid profile email'; |
|
38 | + |
|
39 | + /** |
|
40 | + * {@inheritdoc} |
|
41 | + */ |
|
42 | + protected $apiDocumentation = 'https://www.keycloak.org/docs/latest/securing_apps/#_oidc'; |
|
43 | + |
|
44 | + /** |
|
45 | + * {@inheritdoc} |
|
46 | + */ |
|
47 | + protected function configure() |
|
48 | + { |
|
49 | + parent::configure(); |
|
50 | + |
|
51 | + if (!$this->config->exists('url')) { |
|
52 | + throw new InvalidApplicationCredentialsException( |
|
53 | + 'You must define a provider url' |
|
54 | + ); |
|
55 | + } |
|
56 | + $url = $this->config->get('url'); |
|
57 | + |
|
58 | + if (!$this->config->exists('realm')) { |
|
59 | + throw new InvalidApplicationCredentialsException( |
|
60 | + 'You must define a realm' |
|
61 | + ); |
|
62 | + } |
|
63 | + $realm = $this->config->get('realm'); |
|
64 | + |
|
65 | + $this->apiBaseUrl = $url . '/realms/' . $realm . '/protocol/openid-connect/'; |
|
66 | + |
|
67 | + $this->authorizeUrl = $this->apiBaseUrl . 'auth'; |
|
68 | + $this->accessTokenUrl = $this->apiBaseUrl . 'token'; |
|
69 | + |
|
70 | + } |
|
71 | + |
|
72 | + /** |
|
73 | + * {@inheritdoc} |
|
74 | + */ |
|
75 | + public function getUserProfile() |
|
76 | + { |
|
77 | + $response = $this->apiRequest('userinfo'); |
|
78 | + |
|
79 | + $data = new Data\Collection($response); |
|
80 | + |
|
81 | + if (!$data->exists('sub')) { |
|
82 | + throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
83 | + } |
|
84 | + |
|
85 | + $userProfile = new User\Profile(); |
|
86 | + |
|
87 | + $userProfile->identifier = $data->get('sub'); |
|
88 | + $userProfile->displayName = $data->get('preferred_username'); |
|
89 | + $userProfile->email = $data->get('email'); |
|
90 | + $userProfile->firstName = $data->get('given_name'); |
|
91 | + $userProfile->lastName = $data->get('family_name'); |
|
92 | + $userProfile->emailVerified = $data->get('email_verified'); |
|
93 | + |
|
94 | + // Collect organization claim if provided in the IDToken |
|
95 | + if ($data->exists('organization')) { |
|
96 | + $kc_orgs = array_keys((array) $data->get('organization')); |
|
97 | + $userProfile->data['organization'] = array_shift($kc_orgs); //Get the first key |
|
98 | + } |
|
99 | + |
|
100 | + return $userProfile; |
|
101 | + } |
|
102 | 102 | } |
@@ -12,143 +12,143 @@ |
||
12 | 12 | */ |
13 | 13 | final class Collection |
14 | 14 | { |
15 | - /** |
|
16 | - * Data collection |
|
17 | - * |
|
18 | - * @var mixed |
|
19 | - */ |
|
20 | - protected $collection = null; |
|
21 | - |
|
22 | - /** |
|
23 | - * @param mixed $data |
|
24 | - */ |
|
25 | - public function __construct($data = null) |
|
26 | - { |
|
27 | - $this->collection = (object)$data; |
|
28 | - } |
|
29 | - |
|
30 | - /** |
|
31 | - * Retrieves the whole collection as array |
|
32 | - * |
|
33 | - * @return mixed |
|
34 | - */ |
|
35 | - public function toArray() |
|
36 | - { |
|
37 | - return (array)$this->collection; |
|
38 | - } |
|
39 | - |
|
40 | - /** |
|
41 | - * Retrieves an item |
|
42 | - * |
|
43 | - * @param $property |
|
44 | - * |
|
45 | - * @return mixed |
|
46 | - */ |
|
47 | - public function get($property) |
|
48 | - { |
|
49 | - if ($this->exists($property)) { |
|
50 | - return $this->collection->$property; |
|
51 | - } |
|
52 | - |
|
53 | - return null; |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * Add or update an item |
|
58 | - * |
|
59 | - * @param $property |
|
60 | - * @param mixed $value |
|
61 | - */ |
|
62 | - public function set($property, $value) |
|
63 | - { |
|
64 | - if ($property) { |
|
65 | - $this->collection->$property = $value; |
|
66 | - } |
|
67 | - } |
|
68 | - |
|
69 | - /** |
|
70 | - * .. until I come with a better name.. |
|
71 | - * |
|
72 | - * @param $property |
|
73 | - * |
|
74 | - * @return Collection |
|
75 | - */ |
|
76 | - public function filter($property) |
|
77 | - { |
|
78 | - if ($this->exists($property)) { |
|
79 | - $data = $this->get($property); |
|
80 | - |
|
81 | - if (!is_a($data, 'Collection')) { |
|
82 | - $data = new Collection($data); |
|
83 | - } |
|
84 | - |
|
85 | - return $data; |
|
86 | - } |
|
87 | - |
|
88 | - return new Collection([]); |
|
89 | - } |
|
90 | - |
|
91 | - /** |
|
92 | - * Checks whether an item within the collection |
|
93 | - * |
|
94 | - * @param $property |
|
95 | - * |
|
96 | - * @return bool |
|
97 | - */ |
|
98 | - public function exists($property) |
|
99 | - { |
|
100 | - return property_exists($this->collection, $property); |
|
101 | - } |
|
102 | - |
|
103 | - /** |
|
104 | - * Finds whether the collection is empty |
|
105 | - * |
|
106 | - * @return bool |
|
107 | - */ |
|
108 | - public function isEmpty() |
|
109 | - { |
|
110 | - return !(bool)$this->count(); |
|
111 | - } |
|
112 | - |
|
113 | - /** |
|
114 | - * Count all items in collection |
|
115 | - * |
|
116 | - * @return int |
|
117 | - */ |
|
118 | - public function count() |
|
119 | - { |
|
120 | - return count($this->properties()); |
|
121 | - } |
|
122 | - |
|
123 | - /** |
|
124 | - * Returns all items properties names |
|
125 | - * |
|
126 | - * @return array |
|
127 | - */ |
|
128 | - public function properties() |
|
129 | - { |
|
130 | - $properties = []; |
|
131 | - |
|
132 | - foreach ($this->collection as $key => $value) { |
|
133 | - $properties[] = $key; |
|
134 | - } |
|
135 | - |
|
136 | - return $properties; |
|
137 | - } |
|
138 | - |
|
139 | - /** |
|
140 | - * Returns all items values |
|
141 | - * |
|
142 | - * @return array |
|
143 | - */ |
|
144 | - public function values() |
|
145 | - { |
|
146 | - $values = []; |
|
147 | - |
|
148 | - foreach ($this->collection as $value) { |
|
149 | - $values[] = $value; |
|
150 | - } |
|
151 | - |
|
152 | - return $values; |
|
153 | - } |
|
15 | + /** |
|
16 | + * Data collection |
|
17 | + * |
|
18 | + * @var mixed |
|
19 | + */ |
|
20 | + protected $collection = null; |
|
21 | + |
|
22 | + /** |
|
23 | + * @param mixed $data |
|
24 | + */ |
|
25 | + public function __construct($data = null) |
|
26 | + { |
|
27 | + $this->collection = (object)$data; |
|
28 | + } |
|
29 | + |
|
30 | + /** |
|
31 | + * Retrieves the whole collection as array |
|
32 | + * |
|
33 | + * @return mixed |
|
34 | + */ |
|
35 | + public function toArray() |
|
36 | + { |
|
37 | + return (array)$this->collection; |
|
38 | + } |
|
39 | + |
|
40 | + /** |
|
41 | + * Retrieves an item |
|
42 | + * |
|
43 | + * @param $property |
|
44 | + * |
|
45 | + * @return mixed |
|
46 | + */ |
|
47 | + public function get($property) |
|
48 | + { |
|
49 | + if ($this->exists($property)) { |
|
50 | + return $this->collection->$property; |
|
51 | + } |
|
52 | + |
|
53 | + return null; |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * Add or update an item |
|
58 | + * |
|
59 | + * @param $property |
|
60 | + * @param mixed $value |
|
61 | + */ |
|
62 | + public function set($property, $value) |
|
63 | + { |
|
64 | + if ($property) { |
|
65 | + $this->collection->$property = $value; |
|
66 | + } |
|
67 | + } |
|
68 | + |
|
69 | + /** |
|
70 | + * .. until I come with a better name.. |
|
71 | + * |
|
72 | + * @param $property |
|
73 | + * |
|
74 | + * @return Collection |
|
75 | + */ |
|
76 | + public function filter($property) |
|
77 | + { |
|
78 | + if ($this->exists($property)) { |
|
79 | + $data = $this->get($property); |
|
80 | + |
|
81 | + if (!is_a($data, 'Collection')) { |
|
82 | + $data = new Collection($data); |
|
83 | + } |
|
84 | + |
|
85 | + return $data; |
|
86 | + } |
|
87 | + |
|
88 | + return new Collection([]); |
|
89 | + } |
|
90 | + |
|
91 | + /** |
|
92 | + * Checks whether an item within the collection |
|
93 | + * |
|
94 | + * @param $property |
|
95 | + * |
|
96 | + * @return bool |
|
97 | + */ |
|
98 | + public function exists($property) |
|
99 | + { |
|
100 | + return property_exists($this->collection, $property); |
|
101 | + } |
|
102 | + |
|
103 | + /** |
|
104 | + * Finds whether the collection is empty |
|
105 | + * |
|
106 | + * @return bool |
|
107 | + */ |
|
108 | + public function isEmpty() |
|
109 | + { |
|
110 | + return !(bool)$this->count(); |
|
111 | + } |
|
112 | + |
|
113 | + /** |
|
114 | + * Count all items in collection |
|
115 | + * |
|
116 | + * @return int |
|
117 | + */ |
|
118 | + public function count() |
|
119 | + { |
|
120 | + return count($this->properties()); |
|
121 | + } |
|
122 | + |
|
123 | + /** |
|
124 | + * Returns all items properties names |
|
125 | + * |
|
126 | + * @return array |
|
127 | + */ |
|
128 | + public function properties() |
|
129 | + { |
|
130 | + $properties = []; |
|
131 | + |
|
132 | + foreach ($this->collection as $key => $value) { |
|
133 | + $properties[] = $key; |
|
134 | + } |
|
135 | + |
|
136 | + return $properties; |
|
137 | + } |
|
138 | + |
|
139 | + /** |
|
140 | + * Returns all items values |
|
141 | + * |
|
142 | + * @return array |
|
143 | + */ |
|
144 | + public function values() |
|
145 | + { |
|
146 | + $values = []; |
|
147 | + |
|
148 | + foreach ($this->collection as $value) { |
|
149 | + $values[] = $value; |
|
150 | + } |
|
151 | + |
|
152 | + return $values; |
|
153 | + } |
|
154 | 154 | } |
@@ -24,7 +24,7 @@ discard block |
||
24 | 24 | */ |
25 | 25 | public function __construct($data = null) |
26 | 26 | { |
27 | - $this->collection = (object)$data; |
|
27 | + $this->collection = (object) $data; |
|
28 | 28 | } |
29 | 29 | |
30 | 30 | /** |
@@ -34,7 +34,7 @@ discard block |
||
34 | 34 | */ |
35 | 35 | public function toArray() |
36 | 36 | { |
37 | - return (array)$this->collection; |
|
37 | + return (array) $this->collection; |
|
38 | 38 | } |
39 | 39 | |
40 | 40 | /** |
@@ -78,7 +78,7 @@ discard block |
||
78 | 78 | if ($this->exists($property)) { |
79 | 79 | $data = $this->get($property); |
80 | 80 | |
81 | - if (!is_a($data, 'Collection')) { |
|
81 | + if ( ! is_a($data, 'Collection')) { |
|
82 | 82 | $data = new Collection($data); |
83 | 83 | } |
84 | 84 | |
@@ -107,7 +107,7 @@ discard block |
||
107 | 107 | */ |
108 | 108 | public function isEmpty() |
109 | 109 | { |
110 | - return !(bool)$this->count(); |
|
110 | + return ! (bool) $this->count(); |
|
111 | 111 | } |
112 | 112 | |
113 | 113 | /** |
@@ -17,77 +17,77 @@ |
||
17 | 17 | */ |
18 | 18 | class Spotify extends OAuth2 |
19 | 19 | { |
20 | - /** |
|
21 | - * {@inheritdoc} |
|
22 | - */ |
|
23 | - protected $scope = 'user-read-email'; |
|
24 | - |
|
25 | - /** |
|
26 | - * {@inheritdoc} |
|
27 | - */ |
|
28 | - public $apiBaseUrl = 'https://api.spotify.com/v1/'; |
|
29 | - |
|
30 | - /** |
|
31 | - * {@inheritdoc} |
|
32 | - */ |
|
33 | - public $authorizeUrl = 'https://accounts.spotify.com/authorize'; |
|
34 | - |
|
35 | - /** |
|
36 | - * {@inheritdoc} |
|
37 | - */ |
|
38 | - protected $accessTokenUrl = 'https://accounts.spotify.com/api/token'; |
|
39 | - |
|
40 | - /** |
|
41 | - * {@inheritdoc} |
|
42 | - */ |
|
43 | - protected $apiDocumentation = 'https://developer.spotify.com/documentation/general/guides/authorization-guide/'; |
|
44 | - |
|
45 | - /** |
|
46 | - * {@inheritdoc} |
|
47 | - */ |
|
48 | - public function getUserProfile() |
|
49 | - { |
|
50 | - $response = $this->apiRequest('me'); |
|
51 | - |
|
52 | - $data = new Data\Collection($response); |
|
53 | - |
|
54 | - if (!$data->exists('id')) { |
|
55 | - throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
56 | - } |
|
57 | - |
|
58 | - $userProfile = new User\Profile(); |
|
59 | - |
|
60 | - $userProfile->identifier = $data->get('id'); |
|
61 | - $userProfile->displayName = $data->get('display_name'); |
|
62 | - $userProfile->email = $data->get('email'); |
|
63 | - $userProfile->emailVerified = $data->get('email'); |
|
64 | - $userProfile->profileURL = $data->filter('external_urls')->get('spotify'); |
|
65 | - $userProfile->photoURL = $data->filter('images')->get('url'); |
|
66 | - $userProfile->country = $data->get('country'); |
|
67 | - |
|
68 | - if ($data->exists('birthdate')) { |
|
69 | - $this->fetchBirthday($userProfile, $data->get('birthdate')); |
|
70 | - } |
|
71 | - |
|
72 | - return $userProfile; |
|
73 | - } |
|
74 | - |
|
75 | - /** |
|
76 | - * Fetch use birthday |
|
77 | - * |
|
78 | - * @param User\Profile $userProfile |
|
79 | - * @param $birthday |
|
80 | - * |
|
81 | - * @return User\Profile |
|
82 | - */ |
|
83 | - protected function fetchBirthday(User\Profile $userProfile, $birthday) |
|
84 | - { |
|
85 | - $result = (new Data\Parser())->parseBirthday($birthday); |
|
86 | - |
|
87 | - $userProfile->birthDay = (int)$result[0]; |
|
88 | - $userProfile->birthMonth = (int)$result[1]; |
|
89 | - $userProfile->birthYear = (int)$result[2]; |
|
90 | - |
|
91 | - return $userProfile; |
|
92 | - } |
|
20 | + /** |
|
21 | + * {@inheritdoc} |
|
22 | + */ |
|
23 | + protected $scope = 'user-read-email'; |
|
24 | + |
|
25 | + /** |
|
26 | + * {@inheritdoc} |
|
27 | + */ |
|
28 | + public $apiBaseUrl = 'https://api.spotify.com/v1/'; |
|
29 | + |
|
30 | + /** |
|
31 | + * {@inheritdoc} |
|
32 | + */ |
|
33 | + public $authorizeUrl = 'https://accounts.spotify.com/authorize'; |
|
34 | + |
|
35 | + /** |
|
36 | + * {@inheritdoc} |
|
37 | + */ |
|
38 | + protected $accessTokenUrl = 'https://accounts.spotify.com/api/token'; |
|
39 | + |
|
40 | + /** |
|
41 | + * {@inheritdoc} |
|
42 | + */ |
|
43 | + protected $apiDocumentation = 'https://developer.spotify.com/documentation/general/guides/authorization-guide/'; |
|
44 | + |
|
45 | + /** |
|
46 | + * {@inheritdoc} |
|
47 | + */ |
|
48 | + public function getUserProfile() |
|
49 | + { |
|
50 | + $response = $this->apiRequest('me'); |
|
51 | + |
|
52 | + $data = new Data\Collection($response); |
|
53 | + |
|
54 | + if (!$data->exists('id')) { |
|
55 | + throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
56 | + } |
|
57 | + |
|
58 | + $userProfile = new User\Profile(); |
|
59 | + |
|
60 | + $userProfile->identifier = $data->get('id'); |
|
61 | + $userProfile->displayName = $data->get('display_name'); |
|
62 | + $userProfile->email = $data->get('email'); |
|
63 | + $userProfile->emailVerified = $data->get('email'); |
|
64 | + $userProfile->profileURL = $data->filter('external_urls')->get('spotify'); |
|
65 | + $userProfile->photoURL = $data->filter('images')->get('url'); |
|
66 | + $userProfile->country = $data->get('country'); |
|
67 | + |
|
68 | + if ($data->exists('birthdate')) { |
|
69 | + $this->fetchBirthday($userProfile, $data->get('birthdate')); |
|
70 | + } |
|
71 | + |
|
72 | + return $userProfile; |
|
73 | + } |
|
74 | + |
|
75 | + /** |
|
76 | + * Fetch use birthday |
|
77 | + * |
|
78 | + * @param User\Profile $userProfile |
|
79 | + * @param $birthday |
|
80 | + * |
|
81 | + * @return User\Profile |
|
82 | + */ |
|
83 | + protected function fetchBirthday(User\Profile $userProfile, $birthday) |
|
84 | + { |
|
85 | + $result = (new Data\Parser())->parseBirthday($birthday); |
|
86 | + |
|
87 | + $userProfile->birthDay = (int)$result[0]; |
|
88 | + $userProfile->birthMonth = (int)$result[1]; |
|
89 | + $userProfile->birthYear = (int)$result[2]; |
|
90 | + |
|
91 | + return $userProfile; |
|
92 | + } |
|
93 | 93 | } |
@@ -51,7 +51,7 @@ discard block |
||
51 | 51 | |
52 | 52 | $data = new Data\Collection($response); |
53 | 53 | |
54 | - if (!$data->exists('id')) { |
|
54 | + if ( ! $data->exists('id')) { |
|
55 | 55 | throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
56 | 56 | } |
57 | 57 | |
@@ -84,9 +84,9 @@ discard block |
||
84 | 84 | { |
85 | 85 | $result = (new Data\Parser())->parseBirthday($birthday); |
86 | 86 | |
87 | - $userProfile->birthDay = (int)$result[0]; |
|
88 | - $userProfile->birthMonth = (int)$result[1]; |
|
89 | - $userProfile->birthYear = (int)$result[2]; |
|
87 | + $userProfile->birthDay = (int) $result[0]; |
|
88 | + $userProfile->birthMonth = (int) $result[1]; |
|
89 | + $userProfile->birthYear = (int) $result[2]; |
|
90 | 90 | |
91 | 91 | return $userProfile; |
92 | 92 | } |
@@ -17,47 +17,47 @@ |
||
17 | 17 | */ |
18 | 18 | class Seznam extends OAuth2 |
19 | 19 | { |
20 | - /** |
|
21 | - * {@inheritdoc} |
|
22 | - */ |
|
23 | - protected $apiBaseUrl = 'https://login.szn.cz/'; |
|
24 | - |
|
25 | - /** |
|
26 | - * {@inheritdoc} |
|
27 | - */ |
|
28 | - protected $authorizeUrl = 'https://login.szn.cz/api/v1/oauth/auth'; |
|
29 | - |
|
30 | - /** |
|
31 | - * {@inheritdoc} |
|
32 | - */ |
|
33 | - protected $accessTokenUrl = 'https://login.szn.cz/api/v1/oauth/token'; |
|
34 | - |
|
35 | - /** |
|
36 | - * {@inheritdoc} |
|
37 | - */ |
|
38 | - protected $apiDocumentation = 'https://vyvojari.seznam.cz/oauth/doc'; |
|
39 | - |
|
40 | - /** |
|
41 | - * {@inheritdoc} |
|
42 | - */ |
|
43 | - public function getUserProfile() |
|
44 | - { |
|
45 | - $response = $this->apiRequest('api/v1/user', 'GET', ['format' => 'json']); |
|
46 | - |
|
47 | - $data = new Data\Collection($response); |
|
48 | - |
|
49 | - if (!$data->exists('oauth_user_id')) { |
|
50 | - throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
51 | - } |
|
52 | - |
|
53 | - $userProfile = new User\Profile(); |
|
54 | - |
|
55 | - $userProfile->identifier = $data->get('oauth_user_id'); |
|
56 | - $userProfile->email = $data->get('account_name'); |
|
57 | - $userProfile->firstName = $data->get('firstname'); |
|
58 | - $userProfile->lastName = $data->get('lastname'); |
|
59 | - $userProfile->photoURL = $data->get('avatar_url'); |
|
60 | - |
|
61 | - return $userProfile; |
|
62 | - } |
|
20 | + /** |
|
21 | + * {@inheritdoc} |
|
22 | + */ |
|
23 | + protected $apiBaseUrl = 'https://login.szn.cz/'; |
|
24 | + |
|
25 | + /** |
|
26 | + * {@inheritdoc} |
|
27 | + */ |
|
28 | + protected $authorizeUrl = 'https://login.szn.cz/api/v1/oauth/auth'; |
|
29 | + |
|
30 | + /** |
|
31 | + * {@inheritdoc} |
|
32 | + */ |
|
33 | + protected $accessTokenUrl = 'https://login.szn.cz/api/v1/oauth/token'; |
|
34 | + |
|
35 | + /** |
|
36 | + * {@inheritdoc} |
|
37 | + */ |
|
38 | + protected $apiDocumentation = 'https://vyvojari.seznam.cz/oauth/doc'; |
|
39 | + |
|
40 | + /** |
|
41 | + * {@inheritdoc} |
|
42 | + */ |
|
43 | + public function getUserProfile() |
|
44 | + { |
|
45 | + $response = $this->apiRequest('api/v1/user', 'GET', ['format' => 'json']); |
|
46 | + |
|
47 | + $data = new Data\Collection($response); |
|
48 | + |
|
49 | + if (!$data->exists('oauth_user_id')) { |
|
50 | + throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
|
51 | + } |
|
52 | + |
|
53 | + $userProfile = new User\Profile(); |
|
54 | + |
|
55 | + $userProfile->identifier = $data->get('oauth_user_id'); |
|
56 | + $userProfile->email = $data->get('account_name'); |
|
57 | + $userProfile->firstName = $data->get('firstname'); |
|
58 | + $userProfile->lastName = $data->get('lastname'); |
|
59 | + $userProfile->photoURL = $data->get('avatar_url'); |
|
60 | + |
|
61 | + return $userProfile; |
|
62 | + } |
|
63 | 63 | } |
@@ -46,7 +46,7 @@ |
||
46 | 46 | |
47 | 47 | $data = new Data\Collection($response); |
48 | 48 | |
49 | - if (!$data->exists('oauth_user_id')) { |
|
49 | + if ( ! $data->exists('oauth_user_id')) { |
|
50 | 50 | throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); |
51 | 51 | } |
52 | 52 |
@@ -23,261 +23,261 @@ |
||
23 | 23 | */ |
24 | 24 | abstract class OpenID extends AbstractAdapter implements AdapterInterface |
25 | 25 | { |
26 | - /** |
|
27 | - * LightOpenID instance |
|
28 | - * |
|
29 | - * @var object |
|
30 | - */ |
|
31 | - protected $openIdClient = null; |
|
32 | - |
|
33 | - /** |
|
34 | - * Openid provider identifier |
|
35 | - * |
|
36 | - * @var string |
|
37 | - */ |
|
38 | - protected $openidIdentifier = ''; |
|
39 | - |
|
40 | - /** |
|
41 | - * IPD API Documentation |
|
42 | - * |
|
43 | - * OPTIONAL. |
|
44 | - * |
|
45 | - * @var string |
|
46 | - */ |
|
47 | - protected $apiDocumentation = ''; |
|
48 | - |
|
49 | - /** |
|
50 | - * {@inheritdoc} |
|
51 | - */ |
|
52 | - protected function configure() |
|
53 | - { |
|
54 | - if ($this->config->exists('openid_identifier')) { |
|
55 | - $this->openidIdentifier = $this->config->get('openid_identifier'); |
|
56 | - } |
|
57 | - |
|
58 | - if (empty($this->openidIdentifier)) { |
|
59 | - throw new InvalidOpenidIdentifierException('OpenID adapter requires an openid_identifier.', 4); |
|
60 | - } |
|
61 | - |
|
62 | - $this->setCallback($this->config->get('callback')); |
|
63 | - $this->setApiEndpoints($this->config->get('endpoints')); |
|
64 | - } |
|
65 | - |
|
66 | - /** |
|
67 | - * {@inheritdoc} |
|
68 | - */ |
|
69 | - protected function initialize() |
|
70 | - { |
|
71 | - $hostPort = parse_url($this->callback, PHP_URL_PORT); |
|
72 | - $hostUrl = parse_url($this->callback, PHP_URL_HOST); |
|
73 | - |
|
74 | - if ($hostPort) { |
|
75 | - $hostUrl .= ':' . $hostPort; |
|
76 | - } |
|
77 | - |
|
78 | - // @fixme: add proxy |
|
79 | - $this->openIdClient = new LightOpenID($hostUrl, null); |
|
80 | - } |
|
81 | - |
|
82 | - /** |
|
83 | - * {@inheritdoc} |
|
84 | - */ |
|
85 | - public function authenticate() |
|
86 | - { |
|
87 | - $this->logger->info(sprintf('%s::authenticate()', get_class($this))); |
|
88 | - |
|
89 | - if ($this->isConnected()) { |
|
90 | - return true; |
|
91 | - } |
|
92 | - |
|
93 | - if (empty($_REQUEST['openid_mode'])) { |
|
94 | - $this->authenticateBegin(); |
|
95 | - } else { |
|
96 | - return $this->authenticateFinish(); |
|
97 | - } |
|
98 | - |
|
99 | - return null; |
|
100 | - } |
|
101 | - |
|
102 | - /** |
|
103 | - * {@inheritdoc} |
|
104 | - */ |
|
105 | - public function isConnected() |
|
106 | - { |
|
107 | - return (bool)$this->storage->get($this->providerId . '.user'); |
|
108 | - } |
|
109 | - |
|
110 | - /** |
|
111 | - * {@inheritdoc} |
|
112 | - */ |
|
113 | - public function disconnect() |
|
114 | - { |
|
115 | - $this->storage->delete($this->providerId . '.user'); |
|
116 | - |
|
117 | - return true; |
|
118 | - } |
|
119 | - |
|
120 | - /** |
|
121 | - * Initiate the authorization protocol |
|
122 | - * |
|
123 | - * Include and instantiate LightOpenID |
|
124 | - */ |
|
125 | - protected function authenticateBegin() |
|
126 | - { |
|
127 | - $this->openIdClient->identity = $this->openidIdentifier; |
|
128 | - $this->openIdClient->returnUrl = $this->callback; |
|
129 | - $this->openIdClient->required = [ |
|
130 | - 'namePerson/first', |
|
131 | - 'namePerson/last', |
|
132 | - 'namePerson/friendly', |
|
133 | - 'namePerson', |
|
134 | - 'contact/email', |
|
135 | - 'birthDate', |
|
136 | - 'birthDate/birthDay', |
|
137 | - 'birthDate/birthMonth', |
|
138 | - 'birthDate/birthYear', |
|
139 | - 'person/gender', |
|
140 | - 'pref/language', |
|
141 | - 'contact/postalCode/home', |
|
142 | - 'contact/city/home', |
|
143 | - 'contact/country/home', |
|
144 | - |
|
145 | - 'media/image/default', |
|
146 | - ]; |
|
147 | - |
|
148 | - $authUrl = $this->openIdClient->authUrl(); |
|
149 | - |
|
150 | - $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); |
|
151 | - |
|
152 | - HttpClient\Util::redirect($authUrl); |
|
153 | - } |
|
154 | - |
|
155 | - /** |
|
156 | - * Finalize the authorization process. |
|
157 | - * |
|
158 | - * @throws AuthorizationDeniedException |
|
159 | - * @throws UnexpectedApiResponseException |
|
160 | - */ |
|
161 | - protected function authenticateFinish() |
|
162 | - { |
|
163 | - $this->logger->debug( |
|
164 | - sprintf('%s::authenticateFinish(), callback url:', get_class($this)), |
|
165 | - [HttpClient\Util::getCurrentUrl(true)] |
|
166 | - ); |
|
167 | - |
|
168 | - if ($this->openIdClient->mode == 'cancel') { |
|
169 | - throw new AuthorizationDeniedException('User has cancelled the authentication.'); |
|
170 | - } |
|
171 | - |
|
172 | - if (!$this->openIdClient->validate()) { |
|
173 | - throw new UnexpectedApiResponseException('Invalid response received.'); |
|
174 | - } |
|
175 | - |
|
176 | - $openidAttributes = $this->openIdClient->getAttributes(); |
|
177 | - |
|
178 | - if (!$this->openIdClient->identity) { |
|
179 | - throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); |
|
180 | - } |
|
181 | - |
|
182 | - $userProfile = $this->fetchUserProfile($openidAttributes); |
|
183 | - |
|
184 | - /* with openid providers we only get user profiles once, so we store it */ |
|
185 | - $this->storage->set($this->providerId . '.user', $userProfile); |
|
186 | - } |
|
187 | - |
|
188 | - /** |
|
189 | - * Fetch user profile from received openid attributes |
|
190 | - * |
|
191 | - * @param array $openidAttributes |
|
192 | - * |
|
193 | - * @return User\Profile |
|
194 | - */ |
|
195 | - protected function fetchUserProfile($openidAttributes) |
|
196 | - { |
|
197 | - $data = new Data\Collection($openidAttributes); |
|
198 | - |
|
199 | - $userProfile = new User\Profile(); |
|
200 | - |
|
201 | - $userProfile->identifier = $this->openIdClient->identity; |
|
202 | - |
|
203 | - $userProfile->firstName = $data->get('namePerson/first'); |
|
204 | - $userProfile->lastName = $data->get('namePerson/last'); |
|
205 | - $userProfile->email = $data->get('contact/email'); |
|
206 | - $userProfile->language = $data->get('pref/language'); |
|
207 | - $userProfile->country = $data->get('contact/country/home'); |
|
208 | - $userProfile->zip = $data->get('contact/postalCode/home'); |
|
209 | - $userProfile->gender = $data->get('person/gender'); |
|
210 | - $userProfile->photoURL = $data->get('media/image/default'); |
|
211 | - $userProfile->birthDay = $data->get('birthDate/birthDay'); |
|
212 | - $userProfile->birthMonth = $data->get('birthDate/birthMonth'); |
|
213 | - $userProfile->birthYear = $data->get('birthDate/birthDate'); |
|
214 | - |
|
215 | - $userProfile = $this->fetchUserGender($userProfile, $data->get('person/gender')); |
|
216 | - |
|
217 | - $userProfile = $this->fetchUserDisplayName($userProfile, $data); |
|
218 | - |
|
219 | - return $userProfile; |
|
220 | - } |
|
221 | - |
|
222 | - /** |
|
223 | - * Extract users display names |
|
224 | - * |
|
225 | - * @param User\Profile $userProfile |
|
226 | - * @param Data\Collection $data |
|
227 | - * |
|
228 | - * @return User\Profile |
|
229 | - */ |
|
230 | - protected function fetchUserDisplayName(User\Profile $userProfile, Data\Collection $data) |
|
231 | - { |
|
232 | - $userProfile->displayName = $data->get('namePerson'); |
|
233 | - |
|
234 | - $userProfile->displayName = $userProfile->displayName |
|
235 | - ? $userProfile->displayName |
|
236 | - : $data->get('namePerson/friendly'); |
|
237 | - |
|
238 | - $userProfile->displayName = $userProfile->displayName |
|
239 | - ? $userProfile->displayName |
|
240 | - : trim($userProfile->firstName . ' ' . $userProfile->lastName); |
|
241 | - |
|
242 | - return $userProfile; |
|
243 | - } |
|
244 | - |
|
245 | - /** |
|
246 | - * Extract users gender |
|
247 | - * |
|
248 | - * @param User\Profile $userProfile |
|
249 | - * @param string $gender |
|
250 | - * |
|
251 | - * @return User\Profile |
|
252 | - */ |
|
253 | - protected function fetchUserGender(User\Profile $userProfile, $gender) |
|
254 | - { |
|
255 | - $gender = strtolower((string)$gender); |
|
256 | - |
|
257 | - if ('f' == $gender) { |
|
258 | - $gender = 'female'; |
|
259 | - } |
|
260 | - |
|
261 | - if ('m' == $gender) { |
|
262 | - $gender = 'male'; |
|
263 | - } |
|
264 | - |
|
265 | - $userProfile->gender = $gender; |
|
266 | - |
|
267 | - return $userProfile; |
|
268 | - } |
|
269 | - |
|
270 | - /** |
|
271 | - * OpenID only provide the user profile one. This method will attempt to retrieve the profile from storage. |
|
272 | - */ |
|
273 | - public function getUserProfile() |
|
274 | - { |
|
275 | - $userProfile = $this->storage->get($this->providerId . '.user'); |
|
276 | - |
|
277 | - if (!is_object($userProfile)) { |
|
278 | - throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); |
|
279 | - } |
|
280 | - |
|
281 | - return $userProfile; |
|
282 | - } |
|
26 | + /** |
|
27 | + * LightOpenID instance |
|
28 | + * |
|
29 | + * @var object |
|
30 | + */ |
|
31 | + protected $openIdClient = null; |
|
32 | + |
|
33 | + /** |
|
34 | + * Openid provider identifier |
|
35 | + * |
|
36 | + * @var string |
|
37 | + */ |
|
38 | + protected $openidIdentifier = ''; |
|
39 | + |
|
40 | + /** |
|
41 | + * IPD API Documentation |
|
42 | + * |
|
43 | + * OPTIONAL. |
|
44 | + * |
|
45 | + * @var string |
|
46 | + */ |
|
47 | + protected $apiDocumentation = ''; |
|
48 | + |
|
49 | + /** |
|
50 | + * {@inheritdoc} |
|
51 | + */ |
|
52 | + protected function configure() |
|
53 | + { |
|
54 | + if ($this->config->exists('openid_identifier')) { |
|
55 | + $this->openidIdentifier = $this->config->get('openid_identifier'); |
|
56 | + } |
|
57 | + |
|
58 | + if (empty($this->openidIdentifier)) { |
|
59 | + throw new InvalidOpenidIdentifierException('OpenID adapter requires an openid_identifier.', 4); |
|
60 | + } |
|
61 | + |
|
62 | + $this->setCallback($this->config->get('callback')); |
|
63 | + $this->setApiEndpoints($this->config->get('endpoints')); |
|
64 | + } |
|
65 | + |
|
66 | + /** |
|
67 | + * {@inheritdoc} |
|
68 | + */ |
|
69 | + protected function initialize() |
|
70 | + { |
|
71 | + $hostPort = parse_url($this->callback, PHP_URL_PORT); |
|
72 | + $hostUrl = parse_url($this->callback, PHP_URL_HOST); |
|
73 | + |
|
74 | + if ($hostPort) { |
|
75 | + $hostUrl .= ':' . $hostPort; |
|
76 | + } |
|
77 | + |
|
78 | + // @fixme: add proxy |
|
79 | + $this->openIdClient = new LightOpenID($hostUrl, null); |
|
80 | + } |
|
81 | + |
|
82 | + /** |
|
83 | + * {@inheritdoc} |
|
84 | + */ |
|
85 | + public function authenticate() |
|
86 | + { |
|
87 | + $this->logger->info(sprintf('%s::authenticate()', get_class($this))); |
|
88 | + |
|
89 | + if ($this->isConnected()) { |
|
90 | + return true; |
|
91 | + } |
|
92 | + |
|
93 | + if (empty($_REQUEST['openid_mode'])) { |
|
94 | + $this->authenticateBegin(); |
|
95 | + } else { |
|
96 | + return $this->authenticateFinish(); |
|
97 | + } |
|
98 | + |
|
99 | + return null; |
|
100 | + } |
|
101 | + |
|
102 | + /** |
|
103 | + * {@inheritdoc} |
|
104 | + */ |
|
105 | + public function isConnected() |
|
106 | + { |
|
107 | + return (bool)$this->storage->get($this->providerId . '.user'); |
|
108 | + } |
|
109 | + |
|
110 | + /** |
|
111 | + * {@inheritdoc} |
|
112 | + */ |
|
113 | + public function disconnect() |
|
114 | + { |
|
115 | + $this->storage->delete($this->providerId . '.user'); |
|
116 | + |
|
117 | + return true; |
|
118 | + } |
|
119 | + |
|
120 | + /** |
|
121 | + * Initiate the authorization protocol |
|
122 | + * |
|
123 | + * Include and instantiate LightOpenID |
|
124 | + */ |
|
125 | + protected function authenticateBegin() |
|
126 | + { |
|
127 | + $this->openIdClient->identity = $this->openidIdentifier; |
|
128 | + $this->openIdClient->returnUrl = $this->callback; |
|
129 | + $this->openIdClient->required = [ |
|
130 | + 'namePerson/first', |
|
131 | + 'namePerson/last', |
|
132 | + 'namePerson/friendly', |
|
133 | + 'namePerson', |
|
134 | + 'contact/email', |
|
135 | + 'birthDate', |
|
136 | + 'birthDate/birthDay', |
|
137 | + 'birthDate/birthMonth', |
|
138 | + 'birthDate/birthYear', |
|
139 | + 'person/gender', |
|
140 | + 'pref/language', |
|
141 | + 'contact/postalCode/home', |
|
142 | + 'contact/city/home', |
|
143 | + 'contact/country/home', |
|
144 | + |
|
145 | + 'media/image/default', |
|
146 | + ]; |
|
147 | + |
|
148 | + $authUrl = $this->openIdClient->authUrl(); |
|
149 | + |
|
150 | + $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); |
|
151 | + |
|
152 | + HttpClient\Util::redirect($authUrl); |
|
153 | + } |
|
154 | + |
|
155 | + /** |
|
156 | + * Finalize the authorization process. |
|
157 | + * |
|
158 | + * @throws AuthorizationDeniedException |
|
159 | + * @throws UnexpectedApiResponseException |
|
160 | + */ |
|
161 | + protected function authenticateFinish() |
|
162 | + { |
|
163 | + $this->logger->debug( |
|
164 | + sprintf('%s::authenticateFinish(), callback url:', get_class($this)), |
|
165 | + [HttpClient\Util::getCurrentUrl(true)] |
|
166 | + ); |
|
167 | + |
|
168 | + if ($this->openIdClient->mode == 'cancel') { |
|
169 | + throw new AuthorizationDeniedException('User has cancelled the authentication.'); |
|
170 | + } |
|
171 | + |
|
172 | + if (!$this->openIdClient->validate()) { |
|
173 | + throw new UnexpectedApiResponseException('Invalid response received.'); |
|
174 | + } |
|
175 | + |
|
176 | + $openidAttributes = $this->openIdClient->getAttributes(); |
|
177 | + |
|
178 | + if (!$this->openIdClient->identity) { |
|
179 | + throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); |
|
180 | + } |
|
181 | + |
|
182 | + $userProfile = $this->fetchUserProfile($openidAttributes); |
|
183 | + |
|
184 | + /* with openid providers we only get user profiles once, so we store it */ |
|
185 | + $this->storage->set($this->providerId . '.user', $userProfile); |
|
186 | + } |
|
187 | + |
|
188 | + /** |
|
189 | + * Fetch user profile from received openid attributes |
|
190 | + * |
|
191 | + * @param array $openidAttributes |
|
192 | + * |
|
193 | + * @return User\Profile |
|
194 | + */ |
|
195 | + protected function fetchUserProfile($openidAttributes) |
|
196 | + { |
|
197 | + $data = new Data\Collection($openidAttributes); |
|
198 | + |
|
199 | + $userProfile = new User\Profile(); |
|
200 | + |
|
201 | + $userProfile->identifier = $this->openIdClient->identity; |
|
202 | + |
|
203 | + $userProfile->firstName = $data->get('namePerson/first'); |
|
204 | + $userProfile->lastName = $data->get('namePerson/last'); |
|
205 | + $userProfile->email = $data->get('contact/email'); |
|
206 | + $userProfile->language = $data->get('pref/language'); |
|
207 | + $userProfile->country = $data->get('contact/country/home'); |
|
208 | + $userProfile->zip = $data->get('contact/postalCode/home'); |
|
209 | + $userProfile->gender = $data->get('person/gender'); |
|
210 | + $userProfile->photoURL = $data->get('media/image/default'); |
|
211 | + $userProfile->birthDay = $data->get('birthDate/birthDay'); |
|
212 | + $userProfile->birthMonth = $data->get('birthDate/birthMonth'); |
|
213 | + $userProfile->birthYear = $data->get('birthDate/birthDate'); |
|
214 | + |
|
215 | + $userProfile = $this->fetchUserGender($userProfile, $data->get('person/gender')); |
|
216 | + |
|
217 | + $userProfile = $this->fetchUserDisplayName($userProfile, $data); |
|
218 | + |
|
219 | + return $userProfile; |
|
220 | + } |
|
221 | + |
|
222 | + /** |
|
223 | + * Extract users display names |
|
224 | + * |
|
225 | + * @param User\Profile $userProfile |
|
226 | + * @param Data\Collection $data |
|
227 | + * |
|
228 | + * @return User\Profile |
|
229 | + */ |
|
230 | + protected function fetchUserDisplayName(User\Profile $userProfile, Data\Collection $data) |
|
231 | + { |
|
232 | + $userProfile->displayName = $data->get('namePerson'); |
|
233 | + |
|
234 | + $userProfile->displayName = $userProfile->displayName |
|
235 | + ? $userProfile->displayName |
|
236 | + : $data->get('namePerson/friendly'); |
|
237 | + |
|
238 | + $userProfile->displayName = $userProfile->displayName |
|
239 | + ? $userProfile->displayName |
|
240 | + : trim($userProfile->firstName . ' ' . $userProfile->lastName); |
|
241 | + |
|
242 | + return $userProfile; |
|
243 | + } |
|
244 | + |
|
245 | + /** |
|
246 | + * Extract users gender |
|
247 | + * |
|
248 | + * @param User\Profile $userProfile |
|
249 | + * @param string $gender |
|
250 | + * |
|
251 | + * @return User\Profile |
|
252 | + */ |
|
253 | + protected function fetchUserGender(User\Profile $userProfile, $gender) |
|
254 | + { |
|
255 | + $gender = strtolower((string)$gender); |
|
256 | + |
|
257 | + if ('f' == $gender) { |
|
258 | + $gender = 'female'; |
|
259 | + } |
|
260 | + |
|
261 | + if ('m' == $gender) { |
|
262 | + $gender = 'male'; |
|
263 | + } |
|
264 | + |
|
265 | + $userProfile->gender = $gender; |
|
266 | + |
|
267 | + return $userProfile; |
|
268 | + } |
|
269 | + |
|
270 | + /** |
|
271 | + * OpenID only provide the user profile one. This method will attempt to retrieve the profile from storage. |
|
272 | + */ |
|
273 | + public function getUserProfile() |
|
274 | + { |
|
275 | + $userProfile = $this->storage->get($this->providerId . '.user'); |
|
276 | + |
|
277 | + if (!is_object($userProfile)) { |
|
278 | + throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); |
|
279 | + } |
|
280 | + |
|
281 | + return $userProfile; |
|
282 | + } |
|
283 | 283 | } |
@@ -72,7 +72,7 @@ discard block |
||
72 | 72 | $hostUrl = parse_url($this->callback, PHP_URL_HOST); |
73 | 73 | |
74 | 74 | if ($hostPort) { |
75 | - $hostUrl .= ':' . $hostPort; |
|
75 | + $hostUrl .= ':'.$hostPort; |
|
76 | 76 | } |
77 | 77 | |
78 | 78 | // @fixme: add proxy |
@@ -104,7 +104,7 @@ discard block |
||
104 | 104 | */ |
105 | 105 | public function isConnected() |
106 | 106 | { |
107 | - return (bool)$this->storage->get($this->providerId . '.user'); |
|
107 | + return (bool) $this->storage->get($this->providerId.'.user'); |
|
108 | 108 | } |
109 | 109 | |
110 | 110 | /** |
@@ -112,7 +112,7 @@ discard block |
||
112 | 112 | */ |
113 | 113 | public function disconnect() |
114 | 114 | { |
115 | - $this->storage->delete($this->providerId . '.user'); |
|
115 | + $this->storage->delete($this->providerId.'.user'); |
|
116 | 116 | |
117 | 117 | return true; |
118 | 118 | } |
@@ -169,20 +169,20 @@ discard block |
||
169 | 169 | throw new AuthorizationDeniedException('User has cancelled the authentication.'); |
170 | 170 | } |
171 | 171 | |
172 | - if (!$this->openIdClient->validate()) { |
|
172 | + if ( ! $this->openIdClient->validate()) { |
|
173 | 173 | throw new UnexpectedApiResponseException('Invalid response received.'); |
174 | 174 | } |
175 | 175 | |
176 | 176 | $openidAttributes = $this->openIdClient->getAttributes(); |
177 | 177 | |
178 | - if (!$this->openIdClient->identity) { |
|
178 | + if ( ! $this->openIdClient->identity) { |
|
179 | 179 | throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); |
180 | 180 | } |
181 | 181 | |
182 | 182 | $userProfile = $this->fetchUserProfile($openidAttributes); |
183 | 183 | |
184 | 184 | /* with openid providers we only get user profiles once, so we store it */ |
185 | - $this->storage->set($this->providerId . '.user', $userProfile); |
|
185 | + $this->storage->set($this->providerId.'.user', $userProfile); |
|
186 | 186 | } |
187 | 187 | |
188 | 188 | /** |
@@ -237,7 +237,7 @@ discard block |
||
237 | 237 | |
238 | 238 | $userProfile->displayName = $userProfile->displayName |
239 | 239 | ? $userProfile->displayName |
240 | - : trim($userProfile->firstName . ' ' . $userProfile->lastName); |
|
240 | + : trim($userProfile->firstName.' '.$userProfile->lastName); |
|
241 | 241 | |
242 | 242 | return $userProfile; |
243 | 243 | } |
@@ -252,7 +252,7 @@ discard block |
||
252 | 252 | */ |
253 | 253 | protected function fetchUserGender(User\Profile $userProfile, $gender) |
254 | 254 | { |
255 | - $gender = strtolower((string)$gender); |
|
255 | + $gender = strtolower((string) $gender); |
|
256 | 256 | |
257 | 257 | if ('f' == $gender) { |
258 | 258 | $gender = 'female'; |
@@ -272,9 +272,9 @@ discard block |
||
272 | 272 | */ |
273 | 273 | public function getUserProfile() |
274 | 274 | { |
275 | - $userProfile = $this->storage->get($this->providerId . '.user'); |
|
275 | + $userProfile = $this->storage->get($this->providerId.'.user'); |
|
276 | 276 | |
277 | - if (!is_object($userProfile)) { |
|
277 | + if ( ! is_object($userProfile)) { |
|
278 | 278 | throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); |
279 | 279 | } |
280 | 280 |
@@ -10,114 +10,114 @@ |
||
10 | 10 | |
11 | 11 | class Mastodon extends OAuth2 |
12 | 12 | { |
13 | - /** |
|
14 | - * {@inheritdoc} |
|
15 | - */ |
|
16 | - public $scope = 'read'; |
|
17 | - |
|
18 | - /** |
|
19 | - * {@inheritdoc} |
|
20 | - */ |
|
21 | - protected $apiDocumentation = 'https://docs.joinmastodon.org/spec/oauth/'; |
|
22 | - |
|
23 | - /** |
|
24 | - * {@inheritdoc} |
|
25 | - */ |
|
26 | - protected function configure() |
|
27 | - { |
|
28 | - parent::configure(); |
|
29 | - |
|
30 | - if (!$this->config->exists('url')) { |
|
31 | - throw new InvalidApplicationCredentialsException( |
|
32 | - 'You must define a Mastodon instance url' |
|
33 | - ); |
|
34 | - } |
|
35 | - $url = $this->config->get('url'); |
|
36 | - |
|
37 | - $this->apiBaseUrl = $url . '/api/v1'; |
|
38 | - |
|
39 | - $this->authorizeUrl = $url . '/oauth/authorize'; |
|
40 | - $this->accessTokenUrl = $url . '/oauth/token'; |
|
41 | - } |
|
42 | - |
|
43 | - /** |
|
44 | - * {@inheritdoc} |
|
45 | - */ |
|
46 | - public function getUserProfile() |
|
47 | - { |
|
48 | - $response = $this->apiRequest('accounts/verify_credentials', 'GET', []); |
|
49 | - |
|
50 | - $data = new Data\Collection($response); |
|
51 | - |
|
52 | - if (!$data->exists('id') || !$data->get('id')) { |
|
53 | - throw new UnexpectedApiResponseException( |
|
54 | - 'Provider API returned an unexpected response.' |
|
55 | - ); |
|
56 | - } |
|
57 | - |
|
58 | - $userProfile = new Profile(); |
|
59 | - |
|
60 | - $userProfile->identifier = $data->get('id'); |
|
61 | - $userProfile->displayName = $data->get('username'); |
|
62 | - $userProfile->photoURL = |
|
63 | - $data->get('avatar') ?: $data->get('avatar_static'); |
|
64 | - $userProfile->webSiteURL = $data->get('url'); |
|
65 | - $userProfile->description = $data->get('note'); |
|
66 | - $userProfile->firstName = $data->get('display_name'); |
|
67 | - |
|
68 | - return $userProfile; |
|
69 | - } |
|
70 | - |
|
71 | - public function setUserStatus($status) |
|
72 | - { |
|
73 | - // Prepare request parameters. |
|
74 | - $params = []; |
|
75 | - if (isset($status['message'])) { |
|
76 | - $params['status'] = $status['message']; |
|
77 | - } |
|
78 | - |
|
79 | - if (isset($status['picture'])) { |
|
80 | - $headers = [ |
|
81 | - 'Content-Type' => 'multipart/form-data', |
|
82 | - ]; |
|
83 | - |
|
84 | - $pictures = $status['picture']; |
|
85 | - |
|
86 | - $ids = []; |
|
87 | - |
|
88 | - foreach ($pictures as $picture) { |
|
89 | - $images = $this->apiRequest( |
|
90 | - $this->config->get('url') . '/api/v2/media', |
|
91 | - 'POST', |
|
92 | - [ |
|
93 | - 'file' => new \CurlFile( |
|
94 | - $picture, |
|
95 | - 'image/jpg', |
|
96 | - 'filename' |
|
97 | - ), |
|
98 | - ], |
|
99 | - $headers, |
|
100 | - true |
|
101 | - ); |
|
102 | - |
|
103 | - $ids[] = $images->id; |
|
104 | - } |
|
105 | - |
|
106 | - $params['media_ids'] = $ids; |
|
107 | - } |
|
108 | - |
|
109 | - $headers = [ |
|
110 | - 'Content-Type' => 'application/json', |
|
111 | - ]; |
|
112 | - |
|
113 | - $response = $this->apiRequest( |
|
114 | - 'statuses', |
|
115 | - 'POST', |
|
116 | - $params, |
|
117 | - $headers, |
|
118 | - false |
|
119 | - ); |
|
120 | - |
|
121 | - return $response; |
|
122 | - } |
|
13 | + /** |
|
14 | + * {@inheritdoc} |
|
15 | + */ |
|
16 | + public $scope = 'read'; |
|
17 | + |
|
18 | + /** |
|
19 | + * {@inheritdoc} |
|
20 | + */ |
|
21 | + protected $apiDocumentation = 'https://docs.joinmastodon.org/spec/oauth/'; |
|
22 | + |
|
23 | + /** |
|
24 | + * {@inheritdoc} |
|
25 | + */ |
|
26 | + protected function configure() |
|
27 | + { |
|
28 | + parent::configure(); |
|
29 | + |
|
30 | + if (!$this->config->exists('url')) { |
|
31 | + throw new InvalidApplicationCredentialsException( |
|
32 | + 'You must define a Mastodon instance url' |
|
33 | + ); |
|
34 | + } |
|
35 | + $url = $this->config->get('url'); |
|
36 | + |
|
37 | + $this->apiBaseUrl = $url . '/api/v1'; |
|
38 | + |
|
39 | + $this->authorizeUrl = $url . '/oauth/authorize'; |
|
40 | + $this->accessTokenUrl = $url . '/oauth/token'; |
|
41 | + } |
|
42 | + |
|
43 | + /** |
|
44 | + * {@inheritdoc} |
|
45 | + */ |
|
46 | + public function getUserProfile() |
|
47 | + { |
|
48 | + $response = $this->apiRequest('accounts/verify_credentials', 'GET', []); |
|
49 | + |
|
50 | + $data = new Data\Collection($response); |
|
51 | + |
|
52 | + if (!$data->exists('id') || !$data->get('id')) { |
|
53 | + throw new UnexpectedApiResponseException( |
|
54 | + 'Provider API returned an unexpected response.' |
|
55 | + ); |
|
56 | + } |
|
57 | + |
|
58 | + $userProfile = new Profile(); |
|
59 | + |
|
60 | + $userProfile->identifier = $data->get('id'); |
|
61 | + $userProfile->displayName = $data->get('username'); |
|
62 | + $userProfile->photoURL = |
|
63 | + $data->get('avatar') ?: $data->get('avatar_static'); |
|
64 | + $userProfile->webSiteURL = $data->get('url'); |
|
65 | + $userProfile->description = $data->get('note'); |
|
66 | + $userProfile->firstName = $data->get('display_name'); |
|
67 | + |
|
68 | + return $userProfile; |
|
69 | + } |
|
70 | + |
|
71 | + public function setUserStatus($status) |
|
72 | + { |
|
73 | + // Prepare request parameters. |
|
74 | + $params = []; |
|
75 | + if (isset($status['message'])) { |
|
76 | + $params['status'] = $status['message']; |
|
77 | + } |
|
78 | + |
|
79 | + if (isset($status['picture'])) { |
|
80 | + $headers = [ |
|
81 | + 'Content-Type' => 'multipart/form-data', |
|
82 | + ]; |
|
83 | + |
|
84 | + $pictures = $status['picture']; |
|
85 | + |
|
86 | + $ids = []; |
|
87 | + |
|
88 | + foreach ($pictures as $picture) { |
|
89 | + $images = $this->apiRequest( |
|
90 | + $this->config->get('url') . '/api/v2/media', |
|
91 | + 'POST', |
|
92 | + [ |
|
93 | + 'file' => new \CurlFile( |
|
94 | + $picture, |
|
95 | + 'image/jpg', |
|
96 | + 'filename' |
|
97 | + ), |
|
98 | + ], |
|
99 | + $headers, |
|
100 | + true |
|
101 | + ); |
|
102 | + |
|
103 | + $ids[] = $images->id; |
|
104 | + } |
|
105 | + |
|
106 | + $params['media_ids'] = $ids; |
|
107 | + } |
|
108 | + |
|
109 | + $headers = [ |
|
110 | + 'Content-Type' => 'application/json', |
|
111 | + ]; |
|
112 | + |
|
113 | + $response = $this->apiRequest( |
|
114 | + 'statuses', |
|
115 | + 'POST', |
|
116 | + $params, |
|
117 | + $headers, |
|
118 | + false |
|
119 | + ); |
|
120 | + |
|
121 | + return $response; |
|
122 | + } |
|
123 | 123 | } |
@@ -27,17 +27,17 @@ discard block |
||
27 | 27 | { |
28 | 28 | parent::configure(); |
29 | 29 | |
30 | - if (!$this->config->exists('url')) { |
|
30 | + if ( ! $this->config->exists('url')) { |
|
31 | 31 | throw new InvalidApplicationCredentialsException( |
32 | 32 | 'You must define a Mastodon instance url' |
33 | 33 | ); |
34 | 34 | } |
35 | 35 | $url = $this->config->get('url'); |
36 | 36 | |
37 | - $this->apiBaseUrl = $url . '/api/v1'; |
|
37 | + $this->apiBaseUrl = $url.'/api/v1'; |
|
38 | 38 | |
39 | - $this->authorizeUrl = $url . '/oauth/authorize'; |
|
40 | - $this->accessTokenUrl = $url . '/oauth/token'; |
|
39 | + $this->authorizeUrl = $url.'/oauth/authorize'; |
|
40 | + $this->accessTokenUrl = $url.'/oauth/token'; |
|
41 | 41 | } |
42 | 42 | |
43 | 43 | /** |
@@ -49,7 +49,7 @@ discard block |
||
49 | 49 | |
50 | 50 | $data = new Data\Collection($response); |
51 | 51 | |
52 | - if (!$data->exists('id') || !$data->get('id')) { |
|
52 | + if ( ! $data->exists('id') || ! $data->get('id')) { |
|
53 | 53 | throw new UnexpectedApiResponseException( |
54 | 54 | 'Provider API returned an unexpected response.' |
55 | 55 | ); |
@@ -87,7 +87,7 @@ discard block |
||
87 | 87 | |
88 | 88 | foreach ($pictures as $picture) { |
89 | 89 | $images = $this->apiRequest( |
90 | - $this->config->get('url') . '/api/v2/media', |
|
90 | + $this->config->get('url').'/api/v2/media', |
|
91 | 91 | 'POST', |
92 | 92 | [ |
93 | 93 | 'file' => new \CurlFile( |
@@ -64,278 +64,278 @@ |
||
64 | 64 | */ |
65 | 65 | class Apple extends OAuth2 |
66 | 66 | { |
67 | - /** |
|
68 | - * {@inheritdoc} |
|
69 | - */ |
|
70 | - protected $scope = 'name email'; |
|
71 | - |
|
72 | - /** |
|
73 | - * {@inheritdoc} |
|
74 | - */ |
|
75 | - protected $apiBaseUrl = 'https://appleid.apple.com/auth/'; |
|
76 | - |
|
77 | - /** |
|
78 | - * {@inheritdoc} |
|
79 | - */ |
|
80 | - protected $authorizeUrl = 'https://appleid.apple.com/auth/authorize'; |
|
81 | - |
|
82 | - /** |
|
83 | - * {@inheritdoc} |
|
84 | - */ |
|
85 | - protected $accessTokenUrl = 'https://appleid.apple.com/auth/token'; |
|
86 | - |
|
87 | - /** |
|
88 | - * {@inheritdoc} |
|
89 | - */ |
|
90 | - protected $apiDocumentation = 'https://developer.apple.com/documentation/sign_in_with_apple'; |
|
91 | - |
|
92 | - /** |
|
93 | - * {@inheritdoc} |
|
94 | - * The Sign in with Apple servers require percent encoding (or URL encoding) |
|
95 | - * for its query parameters. If you are using the Sign in with Apple REST API, |
|
96 | - * you must provide values with encoded spaces (`%20`) instead of plus (`+`) signs. |
|
97 | - */ |
|
98 | - protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC3986; |
|
99 | - |
|
100 | - /** |
|
101 | - * {@inheritdoc} |
|
102 | - */ |
|
103 | - protected function initialize() |
|
104 | - { |
|
105 | - parent::initialize(); |
|
106 | - $this->AuthorizeUrlParameters['response_mode'] = 'form_post'; |
|
107 | - |
|
108 | - if ($this->isRefreshTokenAvailable()) { |
|
109 | - $this->tokenRefreshParameters += [ |
|
110 | - 'client_id' => $this->clientId, |
|
111 | - 'client_secret' => $this->clientSecret, |
|
112 | - ]; |
|
113 | - } |
|
114 | - } |
|
115 | - |
|
116 | - /** |
|
117 | - * {@inheritdoc} |
|
118 | - * @throws InvalidApplicationCredentialsException |
|
119 | - */ |
|
120 | - protected function configure() |
|
121 | - { |
|
122 | - $keys = $this->config->get('keys'); |
|
123 | - $keys['secret'] = $this->getSecret(); |
|
124 | - $this->config->set('keys', $keys); |
|
125 | - parent::configure(); |
|
126 | - } |
|
127 | - |
|
128 | - /** |
|
129 | - * {@inheritdoc} |
|
130 | - * |
|
131 | - * include id_token $tokenNames |
|
132 | - */ |
|
133 | - public function getAccessToken() |
|
134 | - { |
|
135 | - $tokenNames = [ |
|
136 | - 'access_token', |
|
137 | - 'id_token', |
|
138 | - 'access_token_secret', |
|
139 | - 'token_type', |
|
140 | - 'refresh_token', |
|
141 | - 'expires_in', |
|
142 | - 'expires_at', |
|
143 | - ]; |
|
144 | - |
|
145 | - $tokens = []; |
|
146 | - |
|
147 | - foreach ($tokenNames as $name) { |
|
148 | - if ($this->getStoredData($name)) { |
|
149 | - $tokens[$name] = $this->getStoredData($name); |
|
150 | - } |
|
151 | - } |
|
152 | - |
|
153 | - return $tokens; |
|
154 | - } |
|
155 | - |
|
156 | - /** |
|
157 | - * {@inheritdoc} |
|
158 | - */ |
|
159 | - protected function validateAccessTokenExchange($response) |
|
160 | - { |
|
161 | - $collection = parent::validateAccessTokenExchange($response); |
|
162 | - |
|
163 | - $this->storeData('id_token', $collection->get('id_token')); |
|
164 | - |
|
165 | - return $collection; |
|
166 | - } |
|
167 | - |
|
168 | - /** |
|
169 | - * Get the user profile |
|
170 | - * |
|
171 | - * @throws HttpClientFailureException |
|
172 | - * @throws InvalidAccessTokenException |
|
173 | - * @throws UnexpectedValueException |
|
174 | - * @throws HttpRequestFailedException |
|
175 | - * @throws Exception |
|
176 | - */ |
|
177 | - public function getUserProfile() |
|
178 | - { |
|
179 | - $id_token = $this->getStoredData('id_token'); |
|
180 | - |
|
181 | - $verifyTokenSignature = |
|
182 | - $this->config->exists('verifyTokenSignature') ? $this->config->get('verifyTokenSignature') : true; |
|
183 | - |
|
184 | - if (!$verifyTokenSignature) { |
|
185 | - // payload extraction by https://github.com/omidborjian |
|
186 | - // https://github.com/hybridauth/hybridauth/issues/1095#issuecomment-626479263 |
|
187 | - // JWT splits the string to 3 components 1) first is header 2) is payload 3) is signature |
|
188 | - $payload = explode('.', $id_token)[1]; |
|
189 | - $payload = json_decode(base64_decode($payload)); |
|
190 | - } else { |
|
191 | - // validate the token signature and get the payload |
|
192 | - $publicKeys = $this->apiRequest('keys'); |
|
193 | - |
|
194 | - JWT::$leeway = 120; |
|
195 | - |
|
196 | - $error = false; |
|
197 | - $payload = null; |
|
198 | - |
|
199 | - foreach ($publicKeys->keys as $publicKey) { |
|
200 | - try { |
|
201 | - $jwk = (array)$publicKey; |
|
202 | - |
|
203 | - $key = PublicKeyLoader::load( |
|
204 | - [ |
|
205 | - 'e' => new BigInteger(base64_decode($jwk['e']), 256), |
|
206 | - 'n' => new BigInteger(base64_decode(strtr($jwk['n'], '-_', '+/'), true), 256) |
|
207 | - ] |
|
208 | - ) |
|
209 | - ->withHash('sha1') |
|
210 | - ->withMGFHash('sha1'); |
|
211 | - |
|
212 | - $pem = (string)$key; |
|
213 | - |
|
214 | - $payload = (version_compare($this->getJwtVersion(), '6.2') < 0) ? |
|
215 | - JWT::decode($id_token, $pem, ['RS256']) : |
|
216 | - JWT::decode($id_token, new Key($pem, 'RS256')); |
|
217 | - break; |
|
218 | - } catch (Exception $e) { |
|
219 | - $error = $e->getMessage(); |
|
220 | - if ($e instanceof ExpiredException) { |
|
221 | - break; |
|
222 | - } |
|
223 | - } |
|
224 | - } |
|
225 | - |
|
226 | - if ($error && !$payload) { |
|
227 | - throw new Exception($error); |
|
228 | - } |
|
229 | - } |
|
230 | - |
|
231 | - $data = new Data\Collection($payload); |
|
232 | - |
|
233 | - if (!$data->exists('sub')) { |
|
234 | - throw new UnexpectedValueException('Missing token payload.'); |
|
235 | - } |
|
236 | - |
|
237 | - $userProfile = new User\Profile(); |
|
238 | - $userProfile->identifier = $data->get('sub'); |
|
239 | - $userProfile->email = $data->get('email'); |
|
240 | - $this->storeData('expires_at', $data->get('exp')); |
|
241 | - |
|
242 | - if (!empty($_REQUEST['user'])) { |
|
243 | - $objUser = json_decode($_REQUEST['user']); |
|
244 | - $user = new Data\Collection($objUser); |
|
245 | - if (!$user->isEmpty()) { |
|
246 | - $name = $user->get('name'); |
|
247 | - if (!empty($name->firstName)) { |
|
248 | - $userProfile->firstName = $name->firstName; |
|
249 | - $userProfile->lastName = $name->lastName; |
|
250 | - $userProfile->displayName = join(' ', [$userProfile->firstName, $userProfile->lastName]); |
|
251 | - } |
|
252 | - } |
|
253 | - } |
|
254 | - |
|
255 | - return $userProfile; |
|
256 | - } |
|
257 | - |
|
258 | - /** |
|
259 | - * Get the Apple secret as a JWT token |
|
260 | - * |
|
261 | - * @return string secret token |
|
262 | - * @throws InvalidApplicationCredentialsException |
|
263 | - */ |
|
264 | - private function getSecret() |
|
265 | - { |
|
266 | - // Your 10-character Team ID |
|
267 | - $team_id = $this->config->filter('keys')->get('team_id'); |
|
268 | - |
|
269 | - if (!$team_id) { |
|
270 | - throw new InvalidApplicationCredentialsException( |
|
271 | - 'Missing parameter team_id: your team id is required to generate the JWS token.' |
|
272 | - ); |
|
273 | - } |
|
274 | - |
|
275 | - // Your Services ID, e.g. com.aaronparecki.services |
|
276 | - $client_id = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); |
|
277 | - |
|
278 | - if (!$client_id) { |
|
279 | - throw new InvalidApplicationCredentialsException( |
|
280 | - 'Missing parameter id: your client id is required to generate the JWS token.' |
|
281 | - ); |
|
282 | - } |
|
283 | - |
|
284 | - // Find the 10-char Key ID value from the portal |
|
285 | - $key_id = $this->config->filter('keys')->get('key_id'); |
|
286 | - |
|
287 | - if (!$key_id) { |
|
288 | - throw new InvalidApplicationCredentialsException( |
|
289 | - 'Missing parameter key_id: your key id is required to generate the JWS token.' |
|
290 | - ); |
|
291 | - } |
|
292 | - |
|
293 | - // Find the 10-char Key ID value from the portal |
|
294 | - $key_content = $this->config->filter('keys')->get('key_content'); |
|
295 | - |
|
296 | - // Save your private key from Apple in a file called `key.txt` |
|
297 | - if (!$key_content) { |
|
298 | - $key_file = $this->config->filter('keys')->get('key_file'); |
|
299 | - |
|
300 | - if (!$key_file) { |
|
301 | - throw new InvalidApplicationCredentialsException( |
|
302 | - 'Missing parameter key_content or key_file: your key is required to generate the JWS token.' |
|
303 | - ); |
|
304 | - } |
|
305 | - |
|
306 | - if (!file_exists($key_file)) { |
|
307 | - throw new InvalidApplicationCredentialsException( |
|
308 | - "Your key file $key_file does not exist." |
|
309 | - ); |
|
310 | - } |
|
311 | - |
|
312 | - $key_content = file_get_contents($key_file); |
|
313 | - } |
|
314 | - |
|
315 | - $data = [ |
|
316 | - 'iat' => time(), |
|
317 | - 'exp' => time() + 86400 * 180, |
|
318 | - 'iss' => $team_id, |
|
319 | - 'aud' => 'https://appleid.apple.com', |
|
320 | - 'sub' => $client_id |
|
321 | - ]; |
|
322 | - |
|
323 | - return JWT::encode($data, $key_content, 'ES256', $key_id); |
|
324 | - } |
|
325 | - |
|
326 | - /** |
|
327 | - * Try to get the installed JWT version |
|
328 | - * |
|
329 | - * If composer 2 is installed use InstalledVersions::getVersion, |
|
330 | - * otherwise return an empty string because no version check is available |
|
331 | - * |
|
332 | - * @return string|null |
|
333 | - */ |
|
334 | - private function getJwtVersion() |
|
335 | - { |
|
336 | - // assume old JWT version if no version check is possible because composer 1 is installed |
|
337 | - return class_exists('Composer\InstalledVersions') ? |
|
338 | - InstalledVersions::getVersion('firebase/php-jwt') : |
|
339 | - ''; |
|
340 | - } |
|
67 | + /** |
|
68 | + * {@inheritdoc} |
|
69 | + */ |
|
70 | + protected $scope = 'name email'; |
|
71 | + |
|
72 | + /** |
|
73 | + * {@inheritdoc} |
|
74 | + */ |
|
75 | + protected $apiBaseUrl = 'https://appleid.apple.com/auth/'; |
|
76 | + |
|
77 | + /** |
|
78 | + * {@inheritdoc} |
|
79 | + */ |
|
80 | + protected $authorizeUrl = 'https://appleid.apple.com/auth/authorize'; |
|
81 | + |
|
82 | + /** |
|
83 | + * {@inheritdoc} |
|
84 | + */ |
|
85 | + protected $accessTokenUrl = 'https://appleid.apple.com/auth/token'; |
|
86 | + |
|
87 | + /** |
|
88 | + * {@inheritdoc} |
|
89 | + */ |
|
90 | + protected $apiDocumentation = 'https://developer.apple.com/documentation/sign_in_with_apple'; |
|
91 | + |
|
92 | + /** |
|
93 | + * {@inheritdoc} |
|
94 | + * The Sign in with Apple servers require percent encoding (or URL encoding) |
|
95 | + * for its query parameters. If you are using the Sign in with Apple REST API, |
|
96 | + * you must provide values with encoded spaces (`%20`) instead of plus (`+`) signs. |
|
97 | + */ |
|
98 | + protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC3986; |
|
99 | + |
|
100 | + /** |
|
101 | + * {@inheritdoc} |
|
102 | + */ |
|
103 | + protected function initialize() |
|
104 | + { |
|
105 | + parent::initialize(); |
|
106 | + $this->AuthorizeUrlParameters['response_mode'] = 'form_post'; |
|
107 | + |
|
108 | + if ($this->isRefreshTokenAvailable()) { |
|
109 | + $this->tokenRefreshParameters += [ |
|
110 | + 'client_id' => $this->clientId, |
|
111 | + 'client_secret' => $this->clientSecret, |
|
112 | + ]; |
|
113 | + } |
|
114 | + } |
|
115 | + |
|
116 | + /** |
|
117 | + * {@inheritdoc} |
|
118 | + * @throws InvalidApplicationCredentialsException |
|
119 | + */ |
|
120 | + protected function configure() |
|
121 | + { |
|
122 | + $keys = $this->config->get('keys'); |
|
123 | + $keys['secret'] = $this->getSecret(); |
|
124 | + $this->config->set('keys', $keys); |
|
125 | + parent::configure(); |
|
126 | + } |
|
127 | + |
|
128 | + /** |
|
129 | + * {@inheritdoc} |
|
130 | + * |
|
131 | + * include id_token $tokenNames |
|
132 | + */ |
|
133 | + public function getAccessToken() |
|
134 | + { |
|
135 | + $tokenNames = [ |
|
136 | + 'access_token', |
|
137 | + 'id_token', |
|
138 | + 'access_token_secret', |
|
139 | + 'token_type', |
|
140 | + 'refresh_token', |
|
141 | + 'expires_in', |
|
142 | + 'expires_at', |
|
143 | + ]; |
|
144 | + |
|
145 | + $tokens = []; |
|
146 | + |
|
147 | + foreach ($tokenNames as $name) { |
|
148 | + if ($this->getStoredData($name)) { |
|
149 | + $tokens[$name] = $this->getStoredData($name); |
|
150 | + } |
|
151 | + } |
|
152 | + |
|
153 | + return $tokens; |
|
154 | + } |
|
155 | + |
|
156 | + /** |
|
157 | + * {@inheritdoc} |
|
158 | + */ |
|
159 | + protected function validateAccessTokenExchange($response) |
|
160 | + { |
|
161 | + $collection = parent::validateAccessTokenExchange($response); |
|
162 | + |
|
163 | + $this->storeData('id_token', $collection->get('id_token')); |
|
164 | + |
|
165 | + return $collection; |
|
166 | + } |
|
167 | + |
|
168 | + /** |
|
169 | + * Get the user profile |
|
170 | + * |
|
171 | + * @throws HttpClientFailureException |
|
172 | + * @throws InvalidAccessTokenException |
|
173 | + * @throws UnexpectedValueException |
|
174 | + * @throws HttpRequestFailedException |
|
175 | + * @throws Exception |
|
176 | + */ |
|
177 | + public function getUserProfile() |
|
178 | + { |
|
179 | + $id_token = $this->getStoredData('id_token'); |
|
180 | + |
|
181 | + $verifyTokenSignature = |
|
182 | + $this->config->exists('verifyTokenSignature') ? $this->config->get('verifyTokenSignature') : true; |
|
183 | + |
|
184 | + if (!$verifyTokenSignature) { |
|
185 | + // payload extraction by https://github.com/omidborjian |
|
186 | + // https://github.com/hybridauth/hybridauth/issues/1095#issuecomment-626479263 |
|
187 | + // JWT splits the string to 3 components 1) first is header 2) is payload 3) is signature |
|
188 | + $payload = explode('.', $id_token)[1]; |
|
189 | + $payload = json_decode(base64_decode($payload)); |
|
190 | + } else { |
|
191 | + // validate the token signature and get the payload |
|
192 | + $publicKeys = $this->apiRequest('keys'); |
|
193 | + |
|
194 | + JWT::$leeway = 120; |
|
195 | + |
|
196 | + $error = false; |
|
197 | + $payload = null; |
|
198 | + |
|
199 | + foreach ($publicKeys->keys as $publicKey) { |
|
200 | + try { |
|
201 | + $jwk = (array)$publicKey; |
|
202 | + |
|
203 | + $key = PublicKeyLoader::load( |
|
204 | + [ |
|
205 | + 'e' => new BigInteger(base64_decode($jwk['e']), 256), |
|
206 | + 'n' => new BigInteger(base64_decode(strtr($jwk['n'], '-_', '+/'), true), 256) |
|
207 | + ] |
|
208 | + ) |
|
209 | + ->withHash('sha1') |
|
210 | + ->withMGFHash('sha1'); |
|
211 | + |
|
212 | + $pem = (string)$key; |
|
213 | + |
|
214 | + $payload = (version_compare($this->getJwtVersion(), '6.2') < 0) ? |
|
215 | + JWT::decode($id_token, $pem, ['RS256']) : |
|
216 | + JWT::decode($id_token, new Key($pem, 'RS256')); |
|
217 | + break; |
|
218 | + } catch (Exception $e) { |
|
219 | + $error = $e->getMessage(); |
|
220 | + if ($e instanceof ExpiredException) { |
|
221 | + break; |
|
222 | + } |
|
223 | + } |
|
224 | + } |
|
225 | + |
|
226 | + if ($error && !$payload) { |
|
227 | + throw new Exception($error); |
|
228 | + } |
|
229 | + } |
|
230 | + |
|
231 | + $data = new Data\Collection($payload); |
|
232 | + |
|
233 | + if (!$data->exists('sub')) { |
|
234 | + throw new UnexpectedValueException('Missing token payload.'); |
|
235 | + } |
|
236 | + |
|
237 | + $userProfile = new User\Profile(); |
|
238 | + $userProfile->identifier = $data->get('sub'); |
|
239 | + $userProfile->email = $data->get('email'); |
|
240 | + $this->storeData('expires_at', $data->get('exp')); |
|
241 | + |
|
242 | + if (!empty($_REQUEST['user'])) { |
|
243 | + $objUser = json_decode($_REQUEST['user']); |
|
244 | + $user = new Data\Collection($objUser); |
|
245 | + if (!$user->isEmpty()) { |
|
246 | + $name = $user->get('name'); |
|
247 | + if (!empty($name->firstName)) { |
|
248 | + $userProfile->firstName = $name->firstName; |
|
249 | + $userProfile->lastName = $name->lastName; |
|
250 | + $userProfile->displayName = join(' ', [$userProfile->firstName, $userProfile->lastName]); |
|
251 | + } |
|
252 | + } |
|
253 | + } |
|
254 | + |
|
255 | + return $userProfile; |
|
256 | + } |
|
257 | + |
|
258 | + /** |
|
259 | + * Get the Apple secret as a JWT token |
|
260 | + * |
|
261 | + * @return string secret token |
|
262 | + * @throws InvalidApplicationCredentialsException |
|
263 | + */ |
|
264 | + private function getSecret() |
|
265 | + { |
|
266 | + // Your 10-character Team ID |
|
267 | + $team_id = $this->config->filter('keys')->get('team_id'); |
|
268 | + |
|
269 | + if (!$team_id) { |
|
270 | + throw new InvalidApplicationCredentialsException( |
|
271 | + 'Missing parameter team_id: your team id is required to generate the JWS token.' |
|
272 | + ); |
|
273 | + } |
|
274 | + |
|
275 | + // Your Services ID, e.g. com.aaronparecki.services |
|
276 | + $client_id = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); |
|
277 | + |
|
278 | + if (!$client_id) { |
|
279 | + throw new InvalidApplicationCredentialsException( |
|
280 | + 'Missing parameter id: your client id is required to generate the JWS token.' |
|
281 | + ); |
|
282 | + } |
|
283 | + |
|
284 | + // Find the 10-char Key ID value from the portal |
|
285 | + $key_id = $this->config->filter('keys')->get('key_id'); |
|
286 | + |
|
287 | + if (!$key_id) { |
|
288 | + throw new InvalidApplicationCredentialsException( |
|
289 | + 'Missing parameter key_id: your key id is required to generate the JWS token.' |
|
290 | + ); |
|
291 | + } |
|
292 | + |
|
293 | + // Find the 10-char Key ID value from the portal |
|
294 | + $key_content = $this->config->filter('keys')->get('key_content'); |
|
295 | + |
|
296 | + // Save your private key from Apple in a file called `key.txt` |
|
297 | + if (!$key_content) { |
|
298 | + $key_file = $this->config->filter('keys')->get('key_file'); |
|
299 | + |
|
300 | + if (!$key_file) { |
|
301 | + throw new InvalidApplicationCredentialsException( |
|
302 | + 'Missing parameter key_content or key_file: your key is required to generate the JWS token.' |
|
303 | + ); |
|
304 | + } |
|
305 | + |
|
306 | + if (!file_exists($key_file)) { |
|
307 | + throw new InvalidApplicationCredentialsException( |
|
308 | + "Your key file $key_file does not exist." |
|
309 | + ); |
|
310 | + } |
|
311 | + |
|
312 | + $key_content = file_get_contents($key_file); |
|
313 | + } |
|
314 | + |
|
315 | + $data = [ |
|
316 | + 'iat' => time(), |
|
317 | + 'exp' => time() + 86400 * 180, |
|
318 | + 'iss' => $team_id, |
|
319 | + 'aud' => 'https://appleid.apple.com', |
|
320 | + 'sub' => $client_id |
|
321 | + ]; |
|
322 | + |
|
323 | + return JWT::encode($data, $key_content, 'ES256', $key_id); |
|
324 | + } |
|
325 | + |
|
326 | + /** |
|
327 | + * Try to get the installed JWT version |
|
328 | + * |
|
329 | + * If composer 2 is installed use InstalledVersions::getVersion, |
|
330 | + * otherwise return an empty string because no version check is available |
|
331 | + * |
|
332 | + * @return string|null |
|
333 | + */ |
|
334 | + private function getJwtVersion() |
|
335 | + { |
|
336 | + // assume old JWT version if no version check is possible because composer 1 is installed |
|
337 | + return class_exists('Composer\InstalledVersions') ? |
|
338 | + InstalledVersions::getVersion('firebase/php-jwt') : |
|
339 | + ''; |
|
340 | + } |
|
341 | 341 | } |
@@ -181,7 +181,7 @@ discard block |
||
181 | 181 | $verifyTokenSignature = |
182 | 182 | $this->config->exists('verifyTokenSignature') ? $this->config->get('verifyTokenSignature') : true; |
183 | 183 | |
184 | - if (!$verifyTokenSignature) { |
|
184 | + if ( ! $verifyTokenSignature) { |
|
185 | 185 | // payload extraction by https://github.com/omidborjian |
186 | 186 | // https://github.com/hybridauth/hybridauth/issues/1095#issuecomment-626479263 |
187 | 187 | // JWT splits the string to 3 components 1) first is header 2) is payload 3) is signature |
@@ -198,7 +198,7 @@ discard block |
||
198 | 198 | |
199 | 199 | foreach ($publicKeys->keys as $publicKey) { |
200 | 200 | try { |
201 | - $jwk = (array)$publicKey; |
|
201 | + $jwk = (array) $publicKey; |
|
202 | 202 | |
203 | 203 | $key = PublicKeyLoader::load( |
204 | 204 | [ |
@@ -209,11 +209,10 @@ discard block |
||
209 | 209 | ->withHash('sha1') |
210 | 210 | ->withMGFHash('sha1'); |
211 | 211 | |
212 | - $pem = (string)$key; |
|
212 | + $pem = (string) $key; |
|
213 | 213 | |
214 | 214 | $payload = (version_compare($this->getJwtVersion(), '6.2') < 0) ? |
215 | - JWT::decode($id_token, $pem, ['RS256']) : |
|
216 | - JWT::decode($id_token, new Key($pem, 'RS256')); |
|
215 | + JWT::decode($id_token, $pem, ['RS256']) : JWT::decode($id_token, new Key($pem, 'RS256')); |
|
217 | 216 | break; |
218 | 217 | } catch (Exception $e) { |
219 | 218 | $error = $e->getMessage(); |
@@ -223,14 +222,14 @@ discard block |
||
223 | 222 | } |
224 | 223 | } |
225 | 224 | |
226 | - if ($error && !$payload) { |
|
225 | + if ($error && ! $payload) { |
|
227 | 226 | throw new Exception($error); |
228 | 227 | } |
229 | 228 | } |
230 | 229 | |
231 | 230 | $data = new Data\Collection($payload); |
232 | 231 | |
233 | - if (!$data->exists('sub')) { |
|
232 | + if ( ! $data->exists('sub')) { |
|
234 | 233 | throw new UnexpectedValueException('Missing token payload.'); |
235 | 234 | } |
236 | 235 | |
@@ -239,12 +238,12 @@ discard block |
||
239 | 238 | $userProfile->email = $data->get('email'); |
240 | 239 | $this->storeData('expires_at', $data->get('exp')); |
241 | 240 | |
242 | - if (!empty($_REQUEST['user'])) { |
|
241 | + if ( ! empty($_REQUEST['user'])) { |
|
243 | 242 | $objUser = json_decode($_REQUEST['user']); |
244 | 243 | $user = new Data\Collection($objUser); |
245 | - if (!$user->isEmpty()) { |
|
244 | + if ( ! $user->isEmpty()) { |
|
246 | 245 | $name = $user->get('name'); |
247 | - if (!empty($name->firstName)) { |
|
246 | + if ( ! empty($name->firstName)) { |
|
248 | 247 | $userProfile->firstName = $name->firstName; |
249 | 248 | $userProfile->lastName = $name->lastName; |
250 | 249 | $userProfile->displayName = join(' ', [$userProfile->firstName, $userProfile->lastName]); |
@@ -266,7 +265,7 @@ discard block |
||
266 | 265 | // Your 10-character Team ID |
267 | 266 | $team_id = $this->config->filter('keys')->get('team_id'); |
268 | 267 | |
269 | - if (!$team_id) { |
|
268 | + if ( ! $team_id) { |
|
270 | 269 | throw new InvalidApplicationCredentialsException( |
271 | 270 | 'Missing parameter team_id: your team id is required to generate the JWS token.' |
272 | 271 | ); |
@@ -275,7 +274,7 @@ discard block |
||
275 | 274 | // Your Services ID, e.g. com.aaronparecki.services |
276 | 275 | $client_id = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); |
277 | 276 | |
278 | - if (!$client_id) { |
|
277 | + if ( ! $client_id) { |
|
279 | 278 | throw new InvalidApplicationCredentialsException( |
280 | 279 | 'Missing parameter id: your client id is required to generate the JWS token.' |
281 | 280 | ); |
@@ -284,7 +283,7 @@ discard block |
||
284 | 283 | // Find the 10-char Key ID value from the portal |
285 | 284 | $key_id = $this->config->filter('keys')->get('key_id'); |
286 | 285 | |
287 | - if (!$key_id) { |
|
286 | + if ( ! $key_id) { |
|
288 | 287 | throw new InvalidApplicationCredentialsException( |
289 | 288 | 'Missing parameter key_id: your key id is required to generate the JWS token.' |
290 | 289 | ); |
@@ -294,16 +293,16 @@ discard block |
||
294 | 293 | $key_content = $this->config->filter('keys')->get('key_content'); |
295 | 294 | |
296 | 295 | // Save your private key from Apple in a file called `key.txt` |
297 | - if (!$key_content) { |
|
296 | + if ( ! $key_content) { |
|
298 | 297 | $key_file = $this->config->filter('keys')->get('key_file'); |
299 | 298 | |
300 | - if (!$key_file) { |
|
299 | + if ( ! $key_file) { |
|
301 | 300 | throw new InvalidApplicationCredentialsException( |
302 | 301 | 'Missing parameter key_content or key_file: your key is required to generate the JWS token.' |
303 | 302 | ); |
304 | 303 | } |
305 | 304 | |
306 | - if (!file_exists($key_file)) { |
|
305 | + if ( ! file_exists($key_file)) { |
|
307 | 306 | throw new InvalidApplicationCredentialsException( |
308 | 307 | "Your key file $key_file does not exist." |
309 | 308 | ); |
@@ -335,7 +334,6 @@ discard block |
||
335 | 334 | { |
336 | 335 | // assume old JWT version if no version check is possible because composer 1 is installed |
337 | 336 | return class_exists('Composer\InstalledVersions') ? |
338 | - InstalledVersions::getVersion('firebase/php-jwt') : |
|
339 | - ''; |
|
337 | + InstalledVersions::getVersion('firebase/php-jwt') : ''; |
|
340 | 338 | } |
341 | 339 | } |
@@ -24,721 +24,721 @@ |
||
24 | 24 | */ |
25 | 25 | abstract class OAuth2 extends AbstractAdapter implements AdapterInterface |
26 | 26 | { |
27 | - /** |
|
28 | - * Client Identifier |
|
29 | - * |
|
30 | - * RFC6749: client_id REQUIRED. The client identifier issued to the client during |
|
31 | - * the registration process described by Section 2.2. |
|
32 | - * |
|
33 | - * http://tools.ietf.org/html/rfc6749#section-2.2 |
|
34 | - * |
|
35 | - * @var string |
|
36 | - */ |
|
37 | - protected $clientId = ''; |
|
38 | - |
|
39 | - /** |
|
40 | - * Client Secret |
|
41 | - * |
|
42 | - * RFC6749: client_secret REQUIRED. The client secret. The client MAY omit the |
|
43 | - * parameter if the client secret is an empty string. |
|
44 | - * |
|
45 | - * http://tools.ietf.org/html/rfc6749#section-2.2 |
|
46 | - * |
|
47 | - * @var string |
|
48 | - */ |
|
49 | - protected $clientSecret = ''; |
|
50 | - |
|
51 | - /** |
|
52 | - * Access Token Scope |
|
53 | - * |
|
54 | - * RFC6749: The authorization and token endpoints allow the client to specify the |
|
55 | - * scope of the access request using the "scope" request parameter. |
|
56 | - * |
|
57 | - * http://tools.ietf.org/html/rfc6749#section-3.3 |
|
58 | - * |
|
59 | - * @var string |
|
60 | - */ |
|
61 | - protected $scope = ''; |
|
62 | - |
|
63 | - /** |
|
64 | - * Base URL to provider API |
|
65 | - * |
|
66 | - * This var will be used to build urls when sending signed requests |
|
67 | - * |
|
68 | - * @var string |
|
69 | - */ |
|
70 | - protected $apiBaseUrl = ''; |
|
71 | - |
|
72 | - /** |
|
73 | - * Authorization Endpoint |
|
74 | - * |
|
75 | - * RFC6749: The authorization endpoint is used to interact with the resource |
|
76 | - * owner and obtain an authorization grant. |
|
77 | - * |
|
78 | - * http://tools.ietf.org/html/rfc6749#section-3.1 |
|
79 | - * |
|
80 | - * @var string |
|
81 | - */ |
|
82 | - protected $authorizeUrl = ''; |
|
83 | - |
|
84 | - /** |
|
85 | - * Access Token Endpoint |
|
86 | - * |
|
87 | - * RFC6749: The token endpoint is used by the client to obtain an access token by |
|
88 | - * presenting its authorization grant or refresh token. |
|
89 | - * |
|
90 | - * http://tools.ietf.org/html/rfc6749#section-3.2 |
|
91 | - * |
|
92 | - * @var string |
|
93 | - */ |
|
94 | - protected $accessTokenUrl = ''; |
|
95 | - |
|
96 | - /** |
|
97 | - * TokenInfo endpoint |
|
98 | - * |
|
99 | - * Access token validation. OPTIONAL. |
|
100 | - * |
|
101 | - * @var string |
|
102 | - */ |
|
103 | - protected $accessTokenInfoUrl = ''; |
|
104 | - |
|
105 | - /** |
|
106 | - * IPD API Documentation |
|
107 | - * |
|
108 | - * OPTIONAL. |
|
109 | - * |
|
110 | - * @var string |
|
111 | - */ |
|
112 | - protected $apiDocumentation = ''; |
|
113 | - |
|
114 | - /** |
|
115 | - * Redirection Endpoint or Callback |
|
116 | - * |
|
117 | - * RFC6749: After completing its interaction with the resource owner, the |
|
118 | - * authorization server directs the resource owner's user-agent back to |
|
119 | - * the client. |
|
120 | - * |
|
121 | - * http://tools.ietf.org/html/rfc6749#section-3.1.2 |
|
122 | - * |
|
123 | - * @var string |
|
124 | - */ |
|
125 | - protected $callback = ''; |
|
126 | - |
|
127 | - /** |
|
128 | - * Authorization Url Parameters |
|
129 | - * |
|
130 | - * @var array |
|
131 | - */ |
|
132 | - protected $AuthorizeUrlParameters = []; |
|
133 | - |
|
134 | - |
|
135 | - /** |
|
136 | - * Authorization Url Parameter encoding type |
|
137 | - * @see https://www.php.net/manual/de/function.http-build-query.php |
|
138 | - * |
|
139 | - * @var string |
|
140 | - */ |
|
141 | - protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC1738; |
|
142 | - |
|
143 | - /** |
|
144 | - * Authorization Request State |
|
145 | - * |
|
146 | - * @var bool |
|
147 | - */ |
|
148 | - protected $supportRequestState = true; |
|
149 | - |
|
150 | - /** |
|
151 | - * Access Token name |
|
152 | - * |
|
153 | - * While most providers will use 'access_token' as name for the Access Token attribute, other do not. |
|
154 | - * On the latter case, this should be set by sub classes. |
|
155 | - * |
|
156 | - * @var string |
|
157 | - */ |
|
158 | - protected $accessTokenName = 'access_token'; |
|
159 | - |
|
160 | - /** |
|
161 | - * Authorization Request HTTP method. |
|
162 | - * |
|
163 | - * @see exchangeCodeForAccessToken() |
|
164 | - * |
|
165 | - * @var string |
|
166 | - */ |
|
167 | - protected $tokenExchangeMethod = 'POST'; |
|
168 | - |
|
169 | - /** |
|
170 | - * Authorization Request URL parameters. |
|
171 | - * |
|
172 | - * Sub classes may change add any additional parameter when necessary. |
|
173 | - * |
|
174 | - * @see exchangeCodeForAccessToken() |
|
175 | - * |
|
176 | - * @var array |
|
177 | - */ |
|
178 | - protected $tokenExchangeParameters = []; |
|
179 | - |
|
180 | - /** |
|
181 | - * Authorization Request HTTP headers. |
|
182 | - * |
|
183 | - * Sub classes may add any additional header when necessary. |
|
184 | - * |
|
185 | - * @see exchangeCodeForAccessToken() |
|
186 | - * |
|
187 | - * @var array |
|
188 | - */ |
|
189 | - protected $tokenExchangeHeaders = []; |
|
190 | - |
|
191 | - /** |
|
192 | - * Refresh Token Request HTTP method. |
|
193 | - * |
|
194 | - * @see refreshAccessToken() |
|
195 | - * |
|
196 | - * @var string |
|
197 | - */ |
|
198 | - protected $tokenRefreshMethod = 'POST'; |
|
199 | - |
|
200 | - /** |
|
201 | - * Refresh Token Request URL parameters. |
|
202 | - * |
|
203 | - * Sub classes may change add any additional parameter when necessary. |
|
204 | - * |
|
205 | - * @see refreshAccessToken() |
|
206 | - * |
|
207 | - * @var array|null |
|
208 | - */ |
|
209 | - protected $tokenRefreshParameters = null; |
|
210 | - |
|
211 | - /** |
|
212 | - * Refresh Token Request HTTP headers. |
|
213 | - * |
|
214 | - * Sub classes may add any additional header when necessary. |
|
215 | - * |
|
216 | - * @see refreshAccessToken() |
|
217 | - * |
|
218 | - * @var array |
|
219 | - */ |
|
220 | - protected $tokenRefreshHeaders = []; |
|
221 | - |
|
222 | - /** |
|
223 | - * Authorization Request URL parameters. |
|
224 | - * |
|
225 | - * Sub classes may change add any additional parameter when necessary. |
|
226 | - * |
|
227 | - * @see apiRequest() |
|
228 | - * |
|
229 | - * @var array |
|
230 | - */ |
|
231 | - protected $apiRequestParameters = []; |
|
232 | - |
|
233 | - /** |
|
234 | - * Authorization Request HTTP headers. |
|
235 | - * |
|
236 | - * Sub classes may add any additional header when necessary. |
|
237 | - * |
|
238 | - * @see apiRequest() |
|
239 | - * |
|
240 | - * @var array |
|
241 | - */ |
|
242 | - protected $apiRequestHeaders = []; |
|
243 | - |
|
244 | - /** |
|
245 | - * {@inheritdoc} |
|
246 | - */ |
|
247 | - protected function configure() |
|
248 | - { |
|
249 | - $this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); |
|
250 | - $this->clientSecret = $this->config->filter('keys')->get('secret'); |
|
251 | - |
|
252 | - if (!$this->clientId || !$this->clientSecret) { |
|
253 | - throw new InvalidApplicationCredentialsException( |
|
254 | - 'Your application id is required in order to connect to ' . $this->providerId |
|
255 | - ); |
|
256 | - } |
|
257 | - |
|
258 | - $this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope; |
|
259 | - |
|
260 | - if ($this->config->exists('tokens')) { |
|
261 | - $this->setAccessToken($this->config->get('tokens')); |
|
262 | - } |
|
27 | + /** |
|
28 | + * Client Identifier |
|
29 | + * |
|
30 | + * RFC6749: client_id REQUIRED. The client identifier issued to the client during |
|
31 | + * the registration process described by Section 2.2. |
|
32 | + * |
|
33 | + * http://tools.ietf.org/html/rfc6749#section-2.2 |
|
34 | + * |
|
35 | + * @var string |
|
36 | + */ |
|
37 | + protected $clientId = ''; |
|
38 | + |
|
39 | + /** |
|
40 | + * Client Secret |
|
41 | + * |
|
42 | + * RFC6749: client_secret REQUIRED. The client secret. The client MAY omit the |
|
43 | + * parameter if the client secret is an empty string. |
|
44 | + * |
|
45 | + * http://tools.ietf.org/html/rfc6749#section-2.2 |
|
46 | + * |
|
47 | + * @var string |
|
48 | + */ |
|
49 | + protected $clientSecret = ''; |
|
50 | + |
|
51 | + /** |
|
52 | + * Access Token Scope |
|
53 | + * |
|
54 | + * RFC6749: The authorization and token endpoints allow the client to specify the |
|
55 | + * scope of the access request using the "scope" request parameter. |
|
56 | + * |
|
57 | + * http://tools.ietf.org/html/rfc6749#section-3.3 |
|
58 | + * |
|
59 | + * @var string |
|
60 | + */ |
|
61 | + protected $scope = ''; |
|
62 | + |
|
63 | + /** |
|
64 | + * Base URL to provider API |
|
65 | + * |
|
66 | + * This var will be used to build urls when sending signed requests |
|
67 | + * |
|
68 | + * @var string |
|
69 | + */ |
|
70 | + protected $apiBaseUrl = ''; |
|
71 | + |
|
72 | + /** |
|
73 | + * Authorization Endpoint |
|
74 | + * |
|
75 | + * RFC6749: The authorization endpoint is used to interact with the resource |
|
76 | + * owner and obtain an authorization grant. |
|
77 | + * |
|
78 | + * http://tools.ietf.org/html/rfc6749#section-3.1 |
|
79 | + * |
|
80 | + * @var string |
|
81 | + */ |
|
82 | + protected $authorizeUrl = ''; |
|
83 | + |
|
84 | + /** |
|
85 | + * Access Token Endpoint |
|
86 | + * |
|
87 | + * RFC6749: The token endpoint is used by the client to obtain an access token by |
|
88 | + * presenting its authorization grant or refresh token. |
|
89 | + * |
|
90 | + * http://tools.ietf.org/html/rfc6749#section-3.2 |
|
91 | + * |
|
92 | + * @var string |
|
93 | + */ |
|
94 | + protected $accessTokenUrl = ''; |
|
95 | + |
|
96 | + /** |
|
97 | + * TokenInfo endpoint |
|
98 | + * |
|
99 | + * Access token validation. OPTIONAL. |
|
100 | + * |
|
101 | + * @var string |
|
102 | + */ |
|
103 | + protected $accessTokenInfoUrl = ''; |
|
104 | + |
|
105 | + /** |
|
106 | + * IPD API Documentation |
|
107 | + * |
|
108 | + * OPTIONAL. |
|
109 | + * |
|
110 | + * @var string |
|
111 | + */ |
|
112 | + protected $apiDocumentation = ''; |
|
113 | + |
|
114 | + /** |
|
115 | + * Redirection Endpoint or Callback |
|
116 | + * |
|
117 | + * RFC6749: After completing its interaction with the resource owner, the |
|
118 | + * authorization server directs the resource owner's user-agent back to |
|
119 | + * the client. |
|
120 | + * |
|
121 | + * http://tools.ietf.org/html/rfc6749#section-3.1.2 |
|
122 | + * |
|
123 | + * @var string |
|
124 | + */ |
|
125 | + protected $callback = ''; |
|
126 | + |
|
127 | + /** |
|
128 | + * Authorization Url Parameters |
|
129 | + * |
|
130 | + * @var array |
|
131 | + */ |
|
132 | + protected $AuthorizeUrlParameters = []; |
|
133 | + |
|
134 | + |
|
135 | + /** |
|
136 | + * Authorization Url Parameter encoding type |
|
137 | + * @see https://www.php.net/manual/de/function.http-build-query.php |
|
138 | + * |
|
139 | + * @var string |
|
140 | + */ |
|
141 | + protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC1738; |
|
142 | + |
|
143 | + /** |
|
144 | + * Authorization Request State |
|
145 | + * |
|
146 | + * @var bool |
|
147 | + */ |
|
148 | + protected $supportRequestState = true; |
|
149 | + |
|
150 | + /** |
|
151 | + * Access Token name |
|
152 | + * |
|
153 | + * While most providers will use 'access_token' as name for the Access Token attribute, other do not. |
|
154 | + * On the latter case, this should be set by sub classes. |
|
155 | + * |
|
156 | + * @var string |
|
157 | + */ |
|
158 | + protected $accessTokenName = 'access_token'; |
|
159 | + |
|
160 | + /** |
|
161 | + * Authorization Request HTTP method. |
|
162 | + * |
|
163 | + * @see exchangeCodeForAccessToken() |
|
164 | + * |
|
165 | + * @var string |
|
166 | + */ |
|
167 | + protected $tokenExchangeMethod = 'POST'; |
|
168 | + |
|
169 | + /** |
|
170 | + * Authorization Request URL parameters. |
|
171 | + * |
|
172 | + * Sub classes may change add any additional parameter when necessary. |
|
173 | + * |
|
174 | + * @see exchangeCodeForAccessToken() |
|
175 | + * |
|
176 | + * @var array |
|
177 | + */ |
|
178 | + protected $tokenExchangeParameters = []; |
|
179 | + |
|
180 | + /** |
|
181 | + * Authorization Request HTTP headers. |
|
182 | + * |
|
183 | + * Sub classes may add any additional header when necessary. |
|
184 | + * |
|
185 | + * @see exchangeCodeForAccessToken() |
|
186 | + * |
|
187 | + * @var array |
|
188 | + */ |
|
189 | + protected $tokenExchangeHeaders = []; |
|
190 | + |
|
191 | + /** |
|
192 | + * Refresh Token Request HTTP method. |
|
193 | + * |
|
194 | + * @see refreshAccessToken() |
|
195 | + * |
|
196 | + * @var string |
|
197 | + */ |
|
198 | + protected $tokenRefreshMethod = 'POST'; |
|
199 | + |
|
200 | + /** |
|
201 | + * Refresh Token Request URL parameters. |
|
202 | + * |
|
203 | + * Sub classes may change add any additional parameter when necessary. |
|
204 | + * |
|
205 | + * @see refreshAccessToken() |
|
206 | + * |
|
207 | + * @var array|null |
|
208 | + */ |
|
209 | + protected $tokenRefreshParameters = null; |
|
210 | + |
|
211 | + /** |
|
212 | + * Refresh Token Request HTTP headers. |
|
213 | + * |
|
214 | + * Sub classes may add any additional header when necessary. |
|
215 | + * |
|
216 | + * @see refreshAccessToken() |
|
217 | + * |
|
218 | + * @var array |
|
219 | + */ |
|
220 | + protected $tokenRefreshHeaders = []; |
|
221 | + |
|
222 | + /** |
|
223 | + * Authorization Request URL parameters. |
|
224 | + * |
|
225 | + * Sub classes may change add any additional parameter when necessary. |
|
226 | + * |
|
227 | + * @see apiRequest() |
|
228 | + * |
|
229 | + * @var array |
|
230 | + */ |
|
231 | + protected $apiRequestParameters = []; |
|
232 | + |
|
233 | + /** |
|
234 | + * Authorization Request HTTP headers. |
|
235 | + * |
|
236 | + * Sub classes may add any additional header when necessary. |
|
237 | + * |
|
238 | + * @see apiRequest() |
|
239 | + * |
|
240 | + * @var array |
|
241 | + */ |
|
242 | + protected $apiRequestHeaders = []; |
|
243 | + |
|
244 | + /** |
|
245 | + * {@inheritdoc} |
|
246 | + */ |
|
247 | + protected function configure() |
|
248 | + { |
|
249 | + $this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); |
|
250 | + $this->clientSecret = $this->config->filter('keys')->get('secret'); |
|
251 | + |
|
252 | + if (!$this->clientId || !$this->clientSecret) { |
|
253 | + throw new InvalidApplicationCredentialsException( |
|
254 | + 'Your application id is required in order to connect to ' . $this->providerId |
|
255 | + ); |
|
256 | + } |
|
257 | + |
|
258 | + $this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope; |
|
259 | + |
|
260 | + if ($this->config->exists('tokens')) { |
|
261 | + $this->setAccessToken($this->config->get('tokens')); |
|
262 | + } |
|
263 | 263 | |
264 | - if ($this->config->exists('supportRequestState')) { |
|
265 | - $this->supportRequestState = $this->config->get('supportRequestState'); |
|
266 | - } |
|
267 | - |
|
268 | - $this->setCallback($this->config->get('callback')); |
|
269 | - $this->setApiEndpoints($this->config->get('endpoints')); |
|
270 | - } |
|
271 | - |
|
272 | - /** |
|
273 | - * {@inheritdoc} |
|
274 | - */ |
|
275 | - protected function initialize() |
|
276 | - { |
|
277 | - $this->AuthorizeUrlParameters = [ |
|
278 | - 'response_type' => 'code', |
|
279 | - 'client_id' => $this->clientId, |
|
280 | - 'redirect_uri' => $this->callback, |
|
281 | - 'scope' => $this->scope, |
|
282 | - ]; |
|
283 | - |
|
284 | - $this->tokenExchangeParameters = [ |
|
285 | - 'client_id' => $this->clientId, |
|
286 | - 'client_secret' => $this->clientSecret, |
|
287 | - 'grant_type' => 'authorization_code', |
|
288 | - 'redirect_uri' => $this->callback |
|
289 | - ]; |
|
290 | - |
|
291 | - $refreshToken = $this->getStoredData('refresh_token'); |
|
292 | - if (!empty($refreshToken)) { |
|
293 | - $this->tokenRefreshParameters = [ |
|
294 | - 'grant_type' => 'refresh_token', |
|
295 | - 'refresh_token' => $refreshToken, |
|
296 | - ]; |
|
297 | - } |
|
298 | - |
|
299 | - $this->apiRequestHeaders = [ |
|
300 | - 'Authorization' => 'Bearer ' . $this->getStoredData('access_token') |
|
301 | - ]; |
|
302 | - } |
|
303 | - |
|
304 | - /** |
|
305 | - * {@inheritdoc} |
|
306 | - */ |
|
307 | - public function authenticate() |
|
308 | - { |
|
309 | - $this->logger->info(sprintf('%s::authenticate()', get_class($this))); |
|
310 | - |
|
311 | - if ($this->isConnected()) { |
|
312 | - return true; |
|
313 | - } |
|
314 | - |
|
315 | - try { |
|
316 | - $this->authenticateCheckError(); |
|
317 | - |
|
318 | - $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); |
|
319 | - |
|
320 | - if (empty($code)) { |
|
321 | - $this->authenticateBegin(); |
|
322 | - } else { |
|
323 | - $this->authenticateFinish(); |
|
324 | - } |
|
325 | - } catch (Exception $e) { |
|
326 | - $this->clearStoredData(); |
|
327 | - |
|
328 | - throw $e; |
|
329 | - } |
|
330 | - |
|
331 | - return null; |
|
332 | - } |
|
333 | - |
|
334 | - /** |
|
335 | - * {@inheritdoc} |
|
336 | - */ |
|
337 | - public function isConnected() |
|
338 | - { |
|
339 | - if ((bool)$this->getStoredData('access_token')) { |
|
340 | - return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable()); |
|
341 | - } |
|
342 | - return false; |
|
343 | - } |
|
344 | - |
|
345 | - /** |
|
346 | - * If we can use a refresh token, then an expired token does not stop us being connected. |
|
347 | - * |
|
348 | - * @return bool |
|
349 | - */ |
|
350 | - public function isRefreshTokenAvailable() |
|
351 | - { |
|
352 | - return is_array($this->tokenRefreshParameters); |
|
353 | - } |
|
354 | - |
|
355 | - /** |
|
356 | - * Authorization Request Error Response |
|
357 | - * |
|
358 | - * RFC6749: If the request fails due to a missing, invalid, or mismatching |
|
359 | - * redirection URI, or if the client identifier is missing or invalid, |
|
360 | - * the authorization server SHOULD inform the resource owner of the error. |
|
361 | - * |
|
362 | - * http://tools.ietf.org/html/rfc6749#section-4.1.2.1 |
|
363 | - * |
|
364 | - * @throws \Hybridauth\Exception\InvalidAuthorizationCodeException |
|
365 | - * @throws \Hybridauth\Exception\AuthorizationDeniedException |
|
366 | - */ |
|
367 | - protected function authenticateCheckError() |
|
368 | - { |
|
369 | - $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS); |
|
370 | - |
|
371 | - if (!empty($error)) { |
|
372 | - $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS); |
|
373 | - $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS); |
|
374 | - |
|
375 | - $collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri); |
|
376 | - |
|
377 | - if ($error == 'access_denied') { |
|
378 | - throw new AuthorizationDeniedException($collated_error); |
|
379 | - } |
|
380 | - |
|
381 | - throw new InvalidAuthorizationCodeException($collated_error); |
|
382 | - } |
|
383 | - } |
|
384 | - |
|
385 | - /** |
|
386 | - * Initiate the authorization protocol |
|
387 | - * |
|
388 | - * Build Authorization URL for Authorization Request and redirect the user-agent to the |
|
389 | - * Authorization Server. |
|
390 | - */ |
|
391 | - protected function authenticateBegin() |
|
392 | - { |
|
393 | - $authUrl = $this->getAuthorizeUrl(); |
|
394 | - |
|
395 | - $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); |
|
396 | - |
|
397 | - HttpClient\Util::redirect($authUrl); |
|
398 | - } |
|
399 | - |
|
400 | - /** |
|
401 | - * Finalize the authorization process |
|
402 | - * |
|
403 | - * @throws \Hybridauth\Exception\HttpClientFailureException |
|
404 | - * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
405 | - * @throws InvalidAccessTokenException |
|
406 | - * @throws InvalidAuthorizationStateException |
|
407 | - */ |
|
408 | - protected function authenticateFinish() |
|
409 | - { |
|
410 | - $this->logger->debug( |
|
411 | - sprintf('%s::authenticateFinish(), callback url:', get_class($this)), |
|
412 | - [HttpClient\Util::getCurrentUrl(true)] |
|
413 | - ); |
|
414 | - |
|
415 | - $state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state'); |
|
416 | - $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); |
|
417 | - |
|
418 | - /** |
|
419 | - * Authorization Request State |
|
420 | - * |
|
421 | - * RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain |
|
422 | - * state between the request and callback. The authorization server includes |
|
423 | - * this value when redirecting the user-agent back to the client. |
|
424 | - * |
|
425 | - * http://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
426 | - */ |
|
427 | - if ($this->supportRequestState |
|
428 | - && (!$state || $this->getStoredData('authorization_state') != $state) |
|
429 | - ) { |
|
430 | - $this->deleteStoredData('authorization_state'); |
|
431 | - throw new InvalidAuthorizationStateException( |
|
432 | - 'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] ' |
|
433 | - . 'of this page is either invalid or has already been consumed.' |
|
434 | - ); |
|
435 | - } |
|
436 | - |
|
437 | - /** |
|
438 | - * Authorization Request Code |
|
439 | - * |
|
440 | - * RFC6749: If the resource owner grants the access request, the authorization |
|
441 | - * server issues an authorization code and delivers it to the client: |
|
442 | - * |
|
443 | - * http://tools.ietf.org/html/rfc6749#section-4.1.2 |
|
444 | - */ |
|
445 | - $response = $this->exchangeCodeForAccessToken($code); |
|
446 | - |
|
447 | - $this->validateAccessTokenExchange($response); |
|
448 | - |
|
449 | - $this->initialize(); |
|
450 | - } |
|
451 | - |
|
452 | - /** |
|
453 | - * Build Authorization URL for Authorization Request |
|
454 | - * |
|
455 | - * RFC6749: The client constructs the request URI by adding the following |
|
456 | - * $parameters to the query component of the authorization endpoint URI: |
|
457 | - * |
|
458 | - * - response_type REQUIRED. Value MUST be set to "code". |
|
459 | - * - client_id REQUIRED. |
|
460 | - * - redirect_uri OPTIONAL. |
|
461 | - * - scope OPTIONAL. |
|
462 | - * - state RECOMMENDED. |
|
463 | - * |
|
464 | - * http://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
465 | - * |
|
466 | - * Sub classes may redefine this method when necessary. |
|
467 | - * |
|
468 | - * @param array $parameters |
|
469 | - * |
|
470 | - * @return string Authorization URL |
|
471 | - */ |
|
472 | - protected function getAuthorizeUrl($parameters = []) |
|
473 | - { |
|
474 | - $this->AuthorizeUrlParameters = !empty($parameters) |
|
475 | - ? $parameters |
|
476 | - : array_replace( |
|
477 | - (array)$this->AuthorizeUrlParameters, |
|
478 | - (array)$this->config->get('authorize_url_parameters') |
|
479 | - ); |
|
480 | - |
|
481 | - if ($this->supportRequestState) { |
|
482 | - if (!isset($this->AuthorizeUrlParameters['state'])) { |
|
483 | - $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'); |
|
484 | - } |
|
485 | - |
|
486 | - $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']); |
|
487 | - } |
|
488 | - |
|
489 | - $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType); |
|
490 | - return $this->authorizeUrl . '?' . $queryParams; |
|
491 | - } |
|
492 | - |
|
493 | - /** |
|
494 | - * Access Token Request |
|
495 | - * |
|
496 | - * This method will exchange the received $code in loginFinish() with an Access Token. |
|
497 | - * |
|
498 | - * RFC6749: The client makes a request to the token endpoint by sending the |
|
499 | - * following parameters using the "application/x-www-form-urlencoded" |
|
500 | - * with a character encoding of UTF-8 in the HTTP request entity-body: |
|
501 | - * |
|
502 | - * - grant_type REQUIRED. Value MUST be set to "authorization_code". |
|
503 | - * - code REQUIRED. The authorization code received from the authorization server. |
|
504 | - * - redirect_uri REQUIRED. |
|
505 | - * - client_id REQUIRED. |
|
506 | - * |
|
507 | - * http://tools.ietf.org/html/rfc6749#section-4.1.3 |
|
508 | - * |
|
509 | - * @param string $code |
|
510 | - * |
|
511 | - * @return string Raw Provider API response |
|
512 | - * @throws \Hybridauth\Exception\HttpClientFailureException |
|
513 | - * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
514 | - */ |
|
515 | - protected function exchangeCodeForAccessToken($code) |
|
516 | - { |
|
517 | - $this->tokenExchangeParameters['code'] = $code; |
|
518 | - |
|
519 | - $response = $this->httpClient->request( |
|
520 | - $this->accessTokenUrl, |
|
521 | - $this->tokenExchangeMethod, |
|
522 | - $this->tokenExchangeParameters, |
|
523 | - $this->tokenExchangeHeaders |
|
524 | - ); |
|
525 | - |
|
526 | - $this->validateApiResponse('Unable to exchange code for API access token'); |
|
527 | - |
|
528 | - return $response; |
|
529 | - } |
|
530 | - |
|
531 | - /** |
|
532 | - * Validate Access Token Response |
|
533 | - * |
|
534 | - * RFC6749: If the access token request is valid and authorized, the |
|
535 | - * authorization server issues an access token and optional refresh token. |
|
536 | - * If the request client authentication failed or is invalid, the authorization |
|
537 | - * server returns an error response as described in Section 5.2. |
|
538 | - * |
|
539 | - * Example of a successful response: |
|
540 | - * |
|
541 | - * HTTP/1.1 200 OK |
|
542 | - * Content-Type: application/json;charset=UTF-8 |
|
543 | - * Cache-Control: no-store |
|
544 | - * Pragma: no-cache |
|
545 | - * |
|
546 | - * { |
|
547 | - * "access_token":"2YotnFZFEjr1zCsicMWpAA", |
|
548 | - * "token_type":"example", |
|
549 | - * "expires_in":3600, |
|
550 | - * "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", |
|
551 | - * "example_parameter":"example_value" |
|
552 | - * } |
|
553 | - * |
|
554 | - * http://tools.ietf.org/html/rfc6749#section-4.1.4 |
|
555 | - * |
|
556 | - * This method uses Data_Parser to attempt to decodes the raw $response (usually JSON) |
|
557 | - * into a data collection. |
|
558 | - * |
|
559 | - * @param string $response |
|
560 | - * |
|
561 | - * @return \Hybridauth\Data\Collection |
|
562 | - * @throws InvalidAccessTokenException |
|
563 | - */ |
|
564 | - protected function validateAccessTokenExchange($response) |
|
565 | - { |
|
566 | - $data = (new Data\Parser())->parse($response); |
|
567 | - |
|
568 | - $collection = new Data\Collection($data); |
|
569 | - |
|
570 | - if (!$collection->exists('access_token')) { |
|
571 | - throw new InvalidAccessTokenException( |
|
572 | - 'Provider returned no access_token: ' . htmlentities($response) |
|
573 | - ); |
|
574 | - } |
|
575 | - |
|
576 | - $this->storeData('access_token', $collection->get('access_token')); |
|
577 | - $this->storeData('token_type', $collection->get('token_type')); |
|
578 | - |
|
579 | - if ($collection->get('refresh_token')) { |
|
580 | - $this->storeData('refresh_token', $collection->get('refresh_token')); |
|
581 | - } |
|
582 | - |
|
583 | - // calculate when the access token expire |
|
584 | - if ($collection->exists('expires_in')) { |
|
585 | - $expires_at = time() + (int)$collection->get('expires_in'); |
|
586 | - |
|
587 | - $this->storeData('expires_in', $collection->get('expires_in')); |
|
588 | - $this->storeData('expires_at', $expires_at); |
|
589 | - } |
|
590 | - |
|
591 | - $this->deleteStoredData('authorization_state'); |
|
592 | - |
|
593 | - $this->initialize(); |
|
594 | - |
|
595 | - return $collection; |
|
596 | - } |
|
597 | - |
|
598 | - /** |
|
599 | - * Refreshing an Access Token |
|
600 | - * |
|
601 | - * RFC6749: If the authorization server issued a refresh token to the |
|
602 | - * client, the client makes a refresh request to the token endpoint by |
|
603 | - * adding the following parameters ... in the HTTP request entity-body: |
|
604 | - * |
|
605 | - * - grant_type REQUIRED. Value MUST be set to "refresh_token". |
|
606 | - * - refresh_token REQUIRED. The refresh token issued to the client. |
|
607 | - * - scope OPTIONAL. |
|
608 | - * |
|
609 | - * http://tools.ietf.org/html/rfc6749#section-6 |
|
610 | - * |
|
611 | - * This method is similar to exchangeCodeForAccessToken(). The only |
|
612 | - * difference is here we exchange refresh_token for a new access_token. |
|
613 | - * |
|
614 | - * @param array $parameters |
|
615 | - * |
|
616 | - * @return string|null Raw Provider API response, or null if we cannot refresh |
|
617 | - * @throws \Hybridauth\Exception\HttpClientFailureException |
|
618 | - * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
619 | - * @throws InvalidAccessTokenException |
|
620 | - */ |
|
621 | - public function refreshAccessToken($parameters = []) |
|
622 | - { |
|
623 | - $this->tokenRefreshParameters = !empty($parameters) |
|
624 | - ? $parameters |
|
625 | - : $this->tokenRefreshParameters; |
|
626 | - |
|
627 | - if (!$this->isRefreshTokenAvailable()) { |
|
628 | - return null; |
|
629 | - } |
|
630 | - |
|
631 | - $response = $this->httpClient->request( |
|
632 | - $this->accessTokenUrl, |
|
633 | - $this->tokenRefreshMethod, |
|
634 | - $this->tokenRefreshParameters, |
|
635 | - $this->tokenRefreshHeaders |
|
636 | - ); |
|
637 | - |
|
638 | - $this->validateApiResponse('Unable to refresh the access token'); |
|
639 | - |
|
640 | - $this->validateRefreshAccessToken($response); |
|
641 | - |
|
642 | - return $response; |
|
643 | - } |
|
644 | - |
|
645 | - /** |
|
646 | - * Check whether access token has expired |
|
647 | - * |
|
648 | - * @param int|null $time |
|
649 | - * @return bool|null |
|
650 | - */ |
|
651 | - public function hasAccessTokenExpired($time = null) |
|
652 | - { |
|
653 | - if ($time === null) { |
|
654 | - $time = time(); |
|
655 | - } |
|
656 | - |
|
657 | - $expires_at = $this->getStoredData('expires_at'); |
|
658 | - if (!$expires_at) { |
|
659 | - return null; |
|
660 | - } |
|
661 | - |
|
662 | - return $expires_at <= $time; |
|
663 | - } |
|
664 | - |
|
665 | - /** |
|
666 | - * Validate Refresh Access Token Request |
|
667 | - * |
|
668 | - * RFC6749: If valid and authorized, the authorization server issues an |
|
669 | - * access token as described in Section 5.1. If the request failed |
|
670 | - * verification or is invalid, the authorization server returns an error |
|
671 | - * response as described in Section 5.2. |
|
672 | - * |
|
673 | - * http://tools.ietf.org/html/rfc6749#section-6 |
|
674 | - * http://tools.ietf.org/html/rfc6749#section-5.1 |
|
675 | - * http://tools.ietf.org/html/rfc6749#section-5.2 |
|
676 | - * |
|
677 | - * This method simply use validateAccessTokenExchange(), however sub |
|
678 | - * classes may redefine it when necessary. |
|
679 | - * |
|
680 | - * @param $response |
|
681 | - * |
|
682 | - * @return \Hybridauth\Data\Collection |
|
683 | - * @throws InvalidAccessTokenException |
|
684 | - */ |
|
685 | - protected function validateRefreshAccessToken($response) |
|
686 | - { |
|
687 | - return $this->validateAccessTokenExchange($response); |
|
688 | - } |
|
689 | - |
|
690 | - /** |
|
691 | - * Send a signed request to provider API |
|
692 | - * |
|
693 | - * RFC6749: Accessing Protected Resources: The client accesses protected |
|
694 | - * resources by presenting the access token to the resource server. The |
|
695 | - * resource server MUST validate the access token and ensure that it has |
|
696 | - * not expired and that its scope covers the requested resource. |
|
697 | - * |
|
698 | - * Note: Since the specifics of error responses is beyond the scope of |
|
699 | - * RFC6749 and OAuth specifications, Hybridauth will consider any HTTP |
|
700 | - * status code that is different than '200 OK' as an ERROR. |
|
701 | - * |
|
702 | - * http://tools.ietf.org/html/rfc6749#section-7 |
|
703 | - * |
|
704 | - * @param string $url |
|
705 | - * @param string $method |
|
706 | - * @param array $parameters |
|
707 | - * @param array $headers |
|
708 | - * @param bool $multipart |
|
709 | - * |
|
710 | - * @return mixed |
|
711 | - * @throws \Hybridauth\Exception\HttpClientFailureException |
|
712 | - * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
713 | - * @throws InvalidAccessTokenException |
|
714 | - */ |
|
715 | - public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) |
|
716 | - { |
|
717 | - // refresh tokens if needed |
|
718 | - $this->maintainToken(); |
|
719 | - if ($this->hasAccessTokenExpired() === true) { |
|
720 | - $this->refreshAccessToken(); |
|
721 | - } |
|
722 | - |
|
723 | - if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { |
|
724 | - $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); |
|
725 | - } |
|
726 | - |
|
727 | - $parameters = array_replace($this->apiRequestParameters, (array)$parameters); |
|
728 | - $headers = array_replace($this->apiRequestHeaders, (array)$headers); |
|
729 | - |
|
730 | - $response = $this->httpClient->request( |
|
731 | - $url, |
|
732 | - $method, // HTTP Request Method. Defaults to GET. |
|
733 | - $parameters, // Request Parameters |
|
734 | - $headers, // Request Headers |
|
735 | - $multipart // Is request multipart |
|
736 | - ); |
|
737 | - |
|
738 | - $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error'); |
|
739 | - |
|
740 | - $response = (new Data\Parser())->parse($response); |
|
741 | - |
|
742 | - return $response; |
|
743 | - } |
|
264 | + if ($this->config->exists('supportRequestState')) { |
|
265 | + $this->supportRequestState = $this->config->get('supportRequestState'); |
|
266 | + } |
|
267 | + |
|
268 | + $this->setCallback($this->config->get('callback')); |
|
269 | + $this->setApiEndpoints($this->config->get('endpoints')); |
|
270 | + } |
|
271 | + |
|
272 | + /** |
|
273 | + * {@inheritdoc} |
|
274 | + */ |
|
275 | + protected function initialize() |
|
276 | + { |
|
277 | + $this->AuthorizeUrlParameters = [ |
|
278 | + 'response_type' => 'code', |
|
279 | + 'client_id' => $this->clientId, |
|
280 | + 'redirect_uri' => $this->callback, |
|
281 | + 'scope' => $this->scope, |
|
282 | + ]; |
|
283 | + |
|
284 | + $this->tokenExchangeParameters = [ |
|
285 | + 'client_id' => $this->clientId, |
|
286 | + 'client_secret' => $this->clientSecret, |
|
287 | + 'grant_type' => 'authorization_code', |
|
288 | + 'redirect_uri' => $this->callback |
|
289 | + ]; |
|
290 | + |
|
291 | + $refreshToken = $this->getStoredData('refresh_token'); |
|
292 | + if (!empty($refreshToken)) { |
|
293 | + $this->tokenRefreshParameters = [ |
|
294 | + 'grant_type' => 'refresh_token', |
|
295 | + 'refresh_token' => $refreshToken, |
|
296 | + ]; |
|
297 | + } |
|
298 | + |
|
299 | + $this->apiRequestHeaders = [ |
|
300 | + 'Authorization' => 'Bearer ' . $this->getStoredData('access_token') |
|
301 | + ]; |
|
302 | + } |
|
303 | + |
|
304 | + /** |
|
305 | + * {@inheritdoc} |
|
306 | + */ |
|
307 | + public function authenticate() |
|
308 | + { |
|
309 | + $this->logger->info(sprintf('%s::authenticate()', get_class($this))); |
|
310 | + |
|
311 | + if ($this->isConnected()) { |
|
312 | + return true; |
|
313 | + } |
|
314 | + |
|
315 | + try { |
|
316 | + $this->authenticateCheckError(); |
|
317 | + |
|
318 | + $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); |
|
319 | + |
|
320 | + if (empty($code)) { |
|
321 | + $this->authenticateBegin(); |
|
322 | + } else { |
|
323 | + $this->authenticateFinish(); |
|
324 | + } |
|
325 | + } catch (Exception $e) { |
|
326 | + $this->clearStoredData(); |
|
327 | + |
|
328 | + throw $e; |
|
329 | + } |
|
330 | + |
|
331 | + return null; |
|
332 | + } |
|
333 | + |
|
334 | + /** |
|
335 | + * {@inheritdoc} |
|
336 | + */ |
|
337 | + public function isConnected() |
|
338 | + { |
|
339 | + if ((bool)$this->getStoredData('access_token')) { |
|
340 | + return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable()); |
|
341 | + } |
|
342 | + return false; |
|
343 | + } |
|
344 | + |
|
345 | + /** |
|
346 | + * If we can use a refresh token, then an expired token does not stop us being connected. |
|
347 | + * |
|
348 | + * @return bool |
|
349 | + */ |
|
350 | + public function isRefreshTokenAvailable() |
|
351 | + { |
|
352 | + return is_array($this->tokenRefreshParameters); |
|
353 | + } |
|
354 | + |
|
355 | + /** |
|
356 | + * Authorization Request Error Response |
|
357 | + * |
|
358 | + * RFC6749: If the request fails due to a missing, invalid, or mismatching |
|
359 | + * redirection URI, or if the client identifier is missing or invalid, |
|
360 | + * the authorization server SHOULD inform the resource owner of the error. |
|
361 | + * |
|
362 | + * http://tools.ietf.org/html/rfc6749#section-4.1.2.1 |
|
363 | + * |
|
364 | + * @throws \Hybridauth\Exception\InvalidAuthorizationCodeException |
|
365 | + * @throws \Hybridauth\Exception\AuthorizationDeniedException |
|
366 | + */ |
|
367 | + protected function authenticateCheckError() |
|
368 | + { |
|
369 | + $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS); |
|
370 | + |
|
371 | + if (!empty($error)) { |
|
372 | + $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS); |
|
373 | + $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS); |
|
374 | + |
|
375 | + $collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri); |
|
376 | + |
|
377 | + if ($error == 'access_denied') { |
|
378 | + throw new AuthorizationDeniedException($collated_error); |
|
379 | + } |
|
380 | + |
|
381 | + throw new InvalidAuthorizationCodeException($collated_error); |
|
382 | + } |
|
383 | + } |
|
384 | + |
|
385 | + /** |
|
386 | + * Initiate the authorization protocol |
|
387 | + * |
|
388 | + * Build Authorization URL for Authorization Request and redirect the user-agent to the |
|
389 | + * Authorization Server. |
|
390 | + */ |
|
391 | + protected function authenticateBegin() |
|
392 | + { |
|
393 | + $authUrl = $this->getAuthorizeUrl(); |
|
394 | + |
|
395 | + $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); |
|
396 | + |
|
397 | + HttpClient\Util::redirect($authUrl); |
|
398 | + } |
|
399 | + |
|
400 | + /** |
|
401 | + * Finalize the authorization process |
|
402 | + * |
|
403 | + * @throws \Hybridauth\Exception\HttpClientFailureException |
|
404 | + * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
405 | + * @throws InvalidAccessTokenException |
|
406 | + * @throws InvalidAuthorizationStateException |
|
407 | + */ |
|
408 | + protected function authenticateFinish() |
|
409 | + { |
|
410 | + $this->logger->debug( |
|
411 | + sprintf('%s::authenticateFinish(), callback url:', get_class($this)), |
|
412 | + [HttpClient\Util::getCurrentUrl(true)] |
|
413 | + ); |
|
414 | + |
|
415 | + $state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state'); |
|
416 | + $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); |
|
417 | + |
|
418 | + /** |
|
419 | + * Authorization Request State |
|
420 | + * |
|
421 | + * RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain |
|
422 | + * state between the request and callback. The authorization server includes |
|
423 | + * this value when redirecting the user-agent back to the client. |
|
424 | + * |
|
425 | + * http://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
426 | + */ |
|
427 | + if ($this->supportRequestState |
|
428 | + && (!$state || $this->getStoredData('authorization_state') != $state) |
|
429 | + ) { |
|
430 | + $this->deleteStoredData('authorization_state'); |
|
431 | + throw new InvalidAuthorizationStateException( |
|
432 | + 'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] ' |
|
433 | + . 'of this page is either invalid or has already been consumed.' |
|
434 | + ); |
|
435 | + } |
|
436 | + |
|
437 | + /** |
|
438 | + * Authorization Request Code |
|
439 | + * |
|
440 | + * RFC6749: If the resource owner grants the access request, the authorization |
|
441 | + * server issues an authorization code and delivers it to the client: |
|
442 | + * |
|
443 | + * http://tools.ietf.org/html/rfc6749#section-4.1.2 |
|
444 | + */ |
|
445 | + $response = $this->exchangeCodeForAccessToken($code); |
|
446 | + |
|
447 | + $this->validateAccessTokenExchange($response); |
|
448 | + |
|
449 | + $this->initialize(); |
|
450 | + } |
|
451 | + |
|
452 | + /** |
|
453 | + * Build Authorization URL for Authorization Request |
|
454 | + * |
|
455 | + * RFC6749: The client constructs the request URI by adding the following |
|
456 | + * $parameters to the query component of the authorization endpoint URI: |
|
457 | + * |
|
458 | + * - response_type REQUIRED. Value MUST be set to "code". |
|
459 | + * - client_id REQUIRED. |
|
460 | + * - redirect_uri OPTIONAL. |
|
461 | + * - scope OPTIONAL. |
|
462 | + * - state RECOMMENDED. |
|
463 | + * |
|
464 | + * http://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
465 | + * |
|
466 | + * Sub classes may redefine this method when necessary. |
|
467 | + * |
|
468 | + * @param array $parameters |
|
469 | + * |
|
470 | + * @return string Authorization URL |
|
471 | + */ |
|
472 | + protected function getAuthorizeUrl($parameters = []) |
|
473 | + { |
|
474 | + $this->AuthorizeUrlParameters = !empty($parameters) |
|
475 | + ? $parameters |
|
476 | + : array_replace( |
|
477 | + (array)$this->AuthorizeUrlParameters, |
|
478 | + (array)$this->config->get('authorize_url_parameters') |
|
479 | + ); |
|
480 | + |
|
481 | + if ($this->supportRequestState) { |
|
482 | + if (!isset($this->AuthorizeUrlParameters['state'])) { |
|
483 | + $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'); |
|
484 | + } |
|
485 | + |
|
486 | + $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']); |
|
487 | + } |
|
488 | + |
|
489 | + $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType); |
|
490 | + return $this->authorizeUrl . '?' . $queryParams; |
|
491 | + } |
|
492 | + |
|
493 | + /** |
|
494 | + * Access Token Request |
|
495 | + * |
|
496 | + * This method will exchange the received $code in loginFinish() with an Access Token. |
|
497 | + * |
|
498 | + * RFC6749: The client makes a request to the token endpoint by sending the |
|
499 | + * following parameters using the "application/x-www-form-urlencoded" |
|
500 | + * with a character encoding of UTF-8 in the HTTP request entity-body: |
|
501 | + * |
|
502 | + * - grant_type REQUIRED. Value MUST be set to "authorization_code". |
|
503 | + * - code REQUIRED. The authorization code received from the authorization server. |
|
504 | + * - redirect_uri REQUIRED. |
|
505 | + * - client_id REQUIRED. |
|
506 | + * |
|
507 | + * http://tools.ietf.org/html/rfc6749#section-4.1.3 |
|
508 | + * |
|
509 | + * @param string $code |
|
510 | + * |
|
511 | + * @return string Raw Provider API response |
|
512 | + * @throws \Hybridauth\Exception\HttpClientFailureException |
|
513 | + * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
514 | + */ |
|
515 | + protected function exchangeCodeForAccessToken($code) |
|
516 | + { |
|
517 | + $this->tokenExchangeParameters['code'] = $code; |
|
518 | + |
|
519 | + $response = $this->httpClient->request( |
|
520 | + $this->accessTokenUrl, |
|
521 | + $this->tokenExchangeMethod, |
|
522 | + $this->tokenExchangeParameters, |
|
523 | + $this->tokenExchangeHeaders |
|
524 | + ); |
|
525 | + |
|
526 | + $this->validateApiResponse('Unable to exchange code for API access token'); |
|
527 | + |
|
528 | + return $response; |
|
529 | + } |
|
530 | + |
|
531 | + /** |
|
532 | + * Validate Access Token Response |
|
533 | + * |
|
534 | + * RFC6749: If the access token request is valid and authorized, the |
|
535 | + * authorization server issues an access token and optional refresh token. |
|
536 | + * If the request client authentication failed or is invalid, the authorization |
|
537 | + * server returns an error response as described in Section 5.2. |
|
538 | + * |
|
539 | + * Example of a successful response: |
|
540 | + * |
|
541 | + * HTTP/1.1 200 OK |
|
542 | + * Content-Type: application/json;charset=UTF-8 |
|
543 | + * Cache-Control: no-store |
|
544 | + * Pragma: no-cache |
|
545 | + * |
|
546 | + * { |
|
547 | + * "access_token":"2YotnFZFEjr1zCsicMWpAA", |
|
548 | + * "token_type":"example", |
|
549 | + * "expires_in":3600, |
|
550 | + * "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", |
|
551 | + * "example_parameter":"example_value" |
|
552 | + * } |
|
553 | + * |
|
554 | + * http://tools.ietf.org/html/rfc6749#section-4.1.4 |
|
555 | + * |
|
556 | + * This method uses Data_Parser to attempt to decodes the raw $response (usually JSON) |
|
557 | + * into a data collection. |
|
558 | + * |
|
559 | + * @param string $response |
|
560 | + * |
|
561 | + * @return \Hybridauth\Data\Collection |
|
562 | + * @throws InvalidAccessTokenException |
|
563 | + */ |
|
564 | + protected function validateAccessTokenExchange($response) |
|
565 | + { |
|
566 | + $data = (new Data\Parser())->parse($response); |
|
567 | + |
|
568 | + $collection = new Data\Collection($data); |
|
569 | + |
|
570 | + if (!$collection->exists('access_token')) { |
|
571 | + throw new InvalidAccessTokenException( |
|
572 | + 'Provider returned no access_token: ' . htmlentities($response) |
|
573 | + ); |
|
574 | + } |
|
575 | + |
|
576 | + $this->storeData('access_token', $collection->get('access_token')); |
|
577 | + $this->storeData('token_type', $collection->get('token_type')); |
|
578 | + |
|
579 | + if ($collection->get('refresh_token')) { |
|
580 | + $this->storeData('refresh_token', $collection->get('refresh_token')); |
|
581 | + } |
|
582 | + |
|
583 | + // calculate when the access token expire |
|
584 | + if ($collection->exists('expires_in')) { |
|
585 | + $expires_at = time() + (int)$collection->get('expires_in'); |
|
586 | + |
|
587 | + $this->storeData('expires_in', $collection->get('expires_in')); |
|
588 | + $this->storeData('expires_at', $expires_at); |
|
589 | + } |
|
590 | + |
|
591 | + $this->deleteStoredData('authorization_state'); |
|
592 | + |
|
593 | + $this->initialize(); |
|
594 | + |
|
595 | + return $collection; |
|
596 | + } |
|
597 | + |
|
598 | + /** |
|
599 | + * Refreshing an Access Token |
|
600 | + * |
|
601 | + * RFC6749: If the authorization server issued a refresh token to the |
|
602 | + * client, the client makes a refresh request to the token endpoint by |
|
603 | + * adding the following parameters ... in the HTTP request entity-body: |
|
604 | + * |
|
605 | + * - grant_type REQUIRED. Value MUST be set to "refresh_token". |
|
606 | + * - refresh_token REQUIRED. The refresh token issued to the client. |
|
607 | + * - scope OPTIONAL. |
|
608 | + * |
|
609 | + * http://tools.ietf.org/html/rfc6749#section-6 |
|
610 | + * |
|
611 | + * This method is similar to exchangeCodeForAccessToken(). The only |
|
612 | + * difference is here we exchange refresh_token for a new access_token. |
|
613 | + * |
|
614 | + * @param array $parameters |
|
615 | + * |
|
616 | + * @return string|null Raw Provider API response, or null if we cannot refresh |
|
617 | + * @throws \Hybridauth\Exception\HttpClientFailureException |
|
618 | + * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
619 | + * @throws InvalidAccessTokenException |
|
620 | + */ |
|
621 | + public function refreshAccessToken($parameters = []) |
|
622 | + { |
|
623 | + $this->tokenRefreshParameters = !empty($parameters) |
|
624 | + ? $parameters |
|
625 | + : $this->tokenRefreshParameters; |
|
626 | + |
|
627 | + if (!$this->isRefreshTokenAvailable()) { |
|
628 | + return null; |
|
629 | + } |
|
630 | + |
|
631 | + $response = $this->httpClient->request( |
|
632 | + $this->accessTokenUrl, |
|
633 | + $this->tokenRefreshMethod, |
|
634 | + $this->tokenRefreshParameters, |
|
635 | + $this->tokenRefreshHeaders |
|
636 | + ); |
|
637 | + |
|
638 | + $this->validateApiResponse('Unable to refresh the access token'); |
|
639 | + |
|
640 | + $this->validateRefreshAccessToken($response); |
|
641 | + |
|
642 | + return $response; |
|
643 | + } |
|
644 | + |
|
645 | + /** |
|
646 | + * Check whether access token has expired |
|
647 | + * |
|
648 | + * @param int|null $time |
|
649 | + * @return bool|null |
|
650 | + */ |
|
651 | + public function hasAccessTokenExpired($time = null) |
|
652 | + { |
|
653 | + if ($time === null) { |
|
654 | + $time = time(); |
|
655 | + } |
|
656 | + |
|
657 | + $expires_at = $this->getStoredData('expires_at'); |
|
658 | + if (!$expires_at) { |
|
659 | + return null; |
|
660 | + } |
|
661 | + |
|
662 | + return $expires_at <= $time; |
|
663 | + } |
|
664 | + |
|
665 | + /** |
|
666 | + * Validate Refresh Access Token Request |
|
667 | + * |
|
668 | + * RFC6749: If valid and authorized, the authorization server issues an |
|
669 | + * access token as described in Section 5.1. If the request failed |
|
670 | + * verification or is invalid, the authorization server returns an error |
|
671 | + * response as described in Section 5.2. |
|
672 | + * |
|
673 | + * http://tools.ietf.org/html/rfc6749#section-6 |
|
674 | + * http://tools.ietf.org/html/rfc6749#section-5.1 |
|
675 | + * http://tools.ietf.org/html/rfc6749#section-5.2 |
|
676 | + * |
|
677 | + * This method simply use validateAccessTokenExchange(), however sub |
|
678 | + * classes may redefine it when necessary. |
|
679 | + * |
|
680 | + * @param $response |
|
681 | + * |
|
682 | + * @return \Hybridauth\Data\Collection |
|
683 | + * @throws InvalidAccessTokenException |
|
684 | + */ |
|
685 | + protected function validateRefreshAccessToken($response) |
|
686 | + { |
|
687 | + return $this->validateAccessTokenExchange($response); |
|
688 | + } |
|
689 | + |
|
690 | + /** |
|
691 | + * Send a signed request to provider API |
|
692 | + * |
|
693 | + * RFC6749: Accessing Protected Resources: The client accesses protected |
|
694 | + * resources by presenting the access token to the resource server. The |
|
695 | + * resource server MUST validate the access token and ensure that it has |
|
696 | + * not expired and that its scope covers the requested resource. |
|
697 | + * |
|
698 | + * Note: Since the specifics of error responses is beyond the scope of |
|
699 | + * RFC6749 and OAuth specifications, Hybridauth will consider any HTTP |
|
700 | + * status code that is different than '200 OK' as an ERROR. |
|
701 | + * |
|
702 | + * http://tools.ietf.org/html/rfc6749#section-7 |
|
703 | + * |
|
704 | + * @param string $url |
|
705 | + * @param string $method |
|
706 | + * @param array $parameters |
|
707 | + * @param array $headers |
|
708 | + * @param bool $multipart |
|
709 | + * |
|
710 | + * @return mixed |
|
711 | + * @throws \Hybridauth\Exception\HttpClientFailureException |
|
712 | + * @throws \Hybridauth\Exception\HttpRequestFailedException |
|
713 | + * @throws InvalidAccessTokenException |
|
714 | + */ |
|
715 | + public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) |
|
716 | + { |
|
717 | + // refresh tokens if needed |
|
718 | + $this->maintainToken(); |
|
719 | + if ($this->hasAccessTokenExpired() === true) { |
|
720 | + $this->refreshAccessToken(); |
|
721 | + } |
|
722 | + |
|
723 | + if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { |
|
724 | + $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); |
|
725 | + } |
|
726 | + |
|
727 | + $parameters = array_replace($this->apiRequestParameters, (array)$parameters); |
|
728 | + $headers = array_replace($this->apiRequestHeaders, (array)$headers); |
|
729 | + |
|
730 | + $response = $this->httpClient->request( |
|
731 | + $url, |
|
732 | + $method, // HTTP Request Method. Defaults to GET. |
|
733 | + $parameters, // Request Parameters |
|
734 | + $headers, // Request Headers |
|
735 | + $multipart // Is request multipart |
|
736 | + ); |
|
737 | + |
|
738 | + $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error'); |
|
739 | + |
|
740 | + $response = (new Data\Parser())->parse($response); |
|
741 | + |
|
742 | + return $response; |
|
743 | + } |
|
744 | 744 | } |
@@ -249,9 +249,9 @@ discard block |
||
249 | 249 | $this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); |
250 | 250 | $this->clientSecret = $this->config->filter('keys')->get('secret'); |
251 | 251 | |
252 | - if (!$this->clientId || !$this->clientSecret) { |
|
252 | + if ( ! $this->clientId || ! $this->clientSecret) { |
|
253 | 253 | throw new InvalidApplicationCredentialsException( |
254 | - 'Your application id is required in order to connect to ' . $this->providerId |
|
254 | + 'Your application id is required in order to connect to '.$this->providerId |
|
255 | 255 | ); |
256 | 256 | } |
257 | 257 | |
@@ -289,7 +289,7 @@ discard block |
||
289 | 289 | ]; |
290 | 290 | |
291 | 291 | $refreshToken = $this->getStoredData('refresh_token'); |
292 | - if (!empty($refreshToken)) { |
|
292 | + if ( ! empty($refreshToken)) { |
|
293 | 293 | $this->tokenRefreshParameters = [ |
294 | 294 | 'grant_type' => 'refresh_token', |
295 | 295 | 'refresh_token' => $refreshToken, |
@@ -297,7 +297,7 @@ discard block |
||
297 | 297 | } |
298 | 298 | |
299 | 299 | $this->apiRequestHeaders = [ |
300 | - 'Authorization' => 'Bearer ' . $this->getStoredData('access_token') |
|
300 | + 'Authorization' => 'Bearer '.$this->getStoredData('access_token') |
|
301 | 301 | ]; |
302 | 302 | } |
303 | 303 | |
@@ -336,8 +336,8 @@ discard block |
||
336 | 336 | */ |
337 | 337 | public function isConnected() |
338 | 338 | { |
339 | - if ((bool)$this->getStoredData('access_token')) { |
|
340 | - return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable()); |
|
339 | + if ((bool) $this->getStoredData('access_token')) { |
|
340 | + return ( ! $this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable()); |
|
341 | 341 | } |
342 | 342 | return false; |
343 | 343 | } |
@@ -368,7 +368,7 @@ discard block |
||
368 | 368 | { |
369 | 369 | $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS); |
370 | 370 | |
371 | - if (!empty($error)) { |
|
371 | + if ( ! empty($error)) { |
|
372 | 372 | $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS); |
373 | 373 | $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS); |
374 | 374 | |
@@ -425,11 +425,11 @@ discard block |
||
425 | 425 | * http://tools.ietf.org/html/rfc6749#section-4.1.1 |
426 | 426 | */ |
427 | 427 | if ($this->supportRequestState |
428 | - && (!$state || $this->getStoredData('authorization_state') != $state) |
|
428 | + && ( ! $state || $this->getStoredData('authorization_state') != $state) |
|
429 | 429 | ) { |
430 | 430 | $this->deleteStoredData('authorization_state'); |
431 | 431 | throw new InvalidAuthorizationStateException( |
432 | - 'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] ' |
|
432 | + 'The authorization state [state='.substr(htmlentities($state), 0, 100).'] ' |
|
433 | 433 | . 'of this page is either invalid or has already been consumed.' |
434 | 434 | ); |
435 | 435 | } |
@@ -471,23 +471,23 @@ discard block |
||
471 | 471 | */ |
472 | 472 | protected function getAuthorizeUrl($parameters = []) |
473 | 473 | { |
474 | - $this->AuthorizeUrlParameters = !empty($parameters) |
|
474 | + $this->AuthorizeUrlParameters = ! empty($parameters) |
|
475 | 475 | ? $parameters |
476 | 476 | : array_replace( |
477 | - (array)$this->AuthorizeUrlParameters, |
|
478 | - (array)$this->config->get('authorize_url_parameters') |
|
477 | + (array) $this->AuthorizeUrlParameters, |
|
478 | + (array) $this->config->get('authorize_url_parameters') |
|
479 | 479 | ); |
480 | 480 | |
481 | 481 | if ($this->supportRequestState) { |
482 | - if (!isset($this->AuthorizeUrlParameters['state'])) { |
|
483 | - $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'); |
|
482 | + if ( ! isset($this->AuthorizeUrlParameters['state'])) { |
|
483 | + $this->AuthorizeUrlParameters['state'] = 'HA-'.str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'); |
|
484 | 484 | } |
485 | 485 | |
486 | 486 | $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']); |
487 | 487 | } |
488 | 488 | |
489 | 489 | $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType); |
490 | - return $this->authorizeUrl . '?' . $queryParams; |
|
490 | + return $this->authorizeUrl.'?'.$queryParams; |
|
491 | 491 | } |
492 | 492 | |
493 | 493 | /** |
@@ -567,9 +567,9 @@ discard block |
||
567 | 567 | |
568 | 568 | $collection = new Data\Collection($data); |
569 | 569 | |
570 | - if (!$collection->exists('access_token')) { |
|
570 | + if ( ! $collection->exists('access_token')) { |
|
571 | 571 | throw new InvalidAccessTokenException( |
572 | - 'Provider returned no access_token: ' . htmlentities($response) |
|
572 | + 'Provider returned no access_token: '.htmlentities($response) |
|
573 | 573 | ); |
574 | 574 | } |
575 | 575 | |
@@ -582,7 +582,7 @@ discard block |
||
582 | 582 | |
583 | 583 | // calculate when the access token expire |
584 | 584 | if ($collection->exists('expires_in')) { |
585 | - $expires_at = time() + (int)$collection->get('expires_in'); |
|
585 | + $expires_at = time() + (int) $collection->get('expires_in'); |
|
586 | 586 | |
587 | 587 | $this->storeData('expires_in', $collection->get('expires_in')); |
588 | 588 | $this->storeData('expires_at', $expires_at); |
@@ -620,11 +620,11 @@ discard block |
||
620 | 620 | */ |
621 | 621 | public function refreshAccessToken($parameters = []) |
622 | 622 | { |
623 | - $this->tokenRefreshParameters = !empty($parameters) |
|
623 | + $this->tokenRefreshParameters = ! empty($parameters) |
|
624 | 624 | ? $parameters |
625 | 625 | : $this->tokenRefreshParameters; |
626 | 626 | |
627 | - if (!$this->isRefreshTokenAvailable()) { |
|
627 | + if ( ! $this->isRefreshTokenAvailable()) { |
|
628 | 628 | return null; |
629 | 629 | } |
630 | 630 | |
@@ -655,7 +655,7 @@ discard block |
||
655 | 655 | } |
656 | 656 | |
657 | 657 | $expires_at = $this->getStoredData('expires_at'); |
658 | - if (!$expires_at) { |
|
658 | + if ( ! $expires_at) { |
|
659 | 659 | return null; |
660 | 660 | } |
661 | 661 | |
@@ -721,21 +721,21 @@ discard block |
||
721 | 721 | } |
722 | 722 | |
723 | 723 | if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { |
724 | - $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); |
|
724 | + $url = rtrim($this->apiBaseUrl, '/').'/'.ltrim($url, '/'); |
|
725 | 725 | } |
726 | 726 | |
727 | - $parameters = array_replace($this->apiRequestParameters, (array)$parameters); |
|
728 | - $headers = array_replace($this->apiRequestHeaders, (array)$headers); |
|
727 | + $parameters = array_replace($this->apiRequestParameters, (array) $parameters); |
|
728 | + $headers = array_replace($this->apiRequestHeaders, (array) $headers); |
|
729 | 729 | |
730 | 730 | $response = $this->httpClient->request( |
731 | 731 | $url, |
732 | - $method, // HTTP Request Method. Defaults to GET. |
|
732 | + $method, // HTTP Request Method. Defaults to GET. |
|
733 | 733 | $parameters, // Request Parameters |
734 | - $headers, // Request Headers |
|
734 | + $headers, // Request Headers |
|
735 | 735 | $multipart // Is request multipart |
736 | 736 | ); |
737 | 737 | |
738 | - $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error'); |
|
738 | + $this->validateApiResponse('Signed API request to '.$url.' has returned an error'); |
|
739 | 739 | |
740 | 740 | $response = (new Data\Parser())->parse($response); |
741 | 741 |