-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e8fc74f
Showing
25 changed files
with
741 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/composer.lock | ||
/vendor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
it: coding-standards dependency-analysis | ||
|
||
coding-standards: vendor | ||
composer normalize | ||
vendor/bin/phpcbf || true | ||
vendor/bin/phpcs | ||
|
||
dependency-analysis: vendor | ||
vendor/bin/composer-require-checker check --verbose | ||
|
||
vendor: composer.json composer.lock | ||
composer validate --strict | ||
composer --no-interaction install --no-progress | ||
|
||
composer.lock: | ||
test -f composer.lock || echo '{}' > composer.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Fredden_AdminAuth - fredden/magento2-module-admin-authentication | ||
|
||
## Installation | ||
This extension is hosted on packagist.org should be installed from there: | ||
|
||
* `composer require fredden/magento2-module-admin-authentication` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"symbol-whitelist": [ | ||
"array", | ||
"bool", | ||
"callable", | ||
"false", | ||
"int", | ||
"parent", | ||
"self", | ||
"string", | ||
"true", | ||
"void", | ||
"Magento\\User\\Model\\UserFactory" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "fredden/magento2-module-admin-authentication", | ||
"description": "A Magento 2 module that allows access to Magento admin via Fredden Auth Portal", | ||
"license": "CC-BY-NC-SA-4.0", | ||
"type": "magento2-module", | ||
"authors": [ | ||
{ | ||
"name": "Dan Wallis", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"homepage": "https://github.com/fredden/magento2-module-admin-authentication", | ||
"require": { | ||
"php": "~8.2.0 || ~8.3.0", | ||
"ext-json": "*", | ||
"ext-random": "*", | ||
"firebase/php-jwt": "^5.5 || ^6.0", | ||
"magento/framework": "^102.0 || ^103.0", | ||
"magento/module-backend": "^101.0 || ^102.0", | ||
"magento/module-captcha": "^100.0.2", | ||
"magento/module-config": "^101.0", | ||
"magento/module-two-factor-auth": "^1.0", | ||
"magento/module-user": "^101.0" | ||
}, | ||
"require-dev": { | ||
"bitexpert/phpstan-magento": "^0.11.0", | ||
"dealerdirect/phpcodesniffer-composer-installer": "^1.0", | ||
"maglnet/composer-require-checker": "^3.8", | ||
"phpcompatibility/php-compatibility": "^9.3", | ||
"phpstan/extension-installer": "^1.3", | ||
"phpstan/phpstan": "^1.10", | ||
"squizlabs/php_codesniffer": "^3.9" | ||
}, | ||
"repositories": { | ||
"magento": { | ||
"type": "composer", | ||
"url": "https://repo-magento-mirror.fooman.co.nz/" | ||
} | ||
}, | ||
"minimum-stability": "stable", | ||
"autoload": { | ||
"psr-4": { | ||
"Fredden\\AdminAuth\\": "src" | ||
}, | ||
"files": [ | ||
"src/registration.php" | ||
] | ||
}, | ||
"config": { | ||
"allow-plugins": { | ||
"dealerdirect/phpcodesniffer-composer-installer": true, | ||
"magento/composer-dependency-version-audit-plugin": false, | ||
"magento/magento-composer-installer": false, | ||
"phpstan/extension-installer": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0"?> | ||
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd"> | ||
<file>src</file> | ||
|
||
<arg name="basepath" value="."/> | ||
<arg name="cache"/> | ||
<arg name="colors"/> | ||
<arg name="extensions" value="php,phtml" /> | ||
<arg value="p"/> | ||
|
||
<rule ref="PSR12" /> | ||
|
||
<config name="testVersion" value="8.2-8.3"/> | ||
<rule ref="PHPCompatibility" /> | ||
</ruleset> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
<?php | ||
|
||
namespace Fredden\AdminAuth\Controller\Adminhtml\Login; | ||
|
||
use Exception; | ||
use Firebase\JWT\JWK; | ||
use Firebase\JWT\JWT; | ||
use Fredden\AdminAuth\Scope\Config; | ||
use Magento\Backend\App\Action\Context; | ||
use Magento\Backend\Controller\Adminhtml\Auth as AuthController; | ||
use Magento\Backend\Model\Auth as AuthModel; | ||
use Magento\Backend\Model\Auth\Session as AuthSession; | ||
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPost; | ||
use Magento\Framework\Controller\Result\JsonFactory; | ||
use Magento\Framework\Controller\ResultInterface; | ||
use Magento\User\Model\ResourceModel\User as UserResource; | ||
use Magento\User\Model\User; | ||
use Magento\User\Model\UserFactory; | ||
use Throwable; | ||
|
||
/** | ||
* This controller is thusly named to as a hack. 'refresh' is one of few names that | ||
* are not subject to authentication verification, which is exactly what this | ||
* controller is designed to bypass. | ||
* | ||
* @see \Magento\Backend\App\Action\Plugin\Authentication::$_openActions | ||
*/ | ||
class Refresh extends AuthController implements HttpPost | ||
{ | ||
public function __construct( | ||
Context $context, | ||
private readonly AuthModel $authModel, | ||
private readonly AuthSession $authSession, | ||
private readonly Config $config, | ||
private readonly JsonFactory $resultJsonFactory, | ||
private readonly UserFactory $userFactory, | ||
private readonly UserResource $userResource, | ||
) { | ||
parent::__construct($context); | ||
} | ||
|
||
/** | ||
* @return ResultInterface | ||
*/ | ||
public function execute() | ||
{ | ||
$token = $this->getRequest()->getPost('token'); | ||
|
||
try { | ||
$payload = $this->parseToken($token); | ||
} catch (Exception $e) { | ||
$result = $this->resultJsonFactory->create(); | ||
return $result->setData( | ||
[ | ||
'status' => 'failure', | ||
'message' => 'Unable to verify token - ' . $e->getMessage(), | ||
] | ||
); | ||
} | ||
|
||
$errors = []; | ||
foreach ($payload['emails'] as $emailAddress) { | ||
$user = $this->userFactory->create(); | ||
$user->setEmail($emailAddress); | ||
$data = $this->userResource->userExists($user); | ||
|
||
if (!$data) { | ||
$errors[$emailAddress] = 'No account for ' . $emailAddress; | ||
continue; | ||
} | ||
|
||
// Existing user found | ||
$user->load($data['user_id']); | ||
|
||
if (!$user->getIsActive()) { | ||
$errors[$emailAddress] = 'Account for ' . $emailAddress . ' is disabled.'; | ||
continue; | ||
} | ||
|
||
return $this->doLogin($user); | ||
} | ||
|
||
if ($errors === []) { | ||
$errors[] = 'No email addresses provided'; | ||
} | ||
|
||
$result = $this->resultJsonFactory->create(); | ||
return $result->setData( | ||
[ | ||
'status' => 'failure', | ||
'message' => implode("\n\n", $errors), | ||
] | ||
); | ||
} | ||
|
||
private function doLogin(User $user): ResultInterface | ||
{ | ||
$result = $this->resultJsonFactory->create(); | ||
|
||
$password = bin2hex(random_bytes(32)); | ||
$user->setPassword($password); | ||
$user->setPasswordConfirmation($password); | ||
$user->save(); | ||
|
||
try { | ||
$this->authModel->login($user->getUserName(), $password); | ||
} catch (Exception $e) { | ||
return $result->setData( | ||
[ | ||
'status' => 'failure', | ||
'message' => $e->getMessage(), | ||
'username' => $user->getUserName(), | ||
'password' => $password, | ||
] | ||
); | ||
} | ||
|
||
// This is used elsewhere in this module to restrict other features to | ||
// only when logging in via this tool. | ||
$this->authSession->setFreddenAdminAuth(true); | ||
|
||
return $result->setData( | ||
[ | ||
'status' => 'success', | ||
'message' => 'Logged in as ' . $user->getUserName(), | ||
'username' => $user->getUserName(), | ||
'password' => $password, | ||
] | ||
); | ||
} | ||
|
||
private function parseToken(string $token): array | ||
{ | ||
try { | ||
$keys = JWK::parseKeySet(['keys' => $this->config->getAuthKeys()]); | ||
return (array) JWT::decode($token, $keys); | ||
} catch (Throwable) { | ||
$keys = JWK::parseKeySet(['keys' => $this->config->getAuthKeys(false)]); | ||
return (array) JWT::decode($token, $keys); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
namespace Fredden\AdminAuth\Plugin\Magento; | ||
|
||
use Magento\Captcha\Model\DefaultModel; | ||
use Magento\Framework\App\RequestInterface; | ||
|
||
class Captcha | ||
{ | ||
public function __construct( | ||
private readonly RequestInterface $request, | ||
) { | ||
} | ||
|
||
public function afterIsRequired(DefaultModel $subject, bool $result): bool | ||
{ | ||
if ( | ||
$result | ||
&& $this->request->getRouteName() === 'Fredden_AdminAuth' | ||
&& $this->request->getControllerName() === 'login' | ||
&& $this->request->getActionName() === 'refresh' | ||
) { | ||
return false; | ||
} | ||
|
||
return $result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
|
||
namespace Fredden\AdminAuth\Plugin\Magento; | ||
|
||
use Magento\Framework\App\RequestInterface; | ||
use Magento\Framework\Locale\OptionInterface; | ||
use Magento\User\Model\User; | ||
|
||
class SetAdminUserLocale | ||
{ | ||
public function __construct( | ||
private readonly OptionInterface $deployedLocales, | ||
private readonly RequestInterface $request, | ||
) { | ||
} | ||
|
||
/** | ||
* Ensure that the user always has a valid interface locale set | ||
*/ | ||
public function afterBeforeSave(User $subject, User $result): User | ||
{ | ||
$availableOptions = $this->deployedLocales->getOptionLocales(); | ||
|
||
if (!$availableOptions) { | ||
return $result; | ||
} | ||
|
||
$currentUserLocale = $subject->getInterfaceLocale(); | ||
$locales = []; | ||
|
||
foreach ($availableOptions as $option) { | ||
if ($option['value'] === $currentUserLocale) { | ||
// User's current locale is available here; there's nothing for us to do. | ||
return $result; | ||
} | ||
|
||
$locales[$option['value']] = $option['value']; | ||
$locales[substr($option['value'], 0, 2)] = $option['value']; | ||
} | ||
|
||
$preferredLocales = []; | ||
|
||
if ($header = $this->request->getHeader('accept-language')) { | ||
$languages = explode(',', $header); | ||
foreach ($languages as $languageOption) { | ||
// TODO: parse and use ';q=' values to weight/sort options. | ||
$languageOption = explode(';', $languageOption)[0]; | ||
$languageOption = trim($languageOption); | ||
$languageOption = str_replace('-', '_', $languageOption); | ||
|
||
$preferredLocales[] = $languageOption; | ||
} | ||
} | ||
|
||
$preferredLocales[] = 'en'; // Add English as a fallback option | ||
|
||
foreach ($preferredLocales as $localeOption) { | ||
if ($localeOption === 'en' && isset($locales['en_GB'])) { | ||
// English is preferred. | ||
$localeOption = 'en_GB'; | ||
} | ||
|
||
if ($localeOption === 'en' && isset($locales['en_US'])) { | ||
// American is next best if English isn't available. | ||
$localeOption = 'en_US'; | ||
} | ||
|
||
if (isset($locales[$localeOption])) { | ||
$subject->setInterfaceLocale($locales[$localeOption]); | ||
return $result; | ||
} | ||
} | ||
|
||
// We didn't find a locale that suits the user's preferences. Let's just | ||
// give them ANY locale that exists. A working admin in a foreign | ||
// language is better than a completely broken admin. The user can set | ||
// another locale when they work out where to click. | ||
$subject->setInterfaceLocale($option['value']); | ||
// $option['value'] comes from the foreach() on line 44. There will | ||
// always be at least one locale in that loop due to Magento internals. | ||
|
||
return $result; | ||
} | ||
} |
Oops, something went wrong.