Skip to content

OF-3322: Move SCRAM credential storage into a dedicated ofUserScram table - #3415

Merged
guusdk merged 10 commits into
igniterealtime:mainfrom
guusdk:OF-3322_SCRAM-to-new-database-table
Jul 3, 2026
Merged

guusdk merged 10 commits into
igniterealtime:mainfrom
guusdk:OF-3322_SCRAM-to-new-database-table

Conversation

@guusdk

@guusdk guusdk commented Jul 2, 2026

Copy link
Copy Markdown
Member

Openfire stores SCRAM credentials (storedKey, serverKey, salt, iterations) as columns on the ofUser table. This hard-codes the assumption that a user has exactly one SCRAM credential (which is SCRAM-SHA-1). Supporting additional mechanisms (SCRAM-SHA-256, SCRAM-SHA-512) requires storing more than one credential per user.

This change relocates SCRAM credentials to a new ofUserScram table keyed by (username, mechanism), without adding any new mechanism. Behavior is intended to be identical to before: SCRAM-SHA-1 remains the only mechanism written or read, and credentials are still regenerated from a stored plaintext/encrypted password when absent. This is a storage refactor only; it is a precursor to a follow-up change that adds the additional SCRAM mechanisms, kept separate so that the schema migration and the new-mechanism logic can be reviewed independently.

This commit introduces a new ofUserScram table added to all base install scripts, and a two-step migration:

  1. upgrade script 39 creates ofUserScram and copies existing SHA-1 credentials from ofUser
  2. upgrade script 40 drops the old columns from ofUser.

User's getSalt/getStoredKey/getServerKey/getIterations accessors (and their setters) expose only the single SCRAM-SHA-1 credential and have no role in the per-mechanism model. They are marked @Deprecated and retained for backwards compatibility with third-party integrations.

AuthProvider, AuthMultiProvider and AuthFactory gain mechanism-aware SCRAM accessors. the existing single-argument methods are retained and delegate to SCRAM-SHA-1, and marked as @Deprecated.

â€Ķable

Openfire stores SCRAM credentials (storedKey, serverKey, salt, iterations) as columns on the ofUser table. This hard-codes the assumption that a user has exactly one SCRAM credential (which is SCRAM-SHA-1). Supporting additional mechanisms (SCRAM-SHA-256, SCRAM-SHA-512) requires storing more than one credential per user.

This change relocates SCRAM credentials to a new ofUserScram table keyed by (`username`, `mechanism`), without adding any new mechanism. Behavior is intended to be identical to before: SCRAM-SHA-1 remains the only mechanism written or read, and credentials are still regenerated from a stored plaintext/encrypted password when absent. This is a storage refactor only; it is a precursor to a follow-up change that adds the additional SCRAM mechanisms, kept separate so that the schema migration and the new-mechanism logic can be reviewed independently.

This commit introduces a new ofUserScram table added to all base install scripts, and a two-step migration:

1. upgrade script 39 creates `ofUserScram` and copies existing SHA-1 credentials from `ofUser`
2.  upgrade script 40 drops the old columns from ofUser.

`User`'s `getSalt`/`getStoredKey`/`getServerKey`/`getIterations` accessors (and their setters) expose only the single SCRAM-SHA-1 credential and have no role in the per-mechanism model. They are marked `@Deprecated` and retained for backwards compatibility with third-party integrations.

`AuthProvider`, `AuthMultiProvider` and `AuthFactory` gain mechanism-aware SCRAM accessors. the existing single-argument methods are retained and delegate to SCRAM-SHA-1, and marked as `@Deprecated`.
@guusdk
guusdk requested review from Fishbowler and Copilot July 2, 2026 10:04
@coderabbitai

coderabbitai Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • â–ķïļ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR moves SCRAM credential storage out of ofUser into ofUserScram across database schemas, adds 39/40 migrations to copy then drop the legacy columns, and updates the schema version target to 40. Java authentication and user-loading code now use mechanism-aware SCRAM accessors backed by ScramCredentialData. Documentation and tests were updated to reflect the new storage split.

Possibly related PRs

Suggested reviewers: Fishbowler, dwd

ðŸšĨ Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description accurately matches the schema migration and SCRAM API changes in 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.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

âĪïļ Share

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

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 implements OF-3322 by refactoring SCRAM credential storage out of ofUser and into a new, mechanism-keyed ofUserScram table (username, mechanism), while keeping runtime behavior limited to SCRAM-SHA-1 (for now) and introducing mechanism-aware SCRAM accessors in the authentication APIs.

