Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes a NullPointerException after import validation #20151

Merged

Conversation

havarnov
Copy link
Contributor

@havarnov havarnov commented May 4, 2023

If the import validation (when getting a user by email) returns null, indicating that the user entity should be removed from local storage, an email equality check results in a NullPointerException.

This commit fixes this issue by explicitly checking for null.

Closes #20150

@havarnov havarnov requested a review from a team May 4, 2023 08:59
@havarnov havarnov requested a review from a team as a code owner May 4, 2023 08:59
@ghost ghost added the team/store label May 4, 2023
@havarnov havarnov force-pushed the bugfix/import-validation-null-pointer-bug branch from da39ded to 0b6248c Compare May 4, 2023 09:00
@@ -340,7 +340,7 @@ public UserModel getUserByEmail(RealmModel realm, String email) {
if (user != null) {
user = importValidation(realm, user);
// Case when email was changed directly in the userStorage and doesn't correspond anymore to the email from local DB
if (email.equalsIgnoreCase(user.getEmail())) {
if (user != null && email.equalsIgnoreCase(user.getEmail())) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be correct to return null here, if importValidation() returns null?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed this question before. I don't think we want to return null in that case. If importValidation returns null it means the user from Keycloak database is invalid and was removed. Returning null in that situation would mean we say Keycloak does not know the user by this email, however, this is not true and it can be still present in an external provider. We need to query the user again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that's what I mean, if importValidation returns null we return user which is set to null.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I wrote, we DO NOT want to return null when validation fails, because some provider may still contain the user.

For example:

  1. Some provider contains user1 and it is synced to KC DB
  2. The user1 is changed in that provider (Keycloak DB is not updated).
  3. If we query this user by email, the validation will fail because the user was changed
  4. Returning null in that case would be wrong because the provider still contains the user. We need to reload the user from the provider (or any other provider knowing user by that email).

Copy link
Contributor

@mhajas mhajas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR @havarnov! Can you please also create some tests for this? I believe the best class to place this is: https://github.com/keycloak/keycloak/blob/main/testsuite/model/src/test/java/org/keycloak/testsuite/model/user/UserSyncTest.java

What we need is:

  1. Create a user in LDAP
  2. Load the user by email (imports user to KC database)
  3. Remove the user from LDAP only
  4. Assert no exception is thrown and no user was returned

@mhajas mhajas self-assigned this May 9, 2023
@mhajas mhajas added the missing/tests Tests are missing label May 9, 2023
@martin-kanis
Copy link
Contributor

@havarnov Thank you for the PR. Will you be able to add the missing test anytime soon? I can offer more guidance if needed.

@havarnov
Copy link
Contributor Author

@martin-kanis sry, I've been busy with other stuff. I'll see if I can make it this week!

I do have one comment I'd like some feedback on, though: https://github.com/keycloak/keycloak/pull/20151/files#r1184988078

@havarnov
Copy link
Contributor Author

I'm having issues running the tests locally. Is there some kind of setup I'm missing maybe?

org.junit.AssumptionViolatedException: Some required providers not found: got: <[interface org.keycloak.models.UserProvider, interface org.keycloak.cluster.ClusterProvider, interface org.keycloak.models.RealmProvider, interface org.keycloak.storage.UserStorageProvider]>, expected: an empty collection

@mhajas
Copy link
Contributor

mhajas commented Aug 10, 2023

I'm having issues running the tests locally. Is there some kind of setup I'm missing maybe?

org.junit.AssumptionViolatedException: Some required providers not found: got: <[interface org.keycloak.models.UserProvider, interface org.keycloak.cluster.ClusterProvider, interface org.keycloak.models.RealmProvider, interface org.keycloak.storage.UserStorageProvider]>, expected: an empty collection

You need to enable some profile in order to have these providers on class path. Try to run the test with -Plegacy-jpa-federation+ldap+infinispan.

@havarnov
Copy link
Contributor Author

@mhajas I'm sry, but I can't get the UserSyncTests to run. I'm doing the following

cd keycloak

# build project
mvn clean install -DskipTests

# run tests
cd testsuite/model
mvn test -Plegacy-jpa-federation+ldap+infinispan -Dtest=UserSyncTest

The actual test fails with:

[ERROR]   UserSyncTest.testManyUsersImport:113 » Model org.keycloak.models.ModelException: Error creating subcontext [uid=user1,ou=People,dc=keycloak,dc=org]

Is there any dependencies I'm missing, e.g. start a specific container or something?

@mhajas
Copy link
Contributor

mhajas commented Aug 21, 2023

Is there any dependencies I'm missing, e.g. start a specific container or something?

No, it should work out of the box. It seems you are doing the correct thing. Have you done any changes to the UserSyncTest class? If yes, can you push the changes so I can give it a try?

@havarnov havarnov force-pushed the bugfix/import-validation-null-pointer-bug branch from 0b6248c to 64794ba Compare August 22, 2023 06:33
@havarnov
Copy link
Contributor Author

No changes to UserSyncTest. I just did a rebase against main, so I get this error on bugfix/import-validation-null-pointer-bug without any changes.

@mhajas
Copy link
Contributor

mhajas commented Aug 22, 2023

I was able to reproduce it @havarnov. It seems there is a bug in the test class. We are not cleaning the LDAP users properly between test methods and it is failing.

It was not caught by the CI because for some reason the test passes for second attempt when we use -Dsurefire.rerunFailingTestsCount=2. I am not sure why it is not reported as flaky test yet though, I am investigating it.

To workaround this, you can run only one test within test class. For example:

mvn test -Plegacy-jpa-federation+ldap+infinispan -Dtest=UserSyncTest#testManyUsersImport

this way it passes.

I will let you know once I resolve this.

@mhajas
Copy link
Contributor

mhajas commented Aug 22, 2023

@havarnov I created a PR for fixing the failing test. You can try whether the code I added works for you:
#22603

@havarnov
Copy link
Contributor Author

@mhajas thanks both the work-around and your PR worked for me :D

If the import validation (when getting a user by email)
returns null, indicating that the user entity should be
removed from local storage, an email equality check results
in a NullPointerException.

This commit fixes this issue by explicitly checking for null.

Closes keycloak#20150
@havarnov havarnov force-pushed the bugfix/import-validation-null-pointer-bug branch from 64794ba to 64a5295 Compare August 22, 2023 13:43
@havarnov
Copy link
Contributor Author

@mhajas I've now added a test that fails if the fix is not applied. But, I'm not totally sure why I need the two calls to UserStorageSyncManager.syncAllUsers() to get this to work. Shouldn't LDAPTestUtils.addLDAPUser() also add the user to the localStorage?

…n it is removed from LDAP

Signed-off-by: Michal Hajas <mhajas@redhat.com>
@mhajas
Copy link
Contributor

mhajas commented Aug 23, 2023

@havarnov We probably don't need to do synchronization at all in this test. I updated the test a little bit. Could you please have a look at whether it makes sense?

Copy link
Contributor

@mhajas mhajas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution @havarnov

@mhajas mhajas merged commit bc55846 into keycloak:main Oct 23, 2023
72 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NPE after import validation in UserStorageManager::getUserByEmail
3 participants