xref: /plugin/twofactor/Provider.php (revision 848a9be0bf692dd7dee51fee25ed5a3a29453972)
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