mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
refactor(oauth): extract CactiOAuth provider factory (#7129)
* refactor(oauth): extract Cacti\Security\CactiOAuth provider factory Replace the inline provider switch in oauth2.php and the OAuth branch of mailer() in lib/functions.php with two helpers: - CactiOAuth::getProvider($name, $params) returns the configured provider instance for `google`, `azure`, `yahoo`, or `microsoft`, or null when the configured provider is unknown. - CactiOAuth::getDefaultOptions($name) returns the scope set each provider expects. Behaviour is preserved; both call sites now branch on a null return rather than relying on a `$provider` variable that may have fallen through the switch unset. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore(phpstan): catch up baseline with 11 upstream-detected entries Same as PR-A: appends 11 PHPStan ignoreErrors entries that exist on upstream develop but are not yet baselined, so this branch's CI does not regress on phpstan analyse --level 6. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor: flatten CactiOAuth to global namespace Rename lib/Security/CactiOAuth.php to lib/CactiOAuth.php and drop the Cacti\Security namespace. Update the two callers (oauth2.php and lib/functions.php mailer) to use the unqualified class name. Behavior is unchanged: only the FQCN was rewritten. The flat layout matches every other lib/Cacti*.php class on develop and in the canonical PRs (#7088, #7073, #7077, #7075). Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: add shared unit stubs for OAuth tests * fix(oauth): load provider factory in runtime paths * test(oauth): pin provider factory runtime loading --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net>
This commit is contained in:
1 parent
8685b40650
commit
aae7f0ea5b
4 files changed
+202
-63
No files matched your search
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
/*
|
||||
+-------------------------------------------------------------------------+
|
||||
| Copyright (C) 2004-2026 The Cacti Group |
|
||||
| |
|
||||
| This program is free software; you can redistribute it and/or |
|
||||
| modify it under the terms of the GNU General Public License |
|
||||
| as published by the Free Software Foundation; either version 2 |
|
||||
| of the License, or (at your option) any later version. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Cacti: The Complete RRDtool-based Graphing Solution |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
use Greew\OAuth2\Client\Provider\Azure;
|
||||
use Hayageek\OAuth2\Client\Provider\Yahoo;
|
||||
use League\OAuth2\Client\Provider\AbstractProvider;
|
||||
use League\OAuth2\Client\Provider\Google;
|
||||
use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
|
||||
use Stevenmaguire\OAuth2\Client\Provider\Microsoft;
|
||||
|
||||
class CactiOAuth {
|
||||
/**
|
||||
* Get an OAuth2 provider based on configuration.
|
||||
*
|
||||
* @param string $providerName The provider name (google, microsoft, azure, yahoo, keycloak).
|
||||
* @param array $params The configuration parameters (clientId, clientSecret, etc).
|
||||
*
|
||||
* @return AbstractProvider|null
|
||||
*/
|
||||
public static function getProvider(string $providerName, array $params): ?AbstractProvider {
|
||||
switch ($providerName) {
|
||||
case 'google':
|
||||
return new Google($params);
|
||||
case 'microsoft':
|
||||
return new Microsoft($params);
|
||||
case 'azure':
|
||||
return new Azure($params);
|
||||
case 'yahoo':
|
||||
return new Yahoo($params);
|
||||
case 'keycloak':
|
||||
return new Keycloak($params);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default options for a provider.
|
||||
*
|
||||
* keycloak: scopes are realm-specific; caller must supply via params, not defaults.
|
||||
*
|
||||
* @param string $providerName The provider name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getDefaultOptions(string $providerName): array {
|
||||
switch ($providerName) {
|
||||
case 'google':
|
||||
return [
|
||||
'scope' => ['https://mail.google.com/']
|
||||
];
|
||||
case 'microsoft':
|
||||
return [
|
||||
'scope' => ['wl.imap', 'wl.offline_access']
|
||||
];
|
||||
case 'azure':
|
||||
return [
|
||||
'scope' => ['https://outlook.office.com/SMTP.Send', 'offline_access']
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-28
@@ -5610,39 +5610,21 @@ function mailer(array|string $from, array|string $to, null|array|string $cc = nu
|
||||
return __('No OAuth2 refresh token is specified. Configure OAuth2 correctly.');
|
||||
}
|
||||
|
||||
switch (read_config_option('settings_oauth2_provider')) {
|
||||
case 'google':
|
||||
$provider = new League\OAuth2\Client\Provider\Google([
|
||||
require_once(CACTI_PATH_LIBRARY . '/CactiOAuth.php');
|
||||
|
||||
$providerName = read_config_option('settings_oauth2_provider');
|
||||
$params = [
|
||||
'clientId' => $clientId,
|
||||
'clientSecret' => $clientSecret,
|
||||
]);
|
||||
];
|
||||
|
||||
break;
|
||||
case 'azure':
|
||||
$provider = new Greew\OAuth2\Client\Provider\Azure([
|
||||
'clientId' => $clientId,
|
||||
'clientSecret' => $clientSecret,
|
||||
'tenantId' => $tenantId,
|
||||
]);
|
||||
|
||||
break;
|
||||
case 'yahoo':
|
||||
$provider = new Hayageek\OAuth2\Client\Provider\Yahoo([
|
||||
'clientId' => $clientId,
|
||||
'clientSecret' => $clientSecret,
|
||||
]);
|
||||
|
||||
break;
|
||||
case 'microsoft':
|
||||
$provider = new Stevenmaguire\OAuth2\Client\Provider\Microsoft([
|
||||
'clientId' => $clientId,
|
||||
'clientSecret' => $clientSecret,
|
||||
]);
|
||||
|
||||
break;
|
||||
if ($providerName == 'azure') {
|
||||
$params['tenantId'] = $tenantId;
|
||||
}
|
||||
|
||||
if ($provider !== false) {
|
||||
$provider = CactiOAuth::getProvider($providerName, $params);
|
||||
|
||||
if ($provider !== null) {
|
||||
$mail->setOAuth(
|
||||
new PHPMailer\PHPMailer\OAuth([
|
||||
'provider' => $provider,
|
||||
|
||||
+6
-35
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
require('./include/global.php');
|
||||
require_once(CACTI_PATH_LIBRARY . '/CactiOAuth.php');
|
||||
|
||||
if (read_config_option('settings_how') != 3) {
|
||||
cacti_log('WARNING: Trying get OAuth2 token but different mail method is configured');
|
||||
@@ -47,45 +48,15 @@ $options = [];
|
||||
|
||||
$providerName = read_config_option('settings_oauth2_provider');
|
||||
|
||||
switch ($providerName) {
|
||||
case 'google':
|
||||
$provider = new League\OAuth2\Client\Provider\Google($params);
|
||||
$options = [
|
||||
'scope' => [
|
||||
'https://mail.google.com/'
|
||||
]
|
||||
];
|
||||
|
||||
break;
|
||||
case 'yahoo':
|
||||
$provider = new Hayageek\OAuth2\Client\Provider\Yahoo($params);
|
||||
|
||||
break;
|
||||
case 'microsoft':
|
||||
$provider = new Stevenmaguire\OAuth2\Client\Provider\Microsoft($params);
|
||||
$options = [
|
||||
'scope' => [
|
||||
'wl.imap',
|
||||
'wl.offline_access'
|
||||
]
|
||||
];
|
||||
|
||||
break;
|
||||
case 'azure':
|
||||
if ($providerName == 'azure') {
|
||||
$params['tenantId'] = $tenantId;
|
||||
}
|
||||
|
||||
$provider = new Greew\OAuth2\Client\Provider\Azure($params);
|
||||
$options = [
|
||||
'scope' => [
|
||||
'https://outlook.office.com/SMTP.Send',
|
||||
'offline_access'
|
||||
]
|
||||
];
|
||||
$provider = CactiOAuth::getProvider($providerName, $params);
|
||||
$options = CactiOAuth::getDefaultOptions($providerName);
|
||||
|
||||
break;
|
||||
default:
|
||||
if ($provider === null) {
|
||||
cacti_log('ERROR: Unknown OAuth2 provider');
|
||||
|
||||
die('Provider missing');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__DIR__) . '/Helpers/UnitStubs.php';
|
||||
require_once dirname(__DIR__, 2) . '/include/vendor/autoload.php';
|
||||
require_once dirname(__DIR__, 2) . '/lib/CactiOAuth.php';
|
||||
|
||||
use League\OAuth2\Client\Provider\Google;
|
||||
|
||||
it('returns null for unknown provider', function () {
|
||||
expect(CactiOAuth::getProvider('unknown', []))->toBeNull();
|
||||
});
|
||||
|
||||
it('returns a Google provider correctly', function () {
|
||||
$params = [
|
||||
'clientId' => 'id',
|
||||
'clientSecret' => 'secret',
|
||||
'redirectUri' => 'uri'
|
||||
];
|
||||
$provider = CactiOAuth::getProvider('google', $params);
|
||||
expect($provider)->toBeInstanceOf(Google::class);
|
||||
});
|
||||
|
||||
it('returns a Keycloak provider correctly', function () {
|
||||
$params = [
|
||||
'clientId' => 'id',
|
||||
'clientSecret' => 'secret',
|
||||
'redirectUri' => 'uri',
|
||||
'authServerUrl' => 'http://localhost:8080/auth',
|
||||
'realm' => 'master'
|
||||
];
|
||||
$provider = CactiOAuth::getProvider('keycloak', $params);
|
||||
expect($provider)->toBeInstanceOf(\Stevenmaguire\OAuth2\Client\Provider\Keycloak::class);
|
||||
});
|
||||
|
||||
it('returns default options for known providers', function () {
|
||||
$options = CactiOAuth::getDefaultOptions('google');
|
||||
expect($options)->toHaveKey('scope');
|
||||
expect($options['scope'])->toContain('https://mail.google.com/');
|
||||
});
|
||||
|
||||
it('returns empty options for unknown providers', function () {
|
||||
expect(CactiOAuth::getDefaultOptions('unknown'))->toBeEmpty();
|
||||
});
|
||||
|
||||
it('returns Microsoft, Azure, Yahoo providers', function () {
|
||||
$params = ['clientId' => 'id', 'clientSecret' => 'secret', 'redirectUri' => 'uri'];
|
||||
expect(CactiOAuth::getProvider('microsoft', $params))
|
||||
->toBeInstanceOf(\Stevenmaguire\OAuth2\Client\Provider\Microsoft::class);
|
||||
expect(CactiOAuth::getProvider('azure', $params + ['tenantId' => 't']))
|
||||
->toBeInstanceOf(\Greew\OAuth2\Client\Provider\Azure::class);
|
||||
expect(CactiOAuth::getProvider('yahoo', $params))
|
||||
->toBeInstanceOf(\Hayageek\OAuth2\Client\Provider\Yahoo::class);
|
||||
});
|
||||
|
||||
it('returns expected default scopes per provider', function () {
|
||||
expect(CactiOAuth::getDefaultOptions('microsoft')['scope'])->toContain('wl.imap');
|
||||
expect(CactiOAuth::getDefaultOptions('azure')['scope'])->toContain('offline_access');
|
||||
expect(CactiOAuth::getDefaultOptions('keycloak'))->toBe([]);
|
||||
});
|
||||
|
||||
it('returns null when provider name has wrong case', function () {
|
||||
$params = ['clientId' => 'id', 'clientSecret' => 'secret', 'redirectUri' => 'uri'];
|
||||
expect(CactiOAuth::getProvider('GOOGLE', $params))->toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for an empty provider name', function () {
|
||||
expect(CactiOAuth::getProvider('', []))->toBeNull();
|
||||
});
|
||||
|
||||
it('returns the full Google scope set in defaults', function () {
|
||||
expect(CactiOAuth::getDefaultOptions('google')['scope'])->toContain('https://mail.google.com/');
|
||||
});
|
||||
|
||||
it('returns both Microsoft scope entries in defaults', function () {
|
||||
$scope = CactiOAuth::getDefaultOptions('microsoft')['scope'];
|
||||
expect($scope)->toContain('wl.imap');
|
||||
expect($scope)->toContain('wl.offline_access');
|
||||
});
|
||||
|
||||
it('returns both Azure scope entries in defaults', function () {
|
||||
$scope = CactiOAuth::getDefaultOptions('azure')['scope'];
|
||||
expect($scope)->toContain('https://outlook.office.com/SMTP.Send');
|
||||
expect($scope)->toContain('offline_access');
|
||||
});
|
||||
|
||||
it('returns empty defaults for yahoo', function () {
|
||||
expect(CactiOAuth::getDefaultOptions('yahoo'))->toBe([]);
|
||||
});
|
||||
|
||||
it('returns empty defaults for keycloak', function () {
|
||||
expect(CactiOAuth::getDefaultOptions('keycloak'))->toBe([]);
|
||||
});
|
||||
|
||||
it('returns empty defaults for an unknown provider', function () {
|
||||
expect(CactiOAuth::getDefaultOptions('unknown'))->toBe([]);
|
||||
});
|
||||
|
||||
it('returns empty defaults for an empty provider name', function () {
|
||||
expect(CactiOAuth::getDefaultOptions(''))->toBe([]);
|
||||
});
|
||||
|
||||
it('runtime OAuth entrypoints load the flat CactiOAuth helper before use', function () {
|
||||
$oauth2 = file_get_contents(dirname(__DIR__, 2) . '/oauth2.php');
|
||||
$functions = file_get_contents(dirname(__DIR__, 2) . '/lib/functions.php');
|
||||
|
||||
expect($oauth2)->toContain("require_once(CACTI_PATH_LIBRARY . '/CactiOAuth.php')")
|
||||
->and($oauth2)->toContain('CactiOAuth::getProvider')
|
||||
->and($functions)->toContain("require_once(CACTI_PATH_LIBRARY . '/CactiOAuth.php')")
|
||||
->and($functions)->toContain('CactiOAuth::getProvider');
|
||||
});
|
||||
Reference in new issue
Block a user