Changes:

  • Introduces ofUserScram across base install scripts and adds DB upgrade steps 39 (create/copy) and 40 (drop old columns).
  • Updates DefaultUserProvider / DefaultAuthProvider to load and persist SCRAM material via ofUserScram and adds mechanism-aware SCRAM API surface area.
  • Adds/updates tests to validate SCRAM storage loading and mechanism-aware API usage.

Reviewed changes

Copilot reviewed 44 out of 44 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
xmppserver/src/test/java/org/jivesoftware/openfire/user/DefaultUserProviderScramStorageTest.java New test verifying DefaultUserProvider.loadUser reads SCRAM from ofUserScram.
xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerTest.java Updates mocks to use mechanism-aware AuthFactory SCRAM accessors.
xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramCredentialObfuscationTest.java Updates test setup to use mechanism-aware SCRAM salt retrieval.
xmppserver/src/test/java/org/jivesoftware/openfire/auth/DefaultAuthProviderScramStorageTest.java New test verifying DefaultAuthProvider consults ofUserScram for SCRAM verification.
xmppserver/src/main/java/org/jivesoftware/openfire/user/User.java Deprecates legacy SCRAM-SHA-1 accessors on User (backwards compatibility).
xmppserver/src/main/java/org/jivesoftware/openfire/user/DefaultUserProvider.java Loads descriptive fields from ofUser and SCRAM-SHA-1 from ofUserScram.
xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServer.java Adds MECHANISM_NAME constant for consistent SCRAM-SHA-1 naming.
xmppserver/src/main/java/org/jivesoftware/openfire/auth/ScramCredentialData.java New value type for per-user, per-mechanism SCRAM credential data.
xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java Moves SCRAM persistence/loading to ofUserScram; adds mechanism-aware getScramCredential.
xmppserver/src/main/java/org/jivesoftware/openfire/auth/AuthProvider.java Adds default mechanism-aware SCRAM accessors; deprecates legacy ones.
xmppserver/src/main/java/org/jivesoftware/openfire/auth/AuthMultiProvider.java Delegates mechanism-aware SCRAM accessors to mapped providers; deprecates legacy ones.
xmppserver/src/main/java/org/jivesoftware/openfire/auth/AuthFactory.java Adds mechanism-aware SCRAM accessors and delegates legacy methods to SCRAM-SHA-1.
xmppserver/src/main/java/org/jivesoftware/database/SchemaManager.java Bumps schema version to 40.
documentation/database-guide.html Documents new ofUserScram table.
distribution/src/database/upgrade/39/openfire_sybase.sql Upgrade step 39 (Sybase): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_sqlserver.sql Upgrade step 39 (SQL Server): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_postgresql.sql Upgrade step 39 (PostgreSQL): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_oracle.sql Upgrade step 39 (Oracle): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_mysql.sql Upgrade step 39 (MySQL): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_mariadb.sql Upgrade step 39 (MariaDB): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_hsqldb.sql Upgrade step 39 (HSQLDB): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_firebird.sql Upgrade step 39 (Firebird): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_db2.sql Upgrade step 39 (DB2): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/39/openfire_cockroachdb.sql Upgrade step 39 (CockroachDB): create/copy SCRAM credentials into ofUserScram.
distribution/src/database/upgrade/40/openfire_sybase.sql Upgrade step 40 (Sybase): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_sqlserver.sql Upgrade step 40 (SQL Server): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_postgresql.sql Upgrade step 40 (PostgreSQL): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_oracle.sql Upgrade step 40 (Oracle): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_mysql.sql Upgrade step 40 (MySQL): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_mariadb.sql Upgrade step 40 (MariaDB): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_hsqldb.sql Upgrade step 40 (HSQLDB): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_firebird.sql Upgrade step 40 (Firebird): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_db2.sql Upgrade step 40 (DB2): drop legacy SCRAM columns from ofUser.
distribution/src/database/upgrade/40/openfire_cockroachdb.sql Upgrade step 40 (CockroachDB): drop legacy SCRAM columns from ofUser.
distribution/src/database/openfire_sybase.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_sqlserver.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_postgresql.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_oracle.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_mysql.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_mariadb.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_hsqldb.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_firebird.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_db2.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.
distribution/src/database/openfire_cockroachdb.sql Base schema: remove SCRAM columns from ofUser, add ofUserScram, bump version.

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

