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