xref: /plugin/pureldap/auth.php (revision fb75804e73edf4af608854927a231691f3206614)
179f39653SAndreas Gohr<?php
21078ec26SAndreas Gohr
3208fe81aSAndreas Gohruse dokuwiki\Extension\AuthPlugin;
41078ec26SAndreas Gohruse dokuwiki\plugin\pureldap\classes\ADClient;
51078ec26SAndreas Gohruse dokuwiki\plugin\pureldap\classes\Client;
61078ec26SAndreas Gohr
779f39653SAndreas Gohr/**
879f39653SAndreas Gohr * DokuWiki Plugin pureldap (Auth Component)
979f39653SAndreas Gohr *
1079f39653SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
1179f39653SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
1279f39653SAndreas Gohr */
13208fe81aSAndreas Gohrclass auth_plugin_pureldap extends AuthPlugin
1479f39653SAndreas Gohr{
151078ec26SAndreas Gohr    /** @var Client */
160f498d06SAndreas Gohr    public $client;
1779f39653SAndreas Gohr
1879f39653SAndreas Gohr    /**
1979f39653SAndreas Gohr     * Constructor.
2079f39653SAndreas Gohr     */
2179f39653SAndreas Gohr    public function __construct()
2279f39653SAndreas Gohr    {
231078ec26SAndreas Gohr        global $conf;
2479f39653SAndreas Gohr        parent::__construct(); // for compatibility
2579f39653SAndreas Gohr
261078ec26SAndreas Gohr        // prepare the base client
271078ec26SAndreas Gohr        $this->loadConfig();
281078ec26SAndreas Gohr        $this->conf['admin_password'] = conf_decodeString($this->conf['admin_password']);
291078ec26SAndreas Gohr        $this->conf['defaultgroup'] = $conf['defaultgroup'];
301078ec26SAndreas Gohr
311078ec26SAndreas Gohr        $this->client = new ADClient($this->conf); // FIXME decide class on config
3222654fdeSAndreas Gohr
3322654fdeSAndreas Gohr        // set capabilities
3422654fdeSAndreas Gohr        $this->cando['getUsers'] = true;
3522654fdeSAndreas Gohr        $this->cando['getGroups'] = true;
3622654fdeSAndreas Gohr        $this->cando['logout'] = !$this->client->getConf('sso');
3708ace392SAndreas Gohr        if ($this->client->getConf('encryption') !== 'none') {
3808ace392SAndreas Gohr            // with encryption passwords can be changed
3908ace392SAndreas Gohr            // for resetting passwords a privileged user is needed
4008ace392SAndreas Gohr            $this->cando['modPass'] = true;
4108ace392SAndreas Gohr        }
4208ace392SAndreas Gohr
4322654fdeSAndreas Gohr
4479f39653SAndreas Gohr        $this->success = true;
4579f39653SAndreas Gohr    }
4679f39653SAndreas Gohr
471078ec26SAndreas Gohr    /** @inheritDoc */
4879f39653SAndreas Gohr    public function checkPass($user, $pass)
4979f39653SAndreas Gohr    {
50bf69b89cSAndreas Gohr        global $INPUT;
51bf69b89cSAndreas Gohr
52bf69b89cSAndreas Gohr        // when SSO is enabled, the login is autotriggered and we simply trust the environment
53bf69b89cSAndreas Gohr        if (
5422654fdeSAndreas Gohr            $this->client->getConf('sso') &&
55bf69b89cSAndreas Gohr            $INPUT->server->str('REMOTE_USER') !== '' &&
56bf69b89cSAndreas Gohr            $INPUT->server->str('REMOTE_USER') == $user
57bf69b89cSAndreas Gohr        ) {
58bf69b89cSAndreas Gohr            return true;
59bf69b89cSAndreas Gohr        }
60bf69b89cSAndreas Gohr
6108ace392SAndreas Gohr        // try to bind with the user credentials, client will stay authenticated as user
6208ace392SAndreas Gohr        $this->client = new ADClient($this->conf); // FIXME decide class on config
63*fb75804eSAndreas Gohr        try {
64*fb75804eSAndreas Gohr            $this->client->authenticate($user, $pass);
65*fb75804eSAndreas Gohr            return true;
66*fb75804eSAndreas Gohr        } catch (\Exception $e) {
67*fb75804eSAndreas Gohr            $this->parseErrorCodesToMessages($e);
68*fb75804eSAndreas Gohr            return false;
69*fb75804eSAndreas Gohr        }
7079f39653SAndreas Gohr    }
7179f39653SAndreas Gohr
721078ec26SAndreas Gohr    /** @inheritDoc */
7379f39653SAndreas Gohr    public function getUserData($user, $requireGroups = true)
7479f39653SAndreas Gohr    {
755a3b9122SAndreas Gohr        $info = $this->client->getCachedUser($user, $requireGroups);
761078ec26SAndreas Gohr        return $info ?: false;
7779f39653SAndreas Gohr    }
7879f39653SAndreas Gohr
7949b4734aSAndreas Gohr    /**
8049b4734aSAndreas Gohr     * @inheritDoc
8149b4734aSAndreas Gohr     */
82b21740b4SAndreas Gohr    public function retrieveUsers($start = 0, $limit = 0, $filter = null)
83b21740b4SAndreas Gohr    {
8485916a2dSAndreas Gohr        return array_slice(
8585916a2dSAndreas Gohr            $this->client->getFilteredUsers(
8685916a2dSAndreas Gohr                $filter,
8749b4734aSAndreas Gohr                Client::FILTER_CONTAINS
8885916a2dSAndreas Gohr            ),
8985916a2dSAndreas Gohr            $start,
90208fe81aSAndreas Gohr            $limit
91208fe81aSAndreas Gohr        );
92b21740b4SAndreas Gohr    }
9379f39653SAndreas Gohr
94b21740b4SAndreas Gohr    /** @inheritDoc */
95b21740b4SAndreas Gohr    public function retrieveGroups($start = 0, $limit = 0)
96b21740b4SAndreas Gohr    {
97b21740b4SAndreas Gohr        return array_slice($this->client->getCachedGroups(), $start, $limit);
98b21740b4SAndreas Gohr    }
9979f39653SAndreas Gohr
1006d90d5c8SAndreas Gohr    /** @inheritDoc */
10179f39653SAndreas Gohr    public function isCaseSensitive()
10279f39653SAndreas Gohr    {
1036d90d5c8SAndreas Gohr        return false;
10479f39653SAndreas Gohr    }
10579f39653SAndreas Gohr
1065da7f46bSAndreas Gohr    /** @inheritDoc */
10779f39653SAndreas Gohr    public function cleanUser($user)
10879f39653SAndreas Gohr    {
109a1128cc0SAndreas Gohr        return $this->client->cleanUser($user);
11079f39653SAndreas Gohr    }
11179f39653SAndreas Gohr
1125da7f46bSAndreas Gohr    /** @inheritDoc */
11379f39653SAndreas Gohr    public function cleanGroup($group)
11479f39653SAndreas Gohr    {
11579f39653SAndreas Gohr        return $group;
11679f39653SAndreas Gohr    }
11779f39653SAndreas Gohr
1186d90d5c8SAndreas Gohr    /** @inheritDoc */
1191078ec26SAndreas Gohr    public function useSessionCache($user)
1201078ec26SAndreas Gohr    {
1216d90d5c8SAndreas Gohr        return true;
1221078ec26SAndreas Gohr    }
12308ace392SAndreas Gohr
12408ace392SAndreas Gohr    /**
12508ace392SAndreas Gohr     * Support password changing
12608ace392SAndreas Gohr     * @inheritDoc
12708ace392SAndreas Gohr     */
12808ace392SAndreas Gohr    public function modifyUser($user, $changes)
12908ace392SAndreas Gohr    {
13008ace392SAndreas Gohr        if (empty($changes['pass'])) {
13108ace392SAndreas Gohr            $this->client->error('Only password changes are supported', __FILE__, __LINE__);
13208ace392SAndreas Gohr            return false;
13308ace392SAndreas Gohr        }
13408ace392SAndreas Gohr
13508ace392SAndreas Gohr        global $INPUT;
13608ace392SAndreas Gohr        return $this->client->setPassword($user, $changes['pass'], $INPUT->str('oldpass', null, true));
13708ace392SAndreas Gohr    }
138*fb75804eSAndreas Gohr
139*fb75804eSAndreas Gohr    /**
140*fb75804eSAndreas Gohr     * Parse error codes from LDAP exceptions and output them as user-friendly messages.
141*fb75804eSAndreas Gohr     *
142*fb75804eSAndreas Gohr     * This is currently tailored for Active Directory bind errors.
143*fb75804eSAndreas Gohr     *
144*fb75804eSAndreas Gohr     * @param Exception $e
145*fb75804eSAndreas Gohr     * @return void
146*fb75804eSAndreas Gohr     */
147*fb75804eSAndreas Gohr    public function parseErrorCodesToMessages(\Exception $e)
148*fb75804eSAndreas Gohr    {
149*fb75804eSAndreas Gohr        // See https://ldapwiki.com/wiki/Wiki.jsp?page=Common%20Active%20Directory%20Bind%20Errors
150*fb75804eSAndreas Gohr        $bind_errors = [
151*fb75804eSAndreas Gohr            '52f' => 'ERROR_ACCOUNT_RESTRICTION',
152*fb75804eSAndreas Gohr            '530' => 'ERROR_INVALID_LOGON_HOURS',
153*fb75804eSAndreas Gohr            '531' => 'ERROR_INVALID_WORKSTATION',
154*fb75804eSAndreas Gohr            '532' => 'ERROR_PASSWORD_EXPIRED',
155*fb75804eSAndreas Gohr            '533' => 'ERROR_ACCOUNT_DISABLED',
156*fb75804eSAndreas Gohr            '701' => 'ERROR_ACCOUNT_EXPIRED',
157*fb75804eSAndreas Gohr            '773' => 'ERROR_PASSWORD_MUST_CHANGE',
158*fb75804eSAndreas Gohr        ];
159*fb75804eSAndreas Gohr
160*fb75804eSAndreas Gohr        if (
161*fb75804eSAndreas Gohr            $e instanceof \FreeDSx\Ldap\Exception\BindException &&
162*fb75804eSAndreas Gohr            $e->getCode() === 49 &&
163*fb75804eSAndreas Gohr            preg_match('/ data ([0-9a-f]{3})/', $e->getMessage(), $matches)
164*fb75804eSAndreas Gohr        ) {
165*fb75804eSAndreas Gohr            $code = $matches[1];
166*fb75804eSAndreas Gohr            if (isset($bind_errors[$code])) {
167*fb75804eSAndreas Gohr                $message = $this->getLang($bind_errors[$code]) ?: $bind_errors[$code];
168*fb75804eSAndreas Gohr
169*fb75804eSAndreas Gohr                // on password expired or must change, add reset hint
170*fb75804eSAndreas Gohr                if ($this->canDo('modPass') && ($code == 532 || $code == 773)) {
171*fb75804eSAndreas Gohr                    $link = '<a href="' . wl('start', ['do' => 'resendpwd']) . '" class="pureldap-reset-link">' .
172*fb75804eSAndreas Gohr                        $this->getLang('pass_reset') . '</a>';
173*fb75804eSAndreas Gohr                    $message .= ' ' . $link;
174*fb75804eSAndreas Gohr                }
175*fb75804eSAndreas Gohr
176*fb75804eSAndreas Gohr                msg($message, -1);
177*fb75804eSAndreas Gohr            }
178*fb75804eSAndreas Gohr        }
179*fb75804eSAndreas Gohr    }
180b21740b4SAndreas Gohr}
181