Skip to content

Refactor SCRAM-SHA-1 unit tests for reuse, readability, and future algorithm coverage - #3334

Merged
guusdk merged 5 commits into
igniterealtime:mainfrom
guusdk:SCRAM_refactor_unittests
May 27, 2026
Merged

guusdk merged 5 commits into
igniterealtime:mainfrom
guusdk:SCRAM_refactor_unittests

Conversation

@guusdk

@guusdk guusdk commented May 19, 2026

Copy link
Copy Markdown
Member

This PR refactors the SCRAM-SHA-1 unit test suite to improve readability, reduce duplication, and prepare the test structure for reuse by additional SCRAM mechanisms (such as SCRAM-SHA-256).

The changes are intentionally non-functional: they reorganize and consolidate test infrastructure without changing protocol behavior.

Highlights include

  • Introduces shared test fixtures and canonical test constants to eliminate hard-coded values and improve semantic clarity.
  • Replaces repetitive setup and protocol message construction with factory/helper methods.
  • Centralizes parsing and exchange handling logic used across tests.
  • Separates generic SCRAM behavior from SHA-1-specific behavior by introducing abstract base test classes.
  • Consolidates fake salt determinism, fake key generation, and user enumeration protection tests into reusable structures intended to support additional SCRAM algorithms.

Please refer to the individual commit messages for more details.

