auth token manager

This commit is contained in:
Yuri Kuznetsov
2020-11-10 10:51:50 +02:00
parent 2d385c163f
commit 989d431acf
16 changed files with 746 additions and 149 deletions
@@ -43,6 +43,7 @@ use Espo\Core\{
Api\ErrorOutput as ApiErrorOutput,
Api\RequestWrapper,
Api\ResponseWrapper,
Authentication\AuthToken\AuthTokenManager,
};
use Slim\{
@@ -64,19 +65,22 @@ class EntryPoint implements ApplicationRunner
protected $entityManager;
protected $clientManager;
protected $applicationUser;
protected $authTokenManager;
public function __construct(
InjectableFactory $injectableFactory,
EntryPointManager $entryPointManager,
EntityManager $entityManager,
ClientManager $clientManager,
ApplicationUser $applicationUser
ApplicationUser $applicationUser,
AuthTokenManager $authTokenManager
) {
$this->injectableFactory = $injectableFactory;
$this->entryPointManager = $entryPointManager;
$this->entityManager = $entityManager;
$this->clientManager = $clientManager;
$this->applicationUser = $applicationUser;
$this->authTokenManager = $authTokenManager;
}
public function run(?StdClass $params = null)
@@ -169,15 +173,14 @@ class EntryPoint implements ApplicationRunner
return $_GET['portalId'];
}
if (!empty($_COOKIE['auth-token'])) {
$token = $this->entityManager
->getRepository('AuthToken')
->where(['token' => $_COOKIE['auth-token']])
->findOne();
if (empty($_COOKIE['auth-token'])) {
return null;
}
if ($token && $token->get('portalId')) {
return $token->get('portalId');
}
$authToken = $this->authTokenManager->get($_COOKIE['auth-token']);
if ($authToken) {
return $authToken->getPortalId();
}
return null;
@@ -0,0 +1,67 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\AuthToken;
/**
* An auth token record.
*/
interface AuthToken
{
/**
* Get a token.
*/
public function getToken() : string;
/**
* Get a user ID.
*/
public function getUserId() : string;
/**
* Get a portal ID. If a token belongs to a specific portal.
*/
public function getPortalId() : ?string;
/**
* Get a token secret. Secret is used as an additional security check.
*/
public function getSecret() : ?string;
/**
* Whether a token is active.
*/
public function isActive() : bool;
/**
* Get a password hash. If a password hash is not stored in token, then return NULL.
* If you store auth tokens remotely it's reasonable to avoid hashes being sent.
*/
public function getHash() : ?string;
}
@@ -0,0 +1,110 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\AuthToken;
use RuntimeException;
/**
* An auth token data. Used for auth token creation.
*/
class AuthTokenData
{
private $userId;
private $portalId = null;
private $hash = null;
private $ipAddress = null;
private $createSecret = false;
private function __construct()
{
}
public function getUserId() : string
{
return $this->userId;
}
public function getPortalId() : ?string
{
return $this->portalId;
}
public function getHash() : ?string
{
return $this->hash;
}
public function getIpAddress() : ?string
{
return $this->ipAddress;
}
public function toCreateSecret() : bool
{
return $this->createSecret;
}
public static function create(array $data) : self
{
$object = new self();
$object->userId = $data['userId'] ?? null;
$object->portalId = $data['portalId'] ?? null;
$object->hash = $data['hash'] ?? null;
$object->ipAddress = $data['ipAddress'] ?? null;
$object->createSecret = $data['createSecret'] ?? false;
$object->validate();
return $object;
}
protected function validate()
{
// @todo Use typed properties when php 7.4 is a min supported version.
if (
isset($this->userId) && !is_string($this->userId) ||
isset($this->portalId) && !is_string($this->portalId) ||
isset($this->hash) && !is_string($this->hash) ||
isset($this->ipAddress) && !is_string($this->ipAddress) ||
!is_bool($this->createSecret)
) {
throw new RuntimeException("Invalid data.");
}
if (!$this->userId) {
throw new RuntimeException("No user ID.");
}
}
}
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\AuthToken;
/**
* Fetches and stores auth tokens.
*/
interface AuthTokenManager
{
/**
* Get an auth token. If does not exist then returns NULL.
*/
public function get(string $token) : ?AuthToken;
/**
* Create an auth token and store it.
*/
public function create(AuthTokenData $authTokenData) : AuthToken;
/**
* Make an auth token inactive (invalid).
*/
public function inactivate(AuthToken $authToken);
/**
* Update a last access date. An implementation can be omitted to avoid a writing operation.
*/
public function renew(AuthToken $authToken);
}
@@ -0,0 +1,165 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\AuthToken;
use Espo\{
ORM\EntityManager,
};
use RuntimeException;
use const MCRYPT_DEV_URANDOM;
class EspoAuthTokenManager implements AuthTokenManager
{
protected $entityManager;
protected $repository;
const TOKEN_RANDOM_LENGTH = 16;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository('AuthToken');
}
public function get(string $token) : ?AuthToken
{
$authToken = $this->entityManager
->getRepository('AuthToken')
->select([
'id',
'isActive',
'token',
'secret',
'userId',
'portalId',
'hash',
'createdAt',
'lastAccess',
'modifiedAt',
])
->where([
'token' => $token,
])
->findOne();
return $authToken;
}
public function create(AuthTokenData $authTokenData) : AuthToken
{
$authToken = $this->repository->getNew();
$authToken->set([
'userId' => $authTokenData->getUserId(),
'portalId' => $authTokenData->getPortalId(),
'hash' => $authTokenData->getHash(),
'ipAddress' => $authTokenData->getIpAddress(),
'lastAccess' => date('Y-m-d H:i:s'),
'token' => $this->generateToken(),
]);
if ($authTokenData->toCreateSecret()) {
$authToken->set('secret', $this->generateToken());
}
$this->validate($authToken);
$this->repository->save($authToken);
return $authToken;
}
public function inactivate(AuthToken $authToken)
{
$this->validateNotChanged($authToken);
$authToken->set('isActive', false);
$this->repository->save($authToken);
}
public function renew(AuthToken $authToken)
{
$this->validateNotChanged($authToken);
if ($authToken->isNew()) {
throw new RuntimeException("Can renew only not new auth token.");
}
$authToken->set('lastAccess', date('Y-m-d H:i:s'));
$this->repository->save($authToken);
}
protected function validate(AuthToken $authToken)
{
if (!$authToken->getToken()) {
throw new RuntimeException("Empty token.");
}
if (!$authToken->getUserId()) {
throw new RuntimeException("Empty user ID.");
}
}
protected function validateNotChanged(AuthToken $authToken)
{
if (
$authToken->isAttributeChanged('token') ||
$authToken->isAttributeChanged('secret') ||
$authToken->isAttributeChanged('hash') ||
$authToken->isAttributeChanged('userId') ||
$authToken->isAttributeChanged('portalId')
) {
throw new RuntimeException("Auth token was changed.");
}
}
protected function generateToken()
{
$length = self::TOKEN_RANDOM_LENGTH;
if (function_exists('random_bytes')) {
return bin2hex(random_bytes($length));
}
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM));
}
if (function_exists('openssl_random_pseudo_bytes')) {
return bin2hex(openssl_random_pseudo_bytes($length));
}
}
}
@@ -46,6 +46,8 @@ use Espo\Core\Authentication\{
Result,
LoginFactory,
TwoFactor\Factory as TwoFAFactory,
AuthToken\AuthTokenManager,
AuthToken\AuthTokenData,
};
use Espo\Core\{
@@ -89,7 +91,8 @@ class Authentication
Metadata $metadata,
EntityManagerProxy $entityManagerProxy,
LoginFactory $authLoginFactory,
TwoFAFactory $auth2FAFactory
TwoFAFactory $auth2FAFactory,
AuthTokenManager $authTokenManager
) {
$this->allowAnyAccess = $allowAnyAccess;
@@ -100,6 +103,7 @@ class Authentication
$this->entityManager = $entityManagerProxy;
$this->authLoginFactory = $authLoginFactory;
$this->auth2FAFactory = $auth2FAFactory;
$this->authTokenManager = $authTokenManager;
}
protected function getDefaultAuthenticationMethod()
@@ -160,15 +164,12 @@ class Authentication
$authTokenIsFound = false;
if (!$authenticationMethod) {
$authToken = $this->entityManager
->getRepository('AuthToken')
->where(['token' => $password])
->findOne();
$authToken = $this->authTokenManager->get($password);
if ($authToken && $authToken->get('secret')) {
if ($authToken && $authToken->getSecret()) {
$sentSecret = $request->getCookieParam('auth-token-secret');
if ($sentSecret !== $authToken->get('secret')) {
if ($sentSecret !== $authToken->getSecret()) {
$authToken = null;
}
}
@@ -178,15 +179,15 @@ class Authentication
$authTokenIsFound = true;
}
if ($authToken && $authToken->get('isActive')) {
if ($authToken && $authToken->isActive()) {
if (!$this->allowAnyAccess) {
if ($this->isPortal() && $authToken->get('portalId') !== $this->getPortal()->id) {
if ($this->isPortal() && $authToken->getPortalId() !== $this->getPortal()->id) {
$GLOBALS['log']->info("AUTH: Trying to login to portal with a token not related to portal.");
return null;
}
if (!$this->isPortal() && $authToken->get('portalId')) {
if (!$this->isPortal() && $authToken->getPortalId()) {
$GLOBALS['log']->info("AUTH: Trying to login to crm with a token related to portal.");
return null;
@@ -194,8 +195,8 @@ class Authentication
}
if ($this->allowAnyAccess) {
if ($authToken->get('portalId') && !$this->isPortal()) {
$portal = $this->entityManager->getEntity('Portal', $authToken->get('portalId'));
if ($authToken->getPortalId() && !$this->isPortal()) {
$portal = $this->entityManager->getEntity('Portal', $authToken->getPortalId());
if ($portal) {
$this->setPortal($portal);
@@ -265,17 +266,15 @@ class Authentication
if (!$result->isSecondStepRequired() && $request->getHeader('Espo-Authorization')) {
if (!$authToken) {
$authToken = $this->createAuthToken($user, $request);
} else {
$this->authTokenManager->renew($authToken);
}
$authToken->set('lastAccess', date('Y-m-d H:i:s'));
$this->entityManager->saveEntity($authToken);
$user->set('token', $authToken->get('token'));
$user->set('authTokenId', $authToken->id);
$user->set('token', $authToken->getToken());
$user->set('authTokenId', $authToken->id ?? null);
if ($authLogRecord) {
$authLogRecord->set('authTokenId', $authToken->id);
$authLogRecord->set('authTokenId', $authToken->id ?? null);
}
}
@@ -283,10 +282,11 @@ class Authentication
$this->entityManager->saveEntity($authLogRecord);
}
if ($authToken && !$authLogRecord) {
if ($authToken && !$authLogRecord && isset($authToken->id)) {
$authLogRecord = $this->entityManager
->getRepository('AuthLogRecord')
->select(['id'])->where([
->select(['id'])
->where([
'authTokenId' => $authToken->id
])
->order('requestTime', true)
@@ -450,38 +450,35 @@ class Authentication
protected function createAuthToken(User $user, Request $request) : AuthToken
{
$createTokenSecret = $request->getHeader('Espo-Authorization-Create-Token-Secret') === 'true';
$createSecret = $request->getHeader('Espo-Authorization-Create-Token-Secret') === 'true';
if ($createTokenSecret) {
if ($createSecret) {
if ($this->config->get('authTokenSecretDisabled')) {
$createTokenSecret = false;
$createSecret = false;
}
}
$authToken = $this->entityManager->getEntity('AuthToken');
$arrayData = [
'hash' => $user->get('password'),
'ipAddress' => $request->getServerParam('REMOTE_ADDR'),
'userId' => $user->id,
'portalId' => $this->isPortal() ? $this->getPortal()->id : null,
'createSecret' => $createSecret,
];
$token = $this->generateToken();
$authToken = $this->authTokenManager->create(
AuthTokenData::create($arrayData)
);
$authToken->set('token', $token);
$authToken->set('hash', $user->get('password'));
$authToken->set('ipAddress', $request->getServerParam('REMOTE_ADDR'));
$authToken->set('userId', $user->id);
if ($createTokenSecret) {
$secret = $this->generateToken();
$authToken->set('secret', $secret);
$this->setSecretInCookie($secret);
}
if ($this->isPortal()) {
$authToken->set('portalId', $this->getPortal()->id);
if ($createSecret) {
$this->setSecretInCookie($authToken->getSecret());
}
if ($this->config->get('authTokenPreventConcurrent')) {
$concurrentAuthTokenList = $this->entityManager
->getRepository('AuthToken')
->select(['id'])->where([
->select(['id'])
->where([
'userId' => $user->id,
'isActive' => true,
])
@@ -497,48 +494,25 @@ class Authentication
return $authToken;
}
protected function generateToken()
{
$length = 16;
if (function_exists('random_bytes')) {
return bin2hex(random_bytes($length));
}
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv($length, \MCRYPT_DEV_URANDOM));
}
if (function_exists('openssl_random_pseudo_bytes')) {
return bin2hex(openssl_random_pseudo_bytes($length));
}
}
public function destroyAuthToken(string $token, Request $request)
{
$authToken = $this->entityManager
->getRepository('AuthToken')
->select(['id', 'isActive', 'secret'])
->where(
['token' => $token]
)
->findOne();
$authToken = $this->authTokenManager->get($token);
if ($authToken) {
$authToken->set('isActive', false);
$this->entityManager->saveEntity($authToken);
if ($authToken->get('secret')) {
$sentSecret = $request->getCookieParam('auth-token-secret');
if ($sentSecret === $authToken->get('secret')) {
$this->setSecretInCookie(null);
}
}
return true;
if (!$authToken) {
return false;
}
$this->authTokenManager->inactivate($authToken);
if ($authToken->getSecret()) {
$sentSecret = $request->getCookieParam('auth-token-secret');
if ($sentSecret === $authToken->getSecret()) {
$this->setSecretInCookie(null);
}
}
return true;
}
protected function createAuthLogRecord(
@@ -29,15 +29,11 @@
namespace Espo\Core\Authentication\Login;
use Espo\Entities\{
User,
AuthToken,
};
use Espo\Core\{
Api\Request,
ORM\EntityManager,
Authentication\Result,
Authentication\AuthToken\AuthToken,
};
class ApiKey implements Login
@@ -53,11 +49,13 @@ class ApiKey implements Login
{
$apiKey = $request->getHeader('X-Api-Key');
$user = $this->entityManager->getRepository('User')->where([
'type' => 'api',
'apiKey' => $apiKey,
'authMethod' => 'ApiKey',
])->findOne();
$user = $this->entityManager->getRepository('User')
->where([
'type' => 'api',
'apiKey' => $apiKey,
'authMethod' => 'ApiKey',
])
->findOne();
if (!$user) {
return Result::fail();
@@ -29,16 +29,12 @@
namespace Espo\Core\Authentication\Login;
use Espo\Entities\{
User,
AuthToken,
};
use Espo\Core\{
ORM\EntityManager,
Api\Request,
Utils\PasswordHash,
Authentication\Result,
Authentication\AuthToken\AuthToken,
};
class Espo implements Login
@@ -54,26 +50,30 @@ class Espo implements Login
public function login(?string $username, ?string $password, ?AuthToken $authToken = null, ?Request $request = null) : Result
{
if (!$password) return Result::fail('Empty password');
if (!$password) {
return Result::fail('Empty password');
}
if ($authToken) {
$hash = $authToken->get('hash');
$hash = $authToken->getHash();
} else {
$hash = $this->passwordHash->hash($password);
}
$user = $this->entityManager->getRepository('User')->where([
'userName' => $username,
'password' => $hash,
'type!=' => ['api', 'system'],
])->findOne();
$user = $this->entityManager->getRepository('User')
->where([
'userName' => $username,
'password' => $hash,
'type!=' => ['api', 'system'],
])
->findOne();
if (!$user) {
return Result::fail();
}
if ($authToken) {
if ($user->id !== $authToken->get('userId')) {
if ($user->id !== $authToken->getUserId()) {
return Result::fail('User and token mismatch');
}
}
@@ -29,17 +29,13 @@
namespace Espo\Core\Authentication\Login;
use Espo\Entities\{
User,
AuthToken,
};
use Espo\Core\{
ORM\EntityManager,
Api\Request,
Utils\Config,
Utils\ApiKey,
Authentication\Result,
Authentication\AuthToken\AuthToken,
};
class Hmac implements Login
@@ -59,18 +55,23 @@ class Hmac implements Login
list($apiKey, $hash) = explode(':', $authString, 2);
$user = $this->entityManager->getRepository('User')->where([
'type' => 'api',
'apiKey' => $apiKey,
'authMethod' => 'Hmac',
])->findOne();
$user = $this->entityManager->getRepository('User')
->where([
'type' => 'api',
'apiKey' => $apiKey,
'authMethod' => 'Hmac',
])
->findOne();
if (!$user) {
return Result::fail();
}
$secretKey = (new ApiKey($this->config))->getSecretKeyForUserId($user->id);
if (!$secretKey) return null;
if (!$secretKey) {
return null;
}
$string = $request->getMethod() . ' ' . $request->getResourcePath();
@@ -31,11 +31,6 @@ namespace Espo\Core\Authentication\Login;
use Espo\Core\Exceptions\Error;
use Espo\Entities\{
User,
AuthToken,
};
use Espo\Core\{
ORM\EntityManager,
Api\Request,
@@ -46,6 +41,7 @@ use Espo\Core\{
Authentication\Result,
Authentication\LDAP\Utils as LDAPUtils,
Authentication\LDAP\Client as LDAPClient,
Authentication\AuthToken\AuthToken,
};
use Exception;
@@ -122,6 +118,7 @@ class LDAP extends Espo
if ($isPortal) {
$useLdapAuthForPortalUser = $this->utils->getOption('portalUserLdapAuth');
if (!$useLdapAuthForPortalUser) {
return parent::login($username, $password, $authToken, $request);
}
@@ -232,7 +229,8 @@ class LDAP extends Espo
return null;
}
$userId = $authToken->get('userId');
$userId = $authToken->getUserId();
$user = $this->entityManager->getEntity('User', $userId);
$tokenUsername = $user->get('userName');
@@ -29,14 +29,10 @@
namespace Espo\Core\Authentication\Login;
use Espo\Entities\{
User,
AuthToken,
};
use Espo\Core\{
Api\Request,
Authentication\Result,
Authentication\AuthToken\AuthToken,
};
/**
@@ -29,36 +29,58 @@
namespace Espo\Core\Console\Commands;
use Espo\Core\ORM\EntityManager;
use Espo\Core\{
ORM\EntityManager,
Authentication\AuthToken\AuthTokenManager,
};
class AuthTokenCheck implements Command
{
protected $entityManager;
protected $authTokenManager;
public function __construct(EntityManager $entityManager)
public function __construct(EntityManager $entityManager, AuthTokenManager $authTokenManager)
{
$this->entityManager = $entityManager;
$this->authTokenManager = $authTokenManager;
}
public function run(array $options, array $flagList, array $argumentList) : ?string
{
$token = $argumentList[0] ?? null;
if (empty($token)) return null;
$entityManager = $this->entityManager;
if (empty($token)) {
return null;
}
$authToken = $entityManager->getRepository('AuthToken')->where([
'token' => $token,
'isActive' => true,
])->findOne();
$authToken = $this->authTokenManager->get($token);
if (!$authToken) return null;
if (!$authToken->get('userId')) return null;
if (!$authToken) {
return null;
}
$userId = $authToken->get('userId');
if (!$authToken->isActive()) {
return null;
}
$user = $entityManager->getEntity('User', $userId);
if (!$user) return null;
if (!$authToken->getUserId()) {
return null;
}
$userId = $authToken->getUserId();
$user = $this->entityManager
->getRepository('User')
->select('id')
->where([
'id' => $userId,
'isActive' => true,
])
->findOne();
if (!$user) {
return null;
}
return $user->id;
}
+35 -3
View File
@@ -25,12 +25,44 @@
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
************************************************************************/
namespace Espo\Entities;
class AuthToken extends \Espo\Core\ORM\Entity
use Espo\Core\{
ORM\Entity as BaseEntity,
Authentication\AuthToken\AuthToken as AuthTokenInterface,
};
class AuthToken extends BaseEntity implements AuthTokenInterface
{
public function getToken() : string
{
return $this->get('token');
}
public function getUserId() : string
{
return $this->get('userId');
}
public function getPortalId() : ?string
{
return $this->get('portalId');
}
public function getSecret() : ?string
{
return $this->get('secret');
}
public function isActive() : bool
{
return $this->get('isActive');
}
public function getHash() : ?string
{
return $this->get('hash');
}
}
@@ -1,4 +1,7 @@
{
"authTokenManager": {
"className": "Espo\\Core\\Authentication\\AuthToken\\EspoAuthTokenManager"
},
"schema": {
"className": "Espo\\Core\\Utils\\Database\\Schema\\Schema"
},
@@ -0,0 +1,120 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Core\Formula;
use Espo\Core\Authentication\AuthToken\AuthTokenData;
class AuthTokenManagerTest extends \tests\integration\Core\BaseTestCase
{
public function testCreateWithSecret()
{
$authTokenManager = $this->getContainer()->get('authTokenManager');
$authTokenData = AuthTokenData::create([
'hash' => 'test-hash',
'ipAddress' => 'ip-address',
'userId' => 'user-id',
'portalId' => 'portal-id',
'createSecret' => true,
]);
$authToken = $authTokenManager->create($authTokenData);
$this->assertEquals($authTokenData->getHash(), $authToken->getHash());
$this->assertEquals($authTokenData->getUserId(), $authToken->getUserId());
$this->assertEquals($authTokenData->getPortalId(), $authToken->getPortalId());
$this->assertTrue($authToken->isActive());
$this->assertNotEmpty($authToken->getToken());
$this->assertNotEmpty($authToken->getSecret());
$this->assertNotEmpty($authToken->get('lastAccess'));
}
public function testCreateWithNoSecretNoPortal()
{
$authTokenManager = $this->getContainer()->get('authTokenManager');
$authTokenData = AuthTokenData::create([
'hash' => 'test-hash',
'ipAddress' => 'ip-address',
'userId' => 'user-id',
'portalId' => null,
'createSecret' => false,
]);
$authToken = $authTokenManager->create($authTokenData);
$this->assertEquals($authTokenData->getHash(), $authToken->getHash());
$this->assertEquals($authTokenData->getUserId(), $authToken->getUserId());
$this->assertEmpty($authToken->getPortalId());
$this->assertNotEmpty($authToken->getToken());
$this->assertEmpty($authToken->getSecret());
}
public function testRenew()
{
$authTokenManager = $this->getContainer()->get('authTokenManager');
$authTokenData = AuthTokenData::create([
'hash' => 'test-hash',
'userId' => 'user-id',
]);
$authToken = $authTokenManager->create($authTokenData);
$authToken = $authTokenManager->get($authToken->getToken());
$authTokenManager->renew($authToken);
$this->assertNotEmpty($authToken->get('lastAccess'));
}
public function testInactivate()
{
$authTokenManager = $this->getContainer()->get('authTokenManager');
$authTokenData = AuthTokenData::create([
'hash' => 'test-hash',
'userId' => 'user-id',
]);
$authToken = $authTokenManager->create($authTokenData);
$authToken = $authTokenManager->get($authToken->getToken());
$authTokenManager->inactivate($authToken);
$this->assertFalse($authToken->isActive());
}
}
@@ -0,0 +1,52 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Authentication\AuthToken;
use Espo\Core\Authentication\AuthToken\AuthTokenData;
class AuthTokenDataTest extends \PHPUnit\Framework\TestCase
{
public function testCreate()
{
$authTokenData = AuthTokenData::create([
'hash' => 'hash',
'ipAddress' => 'ip-address',
'userId' => 'user-id',
'portalId' => 'portal-id',
'createSecret' => true,
]);
$this->assertEquals('hash', $authTokenData->getHash());
$this->assertEquals('ip-address', $authTokenData->getIpAddress());
$this->assertEquals('user-id', $authTokenData->getUserId());
$this->assertEquals('portal-id', $authTokenData->getPortalId());
$this->assertTrue($authTokenData->toCreateSecret());
}
}