mirror of
https://github.com/bdnugget/magento2.git
synced 2026-11-07 09:17:35 +00:00
682 lines
19 KiB
PHP
682 lines
19 KiB
PHP
<?php
|
|
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Magento\User\Model\ResourceModel;
|
|
|
|
use Laminas\Validator\Callback;
|
|
use Laminas\Validator\ValidatorInterface;
|
|
use Magento\Authorization\Model\Acl\Role\Group as RoleGroup;
|
|
use Magento\Authorization\Model\Acl\Role\User as RoleUser;
|
|
use Magento\Authorization\Model\UserContextInterface;
|
|
use Magento\Framework\Acl\Data\CacheInterface;
|
|
use Magento\Framework\App\ObjectManager;
|
|
use Magento\Framework\Exception\LocalizedException;
|
|
use Magento\Framework\Model\AbstractModel;
|
|
use Magento\User\Model\Backend\Config\ObserverConfig;
|
|
use Magento\User\Model\User as ModelUser;
|
|
use Magento\Framework\Encryption\EncryptorInterface;
|
|
|
|
/**
|
|
* ACL user resource
|
|
*
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
|
* @api
|
|
* @since 100.0.2
|
|
*/
|
|
class User extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
|
|
{
|
|
/**
|
|
* Role model
|
|
*
|
|
* @var \Magento\Authorization\Model\RoleFactory
|
|
*/
|
|
protected $_roleFactory;
|
|
|
|
/**
|
|
* @var \Magento\Framework\Stdlib\DateTime
|
|
*/
|
|
protected $dateTime;
|
|
|
|
/**
|
|
* @var CacheInterface
|
|
*/
|
|
private $aclDataCache;
|
|
|
|
/**
|
|
* @var ObserverConfig|null
|
|
*/
|
|
private $observerConfig;
|
|
|
|
/**
|
|
* @var EncryptorInterface|null
|
|
*/
|
|
private $encryptor;
|
|
|
|
/**
|
|
* Construct
|
|
*
|
|
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
|
|
* @param \Magento\Authorization\Model\RoleFactory $roleFactory
|
|
* @param \Magento\Framework\Stdlib\DateTime $dateTime
|
|
* @param string $connectionName
|
|
* @param CacheInterface $aclDataCache
|
|
* @param ObserverConfig|null $observerConfig
|
|
* @param EncryptorInterface|null $encryptor
|
|
*/
|
|
public function __construct(
|
|
\Magento\Framework\Model\ResourceModel\Db\Context $context,
|
|
\Magento\Authorization\Model\RoleFactory $roleFactory,
|
|
\Magento\Framework\Stdlib\DateTime $dateTime,
|
|
$connectionName = null,
|
|
CacheInterface $aclDataCache = null,
|
|
ObserverConfig $observerConfig = null,
|
|
EncryptorInterface $encryptor = null
|
|
) {
|
|
parent::__construct($context, $connectionName);
|
|
$this->_roleFactory = $roleFactory;
|
|
$this->dateTime = $dateTime;
|
|
$this->aclDataCache = $aclDataCache ?: ObjectManager::getInstance()->get(CacheInterface::class);
|
|
$this->observerConfig = $observerConfig ?: ObjectManager::getInstance()->get(ObserverConfig::class);
|
|
$this->encryptor = $encryptor ?? ObjectManager::getInstance()->get(EncryptorInterface::class);
|
|
}
|
|
|
|
/**
|
|
* Define main table
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _construct()
|
|
{
|
|
$this->_init('admin_user', 'user_id');
|
|
}
|
|
|
|
/**
|
|
* Initialize unique fields
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function _initUniqueFields()
|
|
{
|
|
$this->_uniqueFields = [
|
|
['field' => 'email', 'title' => __('Email')],
|
|
['field' => 'username', 'title' => __('User Name')],
|
|
];
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Authenticate user by $username and $password
|
|
*
|
|
* @param ModelUser $user
|
|
* @return $this
|
|
*/
|
|
public function recordLogin(ModelUser $user)
|
|
{
|
|
$connection = $this->getConnection();
|
|
|
|
$data = [
|
|
'logdate' => (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT),
|
|
'lognum' => $user->getLognum() + 1,
|
|
];
|
|
|
|
$user->setLogdate($data['logdate']);
|
|
$user->setLognum($data['lognum']);
|
|
|
|
$condition = ['user_id = ?' => (int)$user->getUserId()];
|
|
|
|
$connection->update($this->getMainTable(), $data, $condition);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load data by specified username
|
|
*
|
|
* @param string $username
|
|
* @return array
|
|
*/
|
|
public function loadByUsername($username)
|
|
{
|
|
$connection = $this->getConnection();
|
|
|
|
$select = $connection->select()->from($this->getMainTable())->where('username=:username');
|
|
|
|
$binds = ['username' => $username];
|
|
|
|
return $connection->fetchRow($select, $binds);
|
|
}
|
|
|
|
/**
|
|
* Check if user is assigned to any role
|
|
*
|
|
* @param int|ModelUser $user
|
|
* @return null|array
|
|
*/
|
|
public function hasAssigned2Role($user)
|
|
{
|
|
if (is_numeric($user)) {
|
|
$userId = $user;
|
|
} elseif ($user instanceof AbstractModel) {
|
|
$userId = $user->getUserId();
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
if ($userId > 0) {
|
|
$connection = $this->getConnection();
|
|
|
|
$select = $connection->select();
|
|
$select->from($this->getTable('authorization_role'))
|
|
->where('parent_id > :parent_id')
|
|
->where('user_id = :user_id')
|
|
->where('user_type = :user_type');
|
|
|
|
$binds = ['parent_id' => 0, 'user_id' => $userId,
|
|
'user_type' => UserContextInterface::USER_TYPE_ADMIN
|
|
];
|
|
|
|
return $connection->fetchAll($select, $binds);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
protected function _beforeSave(AbstractModel $user)
|
|
{
|
|
if ($user->hasRoleId()) {
|
|
$user->setReloadAclFlag(1);
|
|
}
|
|
if ($user->getData('rp_token')) {
|
|
$rpToken = $user->getData('rp_token');
|
|
$user->setRpToken($this->encryptor->encrypt($rpToken));
|
|
}
|
|
|
|
return parent::_beforeSave($user);
|
|
}
|
|
|
|
/**
|
|
* Unserialize user extra data after user save
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return $this
|
|
*/
|
|
protected function _afterSave(AbstractModel $user)
|
|
{
|
|
$user->setExtra($this->getSerializer()->unserialize($user->getExtra()));
|
|
if ($user->hasRoleId()) {
|
|
$this->_clearUserRoles($user);
|
|
$this->_createUserRole($user->getRoleId(), $user);
|
|
}
|
|
if ($user->getData('rp_token')) {
|
|
$rpToken = $user->getData('rp_token');
|
|
$user->setRpToken($this->encryptor->decrypt($rpToken));
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Clear all user-specific roles of provided user
|
|
*
|
|
* @param ModelUser $user
|
|
* @return void
|
|
*/
|
|
public function _clearUserRoles(ModelUser $user)
|
|
{
|
|
$conditions = ['user_id = ?' => (int)$user->getId(), 'user_type = ?' => UserContextInterface::USER_TYPE_ADMIN];
|
|
$this->getConnection()->delete($this->getTable('authorization_role'), $conditions);
|
|
}
|
|
|
|
/**
|
|
* Create role for provided user of provided type
|
|
*
|
|
* @param int $parentId
|
|
* @param ModelUser $user
|
|
* @return void
|
|
*/
|
|
protected function _createUserRole($parentId, ModelUser $user)
|
|
{
|
|
if ($parentId > 0) {
|
|
/** @var \Magento\Authorization\Model\Role $parentRole */
|
|
$parentRole = $this->_roleFactory->create()->load($parentId);
|
|
if ($parentRole->getId()) {
|
|
$data = new \Magento\Framework\DataObject(
|
|
[
|
|
'parent_id' => $parentRole->getId(),
|
|
'tree_level' => $parentRole->getTreeLevel() + 1,
|
|
'sort_order' => 0,
|
|
'role_type' => RoleUser::ROLE_TYPE,
|
|
'user_id' => $user->getId(),
|
|
'user_type' => UserContextInterface::USER_TYPE_ADMIN,
|
|
'role_name' => $user->getFirstName(),
|
|
]
|
|
);
|
|
|
|
$insertData = $this->_prepareDataForTable($data, $this->getTable('authorization_role'));
|
|
$this->getConnection()->insert($this->getTable('authorization_role'), $insertData);
|
|
$this->aclDataCache->clean();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unserialize user extra data after user load
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return $this
|
|
*/
|
|
protected function _afterLoad(AbstractModel $user)
|
|
{
|
|
if (is_string($user->getExtra())) {
|
|
$user->setExtra($this->getSerializer()->unserialize($user->getExtra()));
|
|
}
|
|
if ($user->getData('rp_token')) {
|
|
$rpToken = $user->getData('rp_token');
|
|
$user->setRpToken($this->encryptor->decrypt($rpToken));
|
|
}
|
|
return parent::_afterLoad($user);
|
|
}
|
|
|
|
/**
|
|
* Delete user role record with user
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return bool
|
|
* @throws LocalizedException
|
|
*/
|
|
public function delete(AbstractModel $user)
|
|
{
|
|
$uid = $user->getId();
|
|
if (!$uid) {
|
|
return false;
|
|
}
|
|
|
|
$this->_beforeDelete($user);
|
|
$connection = $this->getConnection();
|
|
$connection->beginTransaction();
|
|
try {
|
|
$connection->delete($this->getMainTable(), ['user_id = ?' => $uid]);
|
|
$connection->delete(
|
|
$this->getTable('authorization_role'),
|
|
['user_id = ?' => $uid, 'user_type = ?' => UserContextInterface::USER_TYPE_ADMIN]
|
|
);
|
|
} catch (LocalizedException $e) {
|
|
$connection->rollBack();
|
|
|
|
return false;
|
|
}
|
|
|
|
$connection->commit();
|
|
$this->_afterDelete($user);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get user roles
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return array
|
|
*/
|
|
public function getRoles(AbstractModel $user)
|
|
{
|
|
if (!$user->getId()) {
|
|
return [];
|
|
}
|
|
|
|
$table = $this->getTable('authorization_role');
|
|
$connection = $this->getConnection();
|
|
|
|
$select = $connection->select()->from(
|
|
$table,
|
|
[]
|
|
)->joinLeft(
|
|
['ar' => $table],
|
|
"(ar.role_id = {$table}.parent_id and ar.role_type = '" . RoleGroup::ROLE_TYPE . "')",
|
|
['role_id']
|
|
)->where(
|
|
"{$table}.user_id = :user_id"
|
|
)->where(
|
|
"{$table}.user_type = :user_type"
|
|
);
|
|
|
|
$binds = ['user_id' => (int)$user->getId(),
|
|
'user_type' => UserContextInterface::USER_TYPE_ADMIN
|
|
];
|
|
|
|
$roles = $connection->fetchCol($select, $binds);
|
|
|
|
if ($roles) {
|
|
return $roles;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Delete user role
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return $this
|
|
*/
|
|
public function deleteFromRole(AbstractModel $user)
|
|
{
|
|
if ($user->getUserId() <= 0) {
|
|
return $this;
|
|
}
|
|
if ($user->getRoleId() <= 0) {
|
|
return $this;
|
|
}
|
|
|
|
$dbh = $this->getConnection();
|
|
|
|
$condition = [
|
|
'user_id = ?' => (int)$user->getId(),
|
|
'parent_id = ?' => (int)$user->getRoleId(),
|
|
'user_type = ?' => UserContextInterface::USER_TYPE_ADMIN
|
|
];
|
|
|
|
$dbh->delete($this->getTable('authorization_role'), $condition);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Check if role user exists
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return array
|
|
*/
|
|
public function roleUserExists(AbstractModel $user)
|
|
{
|
|
if ($user->getUserId() > 0) {
|
|
$roleTable = $this->getTable('authorization_role');
|
|
|
|
$dbh = $this->getConnection();
|
|
|
|
$binds = [
|
|
'parent_id' => $user->getRoleId(),
|
|
'user_id' => $user->getUserId(),
|
|
'user_type' => UserContextInterface::USER_TYPE_ADMIN
|
|
];
|
|
|
|
$select = $dbh->select()->from($roleTable)
|
|
->where('parent_id = :parent_id')
|
|
->where('user_type = :user_type')
|
|
->where('user_id = :user_id');
|
|
|
|
return $dbh->fetchCol($select, $binds);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if user exists
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return array
|
|
*/
|
|
public function userExists(AbstractModel $user)
|
|
{
|
|
$connection = $this->getConnection();
|
|
$select = $connection->select();
|
|
|
|
$binds = [
|
|
'username' => $user->getUsername(),
|
|
'email' => $user->getEmail(),
|
|
'user_id' => (int)$user->getId(),
|
|
];
|
|
|
|
$select->from(
|
|
$this->getMainTable()
|
|
)->where(
|
|
'(username = :username OR email = :email)'
|
|
)->where(
|
|
'user_id <> :user_id'
|
|
);
|
|
|
|
return $connection->fetchRow($select, $binds);
|
|
}
|
|
|
|
/**
|
|
* Whether a user's identity is confirmed
|
|
*
|
|
* @param AbstractModel $user
|
|
* @return bool
|
|
*/
|
|
public function isUserUnique(AbstractModel $user)
|
|
{
|
|
return !$this->userExists($user);
|
|
}
|
|
|
|
/**
|
|
* Save user extra data
|
|
*
|
|
* @param AbstractModel $object
|
|
* @param string $data
|
|
* @return $this
|
|
*/
|
|
public function saveExtra($object, $data)
|
|
{
|
|
if ($object->getId()) {
|
|
$this->getConnection()->update(
|
|
$this->getMainTable(),
|
|
['extra' => $data],
|
|
['user_id = ?' => (int)$object->getId()]
|
|
);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the total user count bypassing any filters applied to collections
|
|
*
|
|
* @return int
|
|
*/
|
|
public function countAll()
|
|
{
|
|
$connection = $this->getConnection();
|
|
$select = $connection->select();
|
|
$select->from($this->getMainTable(), 'COUNT(*)');
|
|
$result = (int)$connection->fetchOne($select);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Add validation rules to be applied before saving an entity
|
|
*
|
|
* @return ValidatorInterface $validator
|
|
*/
|
|
public function getValidationRulesBeforeSave()
|
|
{
|
|
$userIdentity = new Callback([$this, 'isUserUnique']);
|
|
$userIdentity->setMessage(
|
|
__('A user with the same user name or email already exists.'),
|
|
Callback::INVALID_VALUE
|
|
);
|
|
|
|
return $userIdentity;
|
|
}
|
|
|
|
/**
|
|
* Update role users ACL
|
|
*
|
|
* @param \Magento\Authorization\Model\Role $role
|
|
* @return bool
|
|
*/
|
|
public function updateRoleUsersAcl(\Magento\Authorization\Model\Role $role)
|
|
{
|
|
$connection = $this->getConnection();
|
|
$users = $role->getRoleUsers();
|
|
$rowsCount = 0;
|
|
|
|
if (count($users) > 0) {
|
|
$bind = ['reload_acl_flag' => 1];
|
|
$where = ['user_id IN(?)' => $users];
|
|
$rowsCount = $connection->update($this->getTable('admin_user'), $bind, $where);
|
|
}
|
|
|
|
return $rowsCount > 0;
|
|
}
|
|
|
|
/**
|
|
* Unlock specified user record(s)
|
|
*
|
|
* @param int|int[] $userIds
|
|
* @return int number of affected rows
|
|
*/
|
|
public function unlock($userIds)
|
|
{
|
|
if (!is_array($userIds)) {
|
|
$userIds = [$userIds];
|
|
}
|
|
return $this->getConnection()->update(
|
|
$this->getMainTable(),
|
|
['failures_num' => 0, 'first_failure' => null, 'lock_expires' => null],
|
|
$this->getIdFieldName() . ' IN (' . $this->getConnection()->quote($userIds) . ')'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Lock specified user record(s)
|
|
*
|
|
* @param int|int[] $userIds
|
|
* @param int $exceptId
|
|
* @param int $lifetime
|
|
* @return int number of affected rows
|
|
*/
|
|
public function lock($userIds, $exceptId, $lifetime)
|
|
{
|
|
if (!is_array($userIds)) {
|
|
$userIds = [$userIds];
|
|
}
|
|
$exceptId = (int)$exceptId;
|
|
return $this->getConnection()->update(
|
|
$this->getMainTable(),
|
|
['lock_expires' => $this->dateTime->formatDate(time() + $lifetime)],
|
|
"{$this->getIdFieldName()} IN (" . $this->getConnection()->quote(
|
|
$userIds
|
|
) . ")\n AND {$this->getIdFieldName()} <> {$exceptId}"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Increment failures count along with updating lock expire and first failure dates
|
|
*
|
|
* @param ModelUser $user
|
|
* @param int|bool $setLockExpires
|
|
* @param int|bool $setFirstFailure
|
|
* @return void
|
|
*/
|
|
public function updateFailure($user, $setLockExpires = false, $setFirstFailure = false)
|
|
{
|
|
$update = ['failures_num' => new \Zend_Db_Expr('failures_num + 1')];
|
|
if (false !== $setFirstFailure) {
|
|
$update['first_failure'] = $this->dateTime->formatDate($setFirstFailure);
|
|
$update['failures_num'] = 1;
|
|
}
|
|
if (false !== $setLockExpires) {
|
|
$update['lock_expires'] = $this->dateTime->formatDate($setLockExpires);
|
|
}
|
|
$this->getConnection()->update(
|
|
$this->getMainTable(),
|
|
$update,
|
|
$this->getConnection()->quoteInto("{$this->getIdFieldName()} = ?", $user->getId())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Purge and get remaining old password hashes
|
|
*
|
|
* @param ModelUser $user
|
|
* @param int $retainLimit
|
|
* @return array
|
|
*/
|
|
public function getOldPasswords($user, $retainLimit = 4)
|
|
{
|
|
$userId = (int)$user->getId();
|
|
$table = $this->getTable('admin_passwords');
|
|
|
|
// purge expired passwords, except those which should be retained
|
|
$retainPasswordIds = $this->getConnection()->fetchCol(
|
|
$this->getConnection()
|
|
->select()
|
|
->from($table, 'password_id')
|
|
->where('user_id = :user_id')
|
|
->order('password_id ' . \Magento\Framework\DB\Select::SQL_DESC)
|
|
->limit($retainLimit),
|
|
[':user_id' => $userId]
|
|
);
|
|
$where = [
|
|
'user_id = ?' => $userId,
|
|
'last_updated <= ?' => time() - $this->observerConfig->getAdminPasswordLifetime()
|
|
];
|
|
if ($retainPasswordIds) {
|
|
$where['password_id NOT IN (?)'] = $retainPasswordIds;
|
|
}
|
|
$this->getConnection()->delete($table, $where);
|
|
|
|
// get all remaining passwords
|
|
return $this->getConnection()->fetchCol(
|
|
$this->getConnection()
|
|
->select()
|
|
->from($table, 'password_hash')
|
|
->where('user_id = :user_id'),
|
|
[':user_id' => $userId]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Remember a password hash for further usage
|
|
*
|
|
* @param ModelUser $user
|
|
* @param string $passwordHash
|
|
* @param int $lifetime deprecated, password expiration date doesn't save anymore,
|
|
* it is calculated in runtime based on password created date and lifetime config value
|
|
* @return void
|
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
|
*
|
|
* @see \Magento\User\Model\Backend\Config\ObserverConfig::_isLatestPasswordExpired()
|
|
*/
|
|
public function trackPassword($user, $passwordHash, $lifetime = 0)
|
|
{
|
|
$this->getConnection()->insert(
|
|
$this->getTable('admin_passwords'),
|
|
[
|
|
'user_id' => $user->getId(),
|
|
'password_hash' => $passwordHash,
|
|
'last_updated' => time()
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get latest password for specified user id
|
|
*
|
|
* Possible false positive when password was changed several times with different lifetime configuration
|
|
*
|
|
* @param int $userId
|
|
* @return array
|
|
*/
|
|
public function getLatestPassword($userId)
|
|
{
|
|
return $this->getConnection()->fetchRow(
|
|
$this->getConnection()
|
|
->select()
|
|
->from($this->getTable('admin_passwords'))
|
|
->where('user_id = :user_id')
|
|
->order('password_id ' . \Magento\Framework\DB\Select::SQL_DESC)
|
|
->limit(1),
|
|
[':user_id' => $userId]
|
|
);
|
|
}
|
|
}
|