nuxsmin / sysPass

Systems Password Manager
https://syspass.org
GNU General Public License v3.0
976 stars 209 forks source link

Account expired when account is active #1796

Open matejzero opened 2 years ago

matejzero commented 2 years ago

sysPass Version 3.2 (322.21031301)

Describe the bug Sometimes users can't login, getting an account expired error. I have checked on multiple occasions and the account was active and the user was able to login to other AD backed services without a problem.

Clearing the cache folder or restarting the apache / php process doesn't help.

Is it possible to get more info like what LDAP actually returns to see what the real error code received from AD is?

To Reproduce Don't have a reproducible case. It happens every now and then for a random user.

Event log The logs are not really useful I think.

[2021-12-21 08:09:15] syspass.EXCEPTION: logger {"message":"Account expired
#0 /var/www/html/syspass/lib/SP/Services/Auth/LoginService.php(160): SP\Services\Auth\LoginService->authLdap(Object(SP\Providers\Auth\Ldap\LdapAuthData))
#1 /var/www/html/syspass/app/modules/web/Controllers/LoginController.php(65): SP\Services\Auth\LoginService->doLogin()
#2 [internal function]: SP\Modules\Web\Controllers\LoginController->loginAction()
#3 /var/www/html/syspass/lib/SP/Bootstrap.php(240): call_user_func_array(Array,Array)
#4 [internal function]: SP\Bootstrap->SP\{closure}(Object(Klein\Request),Object(Klein\Response),Object(Klein\ServiceProvider),Object(Klein\App),Object(Klein\Klein),Object(Klein\DataCollection\RouteCollection),Array)
#5 /var/www/html/syspass/vendor/klein/klein/src/Klein/Klein.php(879): call_user_func(Object(Closure),Object(Klein\Request),Object(Klein\Response),Object(Klein\ServiceProvider),Object(Klein\App),Object(Klein\Klein),Object(Klein\DataCollection\RouteCollection),Array)
#6 /var/www/html/syspass/vendor/klein/klein/src/Klein/Klein.php(588): Klein\Klein->handleRouteCallback(Object(Klein\Route),Object(Klein\DataCollection\RouteCollection),Array)
#7 /var/www/html/syspass/lib/SP/Bootstrap.php(464): Klein\Klein->dispatch(Object(Klein\Request))
#8 /var/www/html/syspass/lib/Base.php(75): SP\Bootstrap->run(Object(DI\Container))
#9 /var/www/html/syspass/index.php(28): require(String)","caller":"N/A"}

Platform (please complete the following information):

nuxsmin commented 2 years ago

Hello, you can try to enable debug on Configuration > General.

Regards.

matejzero commented 2 years ago

I enabled it and now I have to wait for another case to apear as it is pretty random. Will update the ticket once it happens.

afjunquera commented 2 years ago

We have found ourselves in the same situation today, so I will share our findings to shed some light on the problem.

First of all, let's review some code executed during the authentication process.

In the LdapActions.php file, Syspass defines the following mappings:

    const ATTRIBUTES_MAPPING = [
        'dn' => 'dn',
        'groupmembership' => 'group',
        'memberof' => 'group',
        'displayname' => 'fullname',
        'fullname' => 'fullname',
        'givenname' => 'name',
        'sn' => 'sn',
        'mail' => 'mail',
        'lockouttime' => 'expire'
    ];

Keep in mind this one: 'lockouttime' => 'expire'.

In the very same file, we can find the getAttributes function which most important part is as follows:

        foreach (self::ATTRIBUTES_MAPPING as $attribute => $map) {
            if (isset($results[$attribute])) {
                if (is_array($results[$attribute])) {
                    if ((int)$results[$attribute]['count'] > 1) {
                        unset($results[$attribute]['count']);

                        // Store the whole array
                        $attributeCollection->set($map, $results[$attribute]);
                    } else {
                        // Store first value
                        $attributeCollection->set($map, trim($results[$attribute][0]));
                    }
                } else {
                    $attributeCollection->set($map, trim($results[$attribute]));
                }
            }
        }

After that, in the LdapAuth.php file, we can find this:

$this->ldapAuthData->setExpire($attributes->get('expire'));

And finally, in the AuthProvider.php file:

            if ($ldapAuthData->getExpire() > 0) {
                $ldapAuthData->setStatusCode(LdapAuth::ACCOUNT_EXPIRED);
            } elseif (!$ldapAuthData->isInGroup()) {
                $ldapAuthData->setStatusCode(LdapAuth::ACCOUNT_NO_GROUPS);
            }

Following the code, it seems quite evident (please correct me if I'm wrong) that Syspass checks whether 'lockouttime' is greater than 0 or not. Well, I think this is the problem, at least with AD. Just take a look at the 'lockouttime' user attribute description provided by this link. It states the following:

Lockouttime attribute is only reset following a successful authentication. This implies that the lockoutTime attribute may be non-zero yet the account is not locked out. The only accurately method to determine if the account is locked out, is to add the Lockout-Duration to the lockouttime and compare the result to the current time. Be careful as depending on how you are reading the values you may need account for local time zones and daylight savings time.

Therefore Syspass will always deny access if 'lockouttime' is greater than 0. It doesn't matter if the account is actually locked out or not, users will not be able to log in until they successfully log in to another application that resets its 'lockouttime' attribute. For example, we have managed to overcome this situation by asking our colleagues to log in to corporate mail.

I guess this is the same problem described in #1803.