init
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
/*!
|
||||
* Hybridauth
|
||||
* https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
|
||||
* (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
|
||||
*/
|
||||
|
||||
namespace Hybridauth\Adapter;
|
||||
|
||||
use Hybridauth\Exception\NotImplementedException;
|
||||
use Hybridauth\Exception\InvalidArgumentException;
|
||||
use Hybridauth\Exception\HttpClientFailureException;
|
||||
use Hybridauth\Exception\HttpRequestFailedException;
|
||||
use Hybridauth\Storage\StorageInterface;
|
||||
use Hybridauth\Storage\Session;
|
||||
use Hybridauth\Logger\LoggerInterface;
|
||||
use Hybridauth\Logger\Logger;
|
||||
use Hybridauth\HttpClient\HttpClientInterface;
|
||||
use Hybridauth\HttpClient\Curl as HttpClient;
|
||||
use Hybridauth\Data;
|
||||
|
||||
/**
|
||||
* Class AbstractAdapter
|
||||
*/
|
||||
abstract class AbstractAdapter implements AdapterInterface
|
||||
{
|
||||
use DataStoreTrait;
|
||||
|
||||
/**
|
||||
* Provider ID (unique name).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $providerId = '';
|
||||
|
||||
/**
|
||||
* Specific Provider config.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* Extra Provider parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* Callback url
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $callback = '';
|
||||
|
||||
/**
|
||||
* Storage.
|
||||
*
|
||||
* @var StorageInterface
|
||||
*/
|
||||
public $storage;
|
||||
|
||||
/**
|
||||
* HttpClient.
|
||||
*
|
||||
* @var HttpClientInterface
|
||||
*/
|
||||
public $httpClient;
|
||||
|
||||
/**
|
||||
* Logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
public $logger;
|
||||
|
||||
/**
|
||||
* Whether to validate API status codes of http responses
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $validateApiResponseHttpCode = true;
|
||||
|
||||
/**
|
||||
* Common adapters constructor.
|
||||
*
|
||||
* @param array $config
|
||||
* @param HttpClientInterface $httpClient
|
||||
* @param StorageInterface $storage
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(
|
||||
$config = [],
|
||||
HttpClientInterface $httpClient = null,
|
||||
StorageInterface $storage = null,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->providerId = (new \ReflectionClass($this))->getShortName();
|
||||
|
||||
$this->config = new Data\Collection($config);
|
||||
|
||||
$this->setHttpClient($httpClient);
|
||||
|
||||
$this->setStorage($storage);
|
||||
|
||||
$this->setLogger($logger);
|
||||
|
||||
$this->configure();
|
||||
|
||||
$this->logger->debug(sprintf('Initialize %s, config: ', get_class($this)), $config);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load adapter's configuration
|
||||
*/
|
||||
abstract protected function configure();
|
||||
|
||||
/**
|
||||
* Adapter initializer
|
||||
*/
|
||||
abstract protected function initialize();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
abstract public function isConnected();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function maintainToken()
|
||||
{
|
||||
// Nothing needed for most providers
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserProfile()
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserContacts()
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserPages()
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserActivity($stream)
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUserStatus($status)
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPageStatus($status, $pageId)
|
||||
{
|
||||
throw new NotImplementedException('Provider does not support this feature.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->clearStoredData();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken()
|
||||
{
|
||||
$tokenNames = [
|
||||
'access_token',
|
||||
'access_token_secret',
|
||||
'token_type',
|
||||
'refresh_token',
|
||||
'expires_in',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
$tokens = [];
|
||||
|
||||
foreach ($tokenNames as $name) {
|
||||
if ($this->getStoredData($name)) {
|
||||
$tokens[$name] = $this->getStoredData($name);
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAccessToken($tokens = [])
|
||||
{
|
||||
$this->clearStoredData();
|
||||
|
||||
foreach ($tokens as $token => $value) {
|
||||
$this->storeData($token, $value);
|
||||
}
|
||||
|
||||
// Re-initialize token parameters.
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setHttpClient(HttpClientInterface $httpClient = null)
|
||||
{
|
||||
$this->httpClient = $httpClient ?: new HttpClient();
|
||||
|
||||
if ($this->config->exists('curl_options') && method_exists($this->httpClient, 'setCurlOptions')) {
|
||||
$this->httpClient->setCurlOptions($this->config->get('curl_options'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHttpClient()
|
||||
{
|
||||
return $this->httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStorage(StorageInterface $storage = null)
|
||||
{
|
||||
$this->storage = $storage ?: new Session();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStorage()
|
||||
{
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger = null)
|
||||
{
|
||||
$this->logger = $logger ?: new Logger(
|
||||
$this->config->get('debug_mode'),
|
||||
$this->config->get('debug_file')
|
||||
);
|
||||
|
||||
if (method_exists($this->httpClient, 'setLogger')) {
|
||||
$this->httpClient->setLogger($this->logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLogger()
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Adapter's API callback url
|
||||
*
|
||||
* @param string $callback
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function setCallback($callback)
|
||||
{
|
||||
if (!filter_var($callback, FILTER_VALIDATE_URL)) {
|
||||
throw new InvalidArgumentException('A valid callback url is required.');
|
||||
}
|
||||
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite Adapter's API endpoints
|
||||
*
|
||||
* @param array|Data\Collection $endpoints
|
||||
*/
|
||||
protected function setApiEndpoints($endpoints = null)
|
||||
{
|
||||
if (empty($endpoints)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collection = is_array($endpoints) ? new Data\Collection($endpoints) : $endpoints;
|
||||
|
||||
$this->apiBaseUrl = $collection->get('api_base_url') ?: $this->apiBaseUrl;
|
||||
$this->authorizeUrl = $collection->get('authorize_url') ?: $this->authorizeUrl;
|
||||
$this->accessTokenUrl = $collection->get('access_token_url') ?: $this->accessTokenUrl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate signed API responses Http status code.
|
||||
*
|
||||
* Since the specifics of error responses is beyond the scope of RFC6749 and OAuth Core specifications,
|
||||
* Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR.
|
||||
*
|
||||
* @param string $error String to pre append to message thrown in exception
|
||||
*
|
||||
* @throws HttpClientFailureException
|
||||
* @throws HttpRequestFailedException
|
||||
*/
|
||||
protected function validateApiResponse($error = '')
|
||||
{
|
||||
$error .= !empty($error) ? '. ' : '';
|
||||
|
||||
if ($this->httpClient->getResponseClientError()) {
|
||||
throw new HttpClientFailureException(
|
||||
$error . 'HTTP client error: ' . $this->httpClient->getResponseClientError() . '.'
|
||||
);
|
||||
}
|
||||
|
||||
// if validateApiResponseHttpCode is set to false, we by pass verification of http status code
|
||||
if (!$this->validateApiResponseHttpCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = $this->httpClient->getResponseHttpCode();
|
||||
|
||||
if ($status < 200 || $status > 299) {
|
||||
throw new HttpRequestFailedException(
|
||||
$error . 'HTTP error ' . $this->httpClient->getResponseHttpCode() .
|
||||
'. Raw Provider API response: ' . $this->httpClient->getResponseBody() . '.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/*!
|
||||
* Hybridauth
|
||||
* https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
|
||||
* (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
|
||||
*/
|
||||
|
||||
namespace Hybridauth\Adapter;
|
||||
|
||||
use Hybridauth\HttpClient\HttpClientInterface;
|
||||
use Hybridauth\Storage\StorageInterface;
|
||||
use Hybridauth\Logger\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Interface AdapterInterface
|
||||
*/
|
||||
interface AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Initiate the appropriate protocol and process/automate the authentication or authorization flow.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function authenticate();
|
||||
|
||||
/**
|
||||
* Returns TRUE if the user is connected
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected();
|
||||
|
||||
/**
|
||||
* Clear all access token in storage
|
||||
*/
|
||||
public function disconnect();
|
||||
|
||||
/**
|
||||
* Retrieve the connected user profile
|
||||
*
|
||||
* @return \Hybridauth\User\Profile
|
||||
*/
|
||||
public function getUserProfile();
|
||||
|
||||
/**
|
||||
* Retrieve the connected user contacts list
|
||||
*
|
||||
* @return \Hybridauth\User\Contact[]
|
||||
*/
|
||||
public function getUserContacts();
|
||||
|
||||
/**
|
||||
* Retrieve the connected user pages|companies|groups list
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUserPages();
|
||||
|
||||
/**
|
||||
* Retrieve the user activity stream
|
||||
*
|
||||
* @param string $stream
|
||||
*
|
||||
* @return \Hybridauth\User\Activity[]
|
||||
*/
|
||||
public function getUserActivity($stream);
|
||||
|
||||
/**
|
||||
* Post a status on user wall|timeline|blog|website|etc.
|
||||
*
|
||||
* @param string|array $status
|
||||
*
|
||||
* @return mixed API response
|
||||
*/
|
||||
public function setUserStatus($status);
|
||||
|
||||
/**
|
||||
* Post a status on page|company|group wall.
|
||||
*
|
||||
* @param string|array $status
|
||||
* @param string $pageId
|
||||
*
|
||||
* @return mixed API response
|
||||
*/
|
||||
public function setPageStatus($status, $pageId);
|
||||
|
||||
/**
|
||||
* Send a signed request to provider API
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @param array $headers
|
||||
* @param bool $multipart
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false);
|
||||
|
||||
/**
|
||||
* Do whatever may be necessary to make sure tokens do not expire.
|
||||
* Intended to be be called frequently, e.g. via Cron.
|
||||
*/
|
||||
public function maintainToken();
|
||||
|
||||
/**
|
||||
* Return oauth access tokens.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAccessToken();
|
||||
|
||||
/**
|
||||
* Set oauth access tokens.
|
||||
*
|
||||
* @param array $tokens
|
||||
*/
|
||||
public function setAccessToken($tokens = []);
|
||||
|
||||
/**
|
||||
* Set http client instance.
|
||||
*
|
||||
* @param HttpClientInterface $httpClient
|
||||
*/
|
||||
public function setHttpClient(HttpClientInterface $httpClient = null);
|
||||
|
||||
/**
|
||||
* Return http client instance.
|
||||
*/
|
||||
public function getHttpClient();
|
||||
|
||||
/**
|
||||
* Set storage instance.
|
||||
*
|
||||
* @param StorageInterface $storage
|
||||
*/
|
||||
public function setStorage(StorageInterface $storage = null);
|
||||
|
||||
/**
|
||||
* Return storage instance.
|
||||
*/
|
||||
public function getStorage();
|
||||
|
||||
/**
|
||||
* Set Logger instance.
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger = null);
|
||||
|
||||
/**
|
||||
* Return logger instance.
|
||||
*/
|
||||
public function getLogger();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/*!
|
||||
* Hybridauth
|
||||
* https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
|
||||
* (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
|
||||
*/
|
||||
|
||||
namespace Hybridauth\Adapter;
|
||||
|
||||
/**
|
||||
* Trait DataStoreTrait
|
||||
*/
|
||||
trait DataStoreTrait
|
||||
{
|
||||
/**
|
||||
* Returns storage instance
|
||||
*
|
||||
* @return \Hybridauth\Storage\StorageInterface
|
||||
*/
|
||||
abstract public function getStorage();
|
||||
|
||||
/**
|
||||
* Store a piece of data in storage.
|
||||
*
|
||||
* This method is mainly used for OAuth tokens (access, secret, refresh, and whatnot), but it
|
||||
* can be also used by providers to store any other useful data (i.g., user_id, auth_nonce, etc.)
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function storeData($name, $value = null)
|
||||
{
|
||||
// if empty, we simply delete the thing as we'd want to only store necessary data
|
||||
if (empty($value)) {
|
||||
$this->deleteStoredData($name);
|
||||
}
|
||||
|
||||
$this->getStorage()->set($this->providerId . '.' . $name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a piece of data from storage.
|
||||
*
|
||||
* This method is mainly used for OAuth tokens (access, secret, refresh, and whatnot), but it
|
||||
* can be also used by providers to retrieve from store any other useful data (i.g., user_id,
|
||||
* auth_nonce, etc.)
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getStoredData($name)
|
||||
{
|
||||
return $this->getStorage()->get($this->providerId . '.' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a stored piece of data.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
protected function deleteStoredData($name)
|
||||
{
|
||||
$this->getStorage()->delete($this->providerId . '.' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored data of the instantiated adapter
|
||||
*/
|
||||
protected function clearStoredData()
|
||||
{
|
||||
$this->getStorage()->deleteMatch($this->providerId . '.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,616 @@
|
||||
<?php
|
||||
/*!
|
||||
* Hybridauth
|
||||
* https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
|
||||
* (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
|
||||
*/
|
||||
|
||||
namespace Hybridauth\Adapter;
|
||||
|
||||
use Hybridauth\Exception\Exception;
|
||||
use Hybridauth\Exception\InvalidApplicationCredentialsException;
|
||||
use Hybridauth\Exception\AuthorizationDeniedException;
|
||||
use Hybridauth\Exception\InvalidOauthTokenException;
|
||||
use Hybridauth\Exception\InvalidAccessTokenException;
|
||||
use Hybridauth\Data;
|
||||
use Hybridauth\HttpClient;
|
||||
use Hybridauth\Thirdparty\OAuth\OAuthConsumer;
|
||||
use Hybridauth\Thirdparty\OAuth\OAuthRequest;
|
||||
use Hybridauth\Thirdparty\OAuth\OAuthSignatureMethodHMACSHA1;
|
||||
use Hybridauth\Thirdparty\OAuth\OAuthUtil;
|
||||
|
||||
/**
|
||||
* This class can be used to simplify the authorization flow of OAuth 1 based service providers.
|
||||
*
|
||||
* Subclasses (i.e., providers adapters) can either use the already provided methods or override
|
||||
* them when necessary.
|
||||
*/
|
||||
abstract class OAuth1 extends AbstractAdapter implements AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Base URL to provider API
|
||||
*
|
||||
* This var will be used to build urls when sending signed requests
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $apiBaseUrl = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $authorizeUrl = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $requestTokenUrl = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $accessTokenUrl = '';
|
||||
|
||||
/**
|
||||
* IPD API Documentation
|
||||
*
|
||||
* OPTIONAL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $apiDocumentation = '';
|
||||
|
||||
/**
|
||||
* OAuth Version
|
||||
*
|
||||
* '1.0' OAuth Core 1.0
|
||||
* '1.0a' OAuth Core 1.0 Revision A
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $oauth1Version = '1.0a';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $consumerKey = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $consumerSecret = null;
|
||||
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
protected $OAuthConsumer = null;
|
||||
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
protected $sha1Method = null;
|
||||
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
protected $consumerToken = null;
|
||||
|
||||
/**
|
||||
* Authorization Url Parameters
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $AuthorizeUrlParameters = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $requestTokenMethod = 'POST';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $requestTokenParameters = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $requestTokenHeaders = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tokenExchangeMethod = 'POST';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenExchangeParameters = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenExchangeHeaders = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $apiRequestParameters = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $apiRequestHeaders = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->consumerKey = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key');
|
||||
$this->consumerSecret = $this->config->filter('keys')->get('secret');
|
||||
|
||||
if (!$this->consumerKey || !$this->consumerSecret) {
|
||||
throw new InvalidApplicationCredentialsException(
|
||||
'Your application id is required in order to connect to ' . $this->providerId
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->config->exists('tokens')) {
|
||||
$this->setAccessToken($this->config->get('tokens'));
|
||||
}
|
||||
|
||||
$this->setCallback($this->config->get('callback'));
|
||||
$this->setApiEndpoints($this->config->get('endpoints'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
/**
|
||||
* Set up OAuth Signature and Consumer
|
||||
*
|
||||
* OAuth Core: All Token requests and Protected Resources requests MUST be signed
|
||||
* by the Consumer and verified by the Service Provider.
|
||||
*
|
||||
* The protocol defines three signature methods: HMAC-SHA1, RSA-SHA1, and PLAINTEXT..
|
||||
*
|
||||
* The Consumer declares a signature method in the oauth_signature_method parameter..
|
||||
*
|
||||
* http://oauth.net/core/1.0a/#signing_process
|
||||
*/
|
||||
$this->sha1Method = new OAuthSignatureMethodHMACSHA1();
|
||||
|
||||
$this->OAuthConsumer = new OAuthConsumer(
|
||||
$this->consumerKey,
|
||||
$this->consumerSecret
|
||||
);
|
||||
|
||||
if ($this->getStoredData('request_token')) {
|
||||
$this->consumerToken = new OAuthConsumer(
|
||||
$this->getStoredData('request_token'),
|
||||
$this->getStoredData('request_token_secret')
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getStoredData('access_token')) {
|
||||
$this->consumerToken = new OAuthConsumer(
|
||||
$this->getStoredData('access_token'),
|
||||
$this->getStoredData('access_token_secret')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$this->logger->info(sprintf('%s::authenticate()', get_class($this)));
|
||||
|
||||
if ($this->isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->getStoredData('request_token')) {
|
||||
// Start a new flow.
|
||||
$this->authenticateBegin();
|
||||
} elseif (empty($_GET['oauth_token']) && empty($_GET['denied'])) {
|
||||
// A previous authentication was not finished, and this request is not finishing it.
|
||||
$this->authenticateBegin();
|
||||
} else {
|
||||
// Finish a flow.
|
||||
$this->authenticateFinish();
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
$this->clearStoredData();
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return (bool)$this->getStoredData('access_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the authorization protocol
|
||||
*
|
||||
* 1. Obtaining an Unauthorized Request Token
|
||||
* 2. Build Authorization URL for Authorization Request and redirect the user-agent to the
|
||||
* Authorization Server.
|
||||
*/
|
||||
protected function authenticateBegin()
|
||||
{
|
||||
$response = $this->requestAuthToken();
|
||||
|
||||
$this->validateAuthTokenRequest($response);
|
||||
|
||||
$authUrl = $this->getAuthorizeUrl();
|
||||
|
||||
$this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]);
|
||||
|
||||
HttpClient\Util::redirect($authUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the authorization process
|
||||
*
|
||||
* @throws AuthorizationDeniedException
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
* @throws InvalidAccessTokenException
|
||||
* @throws InvalidOauthTokenException
|
||||
*/
|
||||
protected function authenticateFinish()
|
||||
{
|
||||
$this->logger->debug(
|
||||
sprintf('%s::authenticateFinish(), callback url:', get_class($this)),
|
||||
[HttpClient\Util::getCurrentUrl(true)]
|
||||
);
|
||||
|
||||
$denied = filter_input(INPUT_GET, 'denied');
|
||||
$oauth_problem = filter_input(INPUT_GET, 'oauth_problem');
|
||||
$oauth_token = filter_input(INPUT_GET, 'oauth_token');
|
||||
$oauth_verifier = filter_input(INPUT_GET, 'oauth_verifier');
|
||||
|
||||
if ($denied) {
|
||||
throw new AuthorizationDeniedException(
|
||||
'User denied access request. Provider returned a denied token: ' . htmlentities($denied)
|
||||
);
|
||||
}
|
||||
|
||||
if ($oauth_problem) {
|
||||
throw new InvalidOauthTokenException(
|
||||
'Provider returned an error. oauth_problem: ' . htmlentities($oauth_problem)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$oauth_token) {
|
||||
throw new InvalidOauthTokenException(
|
||||
'Expecting a non-null oauth_token to continue the authorization flow.'
|
||||
);
|
||||
}
|
||||
|
||||
$response = $this->exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier);
|
||||
|
||||
$this->validateAccessTokenExchange($response);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Authorization URL for Authorization Request
|
||||
*
|
||||
* @param array $parameters
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAuthorizeUrl($parameters = [])
|
||||
{
|
||||
$this->AuthorizeUrlParameters = !empty($parameters)
|
||||
? $parameters
|
||||
: array_replace(
|
||||
(array)$this->AuthorizeUrlParameters,
|
||||
(array)$this->config->get('authorize_url_parameters')
|
||||
);
|
||||
|
||||
$this->AuthorizeUrlParameters['oauth_token'] = $this->getStoredData('request_token');
|
||||
|
||||
return $this->authorizeUrl . '?' . http_build_query($this->AuthorizeUrlParameters, '', '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthorized Request Token
|
||||
*
|
||||
* OAuth Core: The Consumer obtains an unauthorized Request Token by asking the Service Provider
|
||||
* to issue a Token. The Request Token's sole purpose is to receive User approval and can only
|
||||
* be used to obtain an Access Token.
|
||||
*
|
||||
* http://oauth.net/core/1.0/#auth_step1
|
||||
* 6.1.1. Consumer Obtains a Request Token
|
||||
*
|
||||
* @return string Raw Provider API response
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
*/
|
||||
protected function requestAuthToken()
|
||||
{
|
||||
/**
|
||||
* OAuth Core 1.0 Revision A: oauth_callback: An absolute URL to which the Service Provider will redirect
|
||||
* the User back when the Obtaining User Authorization step is completed.
|
||||
*
|
||||
* http://oauth.net/core/1.0a/#auth_step1
|
||||
*/
|
||||
if ('1.0a' == $this->oauth1Version) {
|
||||
$this->requestTokenParameters['oauth_callback'] = $this->callback;
|
||||
}
|
||||
|
||||
$response = $this->oauthRequest(
|
||||
$this->requestTokenUrl,
|
||||
$this->requestTokenMethod,
|
||||
$this->requestTokenParameters,
|
||||
$this->requestTokenHeaders
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Unauthorized Request Token Response
|
||||
*
|
||||
* OAuth Core: The Service Provider verifies the signature and Consumer Key. If successful,
|
||||
* it generates a Request Token and Token Secret and returns them to the Consumer in the HTTP
|
||||
* response body.
|
||||
*
|
||||
* http://oauth.net/core/1.0/#auth_step1
|
||||
* 6.1.2. Service Provider Issues an Unauthorized Request Token
|
||||
*
|
||||
* @param string $response
|
||||
*
|
||||
* @return \Hybridauth\Data\Collection
|
||||
* @throws InvalidOauthTokenException
|
||||
*/
|
||||
protected function validateAuthTokenRequest($response)
|
||||
{
|
||||
/**
|
||||
* The response contains the following parameters:
|
||||
*
|
||||
* - oauth_token The Request Token.
|
||||
* - oauth_token_secret The Token Secret.
|
||||
* - oauth_callback_confirmed MUST be present and set to true.
|
||||
*
|
||||
* http://oauth.net/core/1.0/#auth_step1
|
||||
* 6.1.2. Service Provider Issues an Unauthorized Request Token
|
||||
*
|
||||
* Example of a successful response:
|
||||
*
|
||||
* HTTP/1.1 200 OK
|
||||
* Content-Type: text/html; charset=utf-8
|
||||
* Cache-Control: no-store
|
||||
* Pragma: no-cache
|
||||
*
|
||||
* oauth_token=80359084-clg1DEtxQF3wstTcyUdHF3wsdHM&oauth_token_secret=OIF07hPmJB:P
|
||||
* 6qiHTi1znz6qiH3tTcyUdHnz6qiH3tTcyUdH3xW3wsDvV08e&example_parameter=example_value
|
||||
*
|
||||
* OAuthUtil::parse_parameters will attempt to decode the raw response into an array.
|
||||
*/
|
||||
$tokens = OAuthUtil::parse_parameters($response);
|
||||
|
||||
$collection = new Data\Collection($tokens);
|
||||
|
||||
if (!$collection->exists('oauth_token')) {
|
||||
throw new InvalidOauthTokenException(
|
||||
'Provider returned no oauth_token: ' . htmlentities($response)
|
||||
);
|
||||
}
|
||||
|
||||
$this->consumerToken = new OAuthConsumer(
|
||||
$tokens['oauth_token'],
|
||||
$tokens['oauth_token_secret']
|
||||
);
|
||||
|
||||
$this->storeData('request_token', $tokens['oauth_token']);
|
||||
$this->storeData('request_token_secret', $tokens['oauth_token_secret']);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an Access Token
|
||||
*
|
||||
* OAuth Core: The Request Token and Token Secret MUST be exchanged for an Access Token and Token Secret.
|
||||
*
|
||||
* http://oauth.net/core/1.0a/#auth_step3
|
||||
* 6.3.1. Consumer Requests an Access Token
|
||||
*
|
||||
* @param string $oauth_token
|
||||
* @param string $oauth_verifier
|
||||
*
|
||||
* @return string Raw Provider API response
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
*/
|
||||
protected function exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier = '')
|
||||
{
|
||||
$this->tokenExchangeParameters['oauth_token'] = $oauth_token;
|
||||
|
||||
/**
|
||||
* OAuth Core 1.0 Revision A: oauth_verifier: The verification code received from the Service Provider
|
||||
* in the "Service Provider Directs the User Back to the Consumer" step.
|
||||
*
|
||||
* http://oauth.net/core/1.0a/#auth_step3
|
||||
*/
|
||||
if ('1.0a' == $this->oauth1Version) {
|
||||
$this->tokenExchangeParameters['oauth_verifier'] = $oauth_verifier;
|
||||
}
|
||||
|
||||
$response = $this->oauthRequest(
|
||||
$this->accessTokenUrl,
|
||||
$this->tokenExchangeMethod,
|
||||
$this->tokenExchangeParameters,
|
||||
$this->tokenExchangeHeaders
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Access Token Response
|
||||
*
|
||||
* OAuth Core: If successful, the Service Provider generates an Access Token and Token Secret and returns
|
||||
* them in the HTTP response body.
|
||||
*
|
||||
* The Access Token and Token Secret are stored by the Consumer and used when signing Protected Resources requests.
|
||||
*
|
||||
* http://oauth.net/core/1.0a/#auth_step3
|
||||
* 6.3.2. Service Provider Grants an Access Token
|
||||
*
|
||||
* @param string $response
|
||||
*
|
||||
* @return \Hybridauth\Data\Collection
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
protected function validateAccessTokenExchange($response)
|
||||
{
|
||||
/**
|
||||
* The response contains the following parameters:
|
||||
*
|
||||
* - oauth_token The Access Token.
|
||||
* - oauth_token_secret The Token Secret.
|
||||
*
|
||||
* http://oauth.net/core/1.0/#auth_step3
|
||||
* 6.3.2. Service Provider Grants an Access Token
|
||||
*
|
||||
* Example of a successful response:
|
||||
*
|
||||
* HTTP/1.1 200 OK
|
||||
* Content-Type: text/html; charset=utf-8
|
||||
* Cache-Control: no-store
|
||||
* Pragma: no-cache
|
||||
*
|
||||
* oauth_token=sHeLU7Far428zj8PzlWR75&oauth_token_secret=fXb30rzoG&oauth_callback_confirmed=true
|
||||
*
|
||||
* OAuthUtil::parse_parameters will attempt to decode the raw response into an array.
|
||||
*/
|
||||
$tokens = OAuthUtil::parse_parameters($response);
|
||||
|
||||
$collection = new Data\Collection($tokens);
|
||||
|
||||
if (!$collection->exists('oauth_token')) {
|
||||
throw new InvalidAccessTokenException(
|
||||
'Provider returned no access_token: ' . htmlentities($response)
|
||||
);
|
||||
}
|
||||
|
||||
$this->consumerToken = new OAuthConsumer(
|
||||
$collection->get('oauth_token'),
|
||||
$collection->get('oauth_token_secret')
|
||||
);
|
||||
|
||||
$this->storeData('access_token', $collection->get('oauth_token'));
|
||||
$this->storeData('access_token_secret', $collection->get('oauth_token_secret'));
|
||||
|
||||
$this->deleteStoredData('request_token');
|
||||
$this->deleteStoredData('request_token_secret');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signed request to provider API
|
||||
*
|
||||
* Note: Since the specifics of error responses is beyond the scope of RFC6749 and OAuth specifications,
|
||||
* Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @param array $headers
|
||||
* @param bool $multipart
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
*/
|
||||
public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
|
||||
{
|
||||
// refresh tokens if needed
|
||||
$this->maintainToken();
|
||||
|
||||
if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
|
||||
$url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/');
|
||||
}
|
||||
|
||||
$parameters = array_replace($this->apiRequestParameters, (array)$parameters);
|
||||
|
||||
$headers = array_replace($this->apiRequestHeaders, (array)$headers);
|
||||
|
||||
$response = $this->oauthRequest($url, $method, $parameters, $headers, $multipart);
|
||||
|
||||
$response = (new Data\Parser())->parse($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup and Send a Signed Oauth Request
|
||||
*
|
||||
* This method uses OAuth Library.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @param array $headers
|
||||
* @param bool $multipart
|
||||
*
|
||||
* @return string Raw Provider API response
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
*/
|
||||
protected function oauthRequest($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
|
||||
{
|
||||
$signing_parameters = $parameters;
|
||||
if ($multipart) {
|
||||
$signing_parameters = [];
|
||||
}
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token(
|
||||
$this->OAuthConsumer,
|
||||
$this->consumerToken,
|
||||
$method,
|
||||
$uri,
|
||||
$signing_parameters
|
||||
);
|
||||
|
||||
$request->sign_request(
|
||||
$this->sha1Method,
|
||||
$this->OAuthConsumer,
|
||||
$this->consumerToken
|
||||
);
|
||||
|
||||
$uri = $request->get_normalized_http_url();
|
||||
$headers = array_replace($request->to_header(), (array)$headers);
|
||||
|
||||
$response = $this->httpClient->request(
|
||||
$uri,
|
||||
$method,
|
||||
$parameters,
|
||||
$headers,
|
||||
$multipart
|
||||
);
|
||||
|
||||
$this->validateApiResponse('Signed API request to ' . $uri . ' has returned an error');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,739 @@
|
||||
<?php
|
||||
/*!
|
||||
* Hybridauth
|
||||
* https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
|
||||
* (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
|
||||
*/
|
||||
|
||||
namespace Hybridauth\Adapter;
|
||||
|
||||
use Hybridauth\Exception\Exception;
|
||||
use Hybridauth\Exception\InvalidApplicationCredentialsException;
|
||||
use Hybridauth\Exception\InvalidAuthorizationStateException;
|
||||
use Hybridauth\Exception\InvalidAuthorizationCodeException;
|
||||
use Hybridauth\Exception\AuthorizationDeniedException;
|
||||
use Hybridauth\Exception\InvalidAccessTokenException;
|
||||
use Hybridauth\Data;
|
||||
use Hybridauth\HttpClient;
|
||||
|
||||
/**
|
||||
* This class can be used to simplify the authorization flow of OAuth 2 based service providers.
|
||||
*
|
||||
* Subclasses (i.e., providers adapters) can either use the already provided methods or override
|
||||
* them when necessary.
|
||||
*/
|
||||
abstract class OAuth2 extends AbstractAdapter implements AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Client Identifier
|
||||
*
|
||||
* RFC6749: client_id REQUIRED. The client identifier issued to the client during
|
||||
* the registration process described by Section 2.2.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $clientId = '';
|
||||
|
||||
/**
|
||||
* Client Secret
|
||||
*
|
||||
* RFC6749: client_secret REQUIRED. The client secret. The client MAY omit the
|
||||
* parameter if the client secret is an empty string.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-2.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $clientSecret = '';
|
||||
|
||||
/**
|
||||
* Access Token Scope
|
||||
*
|
||||
* RFC6749: The authorization and token endpoints allow the client to specify the
|
||||
* scope of the access request using the "scope" request parameter.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $scope = '';
|
||||
|
||||
/**
|
||||
* Base URL to provider API
|
||||
*
|
||||
* This var will be used to build urls when sending signed requests
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $apiBaseUrl = '';
|
||||
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* RFC6749: The authorization endpoint is used to interact with the resource
|
||||
* owner and obtain an authorization grant.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-3.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $authorizeUrl = '';
|
||||
|
||||
/**
|
||||
* Access Token Endpoint
|
||||
*
|
||||
* RFC6749: The token endpoint is used by the client to obtain an access token by
|
||||
* presenting its authorization grant or refresh token.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-3.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $accessTokenUrl = '';
|
||||
|
||||
/**
|
||||
* TokenInfo endpoint
|
||||
*
|
||||
* Access token validation. OPTIONAL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $accessTokenInfoUrl = '';
|
||||
|
||||
/**
|
||||
* IPD API Documentation
|
||||
*
|
||||
* OPTIONAL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $apiDocumentation = '';
|
||||
|
||||
/**
|
||||
* Redirection Endpoint or Callback
|
||||
*
|
||||
* RFC6749: After completing its interaction with the resource owner, the
|
||||
* authorization server directs the resource owner's user-agent back to
|
||||
* the client.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $callback = '';
|
||||
|
||||
/**
|
||||
* Authorization Url Parameters
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $AuthorizeUrlParameters = [];
|
||||
|
||||
|
||||
/**
|
||||
* Authorization Url Parameter encoding type
|
||||
* @see https://www.php.net/manual/de/function.http-build-query.php
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC1738;
|
||||
|
||||
/**
|
||||
* Authorization Request State
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $supportRequestState = true;
|
||||
|
||||
/**
|
||||
* Access Token name
|
||||
*
|
||||
* While most providers will use 'access_token' as name for the Access Token attribute, other do not.
|
||||
* On the latter case, this should be set by sub classes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $accessTokenName = 'access_token';
|
||||
|
||||
/**
|
||||
* Authorization Request HTTP method.
|
||||
*
|
||||
* @see exchangeCodeForAccessToken()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tokenExchangeMethod = 'POST';
|
||||
|
||||
/**
|
||||
* Authorization Request URL parameters.
|
||||
*
|
||||
* Sub classes may change add any additional parameter when necessary.
|
||||
*
|
||||
* @see exchangeCodeForAccessToken()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenExchangeParameters = [];
|
||||
|
||||
/**
|
||||
* Authorization Request HTTP headers.
|
||||
*
|
||||
* Sub classes may add any additional header when necessary.
|
||||
*
|
||||
* @see exchangeCodeForAccessToken()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenExchangeHeaders = [];
|
||||
|
||||
/**
|
||||
* Refresh Token Request HTTP method.
|
||||
*
|
||||
* @see refreshAccessToken()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tokenRefreshMethod = 'POST';
|
||||
|
||||
/**
|
||||
* Refresh Token Request URL parameters.
|
||||
*
|
||||
* Sub classes may change add any additional parameter when necessary.
|
||||
*
|
||||
* @see refreshAccessToken()
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $tokenRefreshParameters = null;
|
||||
|
||||
/**
|
||||
* Refresh Token Request HTTP headers.
|
||||
*
|
||||
* Sub classes may add any additional header when necessary.
|
||||
*
|
||||
* @see refreshAccessToken()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenRefreshHeaders = [];
|
||||
|
||||
/**
|
||||
* Authorization Request URL parameters.
|
||||
*
|
||||
* Sub classes may change add any additional parameter when necessary.
|
||||
*
|
||||
* @see apiRequest()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $apiRequestParameters = [];
|
||||
|
||||
/**
|
||||
* Authorization Request HTTP headers.
|
||||
*
|
||||
* Sub classes may add any additional header when necessary.
|
||||
*
|
||||
* @see apiRequest()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $apiRequestHeaders = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key');
|
||||
$this->clientSecret = $this->config->filter('keys')->get('secret');
|
||||
|
||||
if (!$this->clientId || !$this->clientSecret) {
|
||||
throw new InvalidApplicationCredentialsException(
|
||||
'Your application id is required in order to connect to ' . $this->providerId
|
||||
);
|
||||
}
|
||||
|
||||
$this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope;
|
||||
|
||||
if ($this->config->exists('tokens')) {
|
||||
$this->setAccessToken($this->config->get('tokens'));
|
||||
}
|
||||
|
||||
$this->setCallback($this->config->get('callback'));
|
||||
$this->setApiEndpoints($this->config->get('endpoints'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
$this->AuthorizeUrlParameters = [
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->clientId,
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => $this->scope,
|
||||
];
|
||||
|
||||
$this->tokenExchangeParameters = [
|
||||
'client_id' => $this->clientId,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $this->callback
|
||||
];
|
||||
|
||||
$refreshToken = $this->getStoredData('refresh_token');
|
||||
if (!empty($refreshToken)) {
|
||||
$this->tokenRefreshParameters = [
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
];
|
||||
}
|
||||
|
||||
$this->apiRequestHeaders = [
|
||||
'Authorization' => 'Bearer ' . $this->getStoredData('access_token')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$this->logger->info(sprintf('%s::authenticate()', get_class($this)));
|
||||
|
||||
if ($this->isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->authenticateCheckError();
|
||||
|
||||
$code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code');
|
||||
|
||||
if (empty($code)) {
|
||||
$this->authenticateBegin();
|
||||
} else {
|
||||
$this->authenticateFinish();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->clearStoredData();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
if ((bool)$this->getStoredData('access_token')) {
|
||||
return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we can use a refresh token, then an expired token does not stop us being connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRefreshTokenAvailable()
|
||||
{
|
||||
return is_array($this->tokenRefreshParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Request Error Response
|
||||
*
|
||||
* RFC6749: If the request fails due to a missing, invalid, or mismatching
|
||||
* redirection URI, or if the client identifier is missing or invalid,
|
||||
* the authorization server SHOULD inform the resource owner of the error.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-4.1.2.1
|
||||
*
|
||||
* @throws \Hybridauth\Exception\InvalidAuthorizationCodeException
|
||||
* @throws \Hybridauth\Exception\AuthorizationDeniedException
|
||||
*/
|
||||
protected function authenticateCheckError()
|
||||
{
|
||||
$error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
|
||||
if (!empty($error)) {
|
||||
$error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
$error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
|
||||
$collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri);
|
||||
|
||||
if ($error == 'access_denied') {
|
||||
throw new AuthorizationDeniedException($collated_error);
|
||||
}
|
||||
|
||||
throw new InvalidAuthorizationCodeException($collated_error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the authorization protocol
|
||||
*
|
||||
* Build Authorization URL for Authorization Request and redirect the user-agent to the
|
||||
* Authorization Server.
|
||||
*/
|
||||
protected function authenticateBegin()
|
||||
{
|
||||
$authUrl = $this->getAuthorizeUrl();
|
||||
|
||||
$this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]);
|
||||
|
||||
HttpClient\Util::redirect($authUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the authorization process
|
||||
*
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
* @throws InvalidAccessTokenException
|
||||
* @throws InvalidAuthorizationStateException
|
||||
*/
|
||||
protected function authenticateFinish()
|
||||
{
|
||||
$this->logger->debug(
|
||||
sprintf('%s::authenticateFinish(), callback url:', get_class($this)),
|
||||
[HttpClient\Util::getCurrentUrl(true)]
|
||||
);
|
||||
|
||||
$state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state');
|
||||
$code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code');
|
||||
|
||||
/**
|
||||
* Authorization Request State
|
||||
*
|
||||
* RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain
|
||||
* state between the request and callback. The authorization server includes
|
||||
* this value when redirecting the user-agent back to the client.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
*/
|
||||
if ($this->supportRequestState
|
||||
&& $this->getStoredData('authorization_state') != $state
|
||||
) {
|
||||
throw new InvalidAuthorizationStateException(
|
||||
'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] '
|
||||
. 'of this page is either invalid or has already been consumed.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Request Code
|
||||
*
|
||||
* RFC6749: If the resource owner grants the access request, the authorization
|
||||
* server issues an authorization code and delivers it to the client:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-4.1.2
|
||||
*/
|
||||
$response = $this->exchangeCodeForAccessToken($code);
|
||||
|
||||
$this->validateAccessTokenExchange($response);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Authorization URL for Authorization Request
|
||||
*
|
||||
* RFC6749: The client constructs the request URI by adding the following
|
||||
* $parameters to the query component of the authorization endpoint URI:
|
||||
*
|
||||
* - response_type REQUIRED. Value MUST be set to "code".
|
||||
* - client_id REQUIRED.
|
||||
* - redirect_uri OPTIONAL.
|
||||
* - scope OPTIONAL.
|
||||
* - state RECOMMENDED.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
*
|
||||
* Sub classes may redefine this method when necessary.
|
||||
*
|
||||
* @param array $parameters
|
||||
*
|
||||
* @return string Authorization URL
|
||||
*/
|
||||
protected function getAuthorizeUrl($parameters = [])
|
||||
{
|
||||
$this->AuthorizeUrlParameters = !empty($parameters)
|
||||
? $parameters
|
||||
: array_replace(
|
||||
(array)$this->AuthorizeUrlParameters,
|
||||
(array)$this->config->get('authorize_url_parameters')
|
||||
);
|
||||
|
||||
if ($this->supportRequestState) {
|
||||
if (!isset($this->AuthorizeUrlParameters['state'])) {
|
||||
$this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
|
||||
}
|
||||
|
||||
$this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']);
|
||||
}
|
||||
|
||||
$queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType);
|
||||
return $this->authorizeUrl . '?' . $queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access Token Request
|
||||
*
|
||||
* This method will exchange the received $code in loginFinish() with an Access Token.
|
||||
*
|
||||
* RFC6749: The client makes a request to the token endpoint by sending the
|
||||
* following parameters using the "application/x-www-form-urlencoded"
|
||||
* with a character encoding of UTF-8 in the HTTP request entity-body:
|
||||
*
|
||||
* - grant_type REQUIRED. Value MUST be set to "authorization_code".
|
||||
* - code REQUIRED. The authorization code received from the authorization server.
|
||||
* - redirect_uri REQUIRED.
|
||||
* - client_id REQUIRED.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
*
|
||||
* @param string $code
|
||||
*
|
||||
* @return string Raw Provider API response
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
*/
|
||||
protected function exchangeCodeForAccessToken($code)
|
||||
{
|
||||
$this->tokenExchangeParameters['code'] = $code;
|
||||
|
||||
$response = $this->httpClient->request(
|
||||
$this->accessTokenUrl,
|
||||
$this->tokenExchangeMethod,
|
||||
$this->tokenExchangeParameters,
|
||||
$this->tokenExchangeHeaders
|
||||
);
|
||||
|
||||
$this->validateApiResponse('Unable to exchange code for API access token');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Access Token Response
|
||||
*
|
||||
* RFC6749: If the access token request is valid and authorized, the
|
||||
* authorization server issues an access token and optional refresh token.
|
||||
* If the request client authentication failed or is invalid, the authorization
|
||||
* server returns an error response as described in Section 5.2.
|
||||
*
|
||||
* Example of a successful response:
|
||||
*
|
||||
* HTTP/1.1 200 OK
|
||||
* Content-Type: application/json;charset=UTF-8
|
||||
* Cache-Control: no-store
|
||||
* Pragma: no-cache
|
||||
*
|
||||
* {
|
||||
* "access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
* "token_type":"example",
|
||||
* "expires_in":3600,
|
||||
* "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
* "example_parameter":"example_value"
|
||||
* }
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||
*
|
||||
* This method uses Data_Parser to attempt to decodes the raw $response (usually JSON)
|
||||
* into a data collection.
|
||||
*
|
||||
* @param string $response
|
||||
*
|
||||
* @return \Hybridauth\Data\Collection
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
protected function validateAccessTokenExchange($response)
|
||||
{
|
||||
$data = (new Data\Parser())->parse($response);
|
||||
|
||||
$collection = new Data\Collection($data);
|
||||
|
||||
if (!$collection->exists('access_token')) {
|
||||
throw new InvalidAccessTokenException(
|
||||
'Provider returned no access_token: ' . htmlentities($response)
|
||||
);
|
||||
}
|
||||
|
||||
$this->storeData('access_token', $collection->get('access_token'));
|
||||
$this->storeData('token_type', $collection->get('token_type'));
|
||||
|
||||
if ($collection->get('refresh_token')) {
|
||||
$this->storeData('refresh_token', $collection->get('refresh_token'));
|
||||
}
|
||||
|
||||
// calculate when the access token expire
|
||||
if ($collection->exists('expires_in')) {
|
||||
$expires_at = time() + (int)$collection->get('expires_in');
|
||||
|
||||
$this->storeData('expires_in', $collection->get('expires_in'));
|
||||
$this->storeData('expires_at', $expires_at);
|
||||
}
|
||||
|
||||
$this->deleteStoredData('authorization_state');
|
||||
|
||||
$this->initialize();
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshing an Access Token
|
||||
*
|
||||
* RFC6749: If the authorization server issued a refresh token to the
|
||||
* client, the client makes a refresh request to the token endpoint by
|
||||
* adding the following parameters ... in the HTTP request entity-body:
|
||||
*
|
||||
* - grant_type REQUIRED. Value MUST be set to "refresh_token".
|
||||
* - refresh_token REQUIRED. The refresh token issued to the client.
|
||||
* - scope OPTIONAL.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-6
|
||||
*
|
||||
* This method is similar to exchangeCodeForAccessToken(). The only
|
||||
* difference is here we exchange refresh_token for a new access_token.
|
||||
*
|
||||
* @param array $parameters
|
||||
*
|
||||
* @return string|null Raw Provider API response, or null if we cannot refresh
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
public function refreshAccessToken($parameters = [])
|
||||
{
|
||||
$this->tokenRefreshParameters = !empty($parameters)
|
||||
? $parameters
|
||||
: $this->tokenRefreshParameters;
|
||||
|
||||
if (!$this->isRefreshTokenAvailable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$response = $this->httpClient->request(
|
||||
$this->accessTokenUrl,
|
||||
$this->tokenRefreshMethod,
|
||||
$this->tokenRefreshParameters,
|
||||
$this->tokenRefreshHeaders
|
||||
);
|
||||
|
||||
$this->validateApiResponse('Unable to refresh the access token');
|
||||
|
||||
$this->validateRefreshAccessToken($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether access token has expired
|
||||
*
|
||||
* @param int|null $time
|
||||
* @return bool|null
|
||||
*/
|
||||
public function hasAccessTokenExpired($time = null)
|
||||
{
|
||||
if ($time === null) {
|
||||
$time = time();
|
||||
}
|
||||
|
||||
$expires_at = $this->getStoredData('expires_at');
|
||||
if (!$expires_at) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $expires_at <= $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Refresh Access Token Request
|
||||
*
|
||||
* RFC6749: If valid and authorized, the authorization server issues an
|
||||
* access token as described in Section 5.1. If the request failed
|
||||
* verification or is invalid, the authorization server returns an error
|
||||
* response as described in Section 5.2.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-6
|
||||
* http://tools.ietf.org/html/rfc6749#section-5.1
|
||||
* http://tools.ietf.org/html/rfc6749#section-5.2
|
||||
*
|
||||
* This method simply use validateAccessTokenExchange(), however sub
|
||||
* classes may redefine it when necessary.
|
||||
*
|
||||
* @param $response
|
||||
*
|
||||
* @return \Hybridauth\Data\Collection
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
protected function validateRefreshAccessToken($response)
|
||||
{
|
||||
return $this->validateAccessTokenExchange($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signed request to provider API
|
||||
*
|
||||
* RFC6749: Accessing Protected Resources: The client accesses protected
|
||||
* resources by presenting the access token to the resource server. The
|
||||
* resource server MUST validate the access token and ensure that it has
|
||||
* not expired and that its scope covers the requested resource.
|
||||
*
|
||||
* Note: Since the specifics of error responses is beyond the scope of
|
||||
* RFC6749 and OAuth specifications, Hybridauth will consider any HTTP
|
||||
* status code that is different than '200 OK' as an ERROR.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6749#section-7
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @param array $headers
|
||||
* @param bool $multipart
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Hybridauth\Exception\HttpClientFailureException
|
||||
* @throws \Hybridauth\Exception\HttpRequestFailedException
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false)
|
||||
{
|
||||
// refresh tokens if needed
|
||||
$this->maintainToken();
|
||||
if ($this->hasAccessTokenExpired() === true) {
|
||||
$this->refreshAccessToken();
|
||||
}
|
||||
|
||||
if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {
|
||||
$url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/');
|
||||
}
|
||||
|
||||
$parameters = array_replace($this->apiRequestParameters, (array)$parameters);
|
||||
$headers = array_replace($this->apiRequestHeaders, (array)$headers);
|
||||
|
||||
$response = $this->httpClient->request(
|
||||
$url,
|
||||
$method, // HTTP Request Method. Defaults to GET.
|
||||
$parameters, // Request Parameters
|
||||
$headers, // Request Headers
|
||||
$multipart // Is request multipart
|
||||
);
|
||||
|
||||
$this->validateApiResponse('Signed API request to ' . $url . ' has returned an error');
|
||||
|
||||
$response = (new Data\Parser())->parse($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
/*!
|
||||
* Hybridauth
|
||||
* https://hybridauth.github.io | https://github.com/hybridauth/hybridauth
|
||||
* (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html
|
||||
*/
|
||||
|
||||
namespace Hybridauth\Adapter;
|
||||
|
||||
use Hybridauth\Exception\InvalidOpenidIdentifierException;
|
||||
use Hybridauth\Exception\AuthorizationDeniedException;
|
||||
use Hybridauth\Exception\UnexpectedApiResponseException;
|
||||
use Hybridauth\Data;
|
||||
use Hybridauth\HttpClient;
|
||||
use Hybridauth\User;
|
||||
use Hybridauth\Thirdparty\OpenID\LightOpenID;
|
||||
|
||||
/**
|
||||
* This class can be used to simplify the authentication flow of OpenID based service providers.
|
||||
*
|
||||
* Subclasses (i.e., providers adapters) can either use the already provided methods or override
|
||||
* them when necessary.
|
||||
*/
|
||||
abstract class OpenID extends AbstractAdapter implements AdapterInterface
|
||||
{
|
||||
/**
|
||||
* LightOpenID instance
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $openIdClient = null;
|
||||
|
||||
/**
|
||||
* Openid provider identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $openidIdentifier = '';
|
||||
|
||||
/**
|
||||
* IPD API Documentation
|
||||
*
|
||||
* OPTIONAL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $apiDocumentation = '';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
if ($this->config->exists('openid_identifier')) {
|
||||
$this->openidIdentifier = $this->config->get('openid_identifier');
|
||||
}
|
||||
|
||||
if (empty($this->openidIdentifier)) {
|
||||
throw new InvalidOpenidIdentifierException('OpenID adapter requires an openid_identifier.', 4);
|
||||
}
|
||||
|
||||
$this->setCallback($this->config->get('callback'));
|
||||
$this->setApiEndpoints($this->config->get('endpoints'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
$hostPort = parse_url($this->callback, PHP_URL_PORT);
|
||||
$hostUrl = parse_url($this->callback, PHP_URL_HOST);
|
||||
|
||||
if ($hostPort) {
|
||||
$hostUrl .= ':' . $hostPort;
|
||||
}
|
||||
|
||||
// @fixme: add proxy
|
||||
$this->openIdClient = new LightOpenID($hostUrl, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$this->logger->info(sprintf('%s::authenticate()', get_class($this)));
|
||||
|
||||
if ($this->isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty($_REQUEST['openid_mode'])) {
|
||||
$this->authenticateBegin();
|
||||
} else {
|
||||
return $this->authenticateFinish();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return (bool)$this->storage->get($this->providerId . '.user');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->storage->delete($this->providerId . '.user');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the authorization protocol
|
||||
*
|
||||
* Include and instantiate LightOpenID
|
||||
*/
|
||||
protected function authenticateBegin()
|
||||
{
|
||||
$this->openIdClient->identity = $this->openidIdentifier;
|
||||
$this->openIdClient->returnUrl = $this->callback;
|
||||
$this->openIdClient->required = [
|
||||
'namePerson/first',
|
||||
'namePerson/last',
|
||||
'namePerson/friendly',
|
||||
'namePerson',
|
||||
'contact/email',
|
||||
'birthDate',
|
||||
'birthDate/birthDay',
|
||||
'birthDate/birthMonth',
|
||||
'birthDate/birthYear',
|
||||
'person/gender',
|
||||
'pref/language',
|
||||
'contact/postalCode/home',
|
||||
'contact/city/home',
|
||||
'contact/country/home',
|
||||
|
||||
'media/image/default',
|
||||
];
|
||||
|
||||
$authUrl = $this->openIdClient->authUrl();
|
||||
|
||||
$this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]);
|
||||
|
||||
HttpClient\Util::redirect($authUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the authorization process.
|
||||
*
|
||||
* @throws AuthorizationDeniedException
|
||||
* @throws UnexpectedApiResponseException
|
||||
*/
|
||||
protected function authenticateFinish()
|
||||
{
|
||||
$this->logger->debug(
|
||||
sprintf('%s::authenticateFinish(), callback url:', get_class($this)),
|
||||
[HttpClient\Util::getCurrentUrl(true)]
|
||||
);
|
||||
|
||||
if ($this->openIdClient->mode == 'cancel') {
|
||||
throw new AuthorizationDeniedException('User has cancelled the authentication.');
|
||||
}
|
||||
|
||||
if (!$this->openIdClient->validate()) {
|
||||
throw new UnexpectedApiResponseException('Invalid response received.');
|
||||
}
|
||||
|
||||
$openidAttributes = $this->openIdClient->getAttributes();
|
||||
|
||||
if (!$this->openIdClient->identity) {
|
||||
throw new UnexpectedApiResponseException('Provider returned an unexpected response.');
|
||||
}
|
||||
|
||||
$userProfile = $this->fetchUserProfile($openidAttributes);
|
||||
|
||||
/* with openid providers we only get user profiles once, so we store it */
|
||||
$this->storage->set($this->providerId . '.user', $userProfile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch user profile from received openid attributes
|
||||
*
|
||||
* @param array $openidAttributes
|
||||
*
|
||||
* @return User\Profile
|
||||
*/
|
||||
protected function fetchUserProfile($openidAttributes)
|
||||
{
|
||||
$data = new Data\Collection($openidAttributes);
|
||||
|
||||
$userProfile = new User\Profile();
|
||||
|
||||
$userProfile->identifier = $this->openIdClient->identity;
|
||||
|
||||
$userProfile->firstName = $data->get('namePerson/first');
|
||||
$userProfile->lastName = $data->get('namePerson/last');
|
||||
$userProfile->email = $data->get('contact/email');
|
||||
$userProfile->language = $data->get('pref/language');
|
||||
$userProfile->country = $data->get('contact/country/home');
|
||||
$userProfile->zip = $data->get('contact/postalCode/home');
|
||||
$userProfile->gender = $data->get('person/gender');
|
||||
$userProfile->photoURL = $data->get('media/image/default');
|
||||
$userProfile->birthDay = $data->get('birthDate/birthDay');
|
||||
$userProfile->birthMonth = $data->get('birthDate/birthMonth');
|
||||
$userProfile->birthYear = $data->get('birthDate/birthDate');
|
||||
|
||||
$userProfile = $this->fetchUserGender($userProfile, $data->get('person/gender'));
|
||||
|
||||
$userProfile = $this->fetchUserDisplayName($userProfile, $data);
|
||||
|
||||
return $userProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract users display names
|
||||
*
|
||||
* @param User\Profile $userProfile
|
||||
* @param Data\Collection $data
|
||||
*
|
||||
* @return User\Profile
|
||||
*/
|
||||
protected function fetchUserDisplayName(User\Profile $userProfile, Data\Collection $data)
|
||||
{
|
||||
$userProfile->displayName = $data->get('namePerson');
|
||||
|
||||
$userProfile->displayName = $userProfile->displayName
|
||||
? $userProfile->displayName
|
||||
: $data->get('namePerson/friendly');
|
||||
|
||||
$userProfile->displayName = $userProfile->displayName
|
||||
? $userProfile->displayName
|
||||
: trim($userProfile->firstName . ' ' . $userProfile->lastName);
|
||||
|
||||
return $userProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract users gender
|
||||
*
|
||||
* @param User\Profile $userProfile
|
||||
* @param string $gender
|
||||
*
|
||||
* @return User\Profile
|
||||
*/
|
||||
protected function fetchUserGender(User\Profile $userProfile, $gender)
|
||||
{
|
||||
$gender = strtolower($gender);
|
||||
|
||||
if ('f' == $gender) {
|
||||
$gender = 'female';
|
||||
}
|
||||
|
||||
if ('m' == $gender) {
|
||||
$gender = 'male';
|
||||
}
|
||||
|
||||
$userProfile->gender = $gender;
|
||||
|
||||
return $userProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenID only provide the user profile one. This method will attempt to retrieve the profile from storage.
|
||||
*/
|
||||
public function getUserProfile()
|
||||
{
|
||||
$userProfile = $this->storage->get($this->providerId . '.user');
|
||||
|
||||
if (!is_object($userProfile)) {
|
||||
throw new UnexpectedApiResponseException('Provider returned an unexpected response.');
|
||||
}
|
||||
|
||||
return $userProfile;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user