This commit is contained in:
Yuri Kuznetsov
2022-07-15 11:50:39 +03:00
parent 6779684834
commit d67eba2684
3 changed files with 171 additions and 82 deletions
+26
View File
@@ -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
*/
+10 -7
View File
@@ -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<string,mixed> $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.");
+135 -75
View File
@@ -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();