TrustedManager.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?php
  2. /**
  3. * MageSpecialist
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to info@magespecialist.it so we can send you a copy immediately.
  14. *
  15. * @category MSP
  16. * @package MSP_TwoFactorAuth
  17. * @copyright Copyright (c) 2017 Skeeller srl (http://www.magespecialist.it)
  18. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  19. */
  20. namespace MSP\TwoFactorAuth\Model;
  21. use Magento\Backend\Model\Auth\Session;
  22. use Magento\Framework\App\RequestInterface;
  23. use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
  24. use Magento\Framework\Json\Decoder;
  25. use Magento\Framework\Json\Encoder;
  26. use Magento\Framework\Stdlib\DateTime\DateTime;
  27. use Magento\User\Model\User;
  28. use MSP\TwoFactorAuth\Api\TfaInterface;
  29. use MSP\TwoFactorAuth\Api\TrustedManagerInterface;
  30. use MSP\TwoFactorAuth\Api\TrustedRepositoryInterface;
  31. use MSP\TwoFactorAuth\Model\ResourceModel\Trusted as TrustedResourceModel;
  32. use Magento\Framework\Stdlib\CookieManagerInterface;
  33. use Magento\Framework\Session\SessionManagerInterface;
  34. use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory;
  35. /**
  36. * Class TrustedManager
  37. * @package MSP\TwoFactorAuth\Model
  38. * @SuppressWarnings("PHPMD.CouplingBetweenObjects")
  39. */
  40. class TrustedManager implements TrustedManagerInterface
  41. {
  42. private $isTrustedDevice = null;
  43. /**
  44. * @var TfaInterface
  45. */
  46. private $tfa;
  47. /**
  48. * @var TrustedFactory
  49. */
  50. private $trustedFactory;
  51. /**
  52. * @var DateTime
  53. */
  54. private $dateTime;
  55. /**
  56. * @var RemoteAddress
  57. */
  58. private $remoteAddress;
  59. /**
  60. * @var Session
  61. */
  62. private $session;
  63. /**
  64. * @var TrustedResourceModel
  65. */
  66. private $trustedResourceModel;
  67. /**
  68. * @var CookieManagerInterface
  69. */
  70. private $cookieManager;
  71. /**
  72. * @var SessionManagerInterface
  73. */
  74. private $sessionManager;
  75. /**
  76. * @var CookieMetadataFactory
  77. */
  78. private $cookieMetadataFactory;
  79. /**
  80. * @var Encoder
  81. */
  82. private $encoder;
  83. /**
  84. * @var Decoder
  85. */
  86. private $decoder;
  87. /**
  88. * @var TrustedRepositoryInterface
  89. */
  90. private $trustedRepository;
  91. /**
  92. * TrustedManager constructor.
  93. * @param TfaInterface $tfa
  94. * @param DateTime $dateTime
  95. * @param Session $session
  96. * @param RemoteAddress $remoteAddress
  97. * @param Encoder $encoder
  98. * @param Decoder $decoder
  99. * @param TrustedResourceModel $trustedResourceModel
  100. * @param CookieManagerInterface $cookieManager
  101. * @param SessionManagerInterface $sessionManager
  102. * @param TrustedRepositoryInterface $trustedRepository
  103. * @param TrustedFactory $trustedFactory
  104. * @param CookieMetadataFactory $cookieMdFactory
  105. * @SuppressWarnings("PHPMD.ExcessiveParameterList")
  106. */
  107. public function __construct(
  108. TfaInterface $tfa,
  109. DateTime $dateTime,
  110. Session $session,
  111. RemoteAddress $remoteAddress,
  112. Encoder $encoder,
  113. Decoder $decoder,
  114. TrustedResourceModel $trustedResourceModel,
  115. CookieManagerInterface $cookieManager,
  116. SessionManagerInterface $sessionManager,
  117. TrustedRepositoryInterface $trustedRepository,
  118. TrustedFactory $trustedFactory,
  119. CookieMetadataFactory $cookieMdFactory
  120. ) {
  121. $this->tfa = $tfa;
  122. $this->trustedFactory = $trustedFactory;
  123. $this->dateTime = $dateTime;
  124. $this->remoteAddress = $remoteAddress;
  125. $this->session = $session;
  126. $this->trustedResourceModel = $trustedResourceModel;
  127. $this->cookieManager = $cookieManager;
  128. $this->sessionManager = $sessionManager;
  129. $this->cookieMetadataFactory = $cookieMdFactory;
  130. $this->encoder = $encoder;
  131. $this->decoder = $decoder;
  132. $this->trustedRepository = $trustedRepository;
  133. }
  134. /**
  135. * Get current user
  136. * @return User|null
  137. */
  138. private function getUser()
  139. {
  140. return $this->session->getUser();
  141. }
  142. /**
  143. * Get device name
  144. * @return string
  145. */
  146. private function getDeviceName()
  147. {
  148. $browser = parse_user_agent();
  149. return $browser['platform'] . ' ' . $browser['browser'] . ' ' . $browser['version'];
  150. }
  151. /**
  152. * Get token collection from cookie
  153. * @return array
  154. */
  155. private function getTokenCollection()
  156. {
  157. try {
  158. return $this->decoder->decode(
  159. $this->cookieManager->getCookie(TrustedManagerInterface::TRUSTED_DEVICE_COOKIE)
  160. );
  161. } catch (\Exception $e) {
  162. return [];
  163. }
  164. }
  165. /**
  166. * Send token as cookie
  167. * @param string $token
  168. * @throws \Magento\Framework\Exception\InputException
  169. * @throws \Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException
  170. * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
  171. */
  172. private function sendTokenCookie($token)
  173. {
  174. $user = $this->getUser();
  175. $tokenCollection = $this->getTokenCollection();
  176. // Enable cookie
  177. $cookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata()
  178. ->setDurationOneYear()
  179. ->setHttpOnly(true)
  180. ->setPath($this->sessionManager->getCookiePath())
  181. ->setDomain($this->sessionManager->getCookieDomain());
  182. $tokenCollection[$user->getUserName()] = $token;
  183. $this->cookieManager->setPublicCookie(
  184. TrustedManagerInterface::TRUSTED_DEVICE_COOKIE,
  185. $this->encoder->encode($tokenCollection),
  186. $cookieMetadata
  187. );
  188. }
  189. /**
  190. * Rotate secret trust token
  191. * @return void
  192. * @throws \Exception
  193. * @throws \Magento\Framework\Exception\InputException
  194. * @throws \Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException
  195. * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
  196. */
  197. public function rotateTrustedDeviceToken()
  198. {
  199. $user = $this->getUser();
  200. $tokenCollection = $this->getTokenCollection();
  201. if (isset($tokenCollection[$user->getUserName()])) {
  202. $token = $tokenCollection[$user->getUserName()];
  203. /** @var $trustEntry Trusted */
  204. $trustEntry = $this->trustedFactory->create();
  205. $this->trustedResourceModel->load($trustEntry, $token, 'token');
  206. if ($trustEntry->getId() && ($trustEntry->getUserId() == $user->getId())) {
  207. $token = sha1(uniqid(time()));
  208. $trustEntry->setToken($token);
  209. $this->trustedResourceModel->save($trustEntry);
  210. $this->sendTokenCookie($token);
  211. }
  212. }
  213. }
  214. /**
  215. * Return true if device is trusted
  216. * @return bool
  217. */
  218. public function isTrustedDevice()
  219. {
  220. if ($this->isTrustedDevice === null) { // Must cache this ina single session to avoid rotation issues
  221. $user = $this->getUser();
  222. $tokenCollection = $this->getTokenCollection();
  223. if (isset($tokenCollection[$user->getUserName()])) {
  224. $token = $tokenCollection[$user->getUserName()];
  225. /** @var $trustEntry Trusted */
  226. $trustEntry = $this->trustedFactory->create();
  227. $this->trustedResourceModel->load($trustEntry, $token, 'token');
  228. $this->isTrustedDevice = $trustEntry->getId() && ($trustEntry->getUserId() == $user->getId());
  229. } else {
  230. $this->isTrustedDevice = false;
  231. }
  232. }
  233. return $this->isTrustedDevice;
  234. }
  235. /**
  236. * Revoke trusted device
  237. * @param int $tokenId
  238. * @return bool
  239. */
  240. public function revokeTrustedDevice($tokenId)
  241. {
  242. $token = $this->trustedRepository->getById($tokenId);
  243. $this->trustedRepository->delete($token);
  244. return true;
  245. }
  246. /**
  247. * Trust a device
  248. * @param $providerCode
  249. * @param RequestInterface $request
  250. * @return boolean
  251. * @throws \Exception
  252. * @throws \Magento\Framework\Exception\InputException
  253. * @throws \Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException
  254. * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
  255. */
  256. public function handleTrustDeviceRequest($providerCode, RequestInterface $request)
  257. {
  258. if ($provider = $this->tfa->getProvider($providerCode)) {
  259. if ($provider->isTrustedDevicesAllowed() &&
  260. $request->getParam('tfa_trust_device') &&
  261. ($request->getParam('tfa_trust_device') != "false") // u2fkey submit translates into a string
  262. ) {
  263. $token = sha1(uniqid(time()));
  264. /** @var $trustEntry Trusted */
  265. $trustEntry = $this->trustedFactory->create();
  266. $trustEntry
  267. ->setToken($token)
  268. ->setDateTime($this->dateTime->date())
  269. ->setUserId($this->getUser()->getId())
  270. ->setLastIp($this->remoteAddress->getRemoteAddress())
  271. ->setDeviceName($this->getDeviceName())
  272. ->setUserAgent($request->getServer('HTTP_USER_AGENT'));
  273. $this->trustedResourceModel->save($trustEntry);
  274. $this->sendTokenCookie($token);
  275. return true;
  276. }
  277. }
  278. return false;
  279. }
  280. }