Skip to content

Commit

Permalink
Add support for client credentials grant
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-vanpraet committed Apr 7, 2022
1 parent 7478f55 commit c445d71
Show file tree
Hide file tree
Showing 2 changed files with 320 additions and 0 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php

// Bootup the Composer autoloader
include __DIR__ . '/vendor/autoload.php';

use Mautic\Auth\ApiAuth;

session_start();

$publicKey = '';
$secretKey = '';
$callback = '';

// ApiAuth->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.

Expand Down
270 changes: 270 additions & 0 deletions lib/Auth/TwoLeggedOAuth2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
<?php

namespace Mautic\Auth;

use Mautic\Exception\IncorrectParametersReturnedException;
use Mautic\Exception\RequiredParameterMissingException;

/**
* OAuth Client modified from https://code.google.com/p/simple-php-oauth/.
*/
class TwoLeggedOAuth2 extends AbstractAuth
{
/**
* Access token URL.
*
* @var string
*/
protected $_access_token_url;

/**
* Access token returned by OAuth server.
*
* @var string
*/
protected $_access_token;

/**
* Consumer or client key.
*
* @var string
*/
protected $_client_id;

/**
* Consumer or client secret.
*
* @var string
*/
protected $_client_secret;

/**
* Unix timestamp for when token expires.
*
* @var string
*/
protected $_expires;

/**
* OAuth2 token type.
*
* @var string
*/
protected $_token_type = 'bearer';

/**
* Set to true if the access token was updated.
*
* @var bool
*/
protected $_access_token_updated = false;

/**
* @param string $baseUrl URL of the Mautic instance
* @param string $clientKey
* @param string $clientSecret
* @param string $accessToken
* @param string $accessTokenExpires
*/
public function setup(

Check warning on line 69 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L69

Added line #L69 was not covered by tests
$baseUrl = null,
$clientKey = null,
$clientSecret = null,
$accessToken = null,
$accessTokenExpires = null
) {
if (empty($clientKey) || empty($clientSecret)) {

Check warning on line 76 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L76

Added line #L76 was not covered by tests
//Throw exception if the required parameters were not found
$this->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!');

Check warning on line 79 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L78-L79

Added lines #L78 - L79 were not covered by tests
}

if (empty($baseUrl)) {

Check warning on line 82 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L82

Added line #L82 was not covered by tests
//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!');

Check warning on line 85 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L84-L85

Added lines #L84 - L85 were not covered by tests
}

$this->_client_id = $clientKey;
$this->_client_secret = $clientSecret;
$this->_access_token = $accessToken;
$this->_access_token_url = $baseUrl.'/oauth/v2/token';

Check warning on line 91 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L88-L91

Added lines #L88 - L91 were not covered by tests

if (!empty($accessToken)) {
$this->setAccessTokenDetails([

Check warning on line 94 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L93-L94

Added lines #L93 - L94 were not covered by tests
'access_token' => $accessToken,
'expires' => $accessTokenExpires,
]);
}
}

/**
* Check to see if the access token was updated.
*
* @return bool
*/
public function accessTokenUpdated()

Check warning on line 106 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L106

Added line #L106 was not covered by tests
{
return $this->_access_token_updated;

Check warning on line 108 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L108

Added line #L108 was not covered by tests
}

/**
* Returns access token data.
*
* @return array
*/
public function getAccessTokenData()

Check warning on line 116 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L116

Added line #L116 was not covered by tests
{
return [

Check warning on line 118 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L118

Added line #L118 was not covered by tests
'access_token' => $this->_access_token,
'expires' => $this->_expires,
'token_type' => $this->_token_type,
];
}

/**
* {@inheritdoc}
*/
public function isAuthorized()

Check warning on line 128 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L128

Added line #L128 was not covered by tests
{
$this->log('isAuthorized()');

Check warning on line 130 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L130

Added line #L130 was not covered by tests

return $this->validateAccessToken();

Check warning on line 132 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L132

Added line #L132 was not covered by tests
}

/**
* Set an existing/already retrieved access token.
*
* @return $this
*/
public function setAccessTokenDetails(array $accessTokenDetails)

Check warning on line 140 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L140

Added line #L140 was not covered by tests
{
$this->_access_token = $accessTokenDetails['access_token'] ?? null;
$this->_expires = $accessTokenDetails['expires'] ?? null;

Check warning on line 143 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L142-L143

Added lines #L142 - L143 were not covered by tests

return $this;

Check warning on line 145 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L145

Added line #L145 was not covered by tests
}

/**
* Validate existing access token.
*
* @return bool
*/
public function validateAccessToken()

Check warning on line 153 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L153

Added line #L153 was not covered by tests
{
$this->log('validateAccessToken()');

Check warning on line 155 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L155

Added line #L155 was not covered by tests

//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');

Check warning on line 159 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L158-L159

Added lines #L158 - L159 were not covered by tests

return false;

Check warning on line 161 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L161

Added line #L161 was not covered by tests
}

//Check for existing access token
if (strlen($this->_access_token) > 0) {
$this->log('has valid access token');

Check warning on line 166 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L165-L166

Added lines #L165 - L166 were not covered by tests

return true;

Check warning on line 168 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L168

Added line #L168 was not covered by tests
}

//If there is no existing access token, it can't be valid
return false;

Check warning on line 172 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L172

Added line #L172 was not covered by tests
}

