xref: /plugin/twofactor/Manager.php (revision 4b9cff8a870f8684b87bcaa2b3aecd09387ffb2e)
1fca58076SAndreas Gohr<?php
2fca58076SAndreas Gohr
3fca58076SAndreas Gohrnamespace dokuwiki\plugin\twofactor;
4fca58076SAndreas Gohr
5a386a536SAndreas Gohruse dokuwiki\Extension\Plugin;
6a386a536SAndreas Gohr
7fca58076SAndreas Gohr/**
8fca58076SAndreas Gohr * Manages the child plugins etc.
9fca58076SAndreas Gohr */
10a386a536SAndreas Gohrclass Manager extends Plugin
118b7620a8SAndreas Gohr{
128b7620a8SAndreas Gohr    /** @var Manager */
138b7620a8SAndreas Gohr    protected static $instance;
148b7620a8SAndreas Gohr
158b7620a8SAndreas Gohr    /** @var bool */
168b7620a8SAndreas Gohr    protected $ready = false;
178b7620a8SAndreas Gohr
188b7620a8SAndreas Gohr    /** @var string[] */
198b7620a8SAndreas Gohr    protected $classes = [];
208b7620a8SAndreas Gohr
218b7620a8SAndreas Gohr    /** @var Provider[] */
228b7620a8SAndreas Gohr    protected $providers;
23fca58076SAndreas Gohr
24fca58076SAndreas Gohr    /**
258b7620a8SAndreas Gohr     * Constructor
26fca58076SAndreas Gohr     */
278b7620a8SAndreas Gohr    protected function __construct()
288b7620a8SAndreas Gohr    {
298b7620a8SAndreas Gohr        $this->classes = $this->getProviderClasses();
308b7620a8SAndreas Gohr
318b7620a8SAndreas Gohr        $attribute = plugin_load('helper', 'attribute');
328b7620a8SAndreas Gohr        if ($attribute === null) {
338b7620a8SAndreas Gohr            msg('The attribute plugin is not available, 2fa disabled', -1);
348b7620a8SAndreas Gohr        }
358b7620a8SAndreas Gohr
368b7620a8SAndreas Gohr        if (!count($this->classes)) {
378b7620a8SAndreas Gohr            msg('No suitable 2fa providers found, 2fa disabled', -1);
388b7620a8SAndreas Gohr            return;
398b7620a8SAndreas Gohr        }
408b7620a8SAndreas Gohr
418b7620a8SAndreas Gohr        $this->ready = true;
428b7620a8SAndreas Gohr    }
438b7620a8SAndreas Gohr
448b7620a8SAndreas Gohr    /**
45*4b9cff8aSAndreas Gohr     * This is not a conventional class, plugin name can't be determined automatically
46*4b9cff8aSAndreas Gohr     * @inheritdoc
47*4b9cff8aSAndreas Gohr     */
48*4b9cff8aSAndreas Gohr    public function getPluginName() {
49*4b9cff8aSAndreas Gohr        return 'twofactor';
50*4b9cff8aSAndreas Gohr    }
51*4b9cff8aSAndreas Gohr
52*4b9cff8aSAndreas Gohr    /**
538b7620a8SAndreas Gohr     * Get the instance of this singleton
548b7620a8SAndreas Gohr     *
558b7620a8SAndreas Gohr     * @return Manager
568b7620a8SAndreas Gohr     */
578b7620a8SAndreas Gohr    public static function getInstance()
588b7620a8SAndreas Gohr    {
598b7620a8SAndreas Gohr        if (self::$instance === null) {
608b7620a8SAndreas Gohr            self::$instance = new Manager();
618b7620a8SAndreas Gohr        }
628b7620a8SAndreas Gohr        return self::$instance;
638b7620a8SAndreas Gohr    }
648b7620a8SAndreas Gohr
658b7620a8SAndreas Gohr    /**
668b7620a8SAndreas Gohr     * Is the plugin ready to be used?
678b7620a8SAndreas Gohr     *
688b7620a8SAndreas Gohr     * @return bool
698b7620a8SAndreas Gohr     */
708b7620a8SAndreas Gohr    public function isReady()
718b7620a8SAndreas Gohr    {
728b7620a8SAndreas Gohr        return $this->ready;
738b7620a8SAndreas Gohr    }
748b7620a8SAndreas Gohr
758b7620a8SAndreas Gohr    /**
76b6119621SAndreas Gohr     * Is a 2fa login required?
77b6119621SAndreas Gohr     *
78b6119621SAndreas Gohr     * @return bool
79a386a536SAndreas Gohr     */
80a386a536SAndreas Gohr    public function isRequired()
81a386a536SAndreas Gohr    {
82a386a536SAndreas Gohr        $set = $this->getConf('optinout');
83a386a536SAndreas Gohr        if ($set === 'mandatory') {
84a386a536SAndreas Gohr            return true;
85a386a536SAndreas Gohr        }
86*4b9cff8aSAndreas Gohr        if ($set === 'optout') {
87*4b9cff8aSAndreas Gohr            $setting = new Settings('twofactor', $this->getUser());
88*4b9cff8aSAndreas Gohr            if ($setting->get('state') !== 'optout') {
89*4b9cff8aSAndreas Gohr                return true;
90*4b9cff8aSAndreas Gohr            }
91*4b9cff8aSAndreas Gohr        }
92a386a536SAndreas Gohr
93a386a536SAndreas Gohr        return false;
94a386a536SAndreas Gohr    }
95a386a536SAndreas Gohr
96a386a536SAndreas Gohr    /**
97a386a536SAndreas Gohr     * Convenience method to get current user
98a386a536SAndreas Gohr     *
99a386a536SAndreas Gohr     * @return string
100a386a536SAndreas Gohr     */
101a386a536SAndreas Gohr    public function getUser()
102a386a536SAndreas Gohr    {
103a386a536SAndreas Gohr        global $INPUT;
104b6119621SAndreas Gohr        $user = $INPUT->server->str('REMOTE_USER');
105b6119621SAndreas Gohr        if (!$user) {
106b6119621SAndreas Gohr            throw new \RuntimeException('2fa user specifics used before user available');
107b6119621SAndreas Gohr        }
108b6119621SAndreas Gohr        return $user;
109a386a536SAndreas Gohr    }
110a386a536SAndreas Gohr
111a386a536SAndreas Gohr    /**
112*4b9cff8aSAndreas Gohr     * Get or set the user opt-out state
113*4b9cff8aSAndreas Gohr     *
114*4b9cff8aSAndreas Gohr     * true: user opted out
115*4b9cff8aSAndreas Gohr     * false: user did not opt out
116*4b9cff8aSAndreas Gohr     *
117*4b9cff8aSAndreas Gohr     * @param bool|null $set
118*4b9cff8aSAndreas Gohr     * @return bool
119*4b9cff8aSAndreas Gohr     */
120*4b9cff8aSAndreas Gohr    public function userOptOutState($set = null)
121*4b9cff8aSAndreas Gohr    {
122*4b9cff8aSAndreas Gohr        // is optout allowed?
123*4b9cff8aSAndreas Gohr        if ($this->getConf('optinout') !== 'optout') return false;
124*4b9cff8aSAndreas Gohr
125*4b9cff8aSAndreas Gohr        $settings = new Settings('twofactor', $this->getUser());
126*4b9cff8aSAndreas Gohr
127*4b9cff8aSAndreas Gohr        if ($set === null) {
128*4b9cff8aSAndreas Gohr            $current = $settings->get('state');
129*4b9cff8aSAndreas Gohr            return $current === 'optout';
130*4b9cff8aSAndreas Gohr        }
131*4b9cff8aSAndreas Gohr
132*4b9cff8aSAndreas Gohr        if ($set) {
133*4b9cff8aSAndreas Gohr            $settings->set('state', 'optout');
134*4b9cff8aSAndreas Gohr        } else {
135*4b9cff8aSAndreas Gohr            $settings->delete('state');
136*4b9cff8aSAndreas Gohr        }
137*4b9cff8aSAndreas Gohr        return $set;
138*4b9cff8aSAndreas Gohr    }
139*4b9cff8aSAndreas Gohr
140*4b9cff8aSAndreas Gohr    /**
1418b7620a8SAndreas Gohr     * Get all available providers
1428b7620a8SAndreas Gohr     *
1438b7620a8SAndreas Gohr     * @return Provider[]
1448b7620a8SAndreas Gohr     */
1458b7620a8SAndreas Gohr    public function getAllProviders()
1468b7620a8SAndreas Gohr    {
147a386a536SAndreas Gohr        $user = $this->getUser();
1488b7620a8SAndreas Gohr
1498b7620a8SAndreas Gohr        if ($this->providers === null) {
1508b7620a8SAndreas Gohr            $this->providers = [];
151a386a536SAndreas Gohr            foreach ($this->classes as $plugin => $class) {
152a386a536SAndreas Gohr                $this->providers[$plugin] = new $class($user);
1538b7620a8SAndreas Gohr            }
1548b7620a8SAndreas Gohr        }
1558b7620a8SAndreas Gohr
1568b7620a8SAndreas Gohr        return $this->providers;
1578b7620a8SAndreas Gohr    }
1588b7620a8SAndreas Gohr
1598b7620a8SAndreas Gohr    /**
160a386a536SAndreas Gohr     * Get all providers that have been already set up by the user
161a386a536SAndreas Gohr     *
162a386a536SAndreas Gohr     * @return Provider[]
163a386a536SAndreas Gohr     */
164a386a536SAndreas Gohr    public function getUserProviders()
165a386a536SAndreas Gohr    {
166a386a536SAndreas Gohr        $list = $this->getAllProviders();
167a386a536SAndreas Gohr        $list = array_filter($list, function ($provider) {
168a386a536SAndreas Gohr            return $provider->isConfigured();
169a386a536SAndreas Gohr        });
170a386a536SAndreas Gohr
171a386a536SAndreas Gohr        return $list;
172a386a536SAndreas Gohr    }
173a386a536SAndreas Gohr
174a386a536SAndreas Gohr    /**
175a386a536SAndreas Gohr     * Get the instance of the given provider
176a386a536SAndreas Gohr     *
177a386a536SAndreas Gohr     * @param string $providerID
178a386a536SAndreas Gohr     * @return Provider
1796c996db8SAndreas Gohr     * @throws \Exception
180a386a536SAndreas Gohr     */
181a386a536SAndreas Gohr    public function getUserProvider($providerID)
182a386a536SAndreas Gohr    {
183a386a536SAndreas Gohr        $providers = $this->getUserProviders();
184a386a536SAndreas Gohr        if (isset($providers[$providerID])) return $providers[$providerID];
1856c996db8SAndreas Gohr        throw new \Exception('Uncofigured provider requested');
186a386a536SAndreas Gohr    }
187a386a536SAndreas Gohr
188a386a536SAndreas Gohr    /**
189b6119621SAndreas Gohr     * Get the user's default provider if any
190b6119621SAndreas Gohr     *
191b6119621SAndreas Gohr     * Autoupdates the apropriate setting
192b6119621SAndreas Gohr     *
193b6119621SAndreas Gohr     * @return Provider|null
194b6119621SAndreas Gohr     */
195b6119621SAndreas Gohr    public function getUserDefaultProvider()
196b6119621SAndreas Gohr    {
197b6119621SAndreas Gohr        $setting = new Settings('twofactor', $this->getUser());
198b6119621SAndreas Gohr        $default = $setting->get('defaultmod');
199b6119621SAndreas Gohr        $providers = $this->getUserProviders();
200b6119621SAndreas Gohr
201b6119621SAndreas Gohr        if (isset($providers[$default])) return $providers[$default];
202b6119621SAndreas Gohr        // still here? no valid setting. Use first available one
203b6119621SAndreas Gohr        $first = array_shift($providers);
204b6119621SAndreas Gohr        if ($first !== null) {
205b6119621SAndreas Gohr            $this->setUserDefaultProvider($first);
206b6119621SAndreas Gohr        }
207b6119621SAndreas Gohr        return $first;
208b6119621SAndreas Gohr    }
209b6119621SAndreas Gohr
210b6119621SAndreas Gohr    /**
211b6119621SAndreas Gohr     * Set the default provider for the user
212b6119621SAndreas Gohr     *
213b6119621SAndreas Gohr     * @param Provider $provider
214b6119621SAndreas Gohr     * @return void
215b6119621SAndreas Gohr     */
216b6119621SAndreas Gohr    public function setUserDefaultProvider($provider)
217b6119621SAndreas Gohr    {
218b6119621SAndreas Gohr        $setting = new Settings('twofactor', $this->getUser());
219b6119621SAndreas Gohr        $setting->set('defaultmod', $provider->getProviderID());
220b6119621SAndreas Gohr    }
221b6119621SAndreas Gohr
222b6119621SAndreas Gohr    /**
2238b7620a8SAndreas Gohr     * Find all available provider classes
2248b7620a8SAndreas Gohr     *
2258b7620a8SAndreas Gohr     * @return string[];
2268b7620a8SAndreas Gohr     */
2278b7620a8SAndreas Gohr    protected function getProviderClasses()
2288b7620a8SAndreas Gohr    {
2298b7620a8SAndreas Gohr        // FIXME this relies on naming alone, we might want to use an action for registering
2308b7620a8SAndreas Gohr        $plugins = plugin_list('helper');
2318b7620a8SAndreas Gohr        $plugins = array_filter($plugins, function ($plugin) {
2328b7620a8SAndreas Gohr            return $plugin !== 'twofactor' && substr($plugin, 0, 9) === 'twofactor';
2338b7620a8SAndreas Gohr        });
2348b7620a8SAndreas Gohr
2358b7620a8SAndreas Gohr        $classes = [];
2368b7620a8SAndreas Gohr        foreach ($plugins as $plugin) {
2378b7620a8SAndreas Gohr            $class = 'helper_plugin_' . $plugin;
2388b7620a8SAndreas Gohr            if (is_a($class, Provider::class, true)) {
239a386a536SAndreas Gohr                $classes[$plugin] = $class;
2408b7620a8SAndreas Gohr            }
2418b7620a8SAndreas Gohr        }
2428b7620a8SAndreas Gohr
2438b7620a8SAndreas Gohr        return $classes;
244fca58076SAndreas Gohr    }
245fca58076SAndreas Gohr
246fca58076SAndreas Gohr}
247