Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for client credentials grant #269

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
Loading