first commit
This commit is contained in:
21
plugins/api-authentication/basic/basic.xml
Normal file
21
plugins/api-authentication/basic/basic.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="api-authentication" method="upgrade">
|
||||
<name>plg_api-authentication_basic</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2005-11</creationDate>
|
||||
<copyright>(C) 2019 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<version>4.0.0</version>
|
||||
<description>PLG_API-AUTHENTICATION_BASIC_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\ApiAuthentication\Basic</namespace>
|
||||
<files>
|
||||
<folder plugin="basic">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_basic.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_basic.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
50
plugins/api-authentication/basic/services/provider.php
Normal file
50
plugins/api-authentication/basic/services/provider.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.basic
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\ApiAuthentication\Basic\Extension\Basic;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = new Basic(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('api-authentication', 'basic'),
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
101
plugins/api-authentication/basic/src/Extension/Basic.php
Normal file
101
plugins/api-authentication/basic/src/Extension/Basic.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.basic
|
||||
*
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\ApiAuthentication\Basic\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\CMS\User\UserHelper;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Authentication plugin
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Basic extends CMSPlugin
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param array $credentials Array holding the user credentials
|
||||
* @param array $options Array of extra options
|
||||
* @param object &$response Authentication response object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onUserAuthenticate($credentials, $options, &$response)
|
||||
{
|
||||
$response->type = 'Basic';
|
||||
|
||||
$username = $this->getApplication()->getInput()->server->get('PHP_AUTH_USER', '', 'USERNAME');
|
||||
$password = $this->getApplication()->getInput()->server->get('PHP_AUTH_PW', '', 'RAW');
|
||||
|
||||
if ($password === '') {
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_EMPTY_PASS_NOT_ALLOWED');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['id', 'password']))
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('username') . ' = :username')
|
||||
->bind(':username', $username);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if ($result) {
|
||||
$match = UserHelper::verifyPassword($password, $result->password, $result->id);
|
||||
|
||||
if ($match === true) {
|
||||
// Bring this in line with the rest of the system
|
||||
$user = $this->getUserFactory()->loadUserById($result->id);
|
||||
$response->email = $user->email;
|
||||
$response->fullname = $user->name;
|
||||
$response->username = $username;
|
||||
|
||||
if ($this->getApplication()->isClient('administrator')) {
|
||||
$response->language = $user->getParam('admin_language');
|
||||
} else {
|
||||
$response->language = $user->getParam('language');
|
||||
}
|
||||
|
||||
$response->status = Authentication::STATUS_SUCCESS;
|
||||
$response->error_message = '';
|
||||
} else {
|
||||
// Invalid password
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_INVALID_PASS');
|
||||
}
|
||||
} else {
|
||||
// Let's hash the entered password even if we don't have a matching user for some extra response time
|
||||
// By doing so, we mitigate side channel user enumeration attacks
|
||||
UserHelper::hashPassword($password);
|
||||
|
||||
// Invalid user
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_NO_USER');
|
||||
}
|
||||
}
|
||||
}
|
||||
52
plugins/api-authentication/token/services/provider.php
Normal file
52
plugins/api-authentication/token/services/provider.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.token
|
||||
*
|
||||
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Filter\InputFilter;
|
||||
use Joomla\Plugin\ApiAuthentication\Token\Extension\Token;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = new Token(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('api-authentication', 'token'),
|
||||
new InputFilter()
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
$plugin->setUserFactory($container->get(UserFactoryInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
396
plugins/api-authentication/token/src/Extension/Token.php
Normal file
396
plugins/api-authentication/token/src/Extension/Token.php
Normal file
@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Apiauthentication.token
|
||||
*
|
||||
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\ApiAuthentication\Token\Extension;
|
||||
|
||||
use Joomla\CMS\Authentication\Authentication;
|
||||
use Joomla\CMS\Crypt\Crypt;
|
||||
use Joomla\CMS\Event\User\AuthenticationEvent;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\User\UserFactoryAwareTrait;
|
||||
use Joomla\Component\Plugins\Administrator\Model\PluginModel;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
use Joomla\Filter\InputFilter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla Token Authentication plugin
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Token extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
use UserFactoryAwareTrait;
|
||||
|
||||
/**
|
||||
* The prefix of the user profile keys, without the dot.
|
||||
*
|
||||
* @var string
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $profileKeyPrefix = 'joomlatoken';
|
||||
|
||||
/**
|
||||
* Allowed HMAC algorithms for the token
|
||||
*
|
||||
* @var string[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $allowedAlgos = ['sha256', 'sha512'];
|
||||
|
||||
/**
|
||||
* The input filter
|
||||
*
|
||||
* @var InputFilter
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onUserAuthenticate' => 'onUserAuthenticate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DispatcherInterface $dispatcher The dispatcher
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param InputFilter $filter The input filter
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function __construct(DispatcherInterface $dispatcher, array $config, InputFilter $filter)
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should handle any authentication and report back to the subject
|
||||
*
|
||||
* @param AuthenticationEvent $event Authentication event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onUserAuthenticate(AuthenticationEvent $event): void
|
||||
{
|
||||
$response = $event->getAuthenticationResponse();
|
||||
|
||||
// Default response is authentication failure.
|
||||
$response->type = 'Token';
|
||||
$response->status = Authentication::STATUS_FAILURE;
|
||||
$response->error_message = $this->getApplication()->getLanguage()->_('JGLOBAL_AUTH_FAIL');
|
||||
|
||||
/**
|
||||
* First look for an HTTP Authorization header with the following format:
|
||||
* Authorization: Bearer <token>
|
||||
* Do keep in mind that Bearer is **case-sensitive**. Whitespace between Bearer and the
|
||||
* token, as well as any whitespace following the token is discarded.
|
||||
*/
|
||||
$authHeader = $this->getApplication()->getInput()->server->get('HTTP_AUTHORIZATION', '', 'string');
|
||||
$tokenString = '';
|
||||
|
||||
// Apache specific fixes. See https://github.com/symfony/symfony/issues/19693
|
||||
if (
|
||||
empty($authHeader) && \PHP_SAPI === 'apache2handler'
|
||||
&& \function_exists('apache_request_headers') && apache_request_headers() !== false
|
||||
) {
|
||||
$apacheHeaders = array_change_key_case(apache_request_headers(), CASE_LOWER);
|
||||
|
||||
if (\array_key_exists('authorization', $apacheHeaders)) {
|
||||
$authHeader = $this->filter->clean($apacheHeaders['authorization'], 'STRING');
|
||||
}
|
||||
}
|
||||
|
||||
if (substr($authHeader, 0, 7) == 'Bearer ') {
|
||||
$parts = explode(' ', $authHeader, 2);
|
||||
$tokenString = trim($parts[1]);
|
||||
$tokenString = $this->filter->clean($tokenString, 'BASE64');
|
||||
}
|
||||
|
||||
if (empty($tokenString)) {
|
||||
$tokenString = $this->getApplication()->getInput()->server->get('HTTP_X_JOOMLA_TOKEN', '', 'string');
|
||||
}
|
||||
|
||||
// No token: authentication failure
|
||||
if (empty($tokenString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The token is a base64 encoded string. Make sure we can decode it.
|
||||
$authString = @base64_decode($tokenString);
|
||||
|
||||
if (empty($authString) || (strpos($authString, ':') === false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstruct the decoded token string to its three discrete parts: algorithm, user ID and
|
||||
* HMAC of the token string saved in the database.
|
||||
*/
|
||||
$parts = explode(':', $authString, 3);
|
||||
|
||||
if (\count($parts) != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($algo, $userId, $tokenHMAC) = $parts;
|
||||
|
||||
/**
|
||||
* Verify the HMAC algorithm requested in the token string is allowed
|
||||
*/
|
||||
$allowedAlgo = \in_array($algo, $this->allowedAlgos);
|
||||
|
||||
/**
|
||||
* Make sure the user ID is an integer
|
||||
*/
|
||||
$userId = (int) $userId;
|
||||
|
||||
/**
|
||||
* Calculate the reference token data HMAC
|
||||
*/
|
||||
try {
|
||||
$siteSecret = $this->getApplication()->get('secret');
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// An empty secret! What kind of monster are you?!
|
||||
if (empty($siteSecret)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$referenceTokenData = $this->getTokenSeedForUser($userId);
|
||||
$referenceTokenData = empty($referenceTokenData) ? '' : $referenceTokenData;
|
||||
$referenceTokenData = base64_decode($referenceTokenData);
|
||||
$referenceHMAC = hash_hmac($algo, $referenceTokenData, $siteSecret);
|
||||
|
||||
// Is the token enabled?
|
||||
$enabled = $this->isTokenEnabledForUser($userId);
|
||||
|
||||
// Do the tokens match? Use a timing safe string comparison to prevent timing attacks.
|
||||
$hashesMatch = Crypt::timingSafeCompare($referenceHMAC, $tokenHMAC);
|
||||
|
||||
// Is the user in the allowed user groups?
|
||||
$inAllowedUserGroups = $this->isInAllowedUserGroup($userId);
|
||||
|
||||
/**
|
||||
* Can we log in?
|
||||
*
|
||||
* DO NOT concatenate in a single line. Due to boolean short-circuit evaluation it might
|
||||
* make timing attacks possible. Using separate lines of code with the previously calculated
|
||||
* boolean value to the right hand side forces PHP to evaluate the conditions in
|
||||
* approximately constant time.
|
||||
*/
|
||||
|
||||
// We need non-empty reference token data (the user must have configured a token)
|
||||
$canLogin = !empty($referenceTokenData);
|
||||
|
||||
// The token must be enabled
|
||||
$canLogin = $enabled && $canLogin;
|
||||
|
||||
// The token hash must be calculated with an allowed algorithm
|
||||
$canLogin = $allowedAlgo && $canLogin;
|
||||
|
||||
// The token HMAC hash coming into the request and our reference must match.
|
||||
$canLogin = $hashesMatch && $canLogin;
|
||||
|
||||
// The user must belong in the allowed user groups
|
||||
$canLogin = $inAllowedUserGroups && $canLogin;
|
||||
|
||||
/**
|
||||
* DO NOT try to be smart and do an early return when either of the individual conditions
|
||||
* are not met. There's a reason we only return after checking all three conditions: it
|
||||
* prevents timing attacks.
|
||||
*/
|
||||
if (!$canLogin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the actual user record
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
// Disallow login for blocked, inactive or password reset required users
|
||||
if ($user->block || !empty(trim($user->activation)) || $user->requireReset) {
|
||||
$response->status = Authentication::STATUS_DENIED;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the response to indicate successful login
|
||||
$response->status = Authentication::STATUS_SUCCESS;
|
||||
$response->error_message = '';
|
||||
$response->username = $user->username;
|
||||
$response->email = $user->email;
|
||||
$response->fullname = $user->name;
|
||||
$response->timezone = $user->get('timezone');
|
||||
$response->language = $user->get('language');
|
||||
|
||||
// Stop event propagation when status is STATUS_SUCCESS
|
||||
$event->stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the token seed string for the given user ID.
|
||||
*
|
||||
* @param int $userId The numeric user ID to return the token seed string for.
|
||||
*
|
||||
* @return string|null Null if there is no token configured or the user doesn't exist.
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getTokenSeedForUser(int $userId): ?string
|
||||
{
|
||||
try {
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('profile_value'))
|
||||
->from($db->quoteName('#__user_profiles'))
|
||||
->where($db->quoteName('profile_key') . ' = :profileKey')
|
||||
->where($db->quoteName('user_id') . ' = :userId');
|
||||
|
||||
$profileKey = $this->profileKeyPrefix . '.token';
|
||||
$query->bind(':profileKey', $profileKey, ParameterType::STRING);
|
||||
$query->bind(':userId', $userId, ParameterType::INTEGER);
|
||||
|
||||
return $db->setQuery($query)->loadResult();
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the token enabled for a given user ID? If the user does not exist or has no token it
|
||||
* returns false.
|
||||
*
|
||||
* @param int $userId The User ID to check whether the token is enabled on their account.
|
||||
*
|
||||
* @return boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function isTokenEnabledForUser(int $userId): bool
|
||||
{
|
||||
try {
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('profile_value'))
|
||||
->from($db->quoteName('#__user_profiles'))
|
||||
->where($db->quoteName('profile_key') . ' = :profileKey')
|
||||
->where($db->quoteName('user_id') . ' = :userId');
|
||||
|
||||
$profileKey = $this->profileKeyPrefix . '.enabled';
|
||||
$query->bind(':profileKey', $profileKey, ParameterType::STRING);
|
||||
$query->bind(':userId', $userId, ParameterType::INTEGER);
|
||||
|
||||
$value = $db->setQuery($query)->loadResult();
|
||||
|
||||
return $value == 1;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a configuration parameter of a different plugin than the current one.
|
||||
*
|
||||
* @param string $folder Plugin folder
|
||||
* @param string $plugin Plugin name
|
||||
* @param string $param Parameter name
|
||||
* @param null $default Default value, in case the parameter is missing
|
||||
*
|
||||
* @return mixed
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getPluginParameter(string $folder, string $plugin, string $param, $default = null)
|
||||
{
|
||||
/** @var PluginModel $model */
|
||||
$model = $this->getApplication()->bootComponent('plugins')
|
||||
->getMVCFactory()->createModel('Plugin', 'Administrator', ['ignore_request' => true]);
|
||||
|
||||
$pluginObject = $model->getItem(['folder' => $folder, 'element' => $plugin]);
|
||||
|
||||
if (!\is_object($pluginObject) || !$pluginObject->enabled || !\array_key_exists($param, $pluginObject->params)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $pluginObject->params[$param];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured user groups which are allowed to have access to tokens.
|
||||
*
|
||||
* @return int[]
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getAllowedUserGroups(): array
|
||||
{
|
||||
$userGroups = $this->getPluginParameter('user', 'token', 'allowedUserGroups', [8]);
|
||||
|
||||
if (empty($userGroups)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!\is_array($userGroups)) {
|
||||
$userGroups = [$userGroups];
|
||||
}
|
||||
|
||||
return $userGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user with the given ID in the allowed User Groups with access to tokens?
|
||||
*
|
||||
* @param int $userId The user ID to check
|
||||
*
|
||||
* @return boolean False when doesn't belong to allowed user groups, user not found, or guest
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function isInAllowedUserGroup($userId)
|
||||
{
|
||||
$allowedUserGroups = $this->getAllowedUserGroups();
|
||||
|
||||
$user = $this->getUserFactory()->loadUserById($userId);
|
||||
|
||||
if ($user->id != $userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->guest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No specifically allowed user groups: allow ALL user groups.
|
||||
if (empty($allowedUserGroups)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$groups = $user->getAuthorisedGroups();
|
||||
$intersection = array_intersect($groups, $allowedUserGroups);
|
||||
|
||||
return !empty($intersection);
|
||||
}
|
||||
}
|
||||
21
plugins/api-authentication/token/token.xml
Normal file
21
plugins/api-authentication/token/token.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="api-authentication" method="upgrade">
|
||||
<name>plg_api-authentication_token</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2019-11</creationDate>
|
||||
<copyright>(C) 2020 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<version>4.0.0</version>
|
||||
<description>PLG_API-AUTHENTICATION_TOKEN_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\ApiAuthentication\Token</namespace>
|
||||
<files>
|
||||
<folder plugin="token">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_token.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_api-authentication_token.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
Reference in New Issue
Block a user