Comment thread xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java Outdated
Comment thread distribution/src/database/upgrade/39/openfire_mysql.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_db2.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_mariadb.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_hsqldb.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_firebird.sql Outdated
Comment on lines +20 to +22
WHERE storedKey IS NOT NULL
AND serverKey IS NOT NULL
AND salt IS NOT NULL;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean... sure? The server would be generating a lot of errors if iterations IS NULL but the other two aren't.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was debating if we'd be better off failing fast, but that'd mean we should fail on any scenario where not all of these valus are non-NULL. In the end I've just added the missing NULL check to each of the scripts.

Comment thread distribution/src/database/upgrade/39/openfire_sybase.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_sybase.sql Outdated

@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: 10

ðŸ§đ Nitpick comments (4)
distribution/src/database/upgrade/39/openfire_mysql.sql (1)

7-15: 📐 Maintainability & Code Quality | ðŸ”ĩ Trivial | ⚡ Quick win

Unnamed primary key breaks naming convention.

Every sibling script (Oracle, SQL Server v39, and the base ofUser PKs) names the constraint <table>_pk. This MySQL script leaves it unnamed, which will get an auto-generated name and diverge if a future script needs to reference/drop it by name.

â™ŧïļ Proposed fix
-  PRIMARY KEY (username, mechanism)
+  CONSTRAINT ofUserScram_pk PRIMARY KEY (username, mechanism)
ðŸĪ– 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 `@distribution/src/database/upgrade/39/openfire_mysql.sql` around lines 7 - 15,
The primary key on ofUserScram is unnamed, which breaks the existing
`<table>_pk` convention used by the sibling upgrade scripts and base table
constraints. Update the CREATE TABLE statement in the openfire_mysql.sql upgrade
for ofUserScram to explicitly name the PRIMARY KEY constraint using the same
naming pattern as the other scripts, so future schema changes can reference it
consistently.
distribution/src/database/upgrade/40/openfire_oracle.sql (1)

6-9: 🚀 Performance & Scalability | ðŸ”ĩ Trivial | ⚡ Quick win

Combine the 4 column drops into one statement.

Oracle supports dropping multiple columns in a single ALTER TABLE ... DROP (...) statement, avoiding four separate full-table passes for constraint/data validation on ofUser.

⚡ Proposed fix
-ALTER TABLE ofUser DROP COLUMN storedKey;
-ALTER TABLE ofUser DROP COLUMN serverKey;
-ALTER TABLE ofUser DROP COLUMN salt;
-ALTER TABLE ofUser DROP COLUMN iterations;
+ALTER TABLE ofUser DROP (storedKey, serverKey, salt, iterations);
ðŸĪ– 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 `@distribution/src/database/upgrade/40/openfire_oracle.sql` around lines 6 - 9,
The four separate ALTER TABLE drops on ofUser should be combined into a single
Oracle DROP statement to avoid multiple table passes. Update the
openfire_oracle.sql migration so the storedKey, serverKey, salt, and iterations
removals are handled together in one ALTER TABLE ... DROP (...) statement, using
the existing ofUser upgrade block as the place to make the change.
distribution/src/database/upgrade/40/openfire_mysql.sql (1)

6-9: 🚀 Performance & Scalability | ðŸ”ĩ Trivial | ⚡ Quick win

Combine the 4 DROP COLUMN statements into one ALTER TABLE.

Four separate statements mean up to four passes over ofUser on MySQL versions/storage engines where DROP COLUMN isn't done via the newer instant metadata-only path; "ALGORITHM=INSTANT, is not supported for DROP COLUMN and it would still need a table rebuild" prior to the multi-column instant support added in 8.0.29. A single combined statement reduces this to one pass.

⚡ Proposed fix
-ALTER TABLE ofUser DROP COLUMN storedKey;
-ALTER TABLE ofUser DROP COLUMN serverKey;
-ALTER TABLE ofUser DROP COLUMN salt;
-ALTER TABLE ofUser DROP COLUMN iterations;
+ALTER TABLE ofUser DROP COLUMN storedKey, DROP COLUMN serverKey, DROP COLUMN salt, DROP COLUMN iterations;

Please confirm the minimum supported MySQL/MariaDB version for Openfire to verify this syntax is compatible across all supported targets.

