diff --git a/app/config/providers.php b/app/config/providers.php index b45ca52a74..140dbeaf6f 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -57,8 +57,18 @@ return [ 'enabled' => true, ], 'apple' => [ - 'developers' => 'https://www.dropbox.com/developers/documentation', + 'developers' => 'https://developer.apple.com/', 'icon' => 'icon-apple', 'enabled' => false, ], + 'amazon' => [ + 'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis', + 'icon' => 'icon-amazon', + 'enabled' => true, + ], + 'vk' => [ + 'developers' => 'https://vk.com/dev', + 'icon' => 'icon-vk', + 'enabled' => true, + ], ]; diff --git a/app/controllers/auth.php b/app/controllers/auth.php index 75d7da05f2..849f783113 100644 --- a/app/controllers/auth.php +++ b/app/controllers/auth.php @@ -730,23 +730,8 @@ $utopia->get('/v1/auth/oauth/:provider/redirect') $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; $validateURL = new URL(); - if (!empty($state)) { - try { - $state = array_merge($defaultState, json_decode($state, true)); - } catch (\Exception $exception) { - throw new Exception('Failed to parse login state params as passed from OAuth provider'); - } - } else { - $state = $defaultState; - } - - if (!$validateURL->isValid($state['success'])) { - throw new Exception('Invalid redirect URL for success login', 400); - } - - if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { - throw new Exception('Invalid redirect URL for failure login', 400); - } + // Uncomment this while testing amazon oAuth + // $state = html_entity_decode($state); $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', ''); $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'); @@ -766,6 +751,24 @@ $utopia->get('/v1/auth/oauth/:provider/redirect') $oauth = new $classname($appId, $appSecret, $callback); + if (!empty($state)) { + try { + $state = array_merge($defaultState, $oauth->parseState($state)); + } catch (\Exception $exception) { + throw new Exception('Failed to parse login state params as passed from OAuth provider'); + } + } else { + $state = $defaultState; + } + + if (!$validateURL->isValid($state['success'])) { + throw new Exception('Invalid redirect URL for success login', 400); + } + + if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { + throw new Exception('Invalid redirect URL for failure login', 400); + } + $accessToken = $oauth->getAccessToken($code); if (empty($accessToken)) { diff --git a/public/images/oauth/amazon.png b/public/images/oauth/amazon.png new file mode 100644 index 0000000000..9ce2da81e0 Binary files /dev/null and b/public/images/oauth/amazon.png differ diff --git a/public/images/oauth/apple.png b/public/images/oauth/apple.png index ca56e16bc8..b0fdfd14f0 100644 Binary files a/public/images/oauth/apple.png and b/public/images/oauth/apple.png differ diff --git a/public/images/oauth/vk.png b/public/images/oauth/vk.png new file mode 100644 index 0000000000..481303ab7c Binary files /dev/null and b/public/images/oauth/vk.png differ diff --git a/src/Auth/OAuth.php b/src/Auth/OAuth.php index 70d7f448cf..a96ebb7a68 100644 --- a/src/Auth/OAuth.php +++ b/src/Auth/OAuth.php @@ -78,6 +78,20 @@ abstract class OAuth */ abstract public function getUserName(string $accessToken):string; + // The parseState function was designed specifically for Amazon OAuth Adapter to override. + // The response from Amazon is html encoded and hence it needs to be html_decoded before + // json_decoding + + /** + * @param $state + * + * @return json + */ + public function parseState(string $state) + { + return json_decode($state, true); + } + /** * @param string $method * @param string $url diff --git a/src/Auth/OAuth/Amazon.php b/src/Auth/OAuth/Amazon.php new file mode 100644 index 0000000000..71bb8aaee6 --- /dev/null +++ b/src/Auth/OAuth/Amazon.php @@ -0,0 +1,138 @@ +appID). + '&redirect_uri='.urlencode($this->callback). + '&response_type=code'. + '&state='.urlencode(json_encode($this->state)). + '&scope=profile'; + } + + /** + * @param string $code + * + * @return string + */ + public function getAccessToken(string $code): string + { + $headers[] = 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8'; + $accessToken = $this->request( + 'POST', + 'https://api.amazon.com/auth/o2/token', + $headers, + 'code=' . urlencode($code) . + '&client_id=' . urlencode($this->appID) . + '&client_secret=' . urlencode($this->appSecret). + '&redirect_uri='.urlencode($this->callback). + '&grant_type=authorization_code' + ); + $accessToken = json_decode($accessToken, true); + + if (isset($accessToken['access_token'])) { + return $accessToken['access_token']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserID(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['user_id'])) { + return $user['user_id']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserEmail(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['email'])) { + return $user['email']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserName(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['name'])) { + return $user['name']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return array + */ + protected function getUser(string $accessToken): array + { + if (empty($this->user)) { + $user = $this->request('GET', 'https://api.amazon.com/user/profile?access_token='.urlencode($accessToken)); + $this->user = json_decode($user, true); + } + return $this->user; + } +} diff --git a/src/Auth/OAuth/Apple.php b/src/Auth/OAuth/Apple.php index 2c9a99b8da..fbe4b65b1a 100644 --- a/src/Auth/OAuth/Apple.php +++ b/src/Auth/OAuth/Apple.php @@ -124,7 +124,7 @@ class Apple extends OAuth { if (empty($this->user)) { $headers[] = 'Authorization: Bearer '. urlencode($accessToken); - $user = $this->request('POST', 'https://api.dropboxapi.com/2/users/get_current_account', $headers); + $user = $this->request('POST', '', $headers); $this->user = json_decode($user, true); } diff --git a/src/Auth/OAuth/Vk.php b/src/Auth/OAuth/Vk.php new file mode 100644 index 0000000000..ad860aff84 --- /dev/null +++ b/src/Auth/OAuth/Vk.php @@ -0,0 +1,150 @@ +appID). + '&redirect_uri='.urlencode($this->callback). + '&response_type=code'. + '&state='.urlencode(json_encode($this->state)). + '&v='.urlencode($this->version). + '&scope=openid+email'; + } + + /** + * @param string $code + * + * @return string + */ + public function getAccessToken(string $code): string + { + $headers[] = 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8'; + $accessToken = $this->request( + 'POST', + 'https://oauth.vk.com/access_token?', + $headers, + 'code=' . urlencode($code) . + '&client_id=' . urlencode($this->appID) . + '&client_secret=' . urlencode($this->appSecret). + '&redirect_uri='.urlencode($this->callback) + ); + $accessToken = json_decode($accessToken, true); + + if (isset($accessToken['email'])) { + $this->user['email'] = $accessToken['email']; + } + + if (isset($accessToken['user_id'])) { + $this->user['user_id'] = $accessToken['user_id']; + } + + if (isset($accessToken['access_token'])) { + return $accessToken['access_token']; + } + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserID(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['user_id'])) { + return $user['user_id']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserEmail(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['email'])) { + return $user['email']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserName(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['name'])) { + return $user['name']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return array + */ + protected function getUser(string $accessToken): array + { + if (empty($this->user['name'])) { + $user = $this->request( + 'GET', + 'https://api.vk.com/method/users.get?'. + 'v='.urlencode($this->version). + '&fields=id,name,email,first_name,last_name'. + '&access_token='.urlencode($accessToken) + ); + + $user = json_decode($user, true); + $this->user['name'] = $user['response'][0]['first_name'] ." ".$user['response'][0]['last_name']; + } + return $this->user; + } +}