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