ðŸĪ– 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 `@distribution/src/database/upgrade/40/openfire_mysql.sql` around lines 6 - 9,
Combine the four separate ALTER TABLE drops on ofUser into a single ALTER TABLE
statement that removes storedKey, serverKey, salt, and iterations together.
Update the openfire_mysql.sql migration in the upgrade/40 script so the ofUser
table is altered once instead of four times, keeping the same column names but
using one multi-drop ALTER TABLE that is compatible with the minimum supported
MySQL/MariaDB version.
xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java (1)

61-62: 📐 Maintainability & Code Quality | ðŸ”ĩ Trivial | ⚖ïļ Poor tradeoff

Duplicated SCRAM-credential SQL with divergent column order across files.

LOAD_SCRAM_CREDENTIAL here selects iterations,salt,storedKey,serverKey, while DefaultUserProvider.LOAD_SCRAM_CREDENTIAL selects salt, serverKey, storedKey, iterations for the same table. Each is internally consistent with its own result-mapping code today, but the duplication with differing column orders is a maintenance hazard: a future edit to one query's column list without updating the matching rs.get*(index) calls (or the sibling query) could silently corrupt credential data.

Consider extracting a shared helper/DAO for reading ofUserScram rows into a ScramCredentialData, used by both DefaultAuthProvider and DefaultUserProvider.

ðŸĪ– 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/auth/DefaultAuthProvider.java`
around lines 61 - 62, The SCRAM credential query is duplicated with a different
column order, which makes the index-based result mapping in DefaultAuthProvider
and DefaultUserProvider fragile. Update the LOAD_SCRAM_CREDENTIAL usage in
DefaultAuthProvider and the matching logic in DefaultUserProvider so both rely
on a single shared helper/DAO for reading ofUserScram rows into
ScramCredentialData, or otherwise ensure one canonical column order is used
everywhere with matching rs.get* indexes.
ðŸĪ– 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 `@distribution/src/database/upgrade/39/openfire_cockroachdb.sql`:
- Around line 13-22: The SCRAM backfill into ofUserScram only filters out rows
with missing keys and salt, but it can still select legacy rows where iterations
is NULL and fail against the NOT NULL target. Update the INSERT ... SELECT in
the SCRAM migration to also exclude rows with missing iterations, so only fully
populated ofUser records are copied into ofUserScram.

In `@distribution/src/database/upgrade/39/openfire_db2.sql`:
- Around line 13-22: The SCRAM backfill in the database upgrade script does not
filter out rows with a missing iterations value, even though
ofUserScram.iterations is NOT NULL. Update the INSERT ... SELECT for ofUserScram
to also exclude legacy rows where iterations IS NULL, alongside the existing
storedKey/serverKey/salt checks, so incomplete SCRAM data is skipped during the
upgrade.

In `@distribution/src/database/upgrade/39/openfire_firebird.sql`:
- Around line 14-23: The SCRAM backfill in the ofUserScram insert only filters
on storedKey, serverKey, and salt, so legacy rows with a NULL iterations value
can still violate the NOT NULL target. Update the INSERT...SELECT for
ofUserScram to also exclude rows where iterations is NULL, keeping the filter
aligned with the NOT NULL columns. Locate the change in the SCRAM upgrade SQL
near the ofUserScram backfill statement.

In `@distribution/src/database/upgrade/39/openfire_hsqldb.sql`:
- Around line 13-22: Filter out incomplete legacy SCRAM rows before the backfill
into ofUserScram. In the INSERT ... SELECT for the ofUserScram upgrade script,
add a condition to exclude rows where iterations is NULL alongside the existing
storedKey, serverKey, and salt checks. This keeps the upgrade from failing on
partially populated ofUser data while still copying only complete SCRAM records.

In `@distribution/src/database/upgrade/39/openfire_mariadb.sql`:
- Around line 13-22: The version-39 SCRAM backfill in the ofUserScram insert
only filters on storedKey, serverKey, and salt, so rows with iterations IS NULL
can still fail the NOT NULL target. Update the INSERT ... SELECT in the
openfire_mariadb.sql upgrade script, and apply the same predicate change to the
matching version-39 backend scripts, so the legacy ofUser rows are only copied
when iterations is also present.

In `@distribution/src/database/upgrade/39/openfire_postgresql.sql`:
- Around line 17-22: The backfill in the PostgreSQL upgrade script must also
exclude legacy rows with NULL iterations before inserting into the new mandatory
column. Update the INSERT ... SELECT that populates ofUserScram so it filters on
iterations in addition to storedKey, serverKey, and salt, using the same
upgrade/backfill logic around ofUserScram and ofUser.

In `@distribution/src/database/upgrade/39/openfire_sqlserver.sql`:
- Around line 17-22: The SCRAM backfill in the v39 upgrade script can fail
because it inserts into ofUserScram.iterations, which is NOT NULL, without
filtering out legacy ofUser rows where iterations is missing. Update the
backfill in the ofUserScram insert to also require iterations IS NOT NULL, and
apply the same guard in the other v39 backfill scripts that populate
ofUserScram. Use the ofUserScram INSERT...SELECT block as the reference point
when making the change.

In `@distribution/src/database/upgrade/39/openfire_sybase.sql`:
- Around line 13-23: The backfill into ofUserScram should also exclude legacy
rows where iterations is null, since ofUserScram.iterations is NOT NULL and the
current INSERT from ofUser only filters storedKey, serverKey, and salt. Update
the INSERT...SELECT in openfire_sybase.sql to add an iterations IS NOT NULL ØīØąØ·
alongside the existing SCRAM field checks so upgrade 39 skips incomplete rows
and allows credential regeneration to handle them instead of failing.

In `@distribution/src/database/upgrade/40/openfire_db2.sql`:
- Around line 6-9: Add a Db2 REORG step after the ofUser column drops in the
openfire_db2.sql upgrade script; the ALTER TABLE DROP COLUMN statements for
storedKey, serverKey, salt, and iterations leave the table in reorg-pending
state. Update the database upgrade flow so that, after these drops complete,
ofUser is explicitly reorganized using the appropriate Db2 REORG command to make
the table available again.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java`:
- Around line 87-92: `getScramCredential` is regenerating SCRAM credentials too
early, which can change the salt mid-handshake and trigger intermittent auth
failures. Update `DefaultAuthProvider.getScramCredential` to first try
`loadScramCredential` and return an existing credential immediately; only call
`getUserInfo` (and any regeneration path it triggers) when no SCRAM credential
is present. Keep the fix consistent with `checkPassword` and the
`loadScramCredential`/`getUserInfo` flow so `setPassword` is not invoked unless
a credential truly needs to be created.

