1<?php 2 3namespace dokuwiki\plugin\twofactor; 4 5use dokuwiki\Extension\Plugin; 6use dokuwiki\Form\Form; 7use dokuwiki\Utf8\PhpString; 8 9/** 10 * Baseclass for all second factor providers 11 */ 12abstract class Provider extends Plugin 13{ 14 /** @var Settings */ 15 protected $settings; 16 17 /** @var string */ 18 protected $providerID; 19 20 /** 21 * Initializes the provider for the given user 22 * @param string $user Current user 23 * @throws \Exception 24 */ 25 public function __construct($user) 26 { 27 $this->providerID = substr(get_called_class(), strlen('helper_plugin_')); 28 $this->settings = new Settings($this->providerID, $user); 29 } 30 31 /** 32 * The ID of this provider 33 * 34 * @return string 35 */ 36 public function getProviderID() 37 { 38 return $this->providerID; 39 } 40 41 /** 42 * Pretty Label for this provider 43 * 44 * @return string 45 */ 46 public function getLabel() 47 { 48 return PhpString::ucfirst($this->providerID); 49 } 50 51 /** 52 * Clear all settings 53 */ 54 public function reset() 55 { 56 $this->settings->purge(); 57 } 58 59 /** 60 * Has this provider been fully configured by the user and thus can be used 61 * for authentication? 62 * 63 * @return bool 64 */ 65 abstract public function isConfigured(); 66 67 /** 68 * Render the configuration form 69 * 70 * This method should add the needed form elements to (re)configure the provider. 71 * The contents of the form may change depending on the current settings. 72 * 73 * No submit button should be added - this is handled by the main plugin. 74 * 75 * @param Form $form The initial form to add elements to 76 * @return Form 77 */ 78 abstract public function renderProfileForm(Form $form); 79 80 /** 81 * Handle any input data 82 * 83 * @return void 84 */ 85 abstract public function handleProfileForm(); 86 87 /** 88 * Transmits the code to the user 89 * 90 * @param string $code The code to transmit 91 * @return string Informational message for the user 92 * @throw \Exception when the message can't be sent 93 */ 94 abstract public function transmitMessage($code); 95 96 // region OTP methods 97 98 /** 99 * Create and store a new secret for this provider 100 * 101 * @return string the new secret 102 * @throws \Exception when no suitable random source is available 103 */ 104 public function initSecret() 105 { 106 $ga = new GoogleAuthenticator(); 107 $secret = $ga->createSecret(); 108 109 $this->settings->set('secret', $secret); 110 return $secret; 111 } 112 113 /** 114 * Generate an auth code 115 * 116 * @return string 117 * @throws \Exception when no code can be created 118 */ 119 public function generateCode() 120 { 121 $secret = $this->settings->get('secret'); 122 if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 123 124 $ga = new GoogleAuthenticator(); 125 return $ga->getCode($secret); 126 } 127 128 /** 129 * Check the given code 130 * 131 * @param string $code 132 * @param int $tolerance 133 * @return string 134 * @throws \Exception when no code can be created 135 */ 136 public function checkCode($code, $tolerance = 2) 137 { 138 $secret = $this->settings->get('secret'); 139 if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 140 141 $ga = new GoogleAuthenticator(); 142 return $ga->verifyCode($secret, $code, $tolerance); 143 } 144 145 // endregion 146 147 // region old shit 148 149 /** 150 * This is called to see if the user can use it to login. 151 * @return bool - True if this module has access to all needed information 152 * to perform a login. 153 */ 154 abstract public function canUse($user = null); 155 156 /** 157 * This is called to see if the module provides login functionality on the 158 * main login page. 159 * @return bool - True if this module provides main login functionality. 160 */ 161 abstract public function canAuthLogin(); 162 163 /** 164 * This is called to process the user configurable portion of the module 165 * inside the user's profile. 166 * @return mixed - True if the user's settings were changed, false if 167 * settings could not be changed, null if no settings were changed, 168 * the string 'verified' if the module was successfully verified, 169 * the string 'failed' if the module failed verification, 170 * the string 'otp' if the module is requesting a one-time password 171 * for verification, 172 * the string 'deleted' if the module was unenrolled. 173 */ 174 public function processProfileForm() 175 { 176 return null; 177 } 178 179 /** 180 * This is called to see if the module can send a message to the user. 181 * @return bool - True if a message can be sent to the user. 182 */ 183 abstract public function canTransmitMessage(); 184 185 /** 186 * This is called to validate the code provided. The default is to see if 187 * the code matches the one-time password. 188 * @return bool - True if the user has successfully authenticated using 189 * this mechanism. 190 */ 191 public function processLogin($code, $user = null) 192 { 193 $twofactor = plugin_load('action', 'twofactor'); 194 $otpQuery = $twofactor->get_otp_code(); 195 if (!$otpQuery) { 196 return false; 197 } 198 list($otp, $modname) = $otpQuery; 199 return ($code == $otp && $code != '' && (count($modname) == 0 || in_array(get_called_class(), $modname))); 200 } 201 202 /** 203 * This is a helper function to get text strings from the twofactor class 204 * calling this module. 205 * @return string - Language string from the calling class. 206 */ 207 protected function _getSharedLang($key) 208 { 209 $twofactor = plugin_load('action', 'twofactor'); 210 return $twofactor->getLang($key); 211 } 212 213 /** 214 * This is a helper function to get shared configuration options from the 215 * twofactor class. 216 * @return string - Language string from the calling class. 217 */ 218 protected function _getSharedConfig($key) 219 { 220 $twofactor = plugin_load('action', 'twofactor'); 221 return $twofactor->getConf($key); 222 } 223 224 /** 225 * This is a helper function to check for the existence of shared 226 * twofactor settings. 227 * @return string - Language string from the calling class. 228 */ 229 protected function _sharedSettingExists($key) 230 { 231 return $this->attribute->exists("twofactor", $key); 232 } 233 234 /** 235 * This is a helper function to get shared twofactor settings. 236 * @return string - Language string from the calling class. 237 */ 238 protected function _sharedSettingGet($key, $default = null, $user = null) 239 { 240 return $this->_sharedSettingExists($key) ? $this->attribute->get("twofactor", $key, $success, $user) : $default; 241 } 242 243 /** 244 * This is a helper function to set shared twofactor settings. 245 * @return string - Language string from the calling class. 246 */ 247 protected function _sharedSettingSet($key, $value) 248 { 249 return $this->attribute->set("twofactor", $key, $value); 250 } 251 252 /** 253 * This is a helper function that lists the names of all available 254 * modules. 255 * @return array - Names of availble modules. 256 */ 257 static public function _listModules() 258 { 259 $modules = plugin_list(); 260 return array_filter($modules, function ($x) { 261 return substr($x, 0, 9) === 'twofactor' && $x !== 'twofactor'; 262 }); 263 } 264 265 /** 266 * This is a helper function that attempts to load the named modules. 267 * @return array - An array of instanced objects from the loaded modules. 268 */ 269 static public function _loadModules($mods) 270 { 271 $objects = array(); 272 foreach ($mods as $mod) { 273 $obj = plugin_load('helper', $mod); 274 if ($obj && is_a($obj, 'Twofactor_Auth_Module')) { 275 $objects[$mod] = $obj; 276 } 277 } 278 return $objects; 279 } 280 281 // endregion 282} 283