ip address whitelist
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldValidators\Settings\AuthIpAddressWhitelist;
|
||||
|
||||
use Espo\Core\FieldValidation\Validator;
|
||||
use Espo\Core\FieldValidation\Validator\Data;
|
||||
use Espo\Core\FieldValidation\Validator\Failure;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Validator<Entity>
|
||||
*/
|
||||
class Valid implements Validator
|
||||
{
|
||||
public function validate(Entity $entity, string $field, Data $data): ?Failure
|
||||
{
|
||||
$list = $entity->get($field);
|
||||
|
||||
if (!is_array($list)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($list as $item) {
|
||||
if (!is_string($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->isValid($item)) {
|
||||
return Failure::create();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isValid(string $item): bool
|
||||
{
|
||||
$address = $item;
|
||||
|
||||
if (count(explode('/', $item)) > 1) {
|
||||
[$address, $mask] = explode('/', $item, 2);
|
||||
|
||||
if (!is_numeric($mask)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mask = (int) $mask;
|
||||
|
||||
if ($mask < 0 || $mask > 128) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false &&
|
||||
filter_var($address, FILTER_VALIDATE_IP) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +237,9 @@ class Auth
|
||||
$response->writeBody($e->getBody());
|
||||
}
|
||||
|
||||
$this->log->notice("Auth exception: {message}", ['message' => $e->getMessage()]);
|
||||
if ($e->getMessage()) {
|
||||
$this->log->notice("Auth exception: {message}", ['message' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ class Authentication
|
||||
* Process logging in.
|
||||
*
|
||||
* @throws ServiceUnavailable
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function login(AuthenticationData $data, Request $request, Response $response): Result
|
||||
{
|
||||
@@ -114,7 +115,11 @@ class Authentication
|
||||
return $this->processFail(Result::fail(FailReason::METHOD_NOT_ALLOWED), $data, $request);
|
||||
}
|
||||
|
||||
$this->hookManager->processBeforeLogin($data, $request);
|
||||
try {
|
||||
$this->hookManager->processBeforeLogin($data, $request);
|
||||
} catch (Forbidden $e) {
|
||||
$this->processForbidden($e);
|
||||
}
|
||||
|
||||
if (!$method && $password === null) {
|
||||
$this->log->error("Auth: Trying to login w/o password.");
|
||||
@@ -240,6 +245,12 @@ class Authentication
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->hookManager->processOnLogin($result, $data, $request);
|
||||
} catch (Forbidden $e) {
|
||||
$this->processForbidden($e, $authLogRecord);
|
||||
}
|
||||
|
||||
if (
|
||||
!$result->isSecondStepRequired() &&
|
||||
$request->getHeader(self::HEADER_ESPO_AUTHORIZATION)
|
||||
@@ -786,4 +797,25 @@ class Authentication
|
||||
|
||||
$user->set('ipAddress', $this->util->obtainIpFromRequest($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function processForbidden(Forbidden $exception, ?AuthLogRecord $authLogRecord = null): never
|
||||
{
|
||||
$this->log->warning('Auth: Forbidden. {message}', [
|
||||
'message' => $exception->getMessage(),
|
||||
'exception' => $exception,
|
||||
]);
|
||||
|
||||
if ($authLogRecord) {
|
||||
$authLogRecord
|
||||
->setIsDenied()
|
||||
->setDenialReason(AuthLogRecord::DENIAL_REASON_FORBIDDEN);
|
||||
|
||||
$this->entityManager->saveEntity($authLogRecord);
|
||||
}
|
||||
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,4 +147,25 @@ class ConfigDataProvider
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function ipAddressCheck(): bool
|
||||
{
|
||||
return (bool) $this->config->get('authIpAddressCheck');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIpAddressWhitelist(): array
|
||||
{
|
||||
return $this->config->get('authIpAddressWhitelist') ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIpAddressCheckExcludedUserIdList(): array
|
||||
{
|
||||
return $this->config->get('authIpAddressCheckExcludedUsersIds') ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ use Espo\Core\Authentication\AuthenticationData;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Authentication\ConfigDataProvider;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Entities\AuthLogRecord;
|
||||
|
||||
@@ -51,7 +50,6 @@ class FailedAttemptsLimit implements BeforeLogin
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private EntityManager $entityManager,
|
||||
private Log $log,
|
||||
private Util $util
|
||||
) {}
|
||||
|
||||
@@ -82,11 +80,11 @@ class FailedAttemptsLimit implements BeforeLogin
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
|
||||
$ip = $this->util->obtainIpFromRequest($request);
|
||||
$apAddress = $this->util->obtainIpFromRequest($request);
|
||||
|
||||
$where = [
|
||||
'requestTime>' => $requestTimeFrom->format('U'),
|
||||
'ipAddress' => $ip,
|
||||
'ipAddress' => $apAddress,
|
||||
'isDenied' => true,
|
||||
];
|
||||
|
||||
@@ -109,8 +107,6 @@ class FailedAttemptsLimit implements BeforeLogin
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log->warning("AUTH: Max failed login attempts exceeded for IP {ipAddress}.", ['ipAddress' => $ip]);
|
||||
|
||||
throw new Forbidden("Max failed login attempts exceeded.");
|
||||
throw new Forbidden("Max failed login attempts exceeded for IP address $apAddress.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Authentication\Hook\Hooks;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Util;
|
||||
use Espo\Core\Authentication\AuthenticationData;
|
||||
use Espo\Core\Authentication\ConfigDataProvider;
|
||||
use Espo\Core\Authentication\Hook\OnLogin;
|
||||
use Espo\Core\Authentication\Result;
|
||||
use Espo\Core\Authentication\Util\IpAddressUtil;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
class IpAddressWhitelist implements OnLogin
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private Util $util,
|
||||
private Config $config,
|
||||
private IpAddressUtil $ipAddressUtil
|
||||
) {}
|
||||
|
||||
public function process(Result $result, AuthenticationData $data, Request $request): void
|
||||
{
|
||||
if (!$this->configDataProvider->ipAddressCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ipAddress = $this->util->obtainIpFromRequest($request);
|
||||
|
||||
if (
|
||||
$ipAddress &&
|
||||
$this->ipAddressUtil->isInWhitelist($ipAddress, $this->configDataProvider->getIpAddressWhitelist())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $result->getUser();
|
||||
|
||||
if ($user && $user->isPortal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($user && $user->isSuperAdmin() && $this->config->get('restrictedMode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$user &&
|
||||
in_array($user->getId(), $this->configDataProvider->getIpAddressCheckExcludedUserIdList())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$username = $user ? $user->getUserName() : '?';
|
||||
|
||||
throw new Forbidden("Not allowed IP address $ipAddress, user: $username.");
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace Espo\Core\Authentication\Hook;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\ServiceUnavailable;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Authentication\AuthenticationData;
|
||||
@@ -41,6 +43,10 @@ class Manager
|
||||
public function __construct(private Metadata $metadata, private InjectableFactory $injectableFactory)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @throws ServiceUnavailable
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function processBeforeLogin(AuthenticationData $data, Request $request): void
|
||||
{
|
||||
foreach ($this->getBeforeLoginHookList() as $hook) {
|
||||
@@ -48,6 +54,16 @@ class Manager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function processOnLogin(Result $result, AuthenticationData $data, Request $request): void
|
||||
{
|
||||
foreach ($this->getOnLoginHookList() as $hook) {
|
||||
$hook->process($result, $data, $request);
|
||||
}
|
||||
}
|
||||
|
||||
public function processOnFail(Result $result, AuthenticationData $data, Request $request): void
|
||||
{
|
||||
foreach ($this->getOnFailHookList() as $hook) {
|
||||
@@ -102,6 +118,21 @@ class Manager
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OnLogin[]
|
||||
*/
|
||||
private function getOnLoginHookList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getHookClassNameList('onLogin') as $className) {
|
||||
/** @var class-string<OnLogin> $className */
|
||||
$list[] = $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OnResult[]
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Authentication\Hook;
|
||||
|
||||
use Espo\Core\Authentication\AuthenticationData;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Authentication\Result;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
/**
|
||||
* Once a login result is ready.
|
||||
*/
|
||||
interface OnLogin
|
||||
{
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function process(Result $result, AuthenticationData $data, Request $request): void;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Authentication\Util;
|
||||
|
||||
use CIDRmatch\CIDRmatch;
|
||||
|
||||
class IpAddressUtil
|
||||
{
|
||||
/**
|
||||
* @param string $ipAddress An IP address.
|
||||
* @param string[] $whitelist A whitelist. IPs or IP ranges in CIDR notation.
|
||||
*/
|
||||
public function isInWhitelist(string $ipAddress, array $whitelist): bool
|
||||
{
|
||||
$cidrMatch = new CIDRmatch();
|
||||
|
||||
foreach ($whitelist as $whiteIpAddress) {
|
||||
if ($ipAddress === $whiteIpAddress) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
str_contains($whiteIpAddress, '/') &&
|
||||
$cidrMatch->match($ipAddress, $whiteIpAddress)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ class AuthLogRecord extends Entity
|
||||
public const DENIAL_REASON_IS_NOT_PORTAL_USER = 'IS_NOT_PORTAL_USER';
|
||||
public const DENIAL_REASON_USER_IS_NOT_IN_PORTAL = 'USER_IS_NOT_IN_PORTAL';
|
||||
public const DENIAL_REASON_IS_SYSTEM_USER = 'IS_SYSTEM_USER';
|
||||
public const DENIAL_REASON_FORBIDDEN = 'FORBIDDEN';
|
||||
|
||||
public function setUsername(?string $username): self
|
||||
{
|
||||
|
||||
@@ -299,5 +299,9 @@ return [
|
||||
'listPagination' => true,
|
||||
'starsLimit' => 500,
|
||||
'quickSearchFullTextAppendWildcard' => false,
|
||||
'authIpAddressCheck' => false,
|
||||
'authIpAddressWhitelist' => [],
|
||||
'authIpAddressCheckExcludedUsersIds' => [],
|
||||
'authIpAddressCheckExcludedUsersNames' => (object) [],
|
||||
'isInstalled' => false,
|
||||
];
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"INACTIVE_USER": "Inactive user",
|
||||
"IS_PORTAL_USER": "Portal user",
|
||||
"IS_NOT_PORTAL_USER": "Not a portal user",
|
||||
"USER_IS_NOT_IN_PORTAL": "User is not related to the portal"
|
||||
"USER_IS_NOT_IN_PORTAL": "User is not related to the portal",
|
||||
"IS_SYSTEM_USER": "Is system user",
|
||||
"FORBIDDEN": "Forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,10 @@
|
||||
"oidcAllowAdminUser": "OIDC Allow OIDC login for admin users",
|
||||
"oidcLogoutUrl": "OIDC Logout URL",
|
||||
"pdfEngine": "PDF Engine",
|
||||
"quickSearchFullTextAppendWildcard": "Append wildcard in quick search"
|
||||
"quickSearchFullTextAppendWildcard": "Append wildcard in quick search",
|
||||
"authIpAddressCheck": "Restrict access by IP address",
|
||||
"authIpAddressWhitelist": "IP Address Whitelist",
|
||||
"authIpAddressCheckExcludedUsers": "Users excluded from check"
|
||||
},
|
||||
"options": {
|
||||
"authenticationMethod": {
|
||||
@@ -308,7 +311,8 @@
|
||||
"Passwords": "Passwords",
|
||||
"2-Factor Authentication": "2-Factor Authentication",
|
||||
"Attachments": "Attachments",
|
||||
"IdP Group": "IdP Group"
|
||||
"IdP Group": "IdP Group",
|
||||
"Access": "Access"
|
||||
},
|
||||
"messages": {
|
||||
"ldapTestConnection": "The connection successfully established."
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
[{"name": "auth2FAForced"}, {"name": "auth2FAInPortal"}]
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Access",
|
||||
"rows": [
|
||||
[{"name": "authIpAddressCheck"}, false],
|
||||
[{"name": "authIpAddressWhitelist"}, {"name": "authIpAddressCheckExcludedUsers"}]
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Passwords",
|
||||
"rows": [
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"beforeLoginHookClassNameList": [
|
||||
"Espo\\Core\\Authentication\\Hook\\Hooks\\FailedAttemptsLimit"
|
||||
],
|
||||
"onLoginHookClassNameList": [
|
||||
"Espo\\Core\\Authentication\\Hook\\Hooks\\IpAddressWhitelist"
|
||||
],
|
||||
"onFailHookClassNameList": [],
|
||||
"onSuccessHookClassNameList": [],
|
||||
"onSuccessByTokenHookClassNameList": [],
|
||||
|
||||
@@ -141,6 +141,15 @@
|
||||
},
|
||||
"starsLimit": {
|
||||
"level": "admin"
|
||||
},
|
||||
"authIpAddressCheck": {
|
||||
"level": "superAdmin"
|
||||
},
|
||||
"authIpAddressWhitelist": {
|
||||
"level": "superAdmin"
|
||||
},
|
||||
"authIpAddressCheckExcludedUsers": {
|
||||
"level": "superAdmin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
"IS_PORTAL_USER",
|
||||
"IS_NOT_PORTAL_USER",
|
||||
"USER_IS_NOT_IN_PORTAL",
|
||||
"IS_SYSTEM_USER"
|
||||
"IS_SYSTEM_USER",
|
||||
"FORBIDDEN"
|
||||
],
|
||||
"readOnly": true
|
||||
},
|
||||
|
||||
@@ -895,6 +895,21 @@
|
||||
"quickSearchFullTextAppendWildcard": {
|
||||
"type": "bool",
|
||||
"tooltip": true
|
||||
},
|
||||
"authIpAddressCheck": {
|
||||
"type": "bool"
|
||||
},
|
||||
"authIpAddressWhitelist": {
|
||||
"type": "array",
|
||||
"allowCustomOptions": true,
|
||||
"noEmptyString": true,
|
||||
"validatorClassNameList": [
|
||||
"Espo\\Classes\\FieldValidators\\Settings\\AuthIpAddressWhitelist\\Valid"
|
||||
]
|
||||
},
|
||||
"authIpAddressCheckExcludedUsers": {
|
||||
"type": "linkMultiple",
|
||||
"entity": "User"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,40 @@ class AdminAuthenticationRecordView extends SettingsEditRecordView {
|
||||
|
||||
saveAndContinueEditingAction = false
|
||||
|
||||
dynamicLogicDefs = {
|
||||
fields: {
|
||||
authIpAddressWhitelist: {
|
||||
visible: {
|
||||
conditionGroup: [
|
||||
{
|
||||
attribute: 'authIpAddressCheck',
|
||||
type: 'isTrue'
|
||||
}
|
||||
]
|
||||
},
|
||||
required: {
|
||||
conditionGroup: [
|
||||
{
|
||||
attribute: 'authIpAddressCheck',
|
||||
type: 'isTrue'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
authIpAddressCheckExcludedUsers: {
|
||||
visible: {
|
||||
conditionGroup: [
|
||||
{
|
||||
attribute: 'authIpAddressCheck',
|
||||
type: 'isTrue'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
panels: {},
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.methodList = [];
|
||||
|
||||
@@ -49,6 +83,12 @@ class AdminAuthenticationRecordView extends SettingsEditRecordView {
|
||||
|
||||
super.setup();
|
||||
|
||||
if (this.getHelper().getAppParam('isRestrictedMode') && !this.getUser().isSuperAdmin()) {
|
||||
this.setFieldReadOnly('authIpAddressCheck', true);
|
||||
this.setFieldReadOnly('authIpAddressWhitelist', true);
|
||||
this.setFieldReadOnly('authIpAddressCheckExcludedUsers', true);
|
||||
}
|
||||
|
||||
this.handlePanelsVisibility();
|
||||
|
||||
this.listenTo(this.model, 'change:authenticationMethod', () => {
|
||||
@@ -69,10 +109,7 @@ class AdminAuthenticationRecordView extends SettingsEditRecordView {
|
||||
}
|
||||
|
||||
setupBeforeFinal() {
|
||||
this.dynamicLogicDefs = {
|
||||
fields: {},
|
||||
panels: {},
|
||||
};
|
||||
this.dynamicLogicDefs = Espo.Utils.cloneDeep(this.dynamicLogicDefs);
|
||||
|
||||
this.methodList.forEach(method => {
|
||||
const fieldList = this.getMetadata().get(['authenticationMethods', method, 'settings', 'fieldList']);
|
||||
|
||||
+2
-1
@@ -51,7 +51,8 @@
|
||||
"picqer/php-barcode-generator": "^2.4",
|
||||
"chillerlan/php-qrcode": "^4.3",
|
||||
"ext-ctype": "*",
|
||||
"lasserafn/php-initial-avatar-generator": "^4.3"
|
||||
"lasserafn/php-initial-avatar-generator": "^4.3",
|
||||
"tholu/php-cidr-match": "^0.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
|
||||
Generated
+54
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a98b5f556eaf6120fad8b36c44d060bf",
|
||||
"content-hash": "c9ba40700e3c077689a1b222e3b749c1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -6609,6 +6609,59 @@
|
||||
],
|
||||
"time": "2021-03-23T23:28:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tholu/php-cidr-match",
|
||||
"version": "0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tholu/php-cidr-match.git",
|
||||
"reference": "50fadb1e43d865167b3ca705fbdbfff87ea5ab96"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/tholu/php-cidr-match/zipball/50fadb1e43d865167b3ca705fbdbfff87ea5ab96",
|
||||
"reference": "50fadb1e43d865167b3ca705fbdbfff87ea5ab96",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": ">2",
|
||||
"phpunit/phpunit": "4.8.36",
|
||||
"squizlabs/php_codesniffer": "3.6.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"CIDRmatch\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Thomas Lutz",
|
||||
"email": "thomaslutz.de@gmail.com",
|
||||
"homepage": "https://github.com/tholu",
|
||||
"role": "Creator, Developer, Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "CIDRmatch is a library to match an IP to an IP range in CIDR notation (IPv4 and IPv6).",
|
||||
"keywords": [
|
||||
"cidr",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/tholu/php-cidr-match/issues",
|
||||
"source": "https://github.com/tholu/php-cidr-match/tree/0.4"
|
||||
},
|
||||
"time": "2022-05-11T06:48:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.11.0",
|
||||
|
||||
@@ -15,6 +15,16 @@
|
||||
},
|
||||
"description": "Array of hook class names. Fired before logging in before credentials are checked. Should implement Espo\\Core\\Authentication\\Hook\\BeforeLogin interface. Important: Need to have __APPEND__ item in the beginning of the array when extending."
|
||||
},
|
||||
"onLoginHookClassNameList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"enum": ["__APPEND__"]}
|
||||
]
|
||||
},
|
||||
"description": "Array of hook class names. Fired once logging in is about to success. Should implement Espo\\Core\\Authentication\\Hook\\OnLogin interface. As of v8.3. Important: Need to have __APPEND__ item in the beginning of the array when extending."
|
||||
},
|
||||
"onFailHookClassNameList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\Authentication\Util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class IpAddressUtilTest extends TestCase
|
||||
{
|
||||
public function testWhitelist(): void
|
||||
{
|
||||
$util = new \Espo\Core\Authentication\Util\IpAddressUtil();
|
||||
|
||||
$this->assertTrue(
|
||||
$util->isInWhitelist('192.168.0.1', [
|
||||
'192.168.0.1',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$util->isInWhitelist('192.168.0.1', [
|
||||
'192.168.0.0',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$util->isInWhitelist('192.168.0.1', [
|
||||
'192.168.0.1',
|
||||
'192.168.0.1',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$util->isInWhitelist('192.168.0.5', [
|
||||
'192.168.0.1',
|
||||
'192.168.0.1/24',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$util->isInWhitelist('0.0.0.5', [
|
||||
'192.168.0.1/24',
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user