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 // region Introspection methods 32 33 /** 34 * The ID of this provider 35 * 36 * @return string 37 */ 38 public function getProviderID() 39 { 40 return $this->providerID; 41 } 42 43 /** 44 * Pretty Label for this provider 45 * 46 * @return string 47 */ 48 public function getLabel() 49 { 50 return PhpString::ucfirst($this->providerID); 51 } 52 53 // endregion 54 // region Configuration methods 55 56 /** 57 * Clear all settings 58 */ 59 public function reset() 60 { 61 $this->settings->purge(); 62 } 63 64 /** 65 * Has this provider been fully configured and verified by the user and thus can be used 66 * for authentication? 67 * 68 * @return bool 69 */ 70 abstract public function isConfigured(); 71 72 /** 73 * Render the configuration form 74 * 75 * This method should add the needed form elements to (re)configure the provider. 76 * The contents of the form may change depending on the current settings. 77 * 78 * No submit button should be added - this is handled by the main plugin. 79 * 80 * @param Form $form The initial form to add elements to 81 * @return Form 82 */ 83 abstract public function renderProfileForm(Form $form); 84 85 /** 86 * Handle any input data 87 * 88 * @return void 89 */ 90 abstract public function handleProfileForm(); 91 92 // endregion 93 // region OTP methods 94 95 /** 96 * Create and store a new secret for this provider 97 * 98 * @return string the new secret 99 * @throws \Exception when no suitable random source is available 100 */ 101 public function initSecret() 102 { 103 $ga = new GoogleAuthenticator(); 104 $secret = $ga->createSecret(); 105 106 $this->settings->set('secret', $secret); 107 return $secret; 108 } 109 110 /** 111 * Generate an auth code 112 * 113 * @return string 114 * @throws \Exception when no code can be created 115 */ 116 public function generateCode() 117 { 118 $secret = $this->settings->get('secret'); 119 if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 120 121 $ga = new GoogleAuthenticator(); 122 return $ga->getCode($secret); 123 } 124 125 /** 126 * Check the given code 127 * 128 * @param string $code 129 * @param int $tolerance 130 * @return bool 131 * @throws \Exception when no code can be created 132 */ 133 public function checkCode($code, $tolerance = 2) 134 { 135 $secret = $this->settings->get('secret'); 136 if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID()); 137 138 $ga = new GoogleAuthenticator(); 139 return $ga->verifyCode($secret, $code, $tolerance); 140 } 141 142 /** 143 * Transmits the code to the user 144 * 145 * If a provider does not transmit anything (eg. TOTP) simply 146 * return the message. 147 * 148 * @param string $code The code to transmit 149 * @return string Informational message for the user 150 * @throw \Exception when the message can't be sent 151 */ 152 abstract public function transmitMessage($code); 153 154 // endregion 155} 156