Skip to content

API key persistence setting should be per fetcher - #16616

Open
kerollosy wants to merge 11 commits into
JabRef:mainfrom
kerollosy:per-fetcher-api-16609
Open

API key persistence setting should be per fetcher#16616
kerollosy wants to merge 11 commits into
JabRef:mainfrom
kerollosy:per-fetcher-api-16609

Conversation

@kerollosy

Copy link
Copy Markdown
Contributor

Summary

Changed the API key persistence mechanism from a global flag to a per‑fetcher setting. Each fetcher now stores its own “save for future use” preference, which is read and written alongside the API key in the preferences system and the system keyring. This resolves the issue where the last edited fetcher’s persistence choice was applied to all fetchers and ensures that enabling/disabling persistence for one fetcher does not affect others.

jabref-contrib-policy:4.2:reviewed​:ok

Steps to test

  1. Open JabRef → File → Preferences → Web Search.
  2. Select a fetcher that requires an API key (e.g., Springer) and click the "Configure API key" button.
  3. In the API Key dialog, check “Save API key to use in future?”, enter a test key, and press OK.
  4. Repeat for a second fetcher, but leave the checkbox unchecked.
  5. Close and reopen JabRef.
  6. Verify that the first fetcher still shows the key as saved (persisted) while the second fetcher does not retain the key after a restart.

Related issues and pull requests

Closes #16609, related to #16519, #16250

AI usage

Claude Code (model claude-opus-5) was used to help navigate through the files

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • If AI tools were used, I disclosed them in the "AI usage" section and reviewed, understood, and take full ownership of all AI-generated code
  • I manually tested my changes in running JabRef (always required)
  • [/] I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • [/] I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • [/] I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Make API key persistence a per-fetcher preference

🐞 Bug fix 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Store “Save API key for future use” per fetcher instead of a global flag.
• Persist each fetcher’s API key + persistence choice via preferences and system keyring.
• Document the fix in the changelog for #16609.
Diagram

graph TD
  A["ApiKeyDialog"] --> B["FetcherViewModel"] --> C["WebSearchTabViewModel"] --> D["ImporterPreferences"] --> E["JabRefCliPreferences"]
  E --> F[("Preferences store")]
  E --> G[("System keyring")]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Separate persist map (don’t extend FetcherApiKey)
  • ➕ Avoids expanding the FetcherApiKey model and any downstream serialization impact
  • ➕ Could keep keyring behavior isolated from the key representation
  • ➖ Adds another parallel preference structure to keep in sync with the API key set
  • ➖ Harder to reason about correctness since key + persist are split across collections
2. Derive persistence from keyring presence
  • ➕ Eliminates the need to store an explicit per-fetcher persist flag
  • ➕ Naturally matches the security model: persisted iff present in keyring
  • ➖ Can’t represent “user chose not to persist” separately from “keyring unavailable/failed”
  • ➖ May make UI state ambiguous without an explicit persisted preference
3. Add explicit migration for old global setting
  • ➕ Preserves existing users’ stored keys and prior persistence behavior across upgrades
  • ➕ Reduces surprise if older installations had preferences written in the previous format
  • ➖ Slightly more code and test surface area (migration + backward-compat handling)
  • ➖ Must define a clear mapping from the old global flag to per-fetcher values

Recommendation: The PR’s approach (persist flag stored per FetcherApiKey) is straightforward and keeps the setting colocated with the key it controls. Consider adding a small migration/backward-compat path for existing preference data (previous global persist value and/or missing persist list) to avoid dropping previously configured fetcher keys on upgrade.

Files changed (6) +69 / -44

Enhancement (1) +19 / -1
FetcherApiKey.javaAdd per-fetcher persist flag to FetcherApiKey +19/-1

Add per-fetcher persist flag to FetcherApiKey

• Extends FetcherApiKey with a persist BooleanProperty, adds accessors, and introduces an overloaded constructor preserving the old signature with a default persist=false.

jablib/src/main/java/org/jabref/logic/preferences/FetcherApiKey.java

Bug fix (3) +48 / -26
ApiKeyDialog.javaBind “save for future use” checkbox to the selected fetcher +1/-1

Bind “save for future use” checkbox to the selected fetcher

• Switches the persistence checkbox binding from a tab-global property to the selected FetcherViewModel’s per-fetcher persist property, while still disabling the checkbox when keyring persistence is unavailable.

jabgui/src/main/java/org/jabref/gui/preferences/websearch/ApiKeyDialog.java

WebSearchTabViewModel.javaTrack and persist API key persistence per fetcher +16/-9

Track and persist API key persistence per fetcher

• Removes the global apikeyPersistProperty and introduces a per-fetcher persistApiKey property on FetcherViewModel. Loads and stores the per-fetcher persist flag via FetcherApiKey when reading from and writing to ImporterPreferences.

jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java

JabRefCliPreferences.javaPersist per-fetcher persistence choice and keyring writes +31/-16

Persist per-fetcher persistence choice and keyring writes

