src/Service/Panel/OAuth/TwitterOAuthClient.php line 87

Open in your IDE?
  1. <?php
  2. namespace Harmonizely\Service\Panel\OAuth;
  3. use Harmonizely\Service\Panel\OAuth\Contract\IOAuthClient;
  4. use Harmonizely\Types\Company\OAuthProviderType;
  5. use Psr\Log\LoggerInterface;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  8. use Symfony\Component\Security\Csrf\CsrfToken;
  9. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  10. use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
  11. use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
  12. use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
  13. use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
  14. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  15. use Symfony\Contracts\HttpClient\HttpClientInterface;
  16. class TwitterOAuthClient implements IOAuthClient
  17. {
  18.     /**
  19.      * @var string
  20.      */
  21.     private string $appKey;
  22.     /**
  23.      * @var string
  24.      */
  25.     private string $appSecret;
  26.     /**
  27.      * @var HttpClientInterface
  28.      */
  29.     private HttpClientInterface $httpClient;
  30.     /**
  31.      * @var UrlGeneratorInterface
  32.      */
  33.     private UrlGeneratorInterface $urlGenerator;
  34.     /**
  35.      * @var LoggerInterface
  36.      */
  37.     private LoggerInterface $logger;
  38.     /**
  39.      * @var CsrfTokenManagerInterface
  40.      */
  41.     private CsrfTokenManagerInterface $csrfTokenManager;
  42.     /**
  43.      * TwitterOAuthClient constructor.
  44.      *
  45.      * @param string $appKey
  46.      * @param string $appSecret
  47.      * @param HttpClientInterface $httpClient
  48.      * @param UrlGeneratorInterface $urlGenerator
  49.      * @param LoggerInterface $logger
  50.      * @param CsrfTokenManagerInterface $csrfTokenManager
  51.      */
  52.     public function __construct(
  53.         string $appKey,
  54.         string $appSecret,
  55.         HttpClientInterface $httpClient,
  56.         UrlGeneratorInterface $urlGenerator,
  57.         LoggerInterface $logger,
  58.         CsrfTokenManagerInterface $csrfTokenManager
  59.     )
  60.     {
  61.         $this->appKey $appKey;
  62.         $this->appSecret $appSecret;
  63.         $this->httpClient $httpClient;
  64.         $this->urlGenerator $urlGenerator;
  65.         $this->logger $logger;
  66.         $this->csrfTokenManager $csrfTokenManager;
  67.     }
  68.     /**
  69.      * @return string
  70.      * @throws OAuthException
  71.      * @throws ClientExceptionInterface
  72.      * @throws RedirectionExceptionInterface
  73.      * @throws ServerExceptionInterface
  74.      * @throws TransportExceptionInterface
  75.      */
  76.     public function getAuthUrl(): string
  77.     {
  78.         $headers $this->getAuthHeaders($this->urlGenerator->generate('login.oauth.callback', [
  79.             'provider' => OAuthProviderType::TYPE_TWITTER,
  80.             'state' => $this->csrfTokenManager->getToken('twitter')->getValue(),
  81.         ], UrlGeneratorInterface::ABSOLUTE_URL));
  82.         $headers['oauth_signature'] = urlencode($this->getSign(
  83.             'https://api.twitter.com/oauth/request_token',
  84.             'POST',
  85.             $headers,
  86.             $this->appSecret '&'
  87.         ));
  88.         $authHeader $this->buildOauthHeader($headers);
  89.         $response $this->httpClient->request('POST''https://api.twitter.com/oauth/request_token', [
  90.             'headers' => [
  91.                 'Authorization' => $authHeader,
  92.             ],
  93.         ]);
  94.         if ($response->getStatusCode() === 200) {
  95.             $result = [];
  96.             parse_str($response->getContent(), $result);
  97.             if (isset($result['oauth_token'])) {
  98.                 return 'https://api.twitter.com/oauth/authenticate?oauth_token=' $result['oauth_token'];
  99.             } else {
  100.                 throw new OAuthException('Token api end point do not return oauth_token');
  101.             }
  102.         } else {
  103.             throw new OAuthException('Token api end point status != 200');
  104.         }
  105.     }
  106.     /**
  107.      * @param Request $request
  108.      * @return Profile
  109.      * @throws OAuthException
  110.      * @throws ClientExceptionInterface
  111.      * @throws DecodingExceptionInterface
  112.      * @throws RedirectionExceptionInterface
  113.      * @throws ServerExceptionInterface
  114.      * @throws TransportExceptionInterface
  115.      */
  116.     public function getProfileAfterCallback(Request $request): Profile
  117.     {
  118.         if ($request->get('app_oauth_token')) {
  119.             return $this->getProfile($request->get('app_oauth_token'));
  120.         }
  121.         if (!$this->csrfTokenManager->isTokenValid(new CsrfToken('twitter'$request->get('state')))) {
  122.             throw new OAuthException('State is not valid');
  123.         }
  124.         $headers $this->getAuthHeaders(null$request->get('oauth_token'));
  125.         $headers['oauth_signature'] = urlencode($this->getSign(
  126.             'https://api.twitter.com/oauth/access_token',
  127.             'POST',
  128.             $headers,
  129.             $this->appSecret '&'
  130.         ));
  131.         $authHeader $this->buildOauthHeader($headers);
  132.         $response $this->httpClient->request('POST''https://api.twitter.com/oauth/access_token', [
  133.             'headers' => [
  134.                 'Authorization' => $authHeader,
  135.             ],
  136.             'body' => [
  137.                 'oauth_verifier' => $request->get('oauth_verifier'),
  138.             ],
  139.         ]);
  140.         if ($response->getStatusCode() === 200) {
  141.             $result = [];
  142.             parse_str($response->getContent(), $result);
  143.             if (isset($result['oauth_token']) && isset($result['oauth_token_secret'])) {
  144.                 $headersForProfile $this->getAuthHeaders(null$result['oauth_token']);
  145.                 $headersForProfile['include_email'] = 'true';
  146.                 $headersForProfile['oauth_signature'] = urlencode($this->getSign(
  147.                     'https://api.twitter.com/1.1/account/verify_credentials.json',
  148.                     'GET',
  149.                     $headersForProfile,
  150.                     urlencode($this->appSecret) . '&' urlencode($result['oauth_token_secret'])
  151.                 ));
  152.                 $authHeader $this->buildOauthHeader($headersForProfile);
  153.                 return $this->getProfile($authHeader);
  154.             } else {
  155.                 throw new OAuthException('Token api end point do not return oauth_token or oauth_token_secret');
  156.             }
  157.         } else {
  158.             throw new OAuthException('Token api end point status != 200');
  159.         }
  160.     }
  161.     /**
  162.      * @param string|null $callback
  163.      * @param string|null $token
  164.      * @return array
  165.      */
  166.     private function getAuthHeaders(?string $callback null, ?string $token null): array
  167.     {
  168.         $headers = [
  169.             'oauth_consumer_key' => $this->appKey,
  170.             'oauth_nonce' => md5(time()),
  171.             'oauth_signature_method' => 'HMAC-SHA1',
  172.             'oauth_timestamp' => time(),
  173.             'oauth_version' => '1.0',
  174.         ];
  175.         if ($callback) {
  176.             $headers['oauth_callback'] = urlencode($callback);
  177.         }
  178.         if ($token) {
  179.             $headers['oauth_token'] = urlencode($token);
  180.         }
  181.         return $headers;
  182.     }
  183.     /**
  184.      * @param array $headers
  185.      * @return string
  186.      */
  187.     private function buildOauthHeader(array $headers): string
  188.     {
  189.         $result 'OAuth realm="",';
  190.         $data = [];
  191.         foreach ($headers as $key => $value) {
  192.             $data[] = $key '="' $value '"';
  193.         }
  194.         return $result implode(','$data);
  195.     }
  196.     /**
  197.      * @param string $baseUri
  198.      * @param string $method
  199.      * @param array $params
  200.      * @param string $secretKey
  201.      * @return string
  202.      */
  203.     private function getSign(string $baseUristring $method, array $paramsstring $secretKey): string
  204.     {
  205.         $urlParams = [];
  206.         ksort($params);
  207.         foreach ($params as $key => $value) {
  208.             $urlParams[] = "$key=" $value;
  209.         }
  210.         $str $method "&" urlencode($baseUri) . '&' urlencode(implode('&'$urlParams));
  211.         return base64_encode(hash_hmac('sha1'$str$secretKeytrue));
  212.     }
  213.     /**
  214.      * @param string $authHeader
  215.      * @return Profile
  216.      * @throws ClientExceptionInterface
  217.      * @throws DecodingExceptionInterface
  218.      * @throws OAuthException
  219.      * @throws RedirectionExceptionInterface
  220.      * @throws ServerExceptionInterface
  221.      * @throws TransportExceptionInterface
  222.      */
  223.     private function getProfile(string $authHeader): Profile
  224.     {
  225. //        $this->logger->error('authHeader', [
  226. //            'authHeader' => $authHeader,
  227. //        ]);
  228.         $responseProfile $this->httpClient->request('GET''https://api.twitter.com/1.1/account/verify_credentials.json', [
  229.             'headers' => [
  230.                 'Authorization' => $authHeader,
  231.             ],
  232.             'query' => [
  233.                 'include_email' => 'true',
  234.             ],
  235.         ]);
  236.         if ($responseProfile->getStatusCode() === 200) {
  237.             $resultProfile $responseProfile->toArray();
  238.             return new Profile(OAuthProviderType::TYPE_TWITTER$resultProfile['id'], $resultProfile['name'], $resultProfile['email']);
  239.         } else {
  240.             $this->logger->error('getProfile', [
  241.                 'code' => $responseProfile->getStatusCode(),
  242.                 'responseProfile' => $responseProfile->getContent(false),
  243.             ]);
  244.             throw new OAuthException('Profile api end point status != 200');
  245.         }
  246.     }
  247. }