<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Security\Model; use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress; use \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory; /** * Admin Sessions Manager Model * * @api * @since 100.1.0 */ class AdminSessionsManager { /** * Admin Session lifetime (sec) */ const ADMIN_SESSION_LIFETIME = 86400; /** * Logout reason when current user has been locked out */ const LOGOUT_REASON_USER_LOCKED = 10; /** * @var ConfigInterface * @since 100.1.0 */ protected $securityConfig; /** * @var \Magento\Backend\Model\Auth\Session * @since 100.1.0 */ protected $authSession; /** * @var AdminSessionInfoFactory * @since 100.1.0 */ protected $adminSessionInfoFactory; /** * @var \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory * @since 100.1.0 */ protected $adminSessionInfoCollectionFactory; /** * @var \Magento\Security\Model\AdminSessionInfo * @since 100.1.0 */ protected $currentSession; /** * @var \Magento\Framework\Stdlib\DateTime\DateTime */ private $dateTime; /** * @var RemoteAddress */ private $remoteAddress; /** * Max lifetime for session prolong to be valid (sec) * * Means that after session was prolonged * all other prolongs will be ignored within this period */ private $maxIntervalBetweenConsecutiveProlongs = 60; /** * @param ConfigInterface $securityConfig * @param \Magento\Backend\Model\Auth\Session $authSession * @param AdminSessionInfoFactory $adminSessionInfoFactory * @param CollectionFactory $adminSessionInfoCollectionFactory * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param RemoteAddress $remoteAddress */ public function __construct( ConfigInterface $securityConfig, \Magento\Backend\Model\Auth\Session $authSession, \Magento\Security\Model\AdminSessionInfoFactory $adminSessionInfoFactory, \Magento\Security\Model\ResourceModel\AdminSessionInfo\CollectionFactory $adminSessionInfoCollectionFactory, \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, RemoteAddress $remoteAddress ) { $this->securityConfig = $securityConfig; $this->authSession = $authSession; $this->adminSessionInfoFactory = $adminSessionInfoFactory; $this->adminSessionInfoCollectionFactory = $adminSessionInfoCollectionFactory; $this->dateTime = $dateTime; $this->remoteAddress = $remoteAddress; } /** * Handle all others active sessions according Sharing Account Setting * * @return $this * @since 100.1.0 */ public function processLogin() { $this->createNewSession(); $olderThen = $this->dateTime->gmtTimestamp() - $this->securityConfig->getAdminSessionLifetime(); if (!$this->securityConfig->isAdminAccountSharingEnabled()) { $result = $this->createAdminSessionInfoCollection()->updateActiveSessionsStatus( AdminSessionInfo::LOGGED_OUT_BY_LOGIN, $this->getCurrentSession()->getUserId(), $this->getCurrentSession()->getSessionId(), $olderThen ); if ($result) { $this->getCurrentSession()->setIsOtherSessionsTerminated(true); } } return $this; } /** * Handle Prolong process * * @return $this * @since 100.1.0 */ public function processProlong() { if ($this->lastProlongIsOldEnough()) { $this->getCurrentSession()->setData( 'updated_at', date( \Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT, $this->authSession->getUpdatedAt() ) ); $this->getCurrentSession()->save(); } return $this; } /** * Handle logout process * * @return $this * @since 100.1.0 */ public function processLogout() { $this->getCurrentSession()->setData( 'status', AdminSessionInfo::LOGGED_OUT ); $this->getCurrentSession()->save(); return $this; } /** * Get current session record * * @return AdminSessionInfo * @since 100.1.0 */ public function getCurrentSession() { if (!$this->currentSession) { $this->currentSession = $this->adminSessionInfoFactory->create(); $this->currentSession->load($this->authSession->getSessionId(), 'session_id'); } return $this->currentSession; } /** * Get logout reason message by status * * @param int $statusCode * @return string * @since 100.1.0 */ public function getLogoutReasonMessageByStatus($statusCode) { switch ((int)$statusCode) { case AdminSessionInfo::LOGGED_IN: $reasonMessage = null; break; case AdminSessionInfo::LOGGED_OUT_BY_LOGIN: $reasonMessage = __( 'Someone logged into this account from another device or browser.' .' Your current session is terminated.' ); break; case AdminSessionInfo::LOGGED_OUT_MANUALLY: $reasonMessage = __( 'Your current session is terminated by another user of this account.' ); break; case self::LOGOUT_REASON_USER_LOCKED: $reasonMessage = __( 'Your account is temporarily disabled. Please try again later.' ); break; default: $reasonMessage = __('Your current session has been expired.'); break; } return $reasonMessage; } /** * Get message with explanation of logout reason * * @return string * @since 100.1.0 */ public function getLogoutReasonMessage() { return $this->getLogoutReasonMessageByStatus( $this->getCurrentSession()->getStatus() ); } /** * Get sessions for current user * * @return \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection * @since 100.1.0 */ public function getSessionsForCurrentUser() { return $this->createAdminSessionInfoCollection() ->filterByUser($this->authSession->getUser()->getId(), \Magento\Security\Model\AdminSessionInfo::LOGGED_IN) ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()) ->loadData(); } /** * Logout another user sessions * * @return $this * @since 100.1.0 */ public function logoutOtherUserSessions() { $collection = $this->createAdminSessionInfoCollection() ->filterByUser( $this->authSession->getUser()->getId(), \Magento\Security\Model\AdminSessionInfo::LOGGED_IN, $this->authSession->getSessionId() ) ->filterExpiredSessions($this->securityConfig->getAdminSessionLifetime()) ->loadData(); $collection->setDataToAll('status', \Magento\Security\Model\AdminSessionInfo::LOGGED_OUT_MANUALLY) ->save(); return $this; } /** * Clean expired Admin Sessions * * @return $this * @since 100.1.0 */ public function cleanExpiredSessions() { $this->createAdminSessionInfoCollection()->deleteSessionsOlderThen( $this->dateTime->gmtTimestamp() - self::ADMIN_SESSION_LIFETIME ); return $this; } /** * Create new record * * @return $this * @since 100.1.0 */ protected function createNewSession() { $this->adminSessionInfoFactory ->create() ->setData( [ 'session_id' => $this->authSession->getSessionId(), 'user_id' => $this->authSession->getUser()->getId(), 'ip' => $this->remoteAddress->getRemoteAddress(), 'status' => AdminSessionInfo::LOGGED_IN ] )->save(); return $this; } /** * @return \Magento\Security\Model\ResourceModel\AdminSessionInfo\Collection * @since 100.1.0 */ protected function createAdminSessionInfoCollection() { return $this->adminSessionInfoCollectionFactory->create(); } /** * Calculates diff between now and last session updated_at * and decides whether new prolong must be triggered or not * * This is done to limit amount of session prolongs and updates to database * within some period of time - X * X - is calculated in getIntervalBetweenConsecutiveProlongs() * * @see getIntervalBetweenConsecutiveProlongs() * @return bool */ private function lastProlongIsOldEnough() { $lastProlongTimestamp = strtotime($this->getCurrentSession()->getUpdatedAt()); $nowTimestamp = $this->authSession->getUpdatedAt(); $diff = $nowTimestamp - $lastProlongTimestamp; return (float) $diff > $this->getIntervalBetweenConsecutiveProlongs(); } /** * Calculates lifetime for session prolong to be valid * * Calculation is based on admin session lifetime * Calculated result is in seconds and is in the interval * between 1 (including) and MAX_INTERVAL_BETWEEN_CONSECUTIVE_PROLONGS (including) * * @return float */ private function getIntervalBetweenConsecutiveProlongs() { return (float) max( 1, min( 4 * log((float)$this->securityConfig->getAdminSessionLifetime()), $this->maxIntervalBetweenConsecutiveProlongs ) ); } }