---

Nitpick comments:
In `@distribution/src/database/upgrade/39/openfire_mysql.sql`:
- Around line 7-15: The primary key on ofUserScram is unnamed, which breaks the
existing `<table>_pk` convention used by the sibling upgrade scripts and base
table constraints. Update the CREATE TABLE statement in the openfire_mysql.sql
upgrade for ofUserScram to explicitly name the PRIMARY KEY constraint using the
same naming pattern as the other scripts, so future schema changes can reference
it consistently.

In `@distribution/src/database/upgrade/40/openfire_mysql.sql`:
- Around line 6-9: Combine the four separate ALTER TABLE drops on ofUser into a
single ALTER TABLE statement that removes storedKey, serverKey, salt, and
iterations together. Update the openfire_mysql.sql migration in the upgrade/40
script so the ofUser table is altered once instead of four times, keeping the
same column names but using one multi-drop ALTER TABLE that is compatible with
the minimum supported MySQL/MariaDB version.

In `@distribution/src/database/upgrade/40/openfire_oracle.sql`:
- Around line 6-9: The four separate ALTER TABLE drops on ofUser should be
combined into a single Oracle DROP statement to avoid multiple table passes.
Update the openfire_oracle.sql migration so the storedKey, serverKey, salt, and
iterations removals are handled together in one ALTER TABLE ... DROP (...)
statement, using the existing ofUser upgrade block as the place to make the
change.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java`:
- Around line 61-62: The SCRAM credential query is duplicated with a different
column order, which makes the index-based result mapping in DefaultAuthProvider
and DefaultUserProvider fragile. Update the LOAD_SCRAM_CREDENTIAL usage in
DefaultAuthProvider and the matching logic in DefaultUserProvider so both rely
on a single shared helper/DAO for reading ofUserScram rows into
ScramCredentialData, or otherwise ensure one canonical column order is used
everywhere with matching rs.get* indexes.
🊄 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: 921be694-8bee-4c1a-8ed5-c30cb9939dd5

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 06464f0 and 197fd4a.

📒 Files selected for processing (44)
  • distribution/src/database/openfire_cockroachdb.sql
  • distribution/src/database/openfire_db2.sql
  • distribution/src/database/openfire_firebird.sql
  • distribution/src/database/openfire_hsqldb.sql
  • distribution/src/database/openfire_mariadb.sql
  • distribution/src/database/openfire_mysql.sql
  • distribution/src/database/openfire_oracle.sql
  • distribution/src/database/openfire_postgresql.sql
  • distribution/src/database/openfire_sqlserver.sql
  • distribution/src/database/openfire_sybase.sql
  • distribution/src/database/upgrade/39/openfire_cockroachdb.sql
  • distribution/src/database/upgrade/39/openfire_db2.sql
  • distribution/src/database/upgrade/39/openfire_firebird.sql
  • distribution/src/database/upgrade/39/openfire_hsqldb.sql
  • distribution/src/database/upgrade/39/openfire_mariadb.sql
  • distribution/src/database/upgrade/39/openfire_mysql.sql
  • distribution/src/database/upgrade/39/openfire_oracle.sql
  • distribution/src/database/upgrade/39/openfire_postgresql.sql
  • distribution/src/database/upgrade/39/openfire_sqlserver.sql
  • distribution/src/database/upgrade/39/openfire_sybase.sql
  • distribution/src/database/upgrade/40/openfire_cockroachdb.sql
  • distribution/src/database/upgrade/40/openfire_db2.sql
  • distribution/src/database/upgrade/40/openfire_firebird.sql
  • distribution/src/database/upgrade/40/openfire_hsqldb.sql
  • distribution/src/database/upgrade/40/openfire_mariadb.sql
  • distribution/src/database/upgrade/40/openfire_mysql.sql
  • distribution/src/database/upgrade/40/openfire_oracle.sql
  • distribution/src/database/upgrade/40/openfire_postgresql.sql
  • distribution/src/database/upgrade/40/openfire_sqlserver.sql
  • distribution/src/database/upgrade/40/openfire_sybase.sql
  • documentation/database-guide.html
  • xmppserver/src/main/java/org/jivesoftware/database/SchemaManager.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/AuthFactory.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/AuthMultiProvider.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/AuthProvider.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/ScramCredentialData.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServer.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/user/DefaultUserProvider.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/user/User.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/auth/DefaultAuthProviderScramStorageTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/AbstractScramCredentialObfuscationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/ScramSha1SaslServerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/user/DefaultUserProviderScramStorageTest.java

Comment thread distribution/src/database/upgrade/39/openfire_cockroachdb.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_db2.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_firebird.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_hsqldb.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_mariadb.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_postgresql.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_sqlserver.sql Outdated
Comment thread distribution/src/database/upgrade/39/openfire_sybase.sql Outdated
Comment thread distribution/src/database/upgrade/40/openfire_db2.sql
AuthFactory's SCRAM accessors are now mechanism-aware; the server still used the deprecated single-arg forms. Switch the five lookups to the two-arg form.

Also pin the test's AuthFactory mock to eq("SCRAM-SHA-1") so a regression to a PLUS-suffixed lookup fails rather than being masked.

@dwd dwd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might as well fix a few things if you've time, but I wouldn't block merging on them.

Comment on lines +20 to +22
WHERE storedKey IS NOT NULL
AND serverKey IS NOT NULL
AND salt IS NOT NULL;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean... sure? The server would be generating a lot of errors if iterations IS NULL but the other two aren't.

Comment thread xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java Outdated
The SCRAM fallback in checkPassword() previously obtained credentials through getScramCredential(), which re-queried the user and opened additional database connections even though checkPassword() already held an open connection and had just loaded the ofUser row. On a busy server this roughly doubled the connections needed per authentication.

This commit loads the password and SCRAM columns together in a single statement, reading all credential material from the one result set on the existing
connection.

Also harden the SCRAM derivation: an empty/invalid testPassword can cause createSaltedPassword() to throw IllegalArgumentException ("Empty key") from the HMAC init. Catch IllegalArgumentException alongside the existing SaslException/NoSuchAlgorithmException so such input fails authentication cleanly instead of escaping as an unchecked exception.

@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.

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

484-518: ðŸĐš Stability & Availability | ðŸ”ĩ Trivial | ðŸ’Ī Low value

Check-then-insert in upsertScramCredential is racy for concurrent first-time writes.

Two concurrent setPassword calls for a brand-new user can both observe TEST_SCRAM_CREDENTIAL returning no row and both take the INSERT branch, causing a primary-key/unique violation on (username, mechanism) for the losing transaction. The transaction abort path handles it safely (surfaced as UserNotFoundException), so impact is low, but a dialect-native upsert or catching the constraint violation and retrying as an update would be more robust.

ðŸĪ– 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/auth/DefaultAuthProvider.java`
around lines 484 - 518, The check-then-insert flow in upsertScramCredential is
race-prone for concurrent first-time writes, since two setPassword calls can
both miss TEST_SCRAM_CREDENTIAL and then collide on INSERT. Replace the
existence check plus separate UPDATE/INSERT branching with a single atomic,
database-native upsert if possible, or otherwise handle the unique constraint
failure and retry as UPDATE. Keep the fix localized to upsertScramCredential and
its use of TEST_SCRAM_CREDENTIAL, UPDATE_SCRAM_CREDENTIAL, and
INSERT_SCRAM_CREDENTIAL.
ðŸĪ– 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.

