diff --git a/README.md b/README.md index 6eabaa1..c07bbd7 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,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 0000000..8b1a398 --- /dev/null +++ b/lib/Auth/TwoLeggedOAuth2.php @@ -0,0 +1,241 @@ +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. + */ + public function getAccessTokenData(): array + { + return [ + 'access_token' => $this->_access_token, + 'expires' => $this->_expires, + 'token_type' => $this->_token_type, + ]; + } + + public function isAuthorized(): bool + { + $this->log('isAuthorized()'); + + return $this->validateAccessToken(); + } + + /** + * Set an existing/already retrieved access token. + * + * @return $this + */ + public function setAccessTokenDetails(array $accessTokenDetails): static + { + $this->_access_token = $accessTokenDetails['access_token'] ?? null; + $this->_expires = isset($accessTokenDetails['expires']) ? (int) $accessTokenDetails['expires'] : null; + + return $this; + } + + /** + * Validate existing access token. + */ + public function validateAccessToken(): bool + { + $this->log('validateAccessToken()'); + + // Check to see if token in session has expired (or will in a few seconds) + if (!empty($this->_access_token) && !empty($this->_expires) && $this->_expires < (time() + 10)) { + $this->log('access token expired'); + + return false; + } + + // Check for existing access token + if (!empty($this->_access_token)) { + $this->log('has valid access token'); + + return true; + } + + // If there is no existing access token, it can't be valid + return false; + } + + /** + * @param bool $isPost + * @param array $parameters + */ + protected function getQueryParameters($isPost, $parameters): array + { + $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 string $url + * @param array $method + */ + protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings): array + { + if ($this->isAuthorized()) { + $headers = array_merge($headers, ['Authorization: Bearer '.$this->_access_token]); + } + + return [$headers, $parameters]; + } + + /** + * Request access token. + * + * @throws IncorrectParametersReturnedException|\Mautic\Exception\UnexpectedResponseFormatException + */ + public function requestAccessToken(): bool + { + $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() + (int) $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); + } +}