1<?php 2 3namespace dokuwiki\plugin\twofactor; 4 5use dokuwiki\Extension\Plugin; 6 7/** 8 * Manages the child plugins etc. 9 */ 10class Manager extends Plugin 11{ 12 /** @var Manager */ 13 protected static $instance; 14 15 /** @var bool */ 16 protected $ready = false; 17 18 /** @var string[] */ 19 protected $classes = []; 20 21 /** @var Provider[] */ 22 protected $providers; 23 24 /** 25 * Constructor 26 */ 27 protected function __construct() 28 { 29 $this->classes = $this->getProviderClasses(); 30 31 $attribute = plugin_load('helper', 'attribute'); 32 if ($attribute === null) { 33 msg('The attribute plugin is not available, 2fa disabled', -1); 34 } 35 36 if (!count($this->classes)) { 37 msg('No suitable 2fa providers found, 2fa disabled', -1); 38 return; 39 } 40 41 $this->ready = true; 42 } 43 44 /** 45 * This is not a conventional class, plugin name can't be determined automatically 46 * @inheritdoc 47 */ 48 public function getPluginName() { 49 return 'twofactor'; 50 } 51 52 /** 53 * Get the instance of this singleton 54 * 55 * @return Manager 56 */ 57 public static function getInstance() 58 { 59 if (self::$instance === null) { 60 self::$instance = new Manager(); 61 } 62 return self::$instance; 63 } 64 65 /** 66 * Is the plugin ready to be used? 67 * 68 * @return bool 69 */ 70 public function isReady() 71 { 72 return $this->ready; 73 } 74 75 /** 76 * Is a 2fa login required? 77 * 78 * @return bool 79 */ 80 public function isRequired() 81 { 82 $set = $this->getConf('optinout'); 83 if ($set === 'mandatory') { 84 return true; 85 } 86 if ($set === 'optout') { 87 $setting = new Settings('twofactor', $this->getUser()); 88 if ($setting->get('state') !== 'optout') { 89 return true; 90 } 91 } 92 93 return false; 94 } 95 96 /** 97 * Convenience method to get current user 98 * 99 * @return string 100 */ 101 public function getUser() 102 { 103 global $INPUT; 104 $user = $INPUT->server->str('REMOTE_USER'); 105 if (!$user) { 106 throw new \RuntimeException('2fa user specifics used before user available'); 107 } 108 return $user; 109 } 110 111 /** 112 * Get or set the user opt-out state 113 * 114 * true: user opted out 115 * false: user did not opt out 116 * 117 * @param bool|null $set 118 * @return bool 119 */ 120 public function userOptOutState($set = null) 121 { 122 // is optout allowed? 123 if ($this->getConf('optinout') !== 'optout') return false; 124 125 $settings = new Settings('twofactor', $this->getUser()); 126 127 if ($set === null) { 128 $current = $settings->get('state'); 129 return $current === 'optout'; 130 } 131 132 if ($set) { 133 $settings->set('state', 'optout'); 134 } else { 135 $settings->delete('state'); 136 } 137 return $set; 138 } 139 140 /** 141 * Get all available providers 142 * 143 * @return Provider[] 144 */ 145 public function getAllProviders() 146 { 147 $user = $this->getUser(); 148 149 if ($this->providers === null) { 150 $this->providers = []; 151 foreach ($this->classes as $plugin => $class) { 152 $this->providers[$plugin] = new $class($user); 153 } 154 } 155 156 return $this->providers; 157 } 158 159 /** 160 * Get all providers that have been already set up by the user 161 * 162 * @return Provider[] 163 */ 164 public function getUserProviders() 165 { 166 $list = $this->getAllProviders(); 167 $list = array_filter($list, function ($provider) { 168 return $provider->isConfigured(); 169 }); 170 171 return $list; 172 } 173 174 /** 175 * Get the instance of the given provider 176 * 177 * @param string $providerID 178 * @return Provider 179 * @throws \Exception 180 */ 181 public function getUserProvider($providerID) 182 { 183 $providers = $this->getUserProviders(); 184 if (isset($providers[$providerID])) return $providers[$providerID]; 185 throw new \Exception('Uncofigured provider requested'); 186 } 187 188 /** 189 * Get the user's default provider if any 190 * 191 * Autoupdates the apropriate setting 192 * 193 * @return Provider|null 194 */ 195 public function getUserDefaultProvider() 196 { 197 $setting = new Settings('twofactor', $this->getUser()); 198 $default = $setting->get('defaultmod'); 199 $providers = $this->getUserProviders(); 200 201 if (isset($providers[$default])) return $providers[$default]; 202 // still here? no valid setting. Use first available one 203 $first = array_shift($providers); 204 if ($first !== null) { 205 $this->setUserDefaultProvider($first); 206 } 207 return $first; 208 } 209 210 /** 211 * Set the default provider for the user 212 * 213 * @param Provider $provider 214 * @return void 215 */ 216 public function setUserDefaultProvider($provider) 217 { 218 $setting = new Settings('twofactor', $this->getUser()); 219 $setting->set('defaultmod', $provider->getProviderID()); 220 } 221 222 /** 223 * Find all available provider classes 224 * 225 * @return string[]; 226 */ 227 protected function getProviderClasses() 228 { 229 // FIXME this relies on naming alone, we might want to use an action for registering 230 $plugins = plugin_list('helper'); 231 $plugins = array_filter($plugins, function ($plugin) { 232 return $plugin !== 'twofactor' && substr($plugin, 0, 9) === 'twofactor'; 233 }); 234 235 $classes = []; 236 foreach ($plugins as $plugin) { 237 $class = 'helper_plugin_' . $plugin; 238 if (is_a($class, Provider::class, true)) { 239 $classes[$plugin] = $class; 240 } 241 } 242 243 return $classes; 244 } 245 246} 247