xref: /plugin/oauth/auth.php (revision fe49fd8259b2adcc61587e73b18695bde171a8d9)
1<?php
2
3use dokuwiki\plugin\oauth\OAuthManager;
4use dokuwiki\plugin\oauth\Session;
5use dokuwiki\Subscriptions\RegistrationSubscriptionSender;
6use OAuth\Common\Exception\Exception as OAuthException;
7
8/**
9 * DokuWiki Plugin oauth (Auth Component)
10 *
11 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
12 * @author  Andreas Gohr <andi@splitbrain.org>
13 */
14class auth_plugin_oauth extends auth_plugin_authplain
15{
16    /** @var helper_plugin_oauth */
17    protected $hlp;
18
19    /** @var OAuthManager */
20    protected $om;
21
22    // region standard auth methods
23
24    /** @inheritDoc */
25    public function __construct()
26    {
27        parent::__construct();
28        $this->cando['external'] = true;
29        $this->hlp = $this->loadHelper('oauth');
30    }
31
32    /** @inheritDoc */
33    public function trustExternal($user, $pass, $sticky = false)
34    {
35        global $INPUT;
36
37        // handle redirects from farmer to animal wiki instances
38        if ($INPUT->has('state') && plugin_load('helper', 'farmer')) {
39            $this->handleFarmState($INPUT->str('state'));
40        }
41
42        try {
43            // either oauth or "normal" plain auth login via form
44            $this->om = new OAuthManager();
45            if ($this->om->continueFlow()) return true;
46            if ($this->getConf('singleService')) {
47                return false; // no normal login in singleService mode
48            }
49            return null; // triggers the normal auth_login()
50        } catch (OAuthException $e) {
51            $this->hlp->showException($e);
52            auth_logoff(); // clears all session and cookie data
53            return false;
54        }
55    }
56
57    /**
58     * Enforce oauth login for certain email domains
59     *
60     * @inheritdoc
61     */
62    public function checkPass($user, $pass)
63    {
64        $ok = parent::checkPass($user, $pass);
65        if(!$ok) return $ok;
66        $domains = $this->hlp->getEnforcedDomains();
67        if($domains === []) return $ok;
68
69        if($this->hlp->checkMail($this->getUserData($user)['mail'], $domains)) {
70            global $lang;
71            // we overwrite the standard bad password message with our own
72            $lang['badlogin'] = $this->getLang('eMailEnforced');
73            return false;
74        }
75        return $ok;
76    }
77
78
79    /**
80     * Enhance function to check against duplicate emails
81     *
82     * @inheritdoc
83     */
84    public function createUser($user, $pwd, $name, $mail, $grps = null)
85    {
86        if ($this->getUserByEmail($mail)) {
87            msg($this->getLang('emailduplicate'), -1);
88            return false;
89        }
90
91        return parent::createUser($user, $pwd, $name, $mail, $grps);
92    }
93
94    /**
95     * Enhance function to check against duplicate emails
96     *
97     * @inheritdoc
98     */
99    public function modifyUser($user, $changes)
100    {
101        global $conf;
102
103        if (isset($changes['mail'])) {
104            $found = $this->getUserByEmail($changes['mail']);
105            if ($found && $found != $user) {
106                msg($this->getLang('emailduplicate'), -1);
107                return false;
108            }
109        }
110
111        $ok = parent::modifyUser($user, $changes);
112
113        // refresh session cache
114        touch($conf['cachedir'] . '/sessionpurge');
115        return $ok;
116    }
117
118    /**
119     * Unset additional stuff in session on logout
120     */
121    public function logOff()
122    {
123        parent::logOff();
124        if (isset($this->om)) {
125            $this->om->logout();
126        }
127        (Session::getInstance())->clear();
128    }
129
130    // endregion
131
132    /**
133     * Register a new user logged in by oauth
134     *
135     * It ensures the username is unique, by adding a number if needed.
136     * Default and service name groups are set here.
137     * Registration notifications are triggered.
138     *
139     * @param array $userinfo This will be updated with the new username
140     * @param string $servicename
141     *
142     * @return bool
143     * @todo - should this be part of the OAuthManager class instead?
144     */
145    public function registerOAuthUser(&$userinfo, $servicename)
146    {
147        global $conf;
148        $user = $userinfo['user'];
149        $count = '';
150        while ($this->getUserData($user . $count)) {
151            if ($count) {
152                $count++;
153            } else {
154                $count = 1;
155            }
156        }
157        $user .= $count;
158        $userinfo['user'] = $user;
159        $groups_on_creation = [];
160        $groups_on_creation[] = $conf['defaultgroup'];
161        $groups_on_creation[] = $this->cleanGroup($servicename); // add service as group
162        $userinfo['grps'] = array_merge((array)$userinfo['grps'], $groups_on_creation);
163
164        // the password set here will remain unknown to the user
165        $ok = $this->triggerUserMod(
166            'create',
167            [
168                $user,
169                auth_pwgen($user),
170                $userinfo['name'],
171                $userinfo['mail'],
172                $userinfo['grps'],
173            ]
174        );
175        if (!$ok) {
176            return false;
177        }
178
179        // send notification about the new user
180        $subscriptionSender = new RegistrationSubscriptionSender();
181        $subscriptionSender->sendRegister($user, $userinfo['name'], $userinfo['mail']);
182
183        return true;
184    }
185
186    /**
187     * Find a user by email address
188     *
189     * @param $mail
190     * @return bool|string
191     */
192    public function getUserByEmail($mail)
193    {
194        if ($this->users === null) {
195            $this->loadUserData();
196        }
197        $mail = strtolower($mail);
198
199        foreach ($this->users as $user => $userinfo) {
200            if (strtolower($userinfo['mail']) === $mail) return $user;
201        }
202
203        return false;
204    }
205
206    /**
207     * Fall back to plain auth strings
208     *
209     * @inheritdoc
210     */
211    public function getLang($id)
212    {
213        $result = parent::getLang($id);
214        if ($result) return $result;
215
216        $parent = new auth_plugin_authplain();
217        return $parent->getLang($id);
218    }
219
220    /**
221     * Farmer plugin support
222     *
223     * When coming back to farmer instance via OAUTH redirectURI, we need to redirect again
224     * to a proper animal instance detected from $state
225     *
226     * @param $state
227     */
228    protected function handleFarmState($state)
229    {
230        /** @var \helper_plugin_farmer $farmer */
231        $farmer = plugin_load('helper', 'farmer', false, true);
232        $data = json_decode(base64_decode(urldecode($state)));
233        if (empty($data->animal) || $farmer->getAnimal() == $data->animal) {
234            return;
235        }
236        $animal = $data->animal;
237        $allAnimals = $farmer->getAllAnimals();
238        if (!in_array($animal, $allAnimals)) {
239            msg('Animal ' . $animal . ' does not exist!');
240            return;
241        }
242        global $INPUT;
243        $url = $farmer->getAnimalURL($animal) . '/doku.php?' . $INPUT->server->str('QUERY_STRING');
244        send_redirect($url);
245    }
246}
247