1fca58076SAndreas Gohr<?php 2fca58076SAndreas Gohr 3fca58076SAndreas Gohrnamespace dokuwiki\plugin\twofactor; 4fca58076SAndreas Gohr 5a386a536SAndreas Gohruse dokuwiki\Extension\Plugin; 6a386a536SAndreas Gohr 7fca58076SAndreas Gohr/** 8fca58076SAndreas Gohr * Manages the child plugins etc. 9fca58076SAndreas Gohr */ 10a386a536SAndreas Gohrclass Manager extends Plugin 118b7620a8SAndreas Gohr{ 128b7620a8SAndreas Gohr /** @var Manager */ 138b7620a8SAndreas Gohr protected static $instance; 148b7620a8SAndreas Gohr 158b7620a8SAndreas Gohr /** @var bool */ 168b7620a8SAndreas Gohr protected $ready = false; 178b7620a8SAndreas Gohr 188b7620a8SAndreas Gohr /** @var string[] */ 198b7620a8SAndreas Gohr protected $classes = []; 208b7620a8SAndreas Gohr 218b7620a8SAndreas Gohr /** @var Provider[] */ 228b7620a8SAndreas Gohr protected $providers; 23fca58076SAndreas Gohr 24fca58076SAndreas Gohr /** 258b7620a8SAndreas Gohr * Constructor 26fca58076SAndreas Gohr */ 278b7620a8SAndreas Gohr protected function __construct() 288b7620a8SAndreas Gohr { 298b7620a8SAndreas Gohr $this->classes = $this->getProviderClasses(); 308b7620a8SAndreas Gohr 318b7620a8SAndreas Gohr $attribute = plugin_load('helper', 'attribute'); 328b7620a8SAndreas Gohr if ($attribute === null) { 338b7620a8SAndreas Gohr msg('The attribute plugin is not available, 2fa disabled', -1); 348b7620a8SAndreas Gohr } 358b7620a8SAndreas Gohr 368b7620a8SAndreas Gohr if (!count($this->classes)) { 378b7620a8SAndreas Gohr msg('No suitable 2fa providers found, 2fa disabled', -1); 388b7620a8SAndreas Gohr return; 398b7620a8SAndreas Gohr } 408b7620a8SAndreas Gohr 418b7620a8SAndreas Gohr $this->ready = true; 428b7620a8SAndreas Gohr } 438b7620a8SAndreas Gohr 448b7620a8SAndreas Gohr /** 45*4b9cff8aSAndreas Gohr * This is not a conventional class, plugin name can't be determined automatically 46*4b9cff8aSAndreas Gohr * @inheritdoc 47*4b9cff8aSAndreas Gohr */ 48*4b9cff8aSAndreas Gohr public function getPluginName() { 49*4b9cff8aSAndreas Gohr return 'twofactor'; 50*4b9cff8aSAndreas Gohr } 51*4b9cff8aSAndreas Gohr 52*4b9cff8aSAndreas Gohr /** 538b7620a8SAndreas Gohr * Get the instance of this singleton 548b7620a8SAndreas Gohr * 558b7620a8SAndreas Gohr * @return Manager 568b7620a8SAndreas Gohr */ 578b7620a8SAndreas Gohr public static function getInstance() 588b7620a8SAndreas Gohr { 598b7620a8SAndreas Gohr if (self::$instance === null) { 608b7620a8SAndreas Gohr self::$instance = new Manager(); 618b7620a8SAndreas Gohr } 628b7620a8SAndreas Gohr return self::$instance; 638b7620a8SAndreas Gohr } 648b7620a8SAndreas Gohr 658b7620a8SAndreas Gohr /** 668b7620a8SAndreas Gohr * Is the plugin ready to be used? 678b7620a8SAndreas Gohr * 688b7620a8SAndreas Gohr * @return bool 698b7620a8SAndreas Gohr */ 708b7620a8SAndreas Gohr public function isReady() 718b7620a8SAndreas Gohr { 728b7620a8SAndreas Gohr return $this->ready; 738b7620a8SAndreas Gohr } 748b7620a8SAndreas Gohr 758b7620a8SAndreas Gohr /** 76b6119621SAndreas Gohr * Is a 2fa login required? 77b6119621SAndreas Gohr * 78b6119621SAndreas Gohr * @return bool 79a386a536SAndreas Gohr */ 80a386a536SAndreas Gohr public function isRequired() 81a386a536SAndreas Gohr { 82a386a536SAndreas Gohr $set = $this->getConf('optinout'); 83a386a536SAndreas Gohr if ($set === 'mandatory') { 84a386a536SAndreas Gohr return true; 85a386a536SAndreas Gohr } 86*4b9cff8aSAndreas Gohr if ($set === 'optout') { 87*4b9cff8aSAndreas Gohr $setting = new Settings('twofactor', $this->getUser()); 88*4b9cff8aSAndreas Gohr if ($setting->get('state') !== 'optout') { 89*4b9cff8aSAndreas Gohr return true; 90*4b9cff8aSAndreas Gohr } 91*4b9cff8aSAndreas Gohr } 92a386a536SAndreas Gohr 93a386a536SAndreas Gohr return false; 94a386a536SAndreas Gohr } 95a386a536SAndreas Gohr 96a386a536SAndreas Gohr /** 97a386a536SAndreas Gohr * Convenience method to get current user 98a386a536SAndreas Gohr * 99a386a536SAndreas Gohr * @return string 100a386a536SAndreas Gohr */ 101a386a536SAndreas Gohr public function getUser() 102a386a536SAndreas Gohr { 103a386a536SAndreas Gohr global $INPUT; 104b6119621SAndreas Gohr $user = $INPUT->server->str('REMOTE_USER'); 105b6119621SAndreas Gohr if (!$user) { 106b6119621SAndreas Gohr throw new \RuntimeException('2fa user specifics used before user available'); 107b6119621SAndreas Gohr } 108b6119621SAndreas Gohr return $user; 109a386a536SAndreas Gohr } 110a386a536SAndreas Gohr 111a386a536SAndreas Gohr /** 112*4b9cff8aSAndreas Gohr * Get or set the user opt-out state 113*4b9cff8aSAndreas Gohr * 114*4b9cff8aSAndreas Gohr * true: user opted out 115*4b9cff8aSAndreas Gohr * false: user did not opt out 116*4b9cff8aSAndreas Gohr * 117*4b9cff8aSAndreas Gohr * @param bool|null $set 118*4b9cff8aSAndreas Gohr * @return bool 119*4b9cff8aSAndreas Gohr */ 120*4b9cff8aSAndreas Gohr public function userOptOutState($set = null) 121*4b9cff8aSAndreas Gohr { 122*4b9cff8aSAndreas Gohr // is optout allowed? 123*4b9cff8aSAndreas Gohr if ($this->getConf('optinout') !== 'optout') return false; 124*4b9cff8aSAndreas Gohr 125*4b9cff8aSAndreas Gohr $settings = new Settings('twofactor', $this->getUser()); 126*4b9cff8aSAndreas Gohr 127*4b9cff8aSAndreas Gohr if ($set === null) { 128*4b9cff8aSAndreas Gohr $current = $settings->get('state'); 129*4b9cff8aSAndreas Gohr return $current === 'optout'; 130*4b9cff8aSAndreas Gohr } 131*4b9cff8aSAndreas Gohr 132*4b9cff8aSAndreas Gohr if ($set) { 133*4b9cff8aSAndreas Gohr $settings->set('state', 'optout'); 134*4b9cff8aSAndreas Gohr } else { 135*4b9cff8aSAndreas Gohr $settings->delete('state'); 136*4b9cff8aSAndreas Gohr } 137*4b9cff8aSAndreas Gohr return $set; 138*4b9cff8aSAndreas Gohr } 139*4b9cff8aSAndreas Gohr 140*4b9cff8aSAndreas Gohr /** 1418b7620a8SAndreas Gohr * Get all available providers 1428b7620a8SAndreas Gohr * 1438b7620a8SAndreas Gohr * @return Provider[] 1448b7620a8SAndreas Gohr */ 1458b7620a8SAndreas Gohr public function getAllProviders() 1468b7620a8SAndreas Gohr { 147a386a536SAndreas Gohr $user = $this->getUser(); 1488b7620a8SAndreas Gohr 1498b7620a8SAndreas Gohr if ($this->providers === null) { 1508b7620a8SAndreas Gohr $this->providers = []; 151a386a536SAndreas Gohr foreach ($this->classes as $plugin => $class) { 152a386a536SAndreas Gohr $this->providers[$plugin] = new $class($user); 1538b7620a8SAndreas Gohr } 1548b7620a8SAndreas Gohr } 1558b7620a8SAndreas Gohr 1568b7620a8SAndreas Gohr return $this->providers; 1578b7620a8SAndreas Gohr } 1588b7620a8SAndreas Gohr 1598b7620a8SAndreas Gohr /** 160a386a536SAndreas Gohr * Get all providers that have been already set up by the user 161a386a536SAndreas Gohr * 162a386a536SAndreas Gohr * @return Provider[] 163a386a536SAndreas Gohr */ 164a386a536SAndreas Gohr public function getUserProviders() 165a386a536SAndreas Gohr { 166a386a536SAndreas Gohr $list = $this->getAllProviders(); 167a386a536SAndreas Gohr $list = array_filter($list, function ($provider) { 168a386a536SAndreas Gohr return $provider->isConfigured(); 169a386a536SAndreas Gohr }); 170a386a536SAndreas Gohr 171a386a536SAndreas Gohr return $list; 172a386a536SAndreas Gohr } 173a386a536SAndreas Gohr 174a386a536SAndreas Gohr /** 175a386a536SAndreas Gohr * Get the instance of the given provider 176a386a536SAndreas Gohr * 177a386a536SAndreas Gohr * @param string $providerID 178a386a536SAndreas Gohr * @return Provider 1796c996db8SAndreas Gohr * @throws \Exception 180a386a536SAndreas Gohr */ 181a386a536SAndreas Gohr public function getUserProvider($providerID) 182a386a536SAndreas Gohr { 183a386a536SAndreas Gohr $providers = $this->getUserProviders(); 184a386a536SAndreas Gohr if (isset($providers[$providerID])) return $providers[$providerID]; 1856c996db8SAndreas Gohr throw new \Exception('Uncofigured provider requested'); 186a386a536SAndreas Gohr } 187a386a536SAndreas Gohr 188a386a536SAndreas Gohr /** 189b6119621SAndreas Gohr * Get the user's default provider if any 190b6119621SAndreas Gohr * 191b6119621SAndreas Gohr * Autoupdates the apropriate setting 192b6119621SAndreas Gohr * 193b6119621SAndreas Gohr * @return Provider|null 194b6119621SAndreas Gohr */ 195b6119621SAndreas Gohr public function getUserDefaultProvider() 196b6119621SAndreas Gohr { 197b6119621SAndreas Gohr $setting = new Settings('twofactor', $this->getUser()); 198b6119621SAndreas Gohr $default = $setting->get('defaultmod'); 199b6119621SAndreas Gohr $providers = $this->getUserProviders(); 200b6119621SAndreas Gohr 201b6119621SAndreas Gohr if (isset($providers[$default])) return $providers[$default]; 202b6119621SAndreas Gohr // still here? no valid setting. Use first available one 203b6119621SAndreas Gohr $first = array_shift($providers); 204b6119621SAndreas Gohr if ($first !== null) { 205b6119621SAndreas Gohr $this->setUserDefaultProvider($first); 206b6119621SAndreas Gohr } 207b6119621SAndreas Gohr return $first; 208b6119621SAndreas Gohr } 209b6119621SAndreas Gohr 210b6119621SAndreas Gohr /** 211b6119621SAndreas Gohr * Set the default provider for the user 212b6119621SAndreas Gohr * 213b6119621SAndreas Gohr * @param Provider $provider 214b6119621SAndreas Gohr * @return void 215b6119621SAndreas Gohr */ 216b6119621SAndreas Gohr public function setUserDefaultProvider($provider) 217b6119621SAndreas Gohr { 218b6119621SAndreas Gohr $setting = new Settings('twofactor', $this->getUser()); 219b6119621SAndreas Gohr $setting->set('defaultmod', $provider->getProviderID()); 220b6119621SAndreas Gohr } 221b6119621SAndreas Gohr 222b6119621SAndreas Gohr /** 2238b7620a8SAndreas Gohr * Find all available provider classes 2248b7620a8SAndreas Gohr * 2258b7620a8SAndreas Gohr * @return string[]; 2268b7620a8SAndreas Gohr */ 2278b7620a8SAndreas Gohr protected function getProviderClasses() 2288b7620a8SAndreas Gohr { 2298b7620a8SAndreas Gohr // FIXME this relies on naming alone, we might want to use an action for registering 2308b7620a8SAndreas Gohr $plugins = plugin_list('helper'); 2318b7620a8SAndreas Gohr $plugins = array_filter($plugins, function ($plugin) { 2328b7620a8SAndreas Gohr return $plugin !== 'twofactor' && substr($plugin, 0, 9) === 'twofactor'; 2338b7620a8SAndreas Gohr }); 2348b7620a8SAndreas Gohr 2358b7620a8SAndreas Gohr $classes = []; 2368b7620a8SAndreas Gohr foreach ($plugins as $plugin) { 2378b7620a8SAndreas Gohr $class = 'helper_plugin_' . $plugin; 2388b7620a8SAndreas Gohr if (is_a($class, Provider::class, true)) { 239a386a536SAndreas Gohr $classes[$plugin] = $class; 2408b7620a8SAndreas Gohr } 2418b7620a8SAndreas Gohr } 2428b7620a8SAndreas Gohr 2438b7620a8SAndreas Gohr return $classes; 244fca58076SAndreas Gohr } 245fca58076SAndreas Gohr 246fca58076SAndreas Gohr} 247