[Bug]: user_ldap crashes during login, password policy needs missing null check(s)
⚠️ This issue respects the following points: ⚠️
- [X] This is a bug, not a question or a configuration/webserver/proxy issue.
- [X] This issue is not already reported on Github (I've searched it).
- [X] Nextcloud Server is up to date. See Maintenance and Release Schedule for supported versions.
- [x] Nextcloud Server is running on 64bit capable CPU, PHP and OS.
- [X] I agree to follow Nextcloud's Code of Conduct.
Bug description
function⋅handlePasswordExpiry($params) in apps/user_ldap/lib/User/User.php fail to handle multiple corener cases:
- First is after this (line 651](https://github.com/nextcloud/server/blob/baf74f0aa1b8947272b10532a3c3a1cff16b6085/apps/user_ldap/lib/User/User.php#L651) This returns nothing if user never changed password (and he isn't on a grace time/reset)!
- I presume (although I did NOT try it) this is wrong too (although it is null checked
line 668
Existing password policy doesn't mean we must have these attributes - pwdPolicy MAY have these attributes
Although I - and most others - will have all MAY attributes set, I actually don't use these, as I think forcing users to change their passwords often leads to passwords more likely to be written down.
But
pwdCheckQualityandpwdMinLengthis not zero on my server, and on other setups, these could be the only attributes set! So these could be neither in cache, nor in search result!
Steps to reproduce
- Setup LDAP server, with a password policy that only has
pwdCheckQualityandpwdMinLengthattributes set. - Connect that user backend to Nextcloud
- Create a new LDAP user. Set their password during creation, not in a separate operation
- Try to login with the new user
Expected behavior
Newly created user should be able to log in!
If no pwdChangedTime attribute does not exist for a given user, it is not necessarily a grace login.
It it doesn't exists for a user
- If no
pwdMaxAgeandpwdExpireWarningis set in policy, then nothing to check LDAP User could be created with throwaway token based registration, where he gets to choose his password during user creation! Maybe they are just asked at a helpdesk PC to type in a password, I know, it's horror.... - If these policy attrs are set, and
pwdChangedTimeis empty, usecreateTimeStamp
Furthermore, don't blindly override ppolicy cache with a search result that doesn't return every attribute
Installation method
Community Manual installation with Archive
Operating system
Other
PHP engine version
PHP 8.0
Web server
Other
Database engine version
PostgreSQL
Is this bug present after an update or on a fresh install?
Fresh Nextcloud Server install
Are you using the Nextcloud Server Encryption module?
Encryption is Disabled
What user-backends are you using?
- [ ] Default user-backend (database)
- [X] LDAP/ Active Directory
- [ ] SSO - SAML
- [ ] Other
Configuration report
Irrelevant
List of activated Apps
Irrelevant
Nextcloud Signing status
No response
Nextcloud Logs
No response
Additional info
No response
Which nc version?
Which nc version?
24.0.7
I attach my hotfix:
diff -urN apps.orig/user_ldap/lib/User/User.php apps/user_ldap/lib/User/User.php
--- apps.orig/user_ldap/lib/User/User.php 2022-11-15 10:29:04.362933828 +0100
+++ apps/user_ldap/lib/User/User.php 2022-11-15 12:31:25.966933828 +0100
@@ -650,16 +650,16 @@
//retrieve relevant user attributes
$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
- if (array_key_exists('pwdpolicysubentry', $result[0])) {
+ if (array_key_exists('0', $result) && array_key_exists('pwdpolicysubentry', $result[0])) {
$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) {
$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
}
}
- $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : [];
- $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : [];
- $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : [];
+ $pwdGraceUseTime = (array_key_exists('0', $result) && array_key_exists('pwdgraceusetime', $result[0])) ? $result[0]['pwdgraceusetime'] : [];
+ $pwdReset = (array_key_exists('0', $result) && array_key_exists('pwdreset', $result[0])) ? $result[0]['pwdreset'] : [];
+ $pwdChangedTime = (array_key_exists('0', $result) && array_key_exists('pwdchangedtime', $result[0])) ? $result[0]['pwdchangedtime'] : [];
//retrieve relevant password policy attributes
$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
@@ -669,9 +669,9 @@
$this->connection->writeToCache($cacheKey, $result);
}
- $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : [];
- $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : [];
- $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : [];
+ $pwdGraceAuthNLimit = (array_key_exists('0', $result) && array_key_exists('pwdgraceauthnlimit', $result[0])) ? $result[0]['pwdgraceauthnlimit'] : [];
+ $pwdMaxAge = (array_key_exists('0', $result) && array_key_exists('pwdmaxage', $result[0])) ? $result[0]['pwdmaxage'] : [];
+ $pwdExpireWarning = (array_key_exists('0', $result) && array_key_exists('pwdexpirewarning', $result[0])) ? $result[0]['pwdexpirewarning'] : [];
//handle grace login
if (!empty($pwdGraceUseTime)) { //was this a grace login?
That hotfix doesn't solve the case if pwdChangedTime doesn't exists for the user AND pwdMaxAge does in policies.
Offtopic: I never wrote PHP (and I am glad I didn't), except in times like this, when something broke and had to comment-out/quick-fix-smthg-with-general-programming-knowhow
Hi, can you please openn a PR with your patch? This will make the discussion around it much easier. Thank you! :)
Hi, please update to 25.0.7 or better 26.0.2 and report back if it fixes the issue. Thank you!
My goal is to add a label like e.g. 26-feedback to this ticket of an up-to-date major Nextcloud version where the bug could be reproduced. However this is not going to work without your help. So thanks for all your effort!
If you don't manage to reproduce the issue in time and the issue gets closed but you can reproduce the issue afterwards, feel free to create a new bug report with up-to-date information by following this link: https://github.com/nextcloud/server/issues/new?assignees=&labels=bug%2C0.+Needs+triage&template=BUG_REPORT.yml&title=%5BBug%5D%3A+
This issue has been automatically marked as stale because it has not had recent activity and seems to be missing some essential information. It will be closed if no further activity occurs. Thank you for your contributions.
This bug is still present in version 26.0.5. This part in the User.php isn't changed right now.
Quick and dirty fix is a null check and a return. I think a patch like from @Samonitari will help. Or it's better to fix the search function?
We're seeing a possibly related error in our 26.0.4 Enterprise.
Users, newly created in LDAP cannot log in and instead get an "Internal Server Error".
The logs say:
[[index] Fehler: Exception: array_key_exists(): Argument #2 ($array) must be of type array, null given in file '/var/www/html/apps/user_ldap/lib/User/User.php' line 653 at <<closure>>
0. /var/www/html/lib/private/AppFramework/App.php line 183
OC\AppFramework\Http\Dispatcher->dispatch(["OC\\Core\\Controller\\LoginController"], "tryLogin")
1. /var/www/html/lib/private/Route/Router.php line 315
OC\AppFramework\App::main("OC\\Core\\Controller\\LoginController", "tryLogin", ["OC\\AppFramewo ... "], ["core.login.tryLogin"])
2. /var/www/html/lib/base.php line 1065
OC\Route\Router->match("/login")
3. /var/www/html/index.php line 36
OC::handleRequest()
Caused by:
TypeError: array_key_exists(): Argument #2 ($array) must be of type array, null given at <<closure>>
0. /var/www/html/apps/user_ldap/lib/User/User.php line 653
array_key_exists("pwdpolicysubentry", "*** sensitive parameters replaced ***")
1. /var/www/html/lib/private/legacy/OC_Hook.php line 105
OCA\User_LDAP\User\User->handlePasswordExpiry([true,"829b70fe- ... "])
2. /var/www/html/lib/private/Server.php line 626
OC_Hook::emit("OC_User", "post_login", [true,"829b70fe- ... "])
3. <<closure>>
OC\Server->OC\{closure}("*** sensitive parameters replaced ***")
4. /var/www/html/lib/private/Hooks/EmitterTrait.php line 105
call_user_func_array(["Closure"], ["*** sensitive ... "])
5. /var/www/html/lib/private/Hooks/PublicEmitter.php line 40
OC\Hooks\BasicEmitter->emit("\\OC\\User", "postLogin", ["*** sensitive ... "])
6. /var/www/html/lib/private/User/Session.php line 397
OC\Hooks\PublicEmitter->emit("\\OC\\User", "postLogin", ["*** sensitive ... "])
7. /var/www/html/lib/private/Authentication/Login/CompleteLoginCommand.php line 39
OC\User\Session->completeLogin("*** sensitive parameters replaced ***")
8. /var/www/html/lib/private/Authentication/Login/ALoginCommand.php line 39
OC\Authentication\Login\CompleteLoginCommand->process(["OC\\Authentication\\Login\\LoginData"])
9. /var/www/html/lib/private/Authentication/Login/LoggedInCheckCommand.php line 60
OC\Authentication\Login\ALoginCommand->processNextOrFinishSuccessfully(["OC\\Authentication\\Login\\LoginData"])
10. /var/www/html/lib/private/Authentication/Login/ALoginCommand.php line 39
OC\Authentication\Login\LoggedInCheckCommand->process(["OC\\Authentication\\Login\\LoginData"])
11. /var/www/html/lib/private/Authentication/Login/EmailLoginCommand.php line 68
OC\Authentication\Login\ALoginCommand->processNextOrFinishSuccessfully(["OC\\Authentication\\Login\\LoginData"])
12. /var/www/html/lib/private/Authentication/Login/ALoginCommand.php line 39
OC\Authentication\Login\EmailLoginCommand->process(["OC\\Authentication\\Login\\LoginData"])
13. /var/www/html/lib/private/Authentication/Login/UidLoginCommand.php line 53
OC\Authentication\Login\ALoginCommand->processNextOrFinishSuccessfully(["OC\\Authentication\\Login\\LoginData"])
14. /var/www/html/lib/private/Authentication/Login/ALoginCommand.php line 39
OC\Authentication\Login\UidLoginCommand->process(["OC\\Authentication\\Login\\LoginData"])
15. /var/www/html/lib/private/Authentication/Login/UserDisabledCheckCommand.php line 57
OC\Authentication\Login\ALoginCommand->processNextOrFinishSuccessfully(["OC\\Authentication\\Login\\LoginData"])
16. /var/www/html/lib/private/Authentication/Login/ALoginCommand.php line 39
OC\Authentication\Login\UserDisabledCheckCommand->process(["OC\\Authentication\\Login\\LoginData"])
17. /var/www/html/lib/private/Authentication/Login/PreLoginHookCommand.php line 52
OC\Authentication\Login\ALoginCommand->processNextOrFinishSuccessfully(["OC\\Authentication\\Login\\LoginData"])
18. /var/www/html/lib/private/Authentication/Login/Chain.php line 107
OC\Authentication\Login\PreLoginHookCommand->process(["OC\\Authentication\\Login\\LoginData"])
19. /var/www/html/core/Controller/LoginController.php line 326
OC\Authentication\Login\Chain->process(["OC\\Authentication\\Login\\LoginData"])
20. /var/www/html/lib/private/AppFramework/Http/Dispatcher.php line 230
OC\Core\Controller\LoginController->tryLogin("*** sensitive parameters replaced ***")
21. /var/www/html/lib/private/AppFramework/Http/Dispatcher.php line 137
OC\AppFramework\Http\Dispatcher->executeController(["OC\\Core\\Controller\\LoginController"], "tryLogin")
22. /var/www/html/lib/private/AppFramework/App.php line 183
OC\AppFramework\Http\Dispatcher->dispatch(["OC\\Core\\Controller\\LoginController"], "tryLogin")
23. /var/www/html/lib/private/Route/Router.php line 315
OC\AppFramework\App::main("OC\\Core\\Controller\\LoginController", "tryLogin", ["OC\\AppFramewo ... "], ["core.login.tryLogin"])
24. /var/www/html/lib/base.php line 1065
OC\Route\Router->match("/login")
25. /var/www/html/index.php line 36
OC::handleRequest()
POST /index.php/login
from 10.216.212.196 by 829b70fe-f300-103d-947c-49ba57c88a4d at 2023-10-10T16:52:03+00:00]
This Bug still exists in NC28. Also circumventing it atm with a null check:
if (is_null($result) || !is_array($result) || empty($result[0]) || !is_array($result[0])) {
return;
}
I'm also not a php coder but this does the trick for me at the moment.
This seams to be solved with #53714 , isn't it?