• Changes fetcher API key serialization to store a per-fetcher persist list alongside names/uses and to read it back into FetcherApiKey. Updates keyring behavior to clear non-persisted keys and write only persisted keys, and removes the old binding to a global persist flag.

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java

Refactor (1) +1 / -17
ImporterPreferences.javaRemove global custom-key persistence flag from importer preferences +1/-17

Remove global custom-key persistence flag from importer preferences

• Drops the persistCustomKeys property and constructor parameter, shifting persistence responsibility to the per-fetcher FetcherApiKey entries. Updates default FetcherApiKey initialization to include the new persist flag.

jablib/src/main/java/org/jabref/logic/importer/ImporterPreferences.java

Documentation (1) +1 / -0
CHANGELOG.mdDocument fix for per-fetcher API key persistence +1/-0

Document fix for per-fetcher API key persistence

• Adds a changelog entry describing the bug where the last edited fetcher’s persistence choice applied globally, and references #16609.

CHANGELOG.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Persist pref migration broken ✓ Resolved 🐞 Bug ≡ Correctness
Description
FETCHER_CUSTOM_KEY_PERSIST is now treated as a string list aligned with fetcher names/uses, so
legacy installs that previously stored it as a single boolean value can fail the size alignment
check and fall back to defaults, effectively dropping saved fetcher keys/settings. A subsequent
preferences save can then overwrite preference lists and clear keyring slots based on the
now-default data, leading to loss of previously persisted secrets.
Code

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[R2408-2410]

+        if (!hasKey(FETCHER_CUSTOM_KEY_NAMES) || !hasKey(FETCHER_CUSTOM_KEY_USES) || !hasKey(FETCHER_CUSTOM_KEY_PERSIST)) {
         return defaults;
     }
Evidence
getFetcherKeys requires a per-fetcher persist list and rejects any size mismatch by returning
defaults, while FETCHER_CUSTOM_KEY_PERSIST is stored/read via string-list helpers even though
booleans are stored via putBoolean. Because ImporterPreferences.apiKeys is bound to
storeFetcherKeys, later writes will persist the new list format and clear keyring slots for
non-persisted entries, so failing to migrate legacy data can lead to loss of previously persisted
secrets.

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2407-2432]
jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2446-2476]
jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[593-634]
jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2339-2358]
jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java[231-266]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`fetcherCustomKeyPersist` is now persisted as a *list* of booleans (one per fetcher), but existing installations may already have this key persisted as a *single boolean* (from the previous global setting). The current loader requires `names.size() == persists.size()` and otherwise returns defaults, which can hide previously saved keys and later cause `storeFetcherKeys` to overwrite/clear keyring entries.
### Issue Context
- `getFetcherKeys` currently requires `FETCHER_CUSTOM_KEY_PERSIST` to exist and be a string-list aligned with `FETCHER_CUSTOM_KEY_NAMES`.
- `getStringList` reads the raw preference value as a string and splits it into a list; legacy boolean storage will not produce an aligned list.
- API key changes are persisted via `bindSet(... storeFetcherKeys ...)`, so once the set is mutated (e.g., when saving preferences), the new format is written back and keyring slots may be cleared.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2407-2432]
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2446-2476]
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[593-634]
### What to implement
- Add a backward-compatible migration path in `getFetcherKeys`:
- If `FETCHER_CUSTOM_KEY_PERSIST` exists but parses to a single-element list (e.g., `["true"]` / `["false"]`) while `names.size() > 1`, treat it as the legacy global flag and expand it to `names.size()` elements.
- Alternatively (or additionally), if parsing indicates a legacy scalar, read it via `getBoolean(FETCHER_CUSTOM_KEY_PERSIST, false)` and expand.
- After successful migration, write back the expanded list using `putStringList(FETCHER_CUSTOM_KEY_PERSIST, expanded)` to normalize the store.
- Also consider relaxing the initial `hasKey(FETCHER_CUSTOM_KEY_PERSIST)` gate: if it’s missing, default `persists` to all-false (or migrate from any legacy/global location if applicable) rather than returning defaults and losing custom keys.
- Keep the size-mismatch guard for truly corrupt data, but avoid dropping all keys when a deterministic migration is possible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. FetcherApiKey constructor has booleans 📘 Rule violation ⚙ Maintainability
Description
A new public FetcherApiKey constructor adds another boolean parameter (persist), creating an
error-prone signature with multiple boolean flags. This reduces call-site clarity and violates the
guideline to avoid boolean parameters in public APIs.
Code

jablib/src/main/java/org/jabref/logic/preferences/FetcherApiKey.java[R14-18]