Please note: Part of this refactoring introduces a test-driven rather than domain-driven change in the implementation under test (ScramSaslServer and ScramSha1SaslServer). Some methods were moved into abstract superclasses primarily to enable polymorphic test reuse. This is a compromise and will be revisited in a future change (that I've got ready to go, but in a different PR).

guusdk added 2 commits May 19, 2026 12:27
This introduces a test fixture that defines stable, shared test data, canonical protocol constants, and repeated values with semantic meaning.

Usage of constants from this new fixture avoids using hard-coded values in tests. This makes intent clearer (by referring to data by a meaningful name, rather than by a 'magic value').
This replaces repeated construction patterns and protocol-building patterns.

By replacing most invocations of `new ScramSha1SaslServer(isPlusMechanism, new HashMap<>(), new ChannelBindingProviderManager(), ScramSha1TestFixtures.SUPPORTED_MECHANISMS);` with a factory method (`newServer(isPlusMechanism)`), a lot of boilerplate code with low informational value has been reduced.

The client `initialMessage` (`"n,,n=" + USER + ",r=" + nonce`) was constructed manually in various variants. By extracting this to a factory method tests become more readable: `final byte[] initialMessage = createClientInitialMessage("n,,", USER, CLIENT_NONCE);` This improves readability a lot, because the SCRAM message construction becomes 'named'. Tests now focus on intent rather than string concatenation.

Similarly, the client finalMessage, which was also constructed with string concatenation in various places, has been extracted to a factory method (`createClientFinalMessage(channelBinding, serverNonce, proof)`). Its usage now becomes declarative (`createClientFinalMessage("biws", nonce, proof)`) rather than protocol-string spaghetti ('"c=biws,r=" + serverNonce + ",p=" + proof').

The 'first exchange' helper (`String doFirstExchange(server)`) has been improved. Instead of returning just the nonce from the first result, it now returns a structure that holds all data from that response.

The old code repeatedly did `Pattern.compile(...); Matcher ...; group(...);` which was getting repetitive. This has been replaced by another centralized parse method (`FirstExchangeResult#fromFirstServerResponse(firstServerResponse)`).

Tests used various mocks of `AuthFactory`, which each set up a partial factory, based on subsets of the same canonical data. These partials (`setupSaltAndIterations()` and `setupSaltAndIterationsAndKeys()`) have been replaced with one method that sets up everything: salt, iterations, password and keys. The new method name is `setupCanonicalAuthData()`. This encodes 'valid SCRAM credentials', avoids subtle divergence between tests, and reduces fragile setup duplication.

All in all, this does not introduce functional changes, but upgrades the code to be better self-describing, and easier to adapt to future use-cases.
@coderabbitai

coderabbitai Bot commented May 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

â„đïļ Recent review info
⚙ïļ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4eb305ce-9ee2-4d36-a784-2cf45544cd34

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 7fb92e9 and 434e1c2.

📒 Files selected for processing (10)
  • xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServer.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramCredentialObfuscationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramSaslServerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/FirstExchangeResult.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1CredentialObfuscationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerFakeKeyTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerSaltTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1TestFixtures.java
ðŸ’Ī Files with no reviewable changes (2)
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerFakeKeyTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerSaltTest.java
🚧 Files skipped from review as they are similar to previous changes (6)
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1CredentialObfuscationTest.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramCredentialObfuscationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/FirstExchangeResult.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramSaslServerTest.java

📝 Walkthrough

Walkthrough

This PR introduces ScramSaslServer (abstract base) with timing-attack/user-enumeration-safe contracts and a byte-level GS2 header extractor, changes ScramSha1SaslServer to extend the new base and convert credential hooks to protected overrides, and consolidates tests by adding AbstractScramCredentialObfuscationTest and AbstractScramSaslServerTest plus FirstExchangeResult and ScramSha1TestFixtures. SCRAM-SHA-1 concrete tests were refactored to use the new abstract test bases and shared fixtures.

Possibly related PRs

  • igniterealtime/Openfire#3282: Directly overlaps with SCRAM -PLUS channel-binding and GS2-header validation changes to ScramSha1SaslServer.

Suggested reviewers

  • Fishbowler
  • dwd
ðŸšĨ Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly explains the refactoring objectives, scope, and key changes, directly relating to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏ïļ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the SCRAM-SHA-1 unit test suite to reduce duplication and to enable reuse by future SCRAM mechanisms (e.g. SCRAM-SHA-256). It introduces shared test fixtures, helper factory methods, parsing utilities, and abstract base test classes, plus a new abstract ScramSaslServer parent class so that test polymorphism is possible. No protocol behavior is changed.

Changes:

  • Adds a new abstract ScramSaslServer superclass and pulls getOrFakeServerKey, getOrFakeStoredKey, getOrCreateSalt, and extractRawGS2Header into it.
  • Centralizes shared SCRAM test logic in AbstractScramSaslServerTest and AbstractScramCredentialObfuscationTest, plus the parsing helper FirstExchangeResult and constants in ScramSha1TestFixtures.
  • Replaces inline test setup/helpers in the SHA-1 tests with overrides of the new base classes; deletes the standalone ScramSha1SaslServerSaltTest and ScramSha1SaslServerFakeKeyTest in favor of ScramSha1CredentialObfuscationTest.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java New abstract base for SCRAM SASL servers; hosts shared GS2 helper and abstract credential hooks
xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServer.java Extends new abstract class; methods promoted to overrides
xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSaslServer (test files) Adds shared fixtures and abstract test bases; deletes per-algorithm duplication
xmppserver/src/test/java/.../ScramSha1TestFixtures.java Canonical SCRAM-SHA-1 test constants
xmppserver/src/test/java/.../FirstExchangeResult.java Parsing helper for the SCRAM first server response
xmppserver/src/test/java/.../AbstractScramSaslServerTest.java Reusable suite of SCRAM SASL contract/protocol tests
xmppserver/src/test/java/.../AbstractScramCredentialObfuscationTest.java Reusable suite for fake-key/salt obfuscation tests
xmppserver/src/test/java/.../ScramSha1SaslServerTest.java Now extends abstract test base; algorithm-specific overrides only
xmppserver/src/test/java/.../ScramSha1CredentialObfuscationTest.java Replaces deleted SaltTest/FakeKeyTest by extending the new abstract base
xmppserver/src/test/java/.../ScramSha1SaslServerSaltTest.java Deleted (consolidated)
xmppserver/src/test/java/.../ScramSha1SaslServerFakeKeyTest.java Deleted (consolidated)

ðŸ’Ą Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

ðŸ§đ Nitpick comments (1)
xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java (1)

17-40: ⚡ Quick win

Make the abstract SCRAM hooks protected.

Lines 17, 27, and 40 currently declare package-private abstract methods. Since ScramSaslServer is itself a public abstract class, these package-private hooks limit extensibility to the same package. The existing subclass (ScramSha1SaslServer) already declares these methods as protected, indicating the intended visibility. Align the base class declarations to protected abstract to properly express the extensibility contract.

â™ŧïļ Proposed fix
-    abstract byte[] getOrFakeServerKey(String username);
+    protected abstract byte[] getOrFakeServerKey(String username);

-    abstract byte[] getOrFakeStoredKey(final String username);
+    protected abstract byte[] getOrFakeStoredKey(final String username);

-    abstract byte[] getOrCreateSalt(final String username);
+    protected abstract byte[] getOrCreateSalt(final String username);
ðŸĪ– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java`
around lines 17 - 40, The three abstract hook methods in ScramSaslServer
(getOrFakeServerKey, getOrFakeStoredKey, getOrCreateSalt) are currently
package-private; change their declarations to protected abstract so subclasses
outside the package (e.g., ScramSha1SaslServer) can override them and the
class's extensibility contract is explicit—update the method modifiers in
ScramSaslServer accordingly.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramSaslServerTest.java`:
- Line 573: The test is passing an already-prefixed proof string to
createClientFinalMessage which itself prefixes "p=", producing malformed
client-final-message; update the three calls to createClientFinalMessage (the
one assigning clientFinalMessage and the other two usages referenced) to pass
the raw Base64 proof bytes (e.g., "dGVzdA==") instead of "p=..." so the helper
will produce a correctly formed "p=<base64>" field.

---

Nitpick comments:
In
`@xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java`:
- Around line 17-40: The three abstract hook methods in ScramSaslServer
(getOrFakeServerKey, getOrFakeStoredKey, getOrCreateSalt) are currently
package-private; change their declarations to protected abstract so subclasses
outside the package (e.g., ScramSha1SaslServer) can override them and the
class's extensibility contract is explicit—update the method modifiers in
ScramSaslServer accordingly.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 00df861a-a06e-44fd-9631-de75ed563a64

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between d226e91 and 7fb92e9.

📒 Files selected for processing (10)
  • xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSaslServer.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServer.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramCredentialObfuscationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramSaslServerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/FirstExchangeResult.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1CredentialObfuscationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerFakeKeyTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerSaltTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1TestFixtures.java
ðŸ’Ī Files with no reviewable changes (2)
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerFakeKeyTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerSaltTest.java

guusdk added 3 commits May 19, 2026 13:04
The goal of this change is to prepare the unit test structure for SHA-256 re-use.

A new abstract test class is created that now contains tests for:
- state machine
- malformed input
- negotiated properties
- channel binding behavior

Tests (and helpers) for protocol semantics, SASL contract behavior and algorithm-independent SCRAM behavior now live in that abstract class. SHA-1-specific cryptographic behavior, mechanism naming and success path details remain in the SCRAM-SHA-1 specific class.

The fake salt determinism, fake key generation and user enumeration protection responsibilities are deliberately not folded into the same abstract class, as that would cause us to end up with a "god test class". There is some duplication in those that is worth removing, but that will be the subject of a future commit.
The fake salt determinism, fake key generation and user enumeration protection tests for SCRAM-SHA-1 are consolidated into one class, which is then
split up in an abstract base class, and a SCRAM-SHA-1 specific subclass. This is expected to make it easier to add test coverage for other algorithms.

Of note is that this change introduces a test-driven rather than domain-driven change in the system under test (`ScramSaslServer` and `ScramSha1SaslServer`): methods were lifted into the abstract superclass primarily because the tests needed polymorphism. This needs improving, which should go in a future commit.
@guusdk
guusdk force-pushed the SCRAM_refactor_unittests branch from 7fb92e9 to 434e1c2 Compare May 19, 2026 11:05
@guusdk
guusdk requested a review from Copilot May 19, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@guusdk
guusdk requested review from Fishbowler and dwd May 19, 2026 11:50
@guusdk
guusdk merged commit 835f42d into igniterealtime:main May 27, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants