1fca58076SAndreas Gohr<?php 2fca58076SAndreas Gohr 3fca58076SAndreas Gohrnamespace dokuwiki\plugin\twofactor; 4fca58076SAndreas Gohr 55f8f561aSAndreas Gohruse dokuwiki\Extension\Event; 6a386a536SAndreas Gohruse dokuwiki\Extension\Plugin; 7a386a536SAndreas Gohr 8fca58076SAndreas Gohr/** 9fca58076SAndreas Gohr * Manages the child plugins etc. 10fca58076SAndreas Gohr */ 11a386a536SAndreas Gohrclass Manager extends Plugin 128b7620a8SAndreas Gohr{ 135f8f561aSAndreas Gohr /** 145f8f561aSAndreas Gohr * Generally all our actions should run before all other plugins 155f8f561aSAndreas Gohr */ 165f8f561aSAndreas Gohr const EVENT_PRIORITY = -5000; 175f8f561aSAndreas Gohr 188b7620a8SAndreas Gohr /** @var Manager */ 198b7620a8SAndreas Gohr protected static $instance; 208b7620a8SAndreas Gohr 218b7620a8SAndreas Gohr /** @var bool */ 228b7620a8SAndreas Gohr protected $ready = false; 238b7620a8SAndreas Gohr 248b7620a8SAndreas Gohr /** @var Provider[] */ 258b7620a8SAndreas Gohr protected $providers; 26fca58076SAndreas Gohr 275f8f561aSAndreas Gohr /** @var bool */ 285f8f561aSAndreas Gohr protected $providersInitialized; 295f8f561aSAndreas Gohr 30fca58076SAndreas Gohr /** 318b7620a8SAndreas Gohr * Constructor 32fca58076SAndreas Gohr */ 338b7620a8SAndreas Gohr protected function __construct() 348b7620a8SAndreas Gohr { 358b7620a8SAndreas Gohr $attribute = plugin_load('helper', 'attribute'); 368b7620a8SAndreas Gohr if ($attribute === null) { 378b7620a8SAndreas Gohr msg('The attribute plugin is not available, 2fa disabled', -1); 385f8f561aSAndreas Gohr return; 398b7620a8SAndreas Gohr } 408b7620a8SAndreas Gohr 415f8f561aSAndreas Gohr $this->loadProviders(); 425f8f561aSAndreas Gohr if (!count($this->providers)) { 438b7620a8SAndreas Gohr msg('No suitable 2fa providers found, 2fa disabled', -1); 448b7620a8SAndreas Gohr return; 458b7620a8SAndreas Gohr } 468b7620a8SAndreas Gohr 478b7620a8SAndreas Gohr $this->ready = true; 488b7620a8SAndreas Gohr } 498b7620a8SAndreas Gohr 508b7620a8SAndreas Gohr /** 514b9cff8aSAndreas Gohr * This is not a conventional class, plugin name can't be determined automatically 524b9cff8aSAndreas Gohr * @inheritdoc 534b9cff8aSAndreas Gohr */ 545f8f561aSAndreas Gohr public function getPluginName() 555f8f561aSAndreas Gohr { 564b9cff8aSAndreas Gohr return 'twofactor'; 574b9cff8aSAndreas Gohr } 584b9cff8aSAndreas Gohr 594b9cff8aSAndreas Gohr /** 608b7620a8SAndreas Gohr * Get the instance of this singleton 618b7620a8SAndreas Gohr * 628b7620a8SAndreas Gohr * @return Manager 638b7620a8SAndreas Gohr */ 648b7620a8SAndreas Gohr public static function getInstance() 658b7620a8SAndreas Gohr { 668b7620a8SAndreas Gohr if (self::$instance === null) { 678b7620a8SAndreas Gohr self::$instance = new Manager(); 688b7620a8SAndreas Gohr } 698b7620a8SAndreas Gohr return self::$instance; 708b7620a8SAndreas Gohr } 718b7620a8SAndreas Gohr 728b7620a8SAndreas Gohr /** 738b7620a8SAndreas Gohr * Is the plugin ready to be used? 748b7620a8SAndreas Gohr * 758b7620a8SAndreas Gohr * @return bool 768b7620a8SAndreas Gohr */ 778b7620a8SAndreas Gohr public function isReady() 788b7620a8SAndreas Gohr { 795f8f561aSAndreas Gohr if (!$this->ready) return false; 805f8f561aSAndreas Gohr try { 815f8f561aSAndreas Gohr $this->getUser(); 825f8f561aSAndreas Gohr } catch (\Exception $ignored) { 835f8f561aSAndreas Gohr return false; 845f8f561aSAndreas Gohr } 855f8f561aSAndreas Gohr 865f8f561aSAndreas Gohr return true; 878b7620a8SAndreas Gohr } 888b7620a8SAndreas Gohr 898b7620a8SAndreas Gohr /** 90b6119621SAndreas Gohr * Is a 2fa login required? 91b6119621SAndreas Gohr * 92b6119621SAndreas Gohr * @return bool 93a386a536SAndreas Gohr */ 94a386a536SAndreas Gohr public function isRequired() 95a386a536SAndreas Gohr { 96a386a536SAndreas Gohr $set = $this->getConf('optinout'); 97a386a536SAndreas Gohr if ($set === 'mandatory') { 98a386a536SAndreas Gohr return true; 99a386a536SAndreas Gohr } 1004b9cff8aSAndreas Gohr if ($set === 'optout') { 1014b9cff8aSAndreas Gohr $setting = new Settings('twofactor', $this->getUser()); 1024b9cff8aSAndreas Gohr if ($setting->get('state') !== 'optout') { 1034b9cff8aSAndreas Gohr return true; 1044b9cff8aSAndreas Gohr } 1054b9cff8aSAndreas Gohr } 106a386a536SAndreas Gohr 107a386a536SAndreas Gohr return false; 108a386a536SAndreas Gohr } 109a386a536SAndreas Gohr 110a386a536SAndreas Gohr /** 111a386a536SAndreas Gohr * Convenience method to get current user 112a386a536SAndreas Gohr * 113a386a536SAndreas Gohr * @return string 114a386a536SAndreas Gohr */ 115a386a536SAndreas Gohr public function getUser() 116a386a536SAndreas Gohr { 117a386a536SAndreas Gohr global $INPUT; 118b6119621SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 119b6119621SAndreas Gohr if (!$user) { 120b6119621SAndreas Gohr throw new \RuntimeException('2fa user specifics used before user available'); 121b6119621SAndreas Gohr } 122b6119621SAndreas Gohr return $user; 123a386a536SAndreas Gohr } 124a386a536SAndreas Gohr 125a386a536SAndreas Gohr /** 1264b9cff8aSAndreas Gohr * Get or set the user opt-out state 1274b9cff8aSAndreas Gohr * 1284b9cff8aSAndreas Gohr * true: user opted out 1294b9cff8aSAndreas Gohr * false: user did not opt out 1304b9cff8aSAndreas Gohr * 1314b9cff8aSAndreas Gohr * @param bool|null $set 1324b9cff8aSAndreas Gohr * @return bool 1334b9cff8aSAndreas Gohr */ 1344b9cff8aSAndreas Gohr public function userOptOutState($set = null) 1354b9cff8aSAndreas Gohr { 1364b9cff8aSAndreas Gohr // is optout allowed? 1374b9cff8aSAndreas Gohr if ($this->getConf('optinout') !== 'optout') return false; 1384b9cff8aSAndreas Gohr 1394b9cff8aSAndreas Gohr $settings = new Settings('twofactor', $this->getUser()); 1404b9cff8aSAndreas Gohr 1414b9cff8aSAndreas Gohr if ($set === null) { 1424b9cff8aSAndreas Gohr $current = $settings->get('state'); 1434b9cff8aSAndreas Gohr return $current === 'optout'; 1444b9cff8aSAndreas Gohr } 1454b9cff8aSAndreas Gohr 1464b9cff8aSAndreas Gohr if ($set) { 1474b9cff8aSAndreas Gohr $settings->set('state', 'optout'); 1484b9cff8aSAndreas Gohr } else { 1494b9cff8aSAndreas Gohr $settings->delete('state'); 1504b9cff8aSAndreas Gohr } 1514b9cff8aSAndreas Gohr return $set; 1524b9cff8aSAndreas Gohr } 1534b9cff8aSAndreas Gohr 1544b9cff8aSAndreas Gohr /** 1558b7620a8SAndreas Gohr * Get all available providers 1568b7620a8SAndreas Gohr * 1578b7620a8SAndreas Gohr * @return Provider[] 1588b7620a8SAndreas Gohr */ 1598b7620a8SAndreas Gohr public function getAllProviders() 1608b7620a8SAndreas Gohr { 161a386a536SAndreas Gohr $user = $this->getUser(); 1628b7620a8SAndreas Gohr 1635f8f561aSAndreas Gohr if (!$this->providersInitialized) { 1645f8f561aSAndreas Gohr // initialize providers with user and ensure the ID is correct 1655f8f561aSAndreas Gohr foreach ($this->providers as $providerID => $provider) { 1665f8f561aSAndreas Gohr if ($providerID !== $provider->getProviderID()) { 1675f8f561aSAndreas Gohr $this->providers[$provider->getProviderID()] = $provider; 1685f8f561aSAndreas Gohr unset($this->providers[$providerID]); 1698b7620a8SAndreas Gohr } 1705f8f561aSAndreas Gohr $provider->init($user); 1715f8f561aSAndreas Gohr } 1725f8f561aSAndreas Gohr $this->providersInitialized = true; 1738b7620a8SAndreas Gohr } 1748b7620a8SAndreas Gohr 1758b7620a8SAndreas Gohr return $this->providers; 1768b7620a8SAndreas Gohr } 1778b7620a8SAndreas Gohr 1788b7620a8SAndreas Gohr /** 179a386a536SAndreas Gohr * Get all providers that have been already set up by the user 180a386a536SAndreas Gohr * 181*1c8522cbSAndreas Gohr * @param bool $configured when set to false, all providers NOT configured are returned 182a386a536SAndreas Gohr * @return Provider[] 183a386a536SAndreas Gohr */ 184*1c8522cbSAndreas Gohr public function getUserProviders($configured = true) 185a386a536SAndreas Gohr { 186a386a536SAndreas Gohr $list = $this->getAllProviders(); 187*1c8522cbSAndreas Gohr $list = array_filter($list, function ($provider) use ($configured) { 188*1c8522cbSAndreas Gohr return $configured ? $provider->isConfigured() : !$provider->isConfigured(); 189a386a536SAndreas Gohr }); 190a386a536SAndreas Gohr 191a386a536SAndreas Gohr return $list; 192a386a536SAndreas Gohr } 193a386a536SAndreas Gohr 194a386a536SAndreas Gohr /** 195a386a536SAndreas Gohr * Get the instance of the given provider 196a386a536SAndreas Gohr * 197a386a536SAndreas Gohr * @param string $providerID 198a386a536SAndreas Gohr * @return Provider 1996c996db8SAndreas Gohr * @throws \Exception 200a386a536SAndreas Gohr */ 201a386a536SAndreas Gohr public function getUserProvider($providerID) 202a386a536SAndreas Gohr { 203a386a536SAndreas Gohr $providers = $this->getUserProviders(); 204a386a536SAndreas Gohr if (isset($providers[$providerID])) return $providers[$providerID]; 2056c996db8SAndreas Gohr throw new \Exception('Uncofigured provider requested'); 206a386a536SAndreas Gohr } 207a386a536SAndreas Gohr 208a386a536SAndreas Gohr /** 209b6119621SAndreas Gohr * Get the user's default provider if any 210b6119621SAndreas Gohr * 211b6119621SAndreas Gohr * Autoupdates the apropriate setting 212b6119621SAndreas Gohr * 213b6119621SAndreas Gohr * @return Provider|null 214b6119621SAndreas Gohr */ 215b6119621SAndreas Gohr public function getUserDefaultProvider() 216b6119621SAndreas Gohr { 217b6119621SAndreas Gohr $setting = new Settings('twofactor', $this->getUser()); 218b6119621SAndreas Gohr $default = $setting->get('defaultmod'); 219b6119621SAndreas Gohr $providers = $this->getUserProviders(); 220b6119621SAndreas Gohr 221b6119621SAndreas Gohr if (isset($providers[$default])) return $providers[$default]; 222b6119621SAndreas Gohr // still here? no valid setting. Use first available one 223b6119621SAndreas Gohr $first = array_shift($providers); 224b6119621SAndreas Gohr if ($first !== null) { 225b6119621SAndreas Gohr $this->setUserDefaultProvider($first); 226b6119621SAndreas Gohr } 227b6119621SAndreas Gohr return $first; 228b6119621SAndreas Gohr } 229b6119621SAndreas Gohr 230b6119621SAndreas Gohr /** 231b6119621SAndreas Gohr * Set the default provider for the user 232b6119621SAndreas Gohr * 233b6119621SAndreas Gohr * @param Provider $provider 234b6119621SAndreas Gohr * @return void 235b6119621SAndreas Gohr */ 236b6119621SAndreas Gohr public function setUserDefaultProvider($provider) 237b6119621SAndreas Gohr { 238b6119621SAndreas Gohr $setting = new Settings('twofactor', $this->getUser()); 239b6119621SAndreas Gohr $setting->set('defaultmod', $provider->getProviderID()); 240b6119621SAndreas Gohr } 241b6119621SAndreas Gohr 242b6119621SAndreas Gohr /** 2435f8f561aSAndreas Gohr * Load all available provider classes 2448b7620a8SAndreas Gohr * 2455f8f561aSAndreas Gohr * @return Provider[]; 2468b7620a8SAndreas Gohr */ 2475f8f561aSAndreas Gohr protected function loadProviders() 2488b7620a8SAndreas Gohr { 2495f8f561aSAndreas Gohr /** @var Provider[] providers */ 2505f8f561aSAndreas Gohr $this->providers = []; 2515f8f561aSAndreas Gohr $event = new Event('PLUGIN_TWOFACTOR_PROVIDER_REGISTER', $this->providers); 2525f8f561aSAndreas Gohr $event->advise_before(false); 2535f8f561aSAndreas Gohr $event->advise_after(); 2545f8f561aSAndreas Gohr return $this->providers; 255fca58076SAndreas Gohr } 256fca58076SAndreas Gohr 257fca58076SAndreas Gohr} 258