+    public FetcherApiKey(String name, boolean use, String key, boolean persist) {
     this.name = new SimpleStringProperty(name);
     this.use = new SimpleBooleanProperty(use);
     this.key = new SimpleStringProperty(key);
+        this.persist = new SimpleBooleanProperty(persist);
Evidence
PR Compliance ID 12 prohibits introducing boolean parameters in public methods. The PR adds a new
public constructor FetcherApiKey(String name, boolean use, String key, boolean persist) which adds
an additional boolean flag (persist) to the public API, increasing ambiguity at call sites.

AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead: AGENTS.md: Avoid boolean parameters in public methods; use separate methods instead
jablib/src/main/java/org/jabref/logic/preferences/FetcherApiKey.java[14-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FetcherApiKey(String name, boolean use, String key, boolean persist)` introduces multiple boolean parameters in a public API, making call sites ambiguous and easy to misuse.
## Issue Context
Compliance requires avoiding boolean parameters in public methods. Prefer an explicit type (e.g., enum/value object) or named factory methods to make intent clear at the call site.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/preferences/FetcherApiKey.java[14-23]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Trivial comments in storeFetcherKeys ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
New comments in storeFetcherKeys/clearCustomFetcherKeys restate what the immediately following
code already makes obvious. This adds noise without documenting rationale.
Code

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[R2468-2471]

+        // Clear keys that should not be persisted
+        if (!keysToClear.isEmpty()) {
+            writeKeyring(keysToClear);
+        }
Evidence
PR Compliance ID 6 disallows adding comments that merely paraphrase the code. The added comments
directly restate the subsequent writeKeyring(...) and remove(...) calls without providing
additional rationale.

AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code: AGENTS.md: Avoid trivial comments; comments should explain 'why', not restate code
jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2468-2475]
jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2487-2490]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Comments were added that restate the code ("Clear keys that should not be persisted", "Write keys that should be persisted", "Remove the preference keys") rather than explaining intent/why.
## Issue Context
Compliance requires comments to explain rationale and avoid trivial restatements.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2468-2475]
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2487-2490]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Cancel mutates persist flag ✓ Resolved 🐞 Bug ≡ Correctness
Description
The API key dialog binds the “Save API key” checkbox bidirectionally to the fetcher view model, so
toggling it mutates the underlying per-fetcher persist flag even if the user presses Cancel. This
breaks expected Cancel semantics and can unintentionally change whether that fetcher’s key gets
persisted/cleared when preferences are later saved.
Code

jabgui/src/main/java/org/jabref/gui/preferences/websearch/ApiKeyDialog.java[R66-67]

+        persistApiKeysCheckBox.selectedProperty().bindBidirectional(fetcherViewModel.persistApiKeyProperty());
     persistApiKeysCheckBox.disableProperty().bind(viewModel.apiKeyPersistAvailable().not());
Evidence
The checkbox is bidirectionally bound to the fetcher model (so it mutates immediately), while
convertResult only applies API key changes on OK; WebSearchTab does not handle dialog results to
undo changes, so Cancel leaves the persist mutation in place.

jabgui/src/main/java/org/jabref/gui/preferences/websearch/ApiKeyDialog.java[48-109]
jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java[213-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`persistApiKeysCheckBox` is bound bidirectionally to `fetcherViewModel.persistApiKeyProperty()`, so the model updates immediately as the user clicks the checkbox. Unlike the API key text (which is applied only on OK in `convertResult`), the persist flag is not rolled back on Cancel.
### Issue Context
This dialog is shown via `showCustomDialogAndWait(...)` and the result is not post-processed, so Cancel must be “transactional” inside the dialog if you want it to discard changes.
### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/preferences/websearch/ApiKeyDialog.java[48-109]
- jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java[213-215]
### What to implement
- Replace the bidirectional binding with a dialog-local property:
- Initialize a local `BooleanProperty persistLocal` with `fetcherViewModel.shouldPersistApiKey()`.
- Bind the checkbox to `persistLocal` (bidirectional).
- In `convertResult`, when `button == ButtonType.OK`, copy `persistLocal.get()` into `fetcherViewModel.setPersistApiKey(...)` (and keep the existing API key commits).
- On Cancel, do nothing (the model remains unchanged).
- Alternatively, store the original persist value on dialog open and explicitly restore it in the non-OK path.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +2468 to +2471
// Clear keys that should not be persisted
if (!keysToClear.isEmpty()) {
writeKeyring(keysToClear);
}

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.

Remediation recommended

2. Trivial comments in storefetcherkeys 📘 Rule violation ⚙ Maintainability

New comments in storeFetcherKeys/clearCustomFetcherKeys restate what the immediately following
code already makes obvious. This adds noise without documenting rationale.
Agent Prompt
## Issue description
Comments were added that restate the code ("Clear keys that should not be persisted", "Write keys that should be persisted", "Remove the preference keys") rather than explaining intent/why.

## Issue Context
Compliance requires comments to explain rationale and avoid trivial restatements.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2468-2475]
- jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java[2487-2490]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java Outdated
Comment thread jabgui/src/main/java/org/jabref/gui/preferences/websearch/ApiKeyDialog.java Outdated
@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Aug 18, 2026
@github-actions github-actions Bot added status: no-bot-comments status: changes-required Pull requests that are not yet complete and removed status: changes-required Pull requests that are not yet complete status: no-bot-comments labels Aug 18, 2026
Remove useless comment
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API key persistence setting is global, not per fetcher

1 participant