Builds the login result from the synchronized access - #488
Conversation
dbbf3c1 to
cf85e74
Compare
Access synchronization runs after the user row has already been read and cached, and the row was never read back, so the authentication result carried the access the user had before synchronization applied what LDAP grants now.
authenticateViaLdap() asserted the plain success code for every caller, including the three whose LDAP entry grants superuser access. Those authentications now report the access synchronization leaves the user with, so the helper takes the expected code.
WebServerAuthLoginResolutionTest stubbed UserModel::getUser() with a fixed value, so in the provisioning case it kept reporting no user even after synchronization had created one. Reading the row back after access synchronization then found nothing and failed the login. The stub now starts reporting the user once synchronization creates it.
4947b25 to
f3214e5
Compare
|
Fixed in f3214e5. Two separate things in that CI output: The skipped test is unrelated. The real failure was a test fixture of mine, not the product. Real synchronization creates the Matomo user, so It only showed up in CI because this branch was cut before #487 merged, so my local runs did not include that test. Rebased onto the current I checked the change did not weaken either test's coverage — reinstating the byte-exact comparison still fails 2 tests in 181 tests, 412 assertions locally. PHPCS clean. |
snake14
left a comment
There was a problem hiding this comment.
Looks good and no issues found during functional testing. 👍
Refs #AS-730.
Problem
Base::synchronizeLdapUser()caches the user row returned by identity synchronization, then runs access synchronization, and never reads the row back:UserSynchronizer::synchronizeLdapUser()ends withreturn $userModel->getUser($syncLogin), which is a fresh read but taken before access synchronization.synchronizePiwikAccessFromLdap()then clears the user's access and applies what LDAP grants now.getUserForLogin()short-circuits on the cached value, somakeSuccessLogin()builds the result from the access the row held beforehand rather than the access synchronization just applied.Access::reloadAccess()takes the request's authority from that result, so for one request the two disagree.Change
Read the row back after access synchronization, keyed on
$syncedLoginrather than$this->login—UserMapper::getExpectedLdapUsername()can append the configured email suffix, so the synchronized login is not always the asserted one.If the user has been deleted meanwhile the re-read returns empty and
makeSuccessLogin()throwsUser couldn't be found, which fails the login. That is the safe direction.Scope
Not specific to web server auth.
synchronizeLdapUser()is also reached fromauthenticateByLdap(), whichLdapAuthandSynchronizedAuthboth call beforemakeSuccessLogin($this->getUserForLogin()), so all three auth implementations are affected by the one change. The relevant setting isenable_synchronize_access_from_ldap, which defaults to0;synchronize_users_after_logindefaults to1.Tests
tests/Unit/WebServerAuthAccessSyncTest.phpdrives the realauthenticate()with an access-synchronization stub that changes the flag, and asserts the result reflects the value synchronization left rather than the one read before it, in both directions. I confirmed the first case fails without the change and passes with it.Plugin unit suite: 155 tests, 357 assertions, OK. PHPCS clean.
Note
#487 is open against the same file. It touches
getUserForLogin(),isSameLogin()andmakeAuthFailure(), notsynchronizeLdapUser(), so the two should merge without conflict. Worth landing #487 first since it is further along.Longer term the seam is worth closing rather than patching:
Base::synchronizeLdapUser()assigning$this->userForLogindirectly is also what let a synchronized row skip the login comparison in #487. Having one synchronization operation return the final post-access row would remove both hazards.Checklist