Nitpick comments:
In
`@xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java`:
- Around line 484-518: The check-then-insert flow in upsertScramCredential is
race-prone for concurrent first-time writes, since two setPassword calls can
both miss TEST_SCRAM_CREDENTIAL and then collide on INSERT. Replace the
existence check plus separate UPDATE/INSERT branching with a single atomic,
database-native upsert if possible, or otherwise handle the unique constraint
failure and retry as UPDATE. Keep the fix localized to upsertScramCredential and
its use of TEST_SCRAM_CREDENTIAL, UPDATE_SCRAM_CREDENTIAL, and
INSERT_SCRAM_CREDENTIAL.

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

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 063a6175-5116-4baa-b300-f858fa5ba008

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 643310c and ce2aa7f.

📒 Files selected for processing (2)
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/DefaultAuthProvider.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/auth/DefaultAuthProviderScramStorageTest.java

guusdk added 3 commits July 2, 2026 21:45
upsertScramCredential() previously did SELECT-then-INSERT/UPDATE, which had a time-of-check/time-of-use race: a concurrent setPassword() could insert the (username, mechanism) row between the existence check and the INSERT, causing a primary-key violation that aborted setPassword().

Switch to UPDATE-first, falling back to INSERT when no row was affected. For a password change the row already exists, so this is also the common single-statement case. If the INSERT loses a race and hits a unique/PK violation, the UPDATE is retried once, since the row exists by then.