/**
* @param $isPost
* @param $parameters
*
* @return array
*/
protected function getQueryParameters($isPost, $parameters)

Check warning on line 181 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L181

Added line #L181 was not covered by tests
{
$query = parent::getQueryParameters($isPost, $parameters);

Check warning on line 183 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L183

Added line #L183 was not covered by tests

if (isset($parameters['file'])) {

Check warning on line 185 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L185

Added line #L185 was not covered by tests
//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'];

Check warning on line 187 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L187

Added line #L187 was not covered by tests
}

return $query;

Check warning on line 190 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L190

Added line #L190 was not covered by tests
}

/**
* @param $url
* @param array $method
*
* @return array
*/
protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings)

Check warning on line 199 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L199

Added line #L199 was not covered by tests
{
if ($this->isAuthorized()) {
$headers = array_merge($headers, ['Authorization: Bearer '.$this->_access_token]);

Check warning on line 202 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L201-L202

Added lines #L201 - L202 were not covered by tests
}

return [$headers, $parameters];

Check warning on line 205 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L205

Added line #L205 was not covered by tests
}

/**
* Request access token.
*
* @return bool
*
* @throws IncorrectParametersReturnedException|\Mautic\Exception\UnexpectedResponseFormatException
*/
public function requestAccessToken()

Check warning on line 215 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L215

Added line #L215 was not covered by tests
{
$this->log('requestAccessToken()');

Check warning on line 217 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L217

Added line #L217 was not covered by tests

$parameters = [

Check warning on line 219 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L219

Added line #L219 was not covered by tests
'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');

Check warning on line 226 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L226

Added line #L226 was not covered by tests

//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']);

Check warning on line 231 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L229-L231

Added lines #L229 - L231 were not covered by tests

$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;

Check warning on line 236 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L233-L236

Added lines #L233 - L236 were not covered by tests

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'];

Check warning on line 241 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L238-L241

Added lines #L238 - L241 were not covered by tests
}

return true;

Check warning on line 244 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L244

Added line #L244 was not covered by tests
}
}

$this->log('response did not have an access token');

Check warning on line 248 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L248

Added line #L248 was not covered by tests

if ($this->_debug) {
$_SESSION['oauth']['debug']['response'] = $params;

Check warning on line 251 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L250-L251

Added lines #L250 - L251 were not covered by tests
}

if (is_array($params)) {
if (isset($params['errors'])) {
$errors = [];
foreach ($params['errors'] as $error) {
$errors[] = $error['message'];

Check warning on line 258 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L254-L258

Added lines #L254 - L258 were not covered by tests
}
$response = implode('; ', $errors);
} else {
$response = print_r($params, true);

Check warning on line 262 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L260-L262

Added lines #L260 - L262 were not covered by tests
}
} else {
$response = $params;

Check warning on line 265 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L264-L265

Added lines #L264 - L265 were not covered by tests
}

throw new IncorrectParametersReturnedException('Incorrect access token parameters returned: '.$response);

Check warning on line 268 in lib/Auth/TwoLeggedOAuth2.php

View check run for this annotation

Codecov / codecov/patch

lib/Auth/TwoLeggedOAuth2.php#L268

Added line #L268 was not covered by tests
}
}

0 comments on commit c445d71

Please sign in to comment.