register_hook( 'PLUGIN_TWOFACTOR_PROVIDER_REGISTER', 'AFTER', $this, 'registerSelf', null, Manager::EVENT_PRIORITY - 1 // providers first ); } /** * Register this class as a twofactor provider * * @param \Doku_Event $event * @return void */ public function registerSelf(\Doku_Event $event) { $event->data[$this->getProviderID()] = $this; } /** * Initializes the provider for the given user * @param string $user Current user */ public function init($user) { $this->settings = new Settings($this->getProviderID(), $user); } // region Introspection methods /** * The user data for the current user * @return array (user=>'login', name=>'full name', mail=>'user@example.com', grps=>['group1', 'group2',...]) */ public function getUserData() { /** @var AuthPlugin $auth */ global $auth; $user = $this->settings->getUser(); $userdata = $auth->getUserData($user); if (!$userdata) throw new \RuntimeException('2fa: Failed to get user details from auth backend'); $userdata['user'] = $user; return $userdata; } /** * The ID of this provider * * @return string */ public function getProviderID() { if (!$this->providerID) { $this->providerID = $this->getPluginName(); } return $this->providerID; } /** * Pretty Label for this provider * * @return string */ public function getLabel() { return PhpString::ucfirst($this->providerID); } // endregion // region Configuration methods /** * Clear all settings */ public function reset() { $this->settings->purge(); } /** * Has this provider been fully configured and verified by the user and thus can be used * for authentication? * * @return bool */ abstract public function isConfigured(); /** * Render the configuration form * * This method should add the needed form elements to (re)configure the provider. * The contents of the form may change depending on the current settings. * * No submit button should be added - this is handled by the main plugin. * * @param Form $form The initial form to add elements to * @return Form */ abstract public function renderProfileForm(Form $form); /** * Handle any input data * * @return void */ abstract public function handleProfileForm(); // endregion // region OTP methods /** * Create and store a new secret for this provider * * @return string the new secret * @throws \Exception when no suitable random source is available */ public function initSecret() { $ga = new GoogleAuthenticator(); $secret = $ga->createSecret(); $this->settings->set('secret', $secret); return $secret; } /** * Get the secret for this provider * * @return string */ public function getSecret() { return $this->settings->get('secret'); } /** * Generate an auth code * * @return string * @throws \Exception when no code can be created */ public function generateCode() { $secret = $this->settings->get('secret'); if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); $ga = new GoogleAuthenticator(); return $ga->getCode($secret); } /** * Check the given code * * @param string $code * @param bool $usermessage should a message about the failed code be shown to the user? * @return bool * @throws \RuntimeException when no code can be created */ public function checkCode($code, $usermessage = true) { $secret = $this->settings->get('secret'); if (!$secret) throw new \RuntimeException('No secret for provider ' . $this->getProviderID()); $ga = new GoogleAuthenticator(); $ok = $ga->verifyCode($secret, $code, $this->getTolerance()); if (!$ok && $usermessage) { msg((Manager::getInstance())->getLang('codefail'), -1); } return $ok; } /** * The tolerance to be used when verifying codes * * This is the allowed time drift in 30 second units (8 means 4 minutes before or after) * Different providers may want to use different tolerances by overriding this method. * * @return int */ public function getTolerance() { return 2; } /** * Transmits the code to the user * * If a provider does not transmit anything (eg. TOTP) simply * return the message. * * @param string $code The code to transmit * @return string Informational message for the user * @throw \Exception when the message can't be sent */ abstract public function transmitMessage($code); // endregion }