From c445d7124837e2e99b2601e8da810c9f06ea855f Mon Sep 17 00:00:00 2001 From: Nick Vanpraet Date: Thu, 7 Apr 2022 16:26:05 +0200 Subject: [PATCH] Add support for client credentials grant --- README.md | 50 +++++++ lib/Auth/TwoLeggedOAuth2.php | 270 +++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100755 lib/Auth/TwoLeggedOAuth2.php diff --git a/README.md b/README.md index d885e798..3073fbc9 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,56 @@ try { } ``` +### Using Two-Legged Authentication (Oauth2 Client Credentials) Instead +The above method uses authorization code flow for Oauth2. Client Credentials is the preferred method of +authentication when the use-case is application to application, where any actions +are triggered by the application itself and not a user taking an action (e.g. cleanup during cron). + +```php +newAuth() will accept an array of Auth settings +$settings = [ + 'AuthMethod' => 'TwoLeggedOAuth2', + 'clientKey' => '', + 'clientSecret' => '', + 'baseUrl' => '', +]; + +/* +// If you already have the access token, et al, pass them in as well to prevent the need for reauthorization +$settings['accessToken'] = $accessToken; +$settings['accessTokenExpires'] = $accessTokenExpires; //UNIX timestamp +*/ + +// Initiate the auth object +$initAuth = new ApiAuth(); +$auth = $initAuth->newAuth($settings, $settings['AuthMethod']); + +if (!$auth->isAuthorized()) { + $auth->requestAccessToken(); + // $accessTokenData will have the following keys: + // access_token, expires, token_type + $accessTokenData = $auth->getAccessTokenData(); + + //store access token data however you want +} + +// Nothing else to do ... It's ready to use. +// Just pass the auth object to the API context you are creating. +``` + ### Using Basic Authentication Instead Instead of messing around with OAuth, you may simply elect to use BasicAuth instead. diff --git a/lib/Auth/TwoLeggedOAuth2.php b/lib/Auth/TwoLeggedOAuth2.php new file mode 100755 index 00000000..f571380c --- /dev/null +++ b/lib/Auth/TwoLeggedOAuth2.php @@ -0,0 +1,270 @@ +log('parameters did not include clientkey and/or clientSecret'); + throw new RequiredParameterMissingException('One or more required parameters was not supplied. Both clientKey and clientSecret required!'); + } + + if (empty($baseUrl)) { + //Throw exception if the required parameters were not found + $this->log('parameters did not include baseUrl'); + throw new RequiredParameterMissingException('One or more required parameters was not supplied. baseUrl required!'); + } + + $this->_client_id = $clientKey; + $this->_client_secret = $clientSecret; + $this->_access_token = $accessToken; + $this->_access_token_url = $baseUrl.'/oauth/v2/token'; + + if (!empty($accessToken)) { + $this->setAccessTokenDetails([ + 'access_token' => $accessToken, + 'expires' => $accessTokenExpires, + ]); + } + } + + /** + * Check to see if the access token was updated. + * + * @return bool + */ + public function accessTokenUpdated() + { + return $this->_access_token_updated; + } + + /** + * Returns access token data. + * + * @return array + */ + public function getAccessTokenData() + { + return [ + 'access_token' => $this->_access_token, + 'expires' => $this->_expires, + 'token_type' => $this->_token_type, + ]; + } + + /** + * {@inheritdoc} + */ + public function isAuthorized() + { + $this->log('isAuthorized()'); + + return $this->validateAccessToken(); + } + + /** + * Set an existing/already retrieved access token. + * + * @return $this + */ + public function setAccessTokenDetails(array $accessTokenDetails) + { + $this->_access_token = $accessTokenDetails['access_token'] ?? null; + $this->_expires = $accessTokenDetails['expires'] ?? null; + + return $this; + } + + /** + * Validate existing access token. + * + * @return bool + */ + public function validateAccessToken() + { + $this->log('validateAccessToken()'); + + //Check to see if token in session has expired + if (strlen($this->_access_token) > 0 && !empty($this->_expires) && $this->_expires < (time() + 10)) { + $this->log('access token expired'); + + return false; + } + + //Check for existing access token + if (strlen($this->_access_token) > 0) { + $this->log('has valid access token'); + + return true; + } + + //If there is no existing access token, it can't be valid + return false; + } + + /** + * @param $isPost + * @param $parameters + * + * @return array + */ + protected function getQueryParameters($isPost, $parameters) + { + $query = parent::getQueryParameters($isPost, $parameters); + + if (isset($parameters['file'])) { + //Mautic's OAuth2 server does not recognize multipart forms so we have to append the access token as part of the URL + $query['access_token'] = $parameters['access_token']; + } + + return $query; + } + + /** + * @param $url + * @param array $method + * + * @return array + */ + protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings) + { + if ($this->isAuthorized()) { + $headers = array_merge($headers, ['Authorization: Bearer '.$this->_access_token]); + } + + return [$headers, $parameters]; + } + + /** + * Request access token. + * + * @return bool + * + * @throws IncorrectParametersReturnedException|\Mautic\Exception\UnexpectedResponseFormatException + */ + public function requestAccessToken() + { + $this->log('requestAccessToken()'); + + $parameters = [ + 'client_id' => $this->_client_id, + 'client_secret' => $this->_client_secret, + 'grant_type' => 'client_credentials', + ]; + + //Make the request + $params = $this->makeRequest($this->_access_token_url, $parameters, 'POST'); + + //Add the token to session + if (is_array($params)) { + if (isset($params['access_token']) && isset($params['expires_in'])) { + $this->log('access token set as '.$params['access_token']); + + $this->_access_token = $params['access_token']; + $this->_expires = time() + $params['expires_in']; + $this->_token_type = (isset($params['token_type'])) ? $params['token_type'] : null; + $this->_access_token_updated = true; + + if ($this->_debug) { + $_SESSION['oauth']['debug']['tokens']['access_token'] = $params['access_token']; + $_SESSION['oauth']['debug']['tokens']['expires_in'] = $params['expires_in']; + $_SESSION['oauth']['debug']['tokens']['token_type'] = $params['token_type']; + } + + return true; + } + } + + $this->log('response did not have an access token'); + + if ($this->_debug) { + $_SESSION['oauth']['debug']['response'] = $params; + } + + if (is_array($params)) { + if (isset($params['errors'])) { + $errors = []; + foreach ($params['errors'] as $error) { + $errors[] = $error['message']; + } + $response = implode('; ', $errors); + } else { + $response = print_r($params, true); + } + } else { + $response = $params; + } + + throw new IncorrectParametersReturnedException('Incorrect access token parameters returned: '.$response); + } +}