Constraint-violation detection (isIntegrityConstraintViolation) combines the JDBC 4 SQLIntegrityConstraintViolationException subclass with an SQLState class-23 check, walking both the cause and getNextException() chains, to work across the databases Openfire supports.
The migration inserts into `ofUserScram.iterations` which is defined `NOT NULL`, but the INSERT ... SELECT only filters on storedKey/serverKey/salt.

If an existing row has SCRAM columns populated but iterations is `NULL`, this upgrade will fail with a `NOT NULL` constraint violation.

This commit adds an explicit `iterations IS NOT NULL` predicate.
SchemaManager executes upgrade scripts by concatenating lines until a line ends with ';' (see SchemaManager.executeSQLScript). The literal 'go' is therefore treated as part of a SQL statement and will cause the upgrade to fail. This script should not use 'go' batch separators. None of the other scripts do, either.

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 44 out of 44 changed files in this pull request and generated 2 comments.

guusdk added 2 commits July 3, 2026 11:15
When the database contains incomplete SCRAM credentials, skip that row, rather than stop evaluating any other rows. This prevents scenarios where authentication fails even though credentials exist.
Normalize all SCRAM mechanism names for credentials obtained from the database. This prevents issues with comparing values if the database would contains non-normalized mechanism names (e.g. the `-PLUS` variant for a mechanism). This should not be put in the database, but protecting against this is cheap hardening.

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 44 out of 44 changed files in this pull request and generated 2 comments.

guusdk added 2 commits July 3, 2026 13:23
`setPassword()` shouljd verify that the UPDATE on ofUser actually affected a row. If the username does not exist, this method should not silently succeed and still upsert a SCRAM row (creating an orphan record in ofUserScram).
@guusdk
guusdk merged commit f5b9964 into igniterealtime:main Jul 3, 2026
80 of 81 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.

3 participants