xref: /plugin/twofactor/Provider.php (revision 16ed3964cc56fca397d2156a2100a20f6e3743bc)
1fca58076SAndreas Gohr<?php
2fca58076SAndreas Gohr
3fca58076SAndreas Gohrnamespace dokuwiki\plugin\twofactor;
4fca58076SAndreas Gohr
55f8f561aSAndreas Gohruse dokuwiki\Extension\ActionPlugin;
6a635cb20SAndreas Gohruse dokuwiki\Form\Form;
7a635cb20SAndreas Gohruse dokuwiki\Utf8\PhpString;
8fca58076SAndreas Gohr
9fca58076SAndreas Gohr/**
10fca58076SAndreas Gohr * Baseclass for all second factor providers
11fca58076SAndreas Gohr */
125f8f561aSAndreas Gohrabstract class Provider extends ActionPlugin
13fca58076SAndreas Gohr{
14a635cb20SAndreas Gohr    /** @var Settings */
15a635cb20SAndreas Gohr    protected $settings;
16fca58076SAndreas Gohr
17a635cb20SAndreas Gohr    /** @var string */
18a635cb20SAndreas Gohr    protected $providerID;
19a635cb20SAndreas Gohr
205f8f561aSAndreas Gohr    /** @inheritdoc */
215f8f561aSAndreas Gohr    public function register(\Doku_Event_Handler $controller)
225f8f561aSAndreas Gohr    {
235f8f561aSAndreas Gohr        $controller->register_hook(
245f8f561aSAndreas Gohr            'PLUGIN_TWOFACTOR_PROVIDER_REGISTER',
255f8f561aSAndreas Gohr            'AFTER',
265f8f561aSAndreas Gohr            $this,
275f8f561aSAndreas Gohr            'registerSelf',
285f8f561aSAndreas Gohr            null,
295f8f561aSAndreas Gohr            Manager::EVENT_PRIORITY - 1 // providers first
305f8f561aSAndreas Gohr        );
315f8f561aSAndreas Gohr    }
325f8f561aSAndreas Gohr
335f8f561aSAndreas Gohr    /**
345f8f561aSAndreas Gohr     * Register this class as a twofactor provider
355f8f561aSAndreas Gohr     *
365f8f561aSAndreas Gohr     * @param \Doku_Event $event
375f8f561aSAndreas Gohr     * @return void
385f8f561aSAndreas Gohr     */
395f8f561aSAndreas Gohr    public function registerSelf(\Doku_Event $event)
405f8f561aSAndreas Gohr    {
415f8f561aSAndreas Gohr        $event->data[$this->getProviderID()] = $this;
425f8f561aSAndreas Gohr    }
435f8f561aSAndreas Gohr
44a635cb20SAndreas Gohr    /**
45a635cb20SAndreas Gohr     * Initializes the provider for the given user
46a635cb20SAndreas Gohr     * @param string $user Current user
47a635cb20SAndreas Gohr     */
485f8f561aSAndreas Gohr    public function init($user)
49a635cb20SAndreas Gohr    {
505f8f561aSAndreas Gohr        $this->settings = new Settings($this->getProviderID(), $user);
51a635cb20SAndreas Gohr    }
52a635cb20SAndreas Gohr
5330625b49SAndreas Gohr    // region Introspection methods
5430625b49SAndreas Gohr
55a635cb20SAndreas Gohr    /**
56a635cb20SAndreas Gohr     * The ID of this provider
57a635cb20SAndreas Gohr     *
58a635cb20SAndreas Gohr     * @return string
59a635cb20SAndreas Gohr     */
60a635cb20SAndreas Gohr    public function getProviderID()
61a635cb20SAndreas Gohr    {
625f8f561aSAndreas Gohr        if (!$this->providerID) {
635f8f561aSAndreas Gohr            $this->providerID = $this->getPluginName();
645f8f561aSAndreas Gohr        }
655f8f561aSAndreas Gohr
66a635cb20SAndreas Gohr        return $this->providerID;
67a635cb20SAndreas Gohr    }
68a635cb20SAndreas Gohr
69a635cb20SAndreas Gohr    /**
70a635cb20SAndreas Gohr     * Pretty Label for this provider
71a635cb20SAndreas Gohr     *
72a635cb20SAndreas Gohr     * @return string
73a635cb20SAndreas Gohr     */
74a635cb20SAndreas Gohr    public function getLabel()
75a635cb20SAndreas Gohr    {
76a635cb20SAndreas Gohr        return PhpString::ucfirst($this->providerID);
77a635cb20SAndreas Gohr    }
78a635cb20SAndreas Gohr
7930625b49SAndreas Gohr    // endregion
8030625b49SAndreas Gohr    // region Configuration methods
8130625b49SAndreas Gohr
82a635cb20SAndreas Gohr    /**
83a635cb20SAndreas Gohr     * Clear all settings
84a635cb20SAndreas Gohr     */
85a635cb20SAndreas Gohr    public function reset()
86a635cb20SAndreas Gohr    {
87a635cb20SAndreas Gohr        $this->settings->purge();
88a635cb20SAndreas Gohr    }
89a635cb20SAndreas Gohr
90a635cb20SAndreas Gohr    /**
91a386a536SAndreas Gohr     * Has this provider been fully configured and verified by the user and thus can be used
92a635cb20SAndreas Gohr     * for authentication?
93a635cb20SAndreas Gohr     *
94a635cb20SAndreas Gohr     * @return bool
95a635cb20SAndreas Gohr     */
96a635cb20SAndreas Gohr    abstract public function isConfigured();
97a635cb20SAndreas Gohr
98a635cb20SAndreas Gohr    /**
99a635cb20SAndreas Gohr     * Render the configuration form
100a635cb20SAndreas Gohr     *
101a635cb20SAndreas Gohr     * This method should add the needed form elements to (re)configure the provider.
102a635cb20SAndreas Gohr     * The contents of the form may change depending on the current settings.
103a635cb20SAndreas Gohr     *
104a635cb20SAndreas Gohr     * No submit button should be added - this is handled by the main plugin.
105a635cb20SAndreas Gohr     *
106a635cb20SAndreas Gohr     * @param Form $form The initial form to add elements to
107a635cb20SAndreas Gohr     * @return Form
108a635cb20SAndreas Gohr     */
109a635cb20SAndreas Gohr    abstract public function renderProfileForm(Form $form);
110a635cb20SAndreas Gohr
111a635cb20SAndreas Gohr    /**
112a635cb20SAndreas Gohr     * Handle any input data
113a635cb20SAndreas Gohr     *
114a635cb20SAndreas Gohr     * @return void
115a635cb20SAndreas Gohr     */
116a635cb20SAndreas Gohr    abstract public function handleProfileForm();
117a635cb20SAndreas Gohr
11830625b49SAndreas Gohr    // endregion
119a635cb20SAndreas Gohr    // region OTP methods
120a635cb20SAndreas Gohr
121a635cb20SAndreas Gohr    /**
122a635cb20SAndreas Gohr     * Create and store a new secret for this provider
123a635cb20SAndreas Gohr     *
124a635cb20SAndreas Gohr     * @return string the new secret
125a635cb20SAndreas Gohr     * @throws \Exception when no suitable random source is available
126a635cb20SAndreas Gohr     */
127a635cb20SAndreas Gohr    public function initSecret()
128a635cb20SAndreas Gohr    {
129a635cb20SAndreas Gohr        $ga = new GoogleAuthenticator();
130a635cb20SAndreas Gohr        $secret = $ga->createSecret();
131a635cb20SAndreas Gohr
132a635cb20SAndreas Gohr        $this->settings->set('secret', $secret);
133a635cb20SAndreas Gohr        return $secret;
134a635cb20SAndreas Gohr    }
135a635cb20SAndreas Gohr
136a635cb20SAndreas Gohr    /**
1376c996db8SAndreas Gohr     * Get the secret for this provider
1386c996db8SAndreas Gohr     *
1396c996db8SAndreas Gohr     * @return string
1406c996db8SAndreas Gohr     */
1416c996db8SAndreas Gohr    public function getSecret()
1426c996db8SAndreas Gohr    {
1436c996db8SAndreas Gohr        return $this->settings->get('secret');
1446c996db8SAndreas Gohr    }
1456c996db8SAndreas Gohr
1466c996db8SAndreas Gohr    /**
147a635cb20SAndreas Gohr     * Generate an auth code
148a635cb20SAndreas Gohr     *
149a635cb20SAndreas Gohr     * @return string
150a635cb20SAndreas Gohr     * @throws \Exception when no code can be created
151a635cb20SAndreas Gohr     */
152a635cb20SAndreas Gohr    public function generateCode()
153a635cb20SAndreas Gohr    {
154a635cb20SAndreas Gohr        $secret = $this->settings->get('secret');
155a635cb20SAndreas Gohr        if (!$secret) throw new \Exception('No secret for provider ' . $this->getProviderID());
156a635cb20SAndreas Gohr
157a635cb20SAndreas Gohr        $ga = new GoogleAuthenticator();
158a635cb20SAndreas Gohr        return $ga->getCode($secret);
159a635cb20SAndreas Gohr    }
160a635cb20SAndreas Gohr
161a635cb20SAndreas Gohr    /**
162a635cb20SAndreas Gohr     * Check the given code
163a635cb20SAndreas Gohr     *
164a635cb20SAndreas Gohr     * @param string $code
165*16ed3964SAndreas Gohr     * @param bool $usermessage should a message about the failed code be shown to the user?
166a386a536SAndreas Gohr     * @return bool
167*16ed3964SAndreas Gohr     * @throws \RuntimeException when no code can be created
168a635cb20SAndreas Gohr     */
169*16ed3964SAndreas Gohr    public function checkCode($code, $usermessage = true)
170a635cb20SAndreas Gohr    {
171a635cb20SAndreas Gohr        $secret = $this->settings->get('secret');
172*16ed3964SAndreas Gohr        if (!$secret) throw new \RuntimeException('No secret for provider ' . $this->getProviderID());
173a635cb20SAndreas Gohr
174a635cb20SAndreas Gohr        $ga = new GoogleAuthenticator();
175*16ed3964SAndreas Gohr        $ok = $ga->verifyCode($secret, $code, $this->getTolerance());
176*16ed3964SAndreas Gohr        if (!$ok && $usermessage) {
177*16ed3964SAndreas Gohr            msg((Manager::getInstance())->getLang('codefail'), -1);
178*16ed3964SAndreas Gohr        }
179*16ed3964SAndreas Gohr        return $ok;
1802fadf188SAndreas Gohr    }
1812fadf188SAndreas Gohr
1822fadf188SAndreas Gohr    /**
1832fadf188SAndreas Gohr     * The tolerance to be used when verifying codes
1842fadf188SAndreas Gohr     *
1852fadf188SAndreas Gohr     * This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
1862fadf188SAndreas Gohr     * Different providers may want to use different tolerances by overriding this method.
1872fadf188SAndreas Gohr     *
1882fadf188SAndreas Gohr     * @return int
1892fadf188SAndreas Gohr     */
1902fadf188SAndreas Gohr    public function getTolerance()
1912fadf188SAndreas Gohr    {
1922fadf188SAndreas Gohr        return 2;
193a635cb20SAndreas Gohr    }
194a635cb20SAndreas Gohr
195a635cb20SAndreas Gohr    /**
19630625b49SAndreas Gohr     * Transmits the code to the user
19730625b49SAndreas Gohr     *
19830625b49SAndreas Gohr     * If a provider does not transmit anything (eg. TOTP) simply
19930625b49SAndreas Gohr     * return the message.
20030625b49SAndreas Gohr     *
20130625b49SAndreas Gohr     * @param string $code The code to transmit
20230625b49SAndreas Gohr     * @return string Informational message for the user
20330625b49SAndreas Gohr     * @throw \Exception when the message can't be sent
204a635cb20SAndreas Gohr     */
20530625b49SAndreas Gohr    abstract public function transmitMessage($code);
206a635cb20SAndreas Gohr
207a635cb20SAndreas Gohr    // endregion
208fca58076SAndreas Gohr}
209