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