1fca58076SAndreas Gohr<?php 2fca58076SAndreas Gohr 3fca58076SAndreas Gohrnamespace dokuwiki\plugin\twofactor; 4fca58076SAndreas Gohr 5fca58076SAndreas Gohruse dokuwiki\Extension\Plugin; 6a635cb20SAndreas Gohruse dokuwiki\Form\Form; 7a635cb20SAndreas 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{ 14a635cb20SAndreas Gohr /** @var Settings */ 15a635cb20SAndreas Gohr protected $settings; 16fca58076SAndreas Gohr 17a635cb20SAndreas Gohr /** @var string */ 18a635cb20SAndreas Gohr protected $providerID; 19a635cb20SAndreas Gohr 20a635cb20SAndreas Gohr /** 21a635cb20SAndreas Gohr * Initializes the provider for the given user 22a635cb20SAndreas Gohr * @param string $user Current user 23a635cb20SAndreas Gohr * @throws \Exception 24a635cb20SAndreas Gohr */ 25a635cb20SAndreas Gohr public function __construct($user) 26a635cb20SAndreas Gohr { 27a635cb20SAndreas Gohr $this->providerID = substr(get_called_class(), strlen('helper_plugin_')); 28a635cb20SAndreas Gohr $this->settings = new Settings($this->providerID, $user); 29a635cb20SAndreas Gohr } 30a635cb20SAndreas Gohr 3130625b49SAndreas Gohr // region Introspection methods 3230625b49SAndreas Gohr 33a635cb20SAndreas Gohr /** 34a635cb20SAndreas Gohr * The ID of this provider 35a635cb20SAndreas Gohr * 36a635cb20SAndreas Gohr * @return string 37a635cb20SAndreas Gohr */ 38a635cb20SAndreas Gohr public function getProviderID() 39a635cb20SAndreas Gohr { 40a635cb20SAndreas Gohr return $this->providerID; 41a635cb20SAndreas Gohr } 42a635cb20SAndreas Gohr 43a635cb20SAndreas Gohr /** 44a635cb20SAndreas Gohr * Pretty Label for this provider 45a635cb20SAndreas Gohr * 46a635cb20SAndreas Gohr * @return string 47a635cb20SAndreas Gohr */ 48a635cb20SAndreas Gohr public function getLabel() 49a635cb20SAndreas Gohr { 50a635cb20SAndreas Gohr return PhpString::ucfirst($this->providerID); 51a635cb20SAndreas Gohr } 52a635cb20SAndreas Gohr 5330625b49SAndreas Gohr // endregion 5430625b49SAndreas Gohr // region Configuration methods 5530625b49SAndreas Gohr 56a635cb20SAndreas Gohr /** 57a635cb20SAndreas Gohr * Clear all settings 58a635cb20SAndreas Gohr */ 59a635cb20SAndreas Gohr public function reset() 60a635cb20SAndreas Gohr { 61a635cb20SAndreas Gohr $this->settings->purge(); 62a635cb20SAndreas Gohr } 63a635cb20SAndreas Gohr 64a635cb20SAndreas Gohr /** 65a386a536SAndreas Gohr * Has this provider been fully configured and verified by the user and thus can be used 66a635cb20SAndreas Gohr * for authentication? 67a635cb20SAndreas Gohr * 68a635cb20SAndreas Gohr * @return bool 69a635cb20SAndreas Gohr */ 70a635cb20SAndreas Gohr abstract public function isConfigured(); 71a635cb20SAndreas Gohr 72a635cb20SAndreas Gohr /** 73a635cb20SAndreas Gohr * Render the configuration form 74a635cb20SAndreas Gohr * 75a635cb20SAndreas Gohr * This method should add the needed form elements to (re)configure the provider. 76a635cb20SAndreas Gohr * The contents of the form may change depending on the current settings. 77a635cb20SAndreas Gohr * 78a635cb20SAndreas Gohr * No submit button should be added - this is handled by the main plugin. 79a635cb20SAndreas Gohr * 80a635cb20SAndreas Gohr * @param Form $form The initial form to add elements to 81a635cb20SAndreas Gohr * @return Form 82a635cb20SAndreas Gohr */ 83a635cb20SAndreas Gohr abstract public function renderProfileForm(Form $form); 84a635cb20SAndreas Gohr 85a635cb20SAndreas Gohr /** 86a635cb20SAndreas Gohr * Handle any input data 87a635cb20SAndreas Gohr * 88a635cb20SAndreas Gohr * @return void 89a635cb20SAndreas Gohr */ 90a635cb20SAndreas Gohr abstract public function handleProfileForm(); 91a635cb20SAndreas Gohr 9230625b49SAndreas Gohr // endregion 93a635cb20SAndreas Gohr // region OTP methods 94a635cb20SAndreas Gohr 95a635cb20SAndreas Gohr /** 96a635cb20SAndreas Gohr * Create and store a new secret for this provider 97a635cb20SAndreas Gohr * 98a635cb20SAndreas Gohr * @return string the new secret 99a635cb20SAndreas Gohr * @throws \Exception when no suitable random source is available 100a635cb20SAndreas Gohr */ 101a635cb20SAndreas Gohr public function initSecret() 102a635cb20SAndreas Gohr { 103a635cb20SAndreas Gohr $ga = new GoogleAuthenticator(); 104a635cb20SAndreas Gohr $secret = $ga->createSecret(); 105a635cb20SAndreas Gohr 106a635cb20SAndreas Gohr $this->settings->set('secret', $secret); 107a635cb20SAndreas Gohr return $secret; 108a635cb20SAndreas Gohr } 109a635cb20SAndreas Gohr 110a635cb20SAndreas Gohr /** 111*6c996db8SAndreas Gohr * Get the secret for this provider 112*6c996db8SAndreas Gohr * 113*6c996db8SAndreas Gohr * @return string 114*6c996db8SAndreas Gohr */ 115*6c996db8SAndreas Gohr public function getSecret() 116*6c996db8SAndreas Gohr { 117*6c996db8SAndreas Gohr return $this->settings->get('secret'); 118*6c996db8SAndreas Gohr } 119*6c996db8SAndreas Gohr 120*6c996db8SAndreas Gohr /** 121a635cb20SAndreas Gohr * Generate an auth code 122a635cb20SAndreas Gohr * 123a635cb20SAndreas Gohr * @return string 124a635cb20SAndreas Gohr * @throws \Exception when no code can be created 125a635cb20SAndreas Gohr */ 126a635cb20SAndreas Gohr public function generateCode() 127a635cb20SAndreas Gohr { 128a635cb20SAndreas Gohr $secret = $this->settings->get('secret'); 129a635cb20SAndreas Gohr if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 130a635cb20SAndreas Gohr 131a635cb20SAndreas Gohr $ga = new GoogleAuthenticator(); 132a635cb20SAndreas Gohr return $ga->getCode($secret); 133a635cb20SAndreas Gohr } 134a635cb20SAndreas Gohr 135a635cb20SAndreas Gohr /** 136a635cb20SAndreas Gohr * Check the given code 137a635cb20SAndreas Gohr * 138a635cb20SAndreas Gohr * @param string $code 139a635cb20SAndreas Gohr * @param int $tolerance 140a386a536SAndreas Gohr * @return bool 141a635cb20SAndreas Gohr * @throws \Exception when no code can be created 142a635cb20SAndreas Gohr */ 143a635cb20SAndreas Gohr public function checkCode($code, $tolerance = 2) 144a635cb20SAndreas Gohr { 145a635cb20SAndreas Gohr $secret = $this->settings->get('secret'); 146a635cb20SAndreas Gohr if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 147a635cb20SAndreas Gohr 148a635cb20SAndreas Gohr $ga = new GoogleAuthenticator(); 149a635cb20SAndreas Gohr return $ga->verifyCode($secret, $code, $tolerance); 150a635cb20SAndreas Gohr } 151a635cb20SAndreas Gohr 152a635cb20SAndreas Gohr /** 15330625b49SAndreas Gohr * Transmits the code to the user 15430625b49SAndreas Gohr * 15530625b49SAndreas Gohr * If a provider does not transmit anything (eg. TOTP) simply 15630625b49SAndreas Gohr * return the message. 15730625b49SAndreas Gohr * 15830625b49SAndreas Gohr * @param string $code The code to transmit 15930625b49SAndreas Gohr * @return string Informational message for the user 16030625b49SAndreas Gohr * @throw \Exception when the message can't be sent 161a635cb20SAndreas Gohr */ 16230625b49SAndreas Gohr abstract public function transmitMessage($code); 163a635cb20SAndreas Gohr 164a635cb20SAndreas Gohr // endregion 165fca58076SAndreas Gohr} 166