xref: /plugin/twofactor/Manager.php (revision 4b9cff8a870f8684b87bcaa2b3aecd09387ffb2e)
1<?php
2
3namespace dokuwiki\plugin\twofactor;
4
5use dokuwiki\Extension\Plugin;
6
7/**
8 * Manages the child plugins etc.
9 */
10class Manager extends Plugin
11{
12    /** @var Manager */
13    protected static $instance;
14
15    /** @var bool */
16    protected $ready = false;
17
18    /** @var string[] */
19    protected $classes = [];
20
21    /** @var Provider[] */
22    protected $providers;
23
24    /**
25     * Constructor
26     */
27    protected function __construct()
28    {
29        $this->classes = $this->getProviderClasses();
30
31        $attribute = plugin_load('helper', 'attribute');
32        if ($attribute === null) {
33            msg('The attribute plugin is not available, 2fa disabled', -1);
34        }
35
36        if (!count($this->classes)) {
37            msg('No suitable 2fa providers found, 2fa disabled', -1);
38            return;
39        }
40
41        $this->ready = true;
42    }
43
44    /**
45     * This is not a conventional class, plugin name can't be determined automatically
46     * @inheritdoc
47     */
48    public function getPluginName() {
49        return 'twofactor';
50    }
51
52    /**
53     * Get the instance of this singleton
54     *
55     * @return Manager
56     */
57    public static function getInstance()
58    {
59        if (self::$instance === null) {
60            self::$instance = new Manager();
61        }
62        return self::$instance;
63    }
64
65    /**
66     * Is the plugin ready to be used?
67     *
68     * @return bool
69     */
70    public function isReady()
71    {
72        return $this->ready;
73    }
74
75    /**
76     * Is a 2fa login required?
77     *
78     * @return bool
79     */
80    public function isRequired()
81    {
82        $set = $this->getConf('optinout');
83        if ($set === 'mandatory') {
84            return true;
85        }
86        if ($set === 'optout') {
87            $setting = new Settings('twofactor', $this->getUser());
88            if ($setting->get('state') !== 'optout') {
89                return true;
90            }
91        }
92
93        return false;
94    }
95
96    /**
97     * Convenience method to get current user
98     *
99     * @return string
100     */
101    public function getUser()
102    {
103        global $INPUT;
104        $user = $INPUT->server->str('REMOTE_USER');
105        if (!$user) {
106            throw new \RuntimeException('2fa user specifics used before user available');
107        }
108        return $user;
109    }
110
111    /**
112     * Get or set the user opt-out state
113     *
114     * true: user opted out
115     * false: user did not opt out
116     *
117     * @param bool|null $set
118     * @return bool
119     */
120    public function userOptOutState($set = null)
121    {
122        // is optout allowed?
123        if ($this->getConf('optinout') !== 'optout') return false;
124
125        $settings = new Settings('twofactor', $this->getUser());
126
127        if ($set === null) {
128            $current = $settings->get('state');
129            return $current === 'optout';
130        }
131
132        if ($set) {
133            $settings->set('state', 'optout');
134        } else {
135            $settings->delete('state');
136        }
137        return $set;
138    }
139
140    /**
141     * Get all available providers
142     *
143     * @return Provider[]
144     */
145    public function getAllProviders()
146    {
147        $user = $this->getUser();
148
149        if ($this->providers === null) {
150            $this->providers = [];
151            foreach ($this->classes as $plugin => $class) {
152                $this->providers[$plugin] = new $class($user);
153            }
154        }
155
156        return $this->providers;
157    }
158
159    /**
160     * Get all providers that have been already set up by the user
161     *
162     * @return Provider[]
163     */
164    public function getUserProviders()
165    {
166        $list = $this->getAllProviders();
167        $list = array_filter($list, function ($provider) {
168            return $provider->isConfigured();
169        });
170
171        return $list;
172    }
173
174    /**
175     * Get the instance of the given provider
176     *
177     * @param string $providerID
178     * @return Provider
179     * @throws \Exception
180     */
181    public function getUserProvider($providerID)
182    {
183        $providers = $this->getUserProviders();
184        if (isset($providers[$providerID])) return $providers[$providerID];
185        throw new \Exception('Uncofigured provider requested');
186    }
187
188    /**
189     * Get the user's default provider if any
190     *
191     * Autoupdates the apropriate setting
192     *
193     * @return Provider|null
194     */
195    public function getUserDefaultProvider()
196    {
197        $setting = new Settings('twofactor', $this->getUser());
198        $default = $setting->get('defaultmod');
199        $providers = $this->getUserProviders();
200
201        if (isset($providers[$default])) return $providers[$default];
202        // still here? no valid setting. Use first available one
203        $first = array_shift($providers);
204        if ($first !== null) {
205            $this->setUserDefaultProvider($first);
206        }
207        return $first;
208    }
209
210    /**
211     * Set the default provider for the user
212     *
213     * @param Provider $provider
214     * @return void
215     */
216    public function setUserDefaultProvider($provider)
217    {
218        $setting = new Settings('twofactor', $this->getUser());
219        $setting->set('defaultmod', $provider->getProviderID());
220    }
221
222    /**
223     * Find all available provider classes
224     *
225     * @return string[];
226     */
227    protected function getProviderClasses()
228    {
229        // FIXME this relies on naming alone, we might want to use an action for registering
230        $plugins = plugin_list('helper');
231        $plugins = array_filter($plugins, function ($plugin) {
232            return $plugin !== 'twofactor' && substr($plugin, 0, 9) === 'twofactor';
233        });
234
235        $classes = [];
236        foreach ($plugins as $plugin) {
237            $class = 'helper_plugin_' . $plugin;
238            if (is_a($class, Provider::class, true)) {
239                $classes[$plugin] = $class;
240            }
241        }
242
243        return $classes;
244    }
245
246}
247