TwoFactor.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <?php
  2. /**
  3. * Two authentication factor handling
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin;
  7. use PhpMyAdmin\Plugins\TwoFactor\Application;
  8. use PhpMyAdmin\Plugins\TwoFactor\Invalid;
  9. use PhpMyAdmin\Plugins\TwoFactor\Key;
  10. use PhpMyAdmin\Plugins\TwoFactorPlugin;
  11. use PragmaRX\Google2FAQRCode\Google2FA;
  12. use Samyoul\U2F\U2FServer\U2FServer;
  13. use function array_merge;
  14. use function class_exists;
  15. use function in_array;
  16. use function ucfirst;
  17. /**
  18. * Two factor authentication wrapper class
  19. */
  20. class TwoFactor
  21. {
  22. /** @var string */
  23. public $user;
  24. /** @var array */
  25. public $config;
  26. /** @var bool */
  27. protected $writable;
  28. /** @var TwoFactorPlugin */
  29. protected $backend;
  30. /** @var array */
  31. protected $available;
  32. /** @var UserPreferences */
  33. private $userPreferences;
  34. /**
  35. * Creates new TwoFactor object
  36. *
  37. * @param string $user User name
  38. */
  39. public function __construct($user)
  40. {
  41. global $dbi;
  42. $dbi->initRelationParamsCache();
  43. $this->userPreferences = new UserPreferences();
  44. $this->user = $user;
  45. $this->available = $this->getAvailableBackends();
  46. $this->config = $this->readConfig();
  47. $this->writable = ($this->config['type'] === 'db');
  48. $this->backend = $this->getBackendForCurrentUser();
  49. }
  50. /**
  51. * Reads the configuration
  52. *
  53. * @return array
  54. */
  55. public function readConfig()
  56. {
  57. $result = [];
  58. $config = $this->userPreferences->load();
  59. if (isset($config['config_data']['2fa'])) {
  60. $result = $config['config_data']['2fa'];
  61. }
  62. $result['type'] = $config['type'];
  63. if (! isset($result['backend'])) {
  64. $result['backend'] = '';
  65. }
  66. if (! isset($result['settings'])) {
  67. $result['settings'] = [];
  68. }
  69. return $result;
  70. }
  71. public function isWritable(): bool
  72. {
  73. return $this->writable;
  74. }
  75. public function getBackend(): TwoFactorPlugin
  76. {
  77. return $this->backend;
  78. }
  79. /**
  80. * @return array
  81. */
  82. public function getAvailable(): array
  83. {
  84. return $this->available;
  85. }
  86. public function showSubmit(): bool
  87. {
  88. $backend = $this->backend;
  89. return $backend::$showSubmit;
  90. }
  91. /**
  92. * Returns list of available backends
  93. *
  94. * @return array
  95. */
  96. public function getAvailableBackends()
  97. {
  98. $result = [];
  99. if ($GLOBALS['cfg']['DBG']['simple2fa']) {
  100. $result[] = 'simple';
  101. }
  102. if (class_exists(Google2FA::class)) {
  103. $result[] = 'application';
  104. }
  105. if (class_exists(U2FServer::class)) {
  106. $result[] = 'key';
  107. }
  108. return $result;
  109. }
  110. /**
  111. * Returns list of missing dependencies
  112. *
  113. * @return array
  114. */
  115. public function getMissingDeps()
  116. {
  117. $result = [];
  118. if (! class_exists(Google2FA::class)) {
  119. $result[] = [
  120. 'class' => Application::getName(),
  121. 'dep' => 'pragmarx/google2fa-qrcode',
  122. ];
  123. }
  124. if (! class_exists('BaconQrCode\Renderer\Image\Png')) {
  125. $result[] = [
  126. 'class' => Application::getName(),
  127. 'dep' => 'bacon/bacon-qr-code',
  128. ];
  129. }
  130. if (! class_exists(U2FServer::class)) {
  131. $result[] = [
  132. 'class' => Key::getName(),
  133. 'dep' => 'samyoul/u2f-php-server',
  134. ];
  135. }
  136. return $result;
  137. }
  138. /**
  139. * Returns class name for given name
  140. *
  141. * @param string $name Backend name
  142. *
  143. * @return string
  144. */
  145. public function getBackendClass($name)
  146. {
  147. $result = TwoFactorPlugin::class;
  148. if (in_array($name, $this->available)) {
  149. $result = 'PhpMyAdmin\\Plugins\\TwoFactor\\' . ucfirst($name);
  150. } elseif (! empty($name)) {
  151. $result = Invalid::class;
  152. }
  153. return $result;
  154. }
  155. /**
  156. * Returns backend for current user
  157. *
  158. * @return TwoFactorPlugin
  159. */
  160. public function getBackendForCurrentUser()
  161. {
  162. $name = $this->getBackendClass($this->config['backend']);
  163. return new $name($this);
  164. }
  165. /**
  166. * Checks authentication, returns true on success
  167. *
  168. * @param bool $skip_session Skip session cache
  169. *
  170. * @return bool
  171. */
  172. public function check($skip_session = false)
  173. {
  174. if ($skip_session) {
  175. return $this->backend->check();
  176. }
  177. if (empty($_SESSION['two_factor_check'])) {
  178. $_SESSION['two_factor_check'] = $this->backend->check();
  179. }
  180. return $_SESSION['two_factor_check'];
  181. }
  182. /**
  183. * Renders user interface to enter two-factor authentication
  184. *
  185. * @return string HTML code
  186. */
  187. public function render()
  188. {
  189. return $this->backend->getError() . $this->backend->render();
  190. }
  191. /**
  192. * Renders user interface to configure two-factor authentication
  193. *
  194. * @return string HTML code
  195. */
  196. public function setup()
  197. {
  198. return $this->backend->getError() . $this->backend->setup();
  199. }
  200. /**
  201. * Saves current configuration.
  202. *
  203. * @return true|Message
  204. */
  205. public function save()
  206. {
  207. return $this->userPreferences->persistOption('2fa', $this->config, null);
  208. }
  209. /**
  210. * Changes two-factor authentication settings
  211. *
  212. * The object might stay in partially changed setup
  213. * if configuration fails.
  214. *
  215. * @param string $name Backend name
  216. *
  217. * @return bool
  218. */
  219. public function configure($name)
  220. {
  221. $this->config = ['backend' => $name];
  222. if ($name === '') {
  223. $cls = $this->getBackendClass($name);
  224. $this->config['settings'] = [];
  225. $this->backend = new $cls($this);
  226. } else {
  227. if (! in_array($name, $this->available)) {
  228. return false;
  229. }
  230. $cls = $this->getBackendClass($name);
  231. $this->config['settings'] = [];
  232. $this->backend = new $cls($this);
  233. if (! $this->backend->configure()) {
  234. return false;
  235. }
  236. }
  237. $result = $this->save();
  238. if ($result !== true) {
  239. echo $result->getDisplay();
  240. }
  241. return true;
  242. }
  243. /**
  244. * Returns array with all available backends
  245. *
  246. * @return array
  247. */
  248. public function getAllBackends()
  249. {
  250. $all = array_merge([''], $this->available);
  251. $backends = [];
  252. foreach ($all as $name) {
  253. $cls = $this->getBackendClass($name);
  254. $backends[] = [
  255. 'id' => $cls::$id,
  256. 'name' => $cls::getName(),
  257. 'description' => $cls::getDescription(),
  258. ];
  259. }
  260. return $backends;
  261. }
  262. }