xref: /plugin/twofactor/Manager.php (revision 1c8522cb9ed54182407ffb118486195e8b313a82)
1fca58076SAndreas Gohr<?php
2fca58076SAndreas Gohr
3fca58076SAndreas Gohrnamespace dokuwiki\plugin\twofactor;
4fca58076SAndreas Gohr
55f8f561aSAndreas Gohruse dokuwiki\Extension\Event;
6a386a536SAndreas Gohruse dokuwiki\Extension\Plugin;
7a386a536SAndreas Gohr
8fca58076SAndreas Gohr/**
9fca58076SAndreas Gohr * Manages the child plugins etc.
10fca58076SAndreas Gohr */
11a386a536SAndreas Gohrclass Manager extends Plugin
128b7620a8SAndreas Gohr{
135f8f561aSAndreas Gohr    /**
145f8f561aSAndreas Gohr     * Generally all our actions should run before all other plugins
155f8f561aSAndreas Gohr     */
165f8f561aSAndreas Gohr    const EVENT_PRIORITY = -5000;
175f8f561aSAndreas Gohr
188b7620a8SAndreas Gohr    /** @var Manager */
198b7620a8SAndreas Gohr    protected static $instance;
208b7620a8SAndreas Gohr
218b7620a8SAndreas Gohr    /** @var bool */
228b7620a8SAndreas Gohr    protected $ready = false;
238b7620a8SAndreas Gohr
248b7620a8SAndreas Gohr    /** @var Provider[] */
258b7620a8SAndreas Gohr    protected $providers;
26fca58076SAndreas Gohr
275f8f561aSAndreas Gohr    /** @var bool */
285f8f561aSAndreas Gohr    protected $providersInitialized;
295f8f561aSAndreas Gohr
30fca58076SAndreas Gohr    /**
318b7620a8SAndreas Gohr     * Constructor
32fca58076SAndreas Gohr     */
338b7620a8SAndreas Gohr    protected function __construct()
348b7620a8SAndreas Gohr    {
358b7620a8SAndreas Gohr        $attribute = plugin_load('helper', 'attribute');
368b7620a8SAndreas Gohr        if ($attribute === null) {
378b7620a8SAndreas Gohr            msg('The attribute plugin is not available, 2fa disabled', -1);
385f8f561aSAndreas Gohr            return;
398b7620a8SAndreas Gohr        }
408b7620a8SAndreas Gohr
415f8f561aSAndreas Gohr        $this->loadProviders();
425f8f561aSAndreas Gohr        if (!count($this->providers)) {
438b7620a8SAndreas Gohr            msg('No suitable 2fa providers found, 2fa disabled', -1);
448b7620a8SAndreas Gohr            return;
458b7620a8SAndreas Gohr        }
468b7620a8SAndreas Gohr
478b7620a8SAndreas Gohr        $this->ready = true;
488b7620a8SAndreas Gohr    }
498b7620a8SAndreas Gohr
508b7620a8SAndreas Gohr    /**
514b9cff8aSAndreas Gohr     * This is not a conventional class, plugin name can't be determined automatically
524b9cff8aSAndreas Gohr     * @inheritdoc
534b9cff8aSAndreas Gohr     */
545f8f561aSAndreas Gohr    public function getPluginName()
555f8f561aSAndreas Gohr    {
564b9cff8aSAndreas Gohr        return 'twofactor';
574b9cff8aSAndreas Gohr    }
584b9cff8aSAndreas Gohr
594b9cff8aSAndreas Gohr    /**
608b7620a8SAndreas Gohr     * Get the instance of this singleton
618b7620a8SAndreas Gohr     *
628b7620a8SAndreas Gohr     * @return Manager
638b7620a8SAndreas Gohr     */
648b7620a8SAndreas Gohr    public static function getInstance()
658b7620a8SAndreas Gohr    {
668b7620a8SAndreas Gohr        if (self::$instance === null) {
678b7620a8SAndreas Gohr            self::$instance = new Manager();
688b7620a8SAndreas Gohr        }
698b7620a8SAndreas Gohr        return self::$instance;
708b7620a8SAndreas Gohr    }
718b7620a8SAndreas Gohr
728b7620a8SAndreas Gohr    /**
738b7620a8SAndreas Gohr     * Is the plugin ready to be used?
748b7620a8SAndreas Gohr     *
758b7620a8SAndreas Gohr     * @return bool
768b7620a8SAndreas Gohr     */
778b7620a8SAndreas Gohr    public function isReady()
788b7620a8SAndreas Gohr    {
795f8f561aSAndreas Gohr        if (!$this->ready) return false;
805f8f561aSAndreas Gohr        try {
815f8f561aSAndreas Gohr            $this->getUser();
825f8f561aSAndreas Gohr        } catch (\Exception $ignored) {
835f8f561aSAndreas Gohr            return false;
845f8f561aSAndreas Gohr        }
855f8f561aSAndreas Gohr
865f8f561aSAndreas Gohr        return true;
878b7620a8SAndreas Gohr    }
888b7620a8SAndreas Gohr
898b7620a8SAndreas Gohr    /**
90b6119621SAndreas Gohr     * Is a 2fa login required?
91b6119621SAndreas Gohr     *
92b6119621SAndreas Gohr     * @return bool
93a386a536SAndreas Gohr     */
94a386a536SAndreas Gohr    public function isRequired()
95a386a536SAndreas Gohr    {
96a386a536SAndreas Gohr        $set = $this->getConf('optinout');
97a386a536SAndreas Gohr        if ($set === 'mandatory') {
98a386a536SAndreas Gohr            return true;
99a386a536SAndreas Gohr        }
1004b9cff8aSAndreas Gohr        if ($set === 'optout') {
1014b9cff8aSAndreas Gohr            $setting = new Settings('twofactor', $this->getUser());
1024b9cff8aSAndreas Gohr            if ($setting->get('state') !== 'optout') {
1034b9cff8aSAndreas Gohr                return true;
1044b9cff8aSAndreas Gohr            }
1054b9cff8aSAndreas Gohr        }
106a386a536SAndreas Gohr
107a386a536SAndreas Gohr        return false;
108a386a536SAndreas Gohr    }
109a386a536SAndreas Gohr
110a386a536SAndreas Gohr    /**
111a386a536SAndreas Gohr     * Convenience method to get current user
112a386a536SAndreas Gohr     *
113a386a536SAndreas Gohr     * @return string
114a386a536SAndreas Gohr     */
115a386a536SAndreas Gohr    public function getUser()
116a386a536SAndreas Gohr    {
117a386a536SAndreas Gohr        global $INPUT;
118b6119621SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
119b6119621SAndreas Gohr        if (!$user) {
120b6119621SAndreas Gohr            throw new \RuntimeException('2fa user specifics used before user available');
121b6119621SAndreas Gohr        }
122b6119621SAndreas Gohr        return $user;
123a386a536SAndreas Gohr    }
124a386a536SAndreas Gohr
125a386a536SAndreas Gohr    /**
1264b9cff8aSAndreas Gohr     * Get or set the user opt-out state
1274b9cff8aSAndreas Gohr     *
1284b9cff8aSAndreas Gohr     * true: user opted out
1294b9cff8aSAndreas Gohr     * false: user did not opt out
1304b9cff8aSAndreas Gohr     *
1314b9cff8aSAndreas Gohr     * @param bool|null $set
1324b9cff8aSAndreas Gohr     * @return bool
1334b9cff8aSAndreas Gohr     */
1344b9cff8aSAndreas Gohr    public function userOptOutState($set = null)
1354b9cff8aSAndreas Gohr    {
1364b9cff8aSAndreas Gohr        // is optout allowed?
1374b9cff8aSAndreas Gohr        if ($this->getConf('optinout') !== 'optout') return false;
1384b9cff8aSAndreas Gohr
1394b9cff8aSAndreas Gohr        $settings = new Settings('twofactor', $this->getUser());
1404b9cff8aSAndreas Gohr
1414b9cff8aSAndreas Gohr        if ($set === null) {
1424b9cff8aSAndreas Gohr            $current = $settings->get('state');
1434b9cff8aSAndreas Gohr            return $current === 'optout';
1444b9cff8aSAndreas Gohr        }
1454b9cff8aSAndreas Gohr
1464b9cff8aSAndreas Gohr        if ($set) {
1474b9cff8aSAndreas Gohr            $settings->set('state', 'optout');
1484b9cff8aSAndreas Gohr        } else {
1494b9cff8aSAndreas Gohr            $settings->delete('state');
1504b9cff8aSAndreas Gohr        }
1514b9cff8aSAndreas Gohr        return $set;
1524b9cff8aSAndreas Gohr    }
1534b9cff8aSAndreas Gohr
1544b9cff8aSAndreas Gohr    /**
1558b7620a8SAndreas Gohr     * Get all available providers
1568b7620a8SAndreas Gohr     *
1578b7620a8SAndreas Gohr     * @return Provider[]
1588b7620a8SAndreas Gohr     */
1598b7620a8SAndreas Gohr    public function getAllProviders()
1608b7620a8SAndreas Gohr    {
161a386a536SAndreas Gohr        $user = $this->getUser();
1628b7620a8SAndreas Gohr
1635f8f561aSAndreas Gohr        if (!$this->providersInitialized) {
1645f8f561aSAndreas Gohr            // initialize providers with user and ensure the ID is correct
1655f8f561aSAndreas Gohr            foreach ($this->providers as $providerID => $provider) {
1665f8f561aSAndreas Gohr                if ($providerID !== $provider->getProviderID()) {
1675f8f561aSAndreas Gohr                    $this->providers[$provider->getProviderID()] = $provider;
1685f8f561aSAndreas Gohr                    unset($this->providers[$providerID]);
1698b7620a8SAndreas Gohr                }
1705f8f561aSAndreas Gohr                $provider->init($user);
1715f8f561aSAndreas Gohr            }
1725f8f561aSAndreas Gohr            $this->providersInitialized = true;
1738b7620a8SAndreas Gohr        }
1748b7620a8SAndreas Gohr
1758b7620a8SAndreas Gohr        return $this->providers;
1768b7620a8SAndreas Gohr    }
1778b7620a8SAndreas Gohr
1788b7620a8SAndreas Gohr    /**
179a386a536SAndreas Gohr     * Get all providers that have been already set up by the user
180a386a536SAndreas Gohr     *
181*1c8522cbSAndreas Gohr     * @param bool $configured when set to false, all providers NOT configured are returned
182a386a536SAndreas Gohr     * @return Provider[]
183a386a536SAndreas Gohr     */
184*1c8522cbSAndreas Gohr    public function getUserProviders($configured = true)
185a386a536SAndreas Gohr    {
186a386a536SAndreas Gohr        $list = $this->getAllProviders();
187*1c8522cbSAndreas Gohr        $list = array_filter($list, function ($provider) use ($configured) {
188*1c8522cbSAndreas Gohr            return $configured ? $provider->isConfigured() : !$provider->isConfigured();
189a386a536SAndreas Gohr        });
190a386a536SAndreas Gohr
191a386a536SAndreas Gohr        return $list;
192a386a536SAndreas Gohr    }
193a386a536SAndreas Gohr
194a386a536SAndreas Gohr    /**
195a386a536SAndreas Gohr     * Get the instance of the given provider
196a386a536SAndreas Gohr     *
197a386a536SAndreas Gohr     * @param string $providerID
198a386a536SAndreas Gohr     * @return Provider
1996c996db8SAndreas Gohr     * @throws \Exception
200a386a536SAndreas Gohr     */
201a386a536SAndreas Gohr    public function getUserProvider($providerID)
202a386a536SAndreas Gohr    {
203a386a536SAndreas Gohr        $providers = $this->getUserProviders();
204a386a536SAndreas Gohr        if (isset($providers[$providerID])) return $providers[$providerID];
2056c996db8SAndreas Gohr        throw new \Exception('Uncofigured provider requested');
206a386a536SAndreas Gohr    }
207a386a536SAndreas Gohr
208a386a536SAndreas Gohr    /**
209b6119621SAndreas Gohr     * Get the user's default provider if any
210b6119621SAndreas Gohr     *
211b6119621SAndreas Gohr     * Autoupdates the apropriate setting
212b6119621SAndreas Gohr     *
213b6119621SAndreas Gohr     * @return Provider|null
214b6119621SAndreas Gohr     */
215b6119621SAndreas Gohr    public function getUserDefaultProvider()
216b6119621SAndreas Gohr    {
217b6119621SAndreas Gohr        $setting = new Settings('twofactor', $this->getUser());
218b6119621SAndreas Gohr        $default = $setting->get('defaultmod');
219b6119621SAndreas Gohr        $providers = $this->getUserProviders();
220b6119621SAndreas Gohr
221b6119621SAndreas Gohr        if (isset($providers[$default])) return $providers[$default];
222b6119621SAndreas Gohr        // still here? no valid setting. Use first available one
223b6119621SAndreas Gohr        $first = array_shift($providers);
224b6119621SAndreas Gohr        if ($first !== null) {
225b6119621SAndreas Gohr            $this->setUserDefaultProvider($first);
226b6119621SAndreas Gohr        }
227b6119621SAndreas Gohr        return $first;
228b6119621SAndreas Gohr    }
229b6119621SAndreas Gohr
230b6119621SAndreas Gohr    /**
231b6119621SAndreas Gohr     * Set the default provider for the user
232b6119621SAndreas Gohr     *
233b6119621SAndreas Gohr     * @param Provider $provider
234b6119621SAndreas Gohr     * @return void
235b6119621SAndreas Gohr     */
236b6119621SAndreas Gohr    public function setUserDefaultProvider($provider)
237b6119621SAndreas Gohr    {
238b6119621SAndreas Gohr        $setting = new Settings('twofactor', $this->getUser());
239b6119621SAndreas Gohr        $setting->set('defaultmod', $provider->getProviderID());
240b6119621SAndreas Gohr    }
241b6119621SAndreas Gohr
242b6119621SAndreas Gohr    /**
2435f8f561aSAndreas Gohr     * Load all available provider classes
2448b7620a8SAndreas Gohr     *
2455f8f561aSAndreas Gohr     * @return Provider[];
2468b7620a8SAndreas Gohr     */
2475f8f561aSAndreas Gohr    protected function loadProviders()
2488b7620a8SAndreas Gohr    {
2495f8f561aSAndreas Gohr        /** @var Provider[] providers */
2505f8f561aSAndreas Gohr        $this->providers = [];
2515f8f561aSAndreas Gohr        $event = new Event('PLUGIN_TWOFACTOR_PROVIDER_REGISTER', $this->providers);
2525f8f561aSAndreas Gohr        $event->advise_before(false);
2535f8f561aSAndreas Gohr        $event->advise_after();
2545f8f561aSAndreas Gohr        return $this->providers;
255fca58076SAndreas Gohr    }
256fca58076SAndreas Gohr
257fca58076SAndreas Gohr}
258