From d67eba26848123fec24d32b2add63de0521bcd35 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 15 Jul 2022 11:50:39 +0300 Subject: [PATCH] ref --- application/Espo/Entities/User.php | 26 +++ application/Espo/Repositories/User.php | 17 +- application/Espo/Services/User.php | 210 ++++++++++++++++--------- 3 files changed, 171 insertions(+), 82 deletions(-) diff --git a/application/Espo/Entities/User.php b/application/Espo/Entities/User.php index b9b7296939..e8c854aabb 100644 --- a/application/Espo/Entities/User.php +++ b/application/Espo/Entities/User.php @@ -214,6 +214,32 @@ class User extends Person return $this->get('userName'); } + public function getAuthMethod(): ?string + { + return $this->get('authMethod'); + } + + public function getContactId(): ?string + { + return $this->get('contactId'); + } + + public function getContact(): ?Link + { + /** @var ?Link */ + $value = $this->getValueObject('contact'); + + return $value; + } + + public function getAccounts(): LinkMultiple + { + /** @var LinkMultiple */ + $value = $this->getValueObject('accounts'); + + return $value; + } + /** * @return ?string */ diff --git a/application/Espo/Repositories/User.php b/application/Espo/Repositories/User.php index 8b0cebcbde..9b688bec00 100644 --- a/application/Espo/Repositories/User.php +++ b/application/Espo/Repositories/User.php @@ -38,14 +38,17 @@ use Espo\Core\Utils\Json; use Espo\Repositories\UserData as UserDataRepository; use Espo\Entities\UserData; +use Espo\Entities\User as UserEntity; /** * @extends Database<\Espo\Entities\User> */ class User extends Database { + private const AUTHENTICATION_METHOD_HMAC = 'Hmac'; + /** - * @param \Espo\Entities\User $entity + * @param UserEntity $entity * @param array $options * @return void * @throws Conflict @@ -53,16 +56,16 @@ class User extends Database */ protected function beforeSave(Entity $entity, array $options = []) { - if ($entity->has('type') && !$entity->get('type')) { - $entity->set('type', 'regular'); + if ($entity->has('type') && !$entity->getType()) { + $entity->set('type', UserEntity::TYPE_REGULAR); } if ($entity->isApi()) { if ($entity->isAttributeChanged('userName')) { - $entity->set('lastName', $entity->get('userName')); + $entity->set('lastName', $entity->getUserName()); } - if ($entity->has('authMethod') && $entity->get('authMethod') !== 'Hmac') { + if ($entity->has('authMethod') && $entity->getAuthMethod() !== self::AUTHENTICATION_METHOD_HMAC) { $entity->clear('secretKey'); } } else { @@ -90,7 +93,7 @@ class User extends Database } if ($entity->isNew()) { - $userName = $entity->get('userName'); + $userName = $entity->getUserName(); if (empty($userName)) { throw new Error("Username can't be empty."); @@ -112,7 +115,7 @@ class User extends Database } } else { if ($entity->isAttributeChanged('userName')) { - $userName = $entity->get('userName'); + $userName = $entity->getUserName(); if (empty($userName)) { throw new Error("Username can't be empty."); diff --git a/application/Espo/Services/User.php b/application/Espo/Services/User.php index 3f8f1b0439..cf699d5f5d 100644 --- a/application/Espo/Services/User.php +++ b/application/Espo/Services/User.php @@ -34,6 +34,7 @@ use Espo\Entities\Email as EmailEntity; use Espo\Entities\PasswordChangeRequest; use Espo\Entities\Portal as PortalEntity; +use Espo\Modules\Crm\Entities\Contact; use Espo\Repositories\Portal as PortalRepository; use Espo\Core\Mail\Sender; @@ -75,6 +76,11 @@ class User extends Record implements use Di\FileManagerSetter; use Di\DataManagerSetter; + private const AUTHENTICATION_METHOD_ESPO = 'Espo'; + private const AUTHENTICATION_METHOD_HMAC = 'Hmac'; + + private const ID_SYSTEM = 'system'; + /** * @var string[] */ @@ -94,16 +100,19 @@ class User extends Record implements */ protected $allowedUserTypeList = ['regular', 'admin', 'portal', 'api']; + /** + * @throws Forbidden + */ public function getEntity(string $id): ?Entity { - if ($id == 'system') { + if ($id === self::ID_SYSTEM) { throw new Forbidden(); } /** @var ?UserEntity $entity */ $entity = parent::getEntity($id); - if ($entity && $entity->isSuperAdmin() && !$this->getUser()->isSuperAdmin()) { + if ($entity && $entity->isSuperAdmin() && !$this->user->isSuperAdmin()) { throw new Forbidden(); } @@ -114,6 +123,11 @@ class User extends Record implements return $entity; } + /** + * @throws Forbidden + * @throws Error + * @throws NotFound + */ public function changePassword( string $userId, string $password, @@ -122,18 +136,20 @@ class User extends Record implements ): void { /** @var ?UserEntity $user */ - $user = $this->getEntityManager()->getEntity('User', $userId); + $user = $this->entityManager->getEntityById(UserEntity::ENTITY_TYPE, $userId); if (!$user) { throw new NotFound(); } - if ($user->isSuperAdmin() && !$this->getUser()->isSuperAdmin()) { + if ($user->isSuperAdmin() && !$this->user->isSuperAdmin()) { throw new Forbidden(); } - if (!$user->isAdmin() && $this->getConfig()->get('authenticationMethod', 'Espo') !== 'Espo') { - throw new Forbidden(); + $authenticationMethod = $this->config->get('authenticationMethod', self::AUTHENTICATION_METHOD_ESPO); + + if (!$user->isAdmin() && $authenticationMethod !== self::AUTHENTICATION_METHOD_ESPO) { + throw new Forbidden("Authentication method is not `Espo`."); } if (empty($password)) { @@ -141,8 +157,8 @@ class User extends Record implements } if ($checkCurrentPassword) { - $u = $this->getEntityManager() - ->getRDBRepository('User') + $u = $this->entityManager + ->getRDBRepository(UserEntity::ENTITY_TYPE) ->where([ 'id' => $user->getId(), 'password' => $this->createPasswordHashUtil()->hash($currentPassword ?? ''), @@ -155,7 +171,7 @@ class User extends Record implements } if (!$this->checkPasswordStrength($password)) { - throw new Forbidden("Change password: Password is weak."); + throw new Forbidden("Password is weak."); } $validLength = $this->fieldValidationManager @@ -167,12 +183,12 @@ class User extends Record implements $user->set('password', $this->hashPassword($password)); - $this->getEntityManager()->saveEntity($user); + $this->entityManager->saveEntity($user); } public function checkPasswordStrength(string $password): bool { - $minLength = $this->getConfig()->get('passwordStrengthLength'); + $minLength = $this->config->get('passwordStrengthLength'); if ($minLength) { if (mb_strlen($password) < $minLength) { @@ -180,7 +196,7 @@ class User extends Record implements } } - $requiredLetterCount = $this->getConfig()->get('passwordStrengthLetterCount'); + $requiredLetterCount = $this->config->get('passwordStrengthLetterCount'); if ($requiredLetterCount) { $letterCount = 0; @@ -196,7 +212,7 @@ class User extends Record implements } } - $requiredNumberCount = $this->getConfig()->get('passwordStrengthNumberCount'); + $requiredNumberCount = $this->config->get('passwordStrengthNumberCount'); if ($requiredNumberCount) { $numberCount = 0; @@ -212,7 +228,7 @@ class User extends Record implements } } - $bothCases = $this->getConfig()->get('passwordStrengthBothCases'); + $bothCases = $this->config->get('passwordStrengthBothCases'); if ($bothCases) { $ucCount = 0; @@ -274,10 +290,10 @@ class User extends Record implements $id = $data->id; - $p = $this->getEntityManager()->getEntity(PasswordChangeRequest::ENTITY_TYPE, $id); + $p = $this->entityManager->getEntity(PasswordChangeRequest::ENTITY_TYPE, $id); if ($p) { - $this->getEntityManager()->removeEntity($p); + $this->entityManager->removeEntity($p); } } @@ -292,12 +308,12 @@ class User extends Record implements { parent::filterInput($data); - if (!$this->getUser()->isSuperAdmin()) { + if (!$this->user->isSuperAdmin()) { unset($data->isSuperAdmin); } - if (!$this->getUser()->isAdmin()) { - if (!$this->getAcl()->checkScope('Team')) { + if (!$this->user->isAdmin()) { + if (!$this->acl->checkScope('Team')) { unset($data->defaultTeamId); } } @@ -350,7 +366,7 @@ class User extends Record implements public function update(string $id, stdClass $data, UpdateParams $params): Entity { - if ($id == 'system') { + if ($id === self::ID_SYSTEM) { throw new Forbidden(); } @@ -366,7 +382,7 @@ class User extends Record implements $data->password = $this->hashPassword($data->password); } - if ($id == $this->getUser()->id) { + if ($id == $this->user->getId()) { unset($data->isActive); unset($data->isPortalUser); unset($data->type); @@ -394,8 +410,8 @@ class User extends Record implements parent::prepareEntityForOutput($entity); if ($entity->isApi()) { - if ($this->getUser()->isAdmin()) { - if ($entity->get('authMethod') === 'Hmac') { + if ($this->user->isAdmin()) { + if ($entity->getAuthMethod() === self::AUTHENTICATION_METHOD_HMAC) { $secretKey = $this->getSecretKeyForUserId($entity->getId()); $entity->set('secretKey', $secretKey); } @@ -413,6 +429,10 @@ class User extends Record implements return $apiKeyUtil->getSecretKeyForUserId($id); } + /** + * @throws Forbidden + * @throws NotFound + */ public function generateNewApiKeyForEntity(string $id): Entity { /** @var ?UserEntity $entity */ @@ -422,7 +442,7 @@ class User extends Record implements throw new NotFound(); } - if (!$this->getUser()->isAdmin()) { + if (!$this->user->isAdmin()) { throw new Forbidden(); } @@ -434,13 +454,13 @@ class User extends Record implements $entity->set('apiKey', $apiKey); - if ($entity->get('authMethod') === 'Hmac') { + if ($entity->getAuthMethod() === self::AUTHENTICATION_METHOD_HMAC) { $secretKey = Util::generateSecretKey(); $entity->set('secretKey', $secretKey); } - $this->getEntityManager()->saveEntity($entity); + $this->entityManager->saveEntity($entity); $this->prepareEntityForOutput($entity); @@ -504,6 +524,12 @@ class User extends Record implements $this->createRecoveryService()->createAndSendRequestForExistingUser($user); } + /** + * @throws Error + * @throws \Espo\Core\Mail\Exceptions\SendingError + * @throws Forbidden + * @throws NotFound + */ public function generateNewPasswordForUser(string $id, bool $allowNonAdmin = false): void { if (!$allowNonAdmin) { @@ -531,7 +557,7 @@ class User extends Record implements throw new Forbidden(); } - if (!$user->get('emailAddress')) { + if (!$user->getEmailAddress()) { throw new Forbidden( "Generate new password: Can't process because user doesn't have email address." ); @@ -569,54 +595,59 @@ class User extends Record implements protected function getInternalUserCount(): int { - return $this->getEntityManager() - ->getRDBRepository('User') + return $this->entityManager + ->getRDBRepository(UserEntity::ENTITY_TYPE) ->where([ 'isActive' => true, - 'type' => ['admin', 'regular'], - 'type!=' => 'system', + 'type' => [ + UserEntity::TYPE_ADMIN, + UserEntity::TYPE_REGULAR, + ], ]) ->count(); } protected function getPortalUserCount(): int { - return $this->getEntityManager() - ->getRDBRepository('User') + return $this->entityManager + ->getRDBRepository(UserEntity::ENTITY_TYPE) ->where([ 'isActive' => true, - 'type' => 'portal', + 'type' => UserEntity::TYPE_PORTAL, ]) ->count(); } + /** + * @throws Forbidden + */ protected function beforeCreateEntity(Entity $entity, $data) { /** @var UserEntity $entity */ if ( - $this->getConfig()->get('userLimit') && - !$this->getUser()->isSuperAdmin() && + $this->config->get('userLimit') && + !$this->user->isSuperAdmin() && !$entity->isPortal() && !$entity->isApi() ) { $userCount = $this->getInternalUserCount(); - if ($userCount >= $this->getConfig()->get('userLimit')) { + if ($userCount >= $this->config->get('userLimit')) { throw new Forbidden( - 'User limit '.$this->getConfig()->get('userLimit').' is reached.' + 'User limit '.$this->config->get('userLimit').' is reached.' ); } } if ( - $this->getConfig()->get('portalUserLimit') && - !$this->getUser()->isSuperAdmin() && + $this->config->get('portalUserLimit') && + !$this->user->isSuperAdmin() && $entity->isPortal() ) { $portalUserCount = $this->getPortalUserCount(); - if ($portalUserCount >= $this->getConfig()->get('portalUserLimit')) { + if ($portalUserCount >= $this->config->get('portalUserLimit')) { throw new Forbidden( - 'Portal user limit ' . $this->getConfig()->get('portalUserLimit').' is reached.' + 'Portal user limit ' . $this->config->get('portalUserLimit').' is reached.' ); } } @@ -626,7 +657,7 @@ class User extends Record implements $entity->set('apiKey', $apiKey); - if ($entity->get('authMethod') === 'Hmac') { + if ($entity->getAuthMethod() === self::AUTHENTICATION_METHOD_HMAC) { $secretKey = Util::generateSecretKey(); $entity->set('secretKey', $secretKey); @@ -635,50 +666,68 @@ class User extends Record implements if (!$entity->isSuperAdmin()) { if ( - $entity->get('type') && - !in_array($entity->get('type'), $this->allowedUserTypeList) + $entity->getType() && + !in_array($entity->getType(), $this->allowedUserTypeList) ) { throw new Forbidden(); } } } + /** + * @throws Forbidden + */ protected function beforeUpdateEntity(Entity $entity, $data) { /** @var UserEntity $entity */ - if ($this->getConfig()->get('userLimit') && !$this->getUser()->isSuperAdmin()) { + if ($this->config->get('userLimit') && !$this->user->isSuperAdmin()) { if ( ( - $entity->get('isActive') && $entity->isAttributeChanged('isActive') && - !$entity->isPortal() && !$entity->isApi() - ) - || + $entity->isActive() && + $entity->isAttributeChanged('isActive') && + !$entity->isPortal() && + !$entity->isApi() + ) || ( - !$entity->isPortal() && !$entity->isApi() && $entity->isAttributeChanged('type') && - ($entity->isRegular() || $entity->isAdmin()) && - ($entity->getFetched('type') == 'portal' || $entity->getFetched('type') == 'api') + !$entity->isPortal() && + !$entity->isApi() && + $entity->isAttributeChanged('type') && + ( + $entity->isRegular() || + $entity->isAdmin() + ) && + ( + $entity->getFetched('type') == UserEntity::TYPE_PORTAL || + $entity->getFetched('type') == UserEntity::TYPE_API + ) ) ) { $userCount = $this->getInternalUserCount(); - if ($userCount >= $this->getConfig()->get('userLimit')) { - throw new Forbidden('User limit '.$this->getConfig()->get('userLimit').' is reached.'); + if ($userCount >= $this->config->get('userLimit')) { + throw new Forbidden('User limit '.$this->config->get('userLimit').' is reached.'); } } } - if ($this->getConfig()->get('portalUserLimit') && !$this->getUser()->isSuperAdmin()) { + if ($this->config->get('portalUserLimit') && !$this->user->isSuperAdmin()) { if ( - ($entity->get('isActive') && $entity->isAttributeChanged('isActive') && $entity->isPortal()) - || - ($entity->isPortal() && $entity->isAttributeChanged('type')) + ( + $entity->isActive() && + $entity->isAttributeChanged('isActive') && + $entity->isPortal() + ) || + ( + $entity->isPortal() && + $entity->isAttributeChanged('type') + ) ) { $portalUserCount = $this->getPortalUserCount(); - if ($portalUserCount >= $this->getConfig()->get('portalUserLimit')) { + if ($portalUserCount >= $this->config->get('portalUserLimit')) { throw new Forbidden( - 'Portal user limit '. $this->getConfig()->get('portalUserLimit').' is reached.' + 'Portal user limit '. $this->config->get('portalUserLimit').' is reached.' ); } } @@ -687,7 +736,7 @@ class User extends Record implements if ($entity->isApi()) { if ( $entity->isAttributeChanged('authMethod') && - $entity->get('authMethod') === 'Hmac' + $entity->getAuthMethod() === self::AUTHENTICATION_METHOD_HMAC ) { $secretKey = Util::generateSecretKey(); @@ -698,8 +747,8 @@ class User extends Record implements if (!$entity->isSuperAdmin()) { if ( $entity->isAttributeChanged('type') && - $entity->get('type') && - !in_array($entity->get('type'), $this->allowedUserTypeList) + $entity->getType() && + !in_array($entity->getType(), $this->allowedUserTypeList) ) { throw new Forbidden(); } @@ -730,8 +779,10 @@ class User extends Record implements $siteUrl = $this->config->getSiteUrl() . '/' . $urlSuffix; if ($user->isPortal()) { - $subjectTpl = $this->templateFileManager->getTemplate('accessInfoPortal', 'subject', 'User'); - $bodyTpl = $this->templateFileManager->getTemplate('accessInfoPortal', 'body', 'User'); + $subjectTpl = $this->templateFileManager + ->getTemplate('accessInfoPortal', 'subject', UserEntity::ENTITY_TYPE); + $bodyTpl = $this->templateFileManager + ->getTemplate('accessInfoPortal', 'body', UserEntity::ENTITY_TYPE); $urlList = []; @@ -761,24 +812,28 @@ class User extends Record implements return [$subjectTpl, $bodyTpl, $data]; } - $subjectTpl = $this->templateFileManager->getTemplate('accessInfo', 'subject', 'User'); - $bodyTpl = $this->templateFileManager->getTemplate('accessInfo', 'body', 'User'); + $subjectTpl = $this->templateFileManager->getTemplate('accessInfo', 'subject', UserEntity::ENTITY_TYPE); + $bodyTpl = $this->templateFileManager->getTemplate('accessInfo', 'body', UserEntity::ENTITY_TYPE); $data['siteUrl'] = $siteUrl; return [$subjectTpl, $bodyTpl, $data]; } + /** + * @throws \Espo\Core\Mail\Exceptions\SendingError + * @throws Error + */ protected function sendPassword(UserEntity $user, string $password): void { - $emailAddress = $user->get('emailAddress'); + $emailAddress = $user->getEmailAddress(); if (empty($emailAddress)) { return; } /** @var EmailEntity $email */ - $email = $this->getEntityManager()->getEntity('Email'); + $email = $this->entityManager->getNewEntity(EmailEntity::ENTITY_TYPE); if (!$this->isSmtpConfigured()) { return; @@ -828,11 +883,11 @@ class User extends Record implements public function delete(string $id, DeleteParams $params): void { - if ($id == 'system') { + if ($id === self::ID_SYSTEM) { throw new Forbidden(); } - if ($id == $this->getUser()->id) { + if ($id == $this->user->getId()) { throw new Forbidden(); } @@ -865,14 +920,15 @@ class User extends Record implements } if ( - $entity->isPortal() && $entity->get('contactId') && + $entity->isPortal() && + $entity->getContactId() && ( property_exists($data, 'firstName') || property_exists($data, 'lastName') || property_exists($data, 'salutationName') ) ) { - $contact = $this->getEntityManager()->getEntity('Contact', $entity->get('contactId')); + $contact = $this->entityManager->getEntityById(Contact::ENTITY_TYPE, $entity->getContactId()); if ($contact) { if (property_exists($data, 'firstName')) { @@ -887,7 +943,7 @@ class User extends Record implements $contact->set('salutationName', $data->salutationName); } - $this->getEntityManager()->saveEntity($contact); + $this->entityManager->saveEntity($contact); } } } @@ -908,6 +964,10 @@ class User extends Record implements $this->dataManager->updateCacheTimestamp(); } + /** + * @throws Error + * @throws \Espo\Core\Mail\Exceptions\SendingError + */ public function sendAccessInfoNew(UserEntity $user): void { $primaryAddress = $user->getEmailAddressGroup()->getPrimary();