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 and verified 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 * If a provider does not transmit anything (eg. TOTP) simply 91 * return the message. 92 * 93 * @param string $code The code to transmit 94 * @return string Informational message for the user 95 * @throw \Exception when the message can't be sent 96 */ 97 abstract public function transmitMessage($code); 98 99 // region OTP methods 100 101 /** 102 * Create and store a new secret for this provider 103 * 104 * @return string the new secret 105 * @throws \Exception when no suitable random source is available 106 */ 107 public function initSecret() 108 { 109 $ga = new GoogleAuthenticator(); 110 $secret = $ga->createSecret(); 111 112 $this->settings->set('secret', $secret); 113 return $secret; 114 } 115 116 /** 117 * Generate an auth code 118 * 119 * @return string 120 * @throws \Exception when no code can be created 121 */ 122 public function generateCode() 123 { 124 $secret = $this->settings->get('secret'); 125 if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 126 127 $ga = new GoogleAuthenticator(); 128 return $ga->getCode($secret); 129 } 130 131 /** 132 * Check the given code 133 * 134 * @param string $code 135 * @param int $tolerance 136 * @return bool 137 * @throws \Exception when no code can be created 138 */ 139 public function checkCode($code, $tolerance = 2) 140 { 141 $secret = $this->settings->get('secret'); 142 if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 143 144 $ga = new GoogleAuthenticator(); 145 return $ga->verifyCode($secret, $code, $tolerance); 146 } 147 148 // endregion 149 150 // region old shit 151 152 /** 153 * This is called to see if the user can use it to login. 154 * @return bool - True if this module has access to all needed information 155 * to perform a login. 156 */ 157 abstract public function canUse($user = null); 158 159 /** 160 * This is called to see if the module provides login functionality on the 161 * main login page. 162 * @return bool - True if this module provides main login functionality. 163 */ 164 abstract public function canAuthLogin(); 165 166 /** 167 * This is called to process the user configurable portion of the module 168 * inside the user's profile. 169 * @return mixed - True if the user's settings were changed, false if 170 * settings could not be changed, null if no settings were changed, 171 * the string 'verified' if the module was successfully verified, 172 * the string 'failed' if the module failed verification, 173 * the string 'otp' if the module is requesting a one-time password 174 * for verification, 175 * the string 'deleted' if the module was unenrolled. 176 */ 177 public function processProfileForm() 178 { 179 return null; 180 } 181 182 /** 183 * This is called to see if the module can send a message to the user. 184 * @return bool - True if a message can be sent to the user. 185 */ 186 abstract public function canTransmitMessage(); 187 188 /** 189 * This is called to validate the code provided. The default is to see if 190 * the code matches the one-time password. 191 * @return bool - True if the user has successfully authenticated using 192 * this mechanism. 193 */ 194 public function processLogin($code, $user = null) 195 { 196 $twofactor = plugin_load('action', 'twofactor'); 197 $otpQuery = $twofactor->get_otp_code(); 198 if (!$otpQuery) { 199 return false; 200 } 201 list($otp, $modname) = $otpQuery; 202 return ($code == $otp && $code != '' && (count($modname) == 0 || in_array(get_called_class(), $modname))); 203 } 204 205 /** 206 * This is a helper function to get text strings from the twofactor class 207 * calling this module. 208 * @return string - Language string from the calling class. 209 */ 210 protected function _getSharedLang($key) 211 { 212 $twofactor = plugin_load('action', 'twofactor'); 213 return $twofactor->getLang($key); 214 } 215 216 /** 217 * This is a helper function to get shared configuration options from the 218 * twofactor class. 219 * @return string - Language string from the calling class. 220 */ 221 protected function _getSharedConfig($key) 222 { 223 $twofactor = plugin_load('action', 'twofactor'); 224 return $twofactor->getConf($key); 225 } 226 227 /** 228 * This is a helper function to check for the existence of shared 229 * twofactor settings. 230 * @return string - Language string from the calling class. 231 */ 232 protected function _sharedSettingExists($key) 233 { 234 return $this->attribute->exists("twofactor", $key); 235 } 236 237 /** 238 * This is a helper function to get shared twofactor settings. 239 * @return string - Language string from the calling class. 240 */ 241 protected function _sharedSettingGet($key, $default = null, $user = null) 242 { 243 return $this->_sharedSettingExists($key) ? $this->attribute->get("twofactor", $key, $success, $user) : $default; 244 } 245 246 /** 247 * This is a helper function to set shared twofactor settings. 248 * @return string - Language string from the calling class. 249 */ 250 protected function _sharedSettingSet($key, $value) 251 { 252 return $this->attribute->set("twofactor", $key, $value); 253 } 254 255 256 257 /** 258 * This is a helper function that attempts to load the named modules. 259 * @return array - An array of instanced objects from the loaded modules. 260 */ 261 static public function _loadModules($mods) 262 { 263 $objects = array(); 264 foreach ($mods as $mod) { 265 $obj = plugin_load('helper', $mod); 266 if ($obj && is_a($obj, 'Twofactor_Auth_Module')) { 267 $objects[$mod] = $obj; 268 } 269 } 270 return $objects; 271 } 272 273 // endregion 274} 275