Repository navigation
Benchmark PR 38446 - #17
celmis-codereviewer wants to merge 1 commit into
Conversation
Signed-off-by: rtufisi <rtufisi@phasetwo.io>
celmis-codereviewer
left a comment
There was a problem hiding this comment.
❌ CHANGES REQUESTED — blocking findings
Full findings and scope are in the review summary comment on this pull request — one persistent comment, updated in place on every run.
| public Stream<CredentialModel> getCredentials(RealmModel realm, UserModel user) { | ||
| var myUser = getMyUser(user); | ||
| RecoveryAuthnCodesCredentialModel model; | ||
| List<CredentialModel> credentialModels = new ArrayList<>(); |
There was a problem hiding this comment.
Why: myUser returned by getMyUser(user) on line 232 can be null when the user is not found in storage; dereferenced without a check on line 234, throwing a NullPointerException.
🟠 NullPointerException when user is not present in storage
In getCredentials, getMyUser(user) on line 232 will return null if the user is not managed by this storage provider. Line 234 then accesses myUser.recoveryCodes directly without checking if myUser is null, which will result in a NullPointerException. Other methods in this class (such as isConfiguredFor and isValid) explicitly check if (myUser == null) before accessing user fields.
| List<CredentialModel> credentialModels = new ArrayList<>(); | |
| var myUser = getMyUser(user); | |
| if (myUser == null) { | |
| return Stream.empty(); | |
| } | |
| RecoveryAuthnCodesCredentialModel model; |
agent: defect · rule: defect.null-dereference · confidence: 0.95
| List<CredentialModel> credentialModels = new ArrayList<>(); | ||
| if (myUser.recoveryCodes != null) { | ||
| try { | ||
| model = RecoveryAuthnCodesCredentialModel.createFromValues( |
There was a problem hiding this comment.
Why: myUser.recoveryCodes.getCredentialData() holds a JSON array of strings, so JsonSerialization.readValue on line 237 returns a List of String objects, causing a ClassCastException when RecoveryAuthnCodesCredentialModel methods expect elements of type RecoveryAuthnCodeRepresentation.
🟠 Incompatible list element type deserialized for RecoveryAuthnCodesCredentialModel
In BackwardsCompatibilityUserStorage.updateCredential, recovery codes credentials are stored by serializing generatedCodes (List<String>) into JSON format. When getCredentials reads this credential data on line 237 using JsonSerialization.readValue(..., List.class), Jackson produces a List<String>. Passing this raw list to RecoveryAuthnCodesCredentialModel.createFromValues(...) succeeds at compile time due to raw type unchecked conversion, but causes a ClassCastException at runtime whenever the model attempts to access elements expecting RecoveryAuthnCodeRepresentation objects.
| model = RecoveryAuthnCodesCredentialModel.createFromValues( | |
| List<String> rawCodes = JsonSerialization.readValue(myUser.recoveryCodes.getCredentialData(), List.class); | |
| model = RecoveryAuthnCodesCredentialModel.createFromRawValues( | |
| rawCodes, | |
| myUser.recoveryCodes.getCreatedDate(), | |
| myUser.recoveryCodes.getUserLabel() | |
| ); |
agent: defect · rule: defect.class-cast · confidence: 0.95
|
|
||
| // Authenticate as the user | ||
| testAppHelper.startLogin("otp1", "pass"); | ||
| enterRecoveryCodes(enterRecoveryAuthnCodePage, driver, 0, recoveryKeys); |
There was a problem hiding this comment.
Why: When testRecoveryKeysSetupAndLogin runs on line 259 with expectedCode 0, getRecoveryAuthnCodeToEnterNumber on line 479 returns 1 for the 1-indexed recovery code, causing the assertion on line 480 to fail.
🟠 Incorrect expected code number passed to enterRecoveryCodes
In Keycloak, recovery code numbers presented on the login screen are 1-indexed (e.g. code #1). Therefore, enterRecoveryAuthnCodePage.getRecoveryAuthnCodeToEnterNumber() on line 479 returns 1 for the first code. Passing 0 as expectedCode on line 259 causes assertEquals("Incorrect code presented to login", 0, 1) on line 480 to fail with an AssertionError during test execution.
| enterRecoveryCodes(enterRecoveryAuthnCodePage, driver, 0, recoveryKeys); | |
| enterRecoveryCodes(enterRecoveryAuthnCodePage, driver, 1, recoveryKeys); |
agent: defect · rule: defect.assertion-failure · confidence: 0.95
| enterRecoveryAuthnCodePage.setDriver(driver); | ||
| enterRecoveryAuthnCodePage.assertCurrent(); | ||
| int requestedCode = enterRecoveryAuthnCodePage.getRecoveryAuthnCodeToEnterNumber(); | ||
| org.junit.Assert.assertEquals("Incorrect code presented to login", expectedCode, requestedCode); |
There was a problem hiding this comment.
Why: requestedCode on line 479 holds 1 for the first recovery code; used directly as a 0-based index into generatedRecoveryAuthnCodes on line 481, fetching index 1 (the second code) instead of index 0.
🟠 Off-by-one indexing into generatedRecoveryAuthnCodes list
Recovery code numbers returned by getRecoveryAuthnCodeToEnterNumber() are 1-based (1, 2, ...), while generatedRecoveryAuthnCodes is a zero-indexed List. Line 481 uses requestedCode directly as the index, so when code #1 is requested (requestedCode == 1), generatedRecoveryAuthnCodes.get(1) fetches the second code in the list instead of the first code at index 0.
| org.junit.Assert.assertEquals("Incorrect code presented to login", expectedCode, requestedCode); | |
| enterRecoveryAuthnCodePage.enterRecoveryAuthnCode(generatedRecoveryAuthnCodes.get(requestedCode - 1)); |
agent: defect · rule: defect.off-by-one · confidence: 0.95
🤖 Code Review for PR #17❌ CHANGES REQUESTED — blocking findings Findings
Scope
Performance
Powered by Code Analyzer · context: tree-sitter graph + cve, structural, security, contract, defect |
Benchmark reproduction of keycloak#38446