Skip to content

Downcase consortium ID when determining access to individual DOI records for cases where doi.provider.consortium_id is downcased#1498

Open
codycooperross wants to merge 1 commit intomasterfrom
consortium-admin-bug
Open

Downcase consortium ID when determining access to individual DOI records for cases where doi.provider.consortium_id is downcased#1498
codycooperross wants to merge 1 commit intomasterfrom
consortium-admin-bug

Conversation

@codycooperross
Copy link
Contributor

@codycooperross codycooperross commented Mar 18, 2026

Purpose

See issue below. This PR attempts to resolve a bug wherein consortium accounts cannot access draft/registered DOI pages in Fabrica due to a casing mismatch in consortium symbols.

Possibly related to the following issue, which stemmed from a Rails upgrade some time ago:

closes: datacite/datacite#2506

Approach

Open Questions and Pre-Merge TODOs

Learning

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

  • New feature (non-breaking change which adds functionality)

  • Breaking change (fix or feature that would cause existing functionality to change)

Reviewer, please remember our guidelines:

  • Be humble in the language and feedback you give, ask don't tell.
  • Consider using positive language as opposed to neutral when offering feedback. This is to avoid the negative bias that can occur with neutral language appearing negative.
  • Offer suggestions on how to improve code e.g. simplification or expanding clarity.
  • Ensure you give reasons for the changes you are proposing.

Summary by CodeRabbit

Bug Fixes

  • Fixed case sensitivity handling in provider authentication validation for consortium administrators, ensuring consistent authentication behavior regardless of letter casing.

@codycooperross codycooperross requested a review from a team March 18, 2026 18:05
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

A case-sensitivity fix in the not_allowed_by_doi_and_user method changes consortium admin user authentication logic to perform case-insensitive provider ID comparison by downcasing both sides instead of upcasing, addressing access issues with draft records.

Changes

Cohort / File(s) Summary
Authentication Case-Sensitivity Fix
app/models/concerns/authenticable.rb
Modified provider ID comparison logic in consortium admin branch to use downcase instead of upcase for case-insensitive matching between user provider ID and DOI consortium ID.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: downcasing consortium ID for access determination when the stored DOI consortium ID is already downcased, directly addressing the bug fix objective.
Linked Issues check ✅ Passed The PR change directly resolves the casing mismatch reported in issue #2506 by downcasing both provider_id and consortium_id for comparison, enabling consortium accounts to reliably access draft/registered DOI records regardless of stored casing.
Out of Scope Changes check ✅ Passed The change is narrowly scoped to the specific casing comparison issue in the authenticable concern; no unrelated modifications to other functionality, files, or concerns are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch consortium-admin-bug
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

Copy link

@coderabbitai coderabbitai bot left a comment

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)
app/models/concerns/authenticable.rb (1)

228-231: Consider applying the same case normalization for provider_admin comparison.

For consistency, the provider_admin / provider_user comparison at line 230 also compares user.provider_id directly with doi.provider_id without case normalization. If similar casing mismatches exist for non-consortium providers, this could cause the same access issues.

If the underlying data is expected to be consistent (unlike the consortium case), this can be deferred. Otherwise, consider normalizing:

user.provider_id.downcase == doi.provider_id.downcase
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/concerns/authenticable.rb` around lines 228 - 231, The comparison
that returns false for users with roles provider_admin/provider_user currently
checks user.provider_id == doi.provider_id without normalizing case, so update
the conditional in authenticable.rb (the if that references user.role_id,
provider_admin/provider_user, user.provider_id and doi.provider_id) to compare
normalized values instead (e.g., compare user.provider_id.to_s.downcase with
doi.provider_id.to_s.downcase) to avoid nil errors and casing mismatches; keep
the rest of the condition logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/models/concerns/authenticable.rb`:
- Around line 223-227: The conditional that checks consortium access calls
doi.provider.consortium_id.downcase without nil guards, which can raise
NoMethodError; update the condition in authenticable.rb to first ensure
doi.provider and doi.provider.consortium_id are present (or use
Doi.consortium_id helper) before calling downcase, e.g. add presence checks
around doi.provider and consortium_id in the existing if that returns false, and
mirror the nil-safe pattern used in Doi.consortium_id and in
app/models/ability.rb (line ~58) to prevent calling .downcase on nil.

---

Nitpick comments:
In `@app/models/concerns/authenticable.rb`:
- Around line 228-231: The comparison that returns false for users with roles
provider_admin/provider_user currently checks user.provider_id ==
doi.provider_id without normalizing case, so update the conditional in
authenticable.rb (the if that references user.role_id,
provider_admin/provider_user, user.provider_id and doi.provider_id) to compare
normalized values instead (e.g., compare user.provider_id.to_s.downcase with
doi.provider_id.to_s.downcase) to avoid nil errors and casing mismatches; keep
the rest of the condition logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83327960-eef2-48e2-a609-e527735db7d2

📥 Commits

Reviewing files that changed from the base of the PR and between 9969471 and 07097d2.

📒 Files selected for processing (1)
  • app/models/concerns/authenticable.rb

Comment on lines 223 to 227
if %w[consortium_admin].include?(user.role_id) &&
user.provider_id.present? &&
user.provider_id.upcase == doi.provider.consortium_id
user.provider_id.downcase == doi.provider.consortium_id.downcase
return false
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for existing nil safety patterns around consortium_id access

# Check how consortium_id is typically accessed
rg -n -C2 'consortium_id' --type ruby -g '!spec/**'

# Check for safe navigation patterns with provider
rg -n '\.provider&\.' --type ruby

Repository: datacite/lupo

Length of output: 29447


🏁 Script executed:

# Find the not_allowed_by_doi_and_user method
rg -n 'def not_allowed_by_doi_and_user' app/models/concerns/authenticable.rb -A 30

Repository: datacite/lupo

Length of output: 1218


🏁 Script executed:

# Check the Doi model to see if provider association is always present
rg -n 'belongs_to.*provider|has_one.*provider' app/models/doi.rb -A 2 -B 2

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Check if there are guards/early returns before line 225
sed -n '200,227p' app/models/concerns/authenticable.rb

Repository: datacite/lupo

Length of output: 877


🏁 Script executed:

# Test .casecmp behavior with nil in Ruby
python3 << 'EOF'
# casecmp is Ruby's case-insensitive comparison method
# Let's verify the behavior
print("Testing .casecmp() with nil:")
print("In Ruby, calling nil.casecmp(x) raises NoMethodError")
print("In Ruby, calling 'string'.casecmp(nil) raises TypeError")
EOF

Repository: datacite/lupo

Length of output: 194


🏁 Script executed:

# Check Doi model associations and validations
rg -n 'belongs_to.*provider|validates.*provider' app/models/doi.rb -A 2 -B 1 | head -30

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Check if doi.provider is ever nil in the codebase by searching for safe navigation usage
rg -n 'doi\.provider&\.' --type ruby

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Check the Provider model to confirm consortium_id can be nil
rg -n 'consortium_id.present\?' app/models/provider.rb

Repository: datacite/lupo

Length of output: 144


🏁 Script executed:

# Find and examine the Doi model associations
rg -n 'class Doi' app/models/doi.rb -A 50 | head -80

Repository: datacite/lupo

Length of output: 1762


🏁 Script executed:

# Check for foreign key constraints and associations
rg -n 'belongs_to.*provider|has_many.*doi|foreign_key.*provider' app/models/doi.rb app/models/provider.rb

Repository: datacite/lupo

Length of output: 2863


🏁 Script executed:

# Search for test cases showing the behavior when provider is nil
rg -n 'provider.*nil\|nil.*provider' spec/ -A 2 -B 2 | head -40

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Find the belongs_to :client association in Doi model
rg -n 'belongs_to.*client' app/models/doi.rb -A 2

Repository: datacite/lupo

Length of output: 394


🏁 Script executed:

# Check Client model for belongs_to :provider
rg -n 'belongs_to.*provider' app/models/client.rb -A 2

Repository: datacite/lupo

Length of output: 218


🏁 Script executed:

# Check if provider is always present in the DOI context
rg -n 'doi\.provider' spec/ --type ruby | head -20

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Verify the behavior by looking at actual code accessing doi.provider
rg -n 'doi\.provider\.' app/models/ --type ruby -A 1 | head -40

Repository: datacite/lupo

Length of output: 495


🏁 Script executed:

# Check if Doi has a direct provider_id column
rg -n 'provider_id' app/models/doi.rb | grep -E 'belongs_to|has_one|t\.' | head -10

Repository: datacite/lupo

Length of output: 125


🏁 Script executed:

# Look at the Doi schema to see if it has provider_id column
rg -n 'provider_id' db/schema.rb | grep -A 2 -B 2 'dois'

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Check the actual usage context - is line 225 guaranteed to have provider?
rg -n 'not_allowed_by_doi_and_user' app/ --type ruby -B 5 | head -50

Repository: datacite/lupo

Length of output: 1908


🏁 Script executed:

# Check ability.rb context to see if there are guards
sed -n '28,68p' app/models/ability.rb

Repository: datacite/lupo

Length of output: 1777


🏁 Script executed:

# Check if the query that fetches DOIs at the call site guarantees a provider
sed -n '435,450p' app/controllers/datacite_dois_controller.rb

Repository: datacite/lupo

Length of output: 587


🏁 Script executed:

# Check if there are any tests that test this method with nil provider scenarios
rg -n 'not_allowed_by_doi_and_user' spec/ --type ruby -B 3 -A 5 | head -60

Repository: datacite/lupo

Length of output: 3831


🏁 Script executed:

# Check the Doi.consortium_id method implementation
sed -n '1895,1905p' app/models/doi.rb

Repository: datacite/lupo

Length of output: 387


🏁 Script executed:

# Check if DOI has provider_id column directly
cat -n db/schema.rb | grep -A 200 'create_table "dois"' | grep -E 'provider_id|^[0-9]+-\s*end'

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Check the Doi model's direct columns vs associations
sed -n '80,140p' app/models/doi.rb

Repository: datacite/lupo

Length of output: 4484


🏁 Script executed:

# Check if there's a guard before calling not_allowed_by_doi_and_user with a client
rg -n 'client.*nil\|client.present' app/models/concerns/authenticable.rb app/controllers/datacite_dois_controller.rb

Repository: datacite/lupo

Length of output: 39


🏁 Script executed:

# Verify the fix approach by checking how Doi.consortium_id method handles this
sed -n '1895,1905p' app/models/doi.rb

Repository: datacite/lupo

Length of output: 387


Add nil safety check for doi.provider and consortium_id to prevent NoMethodError.

If the DOI's client is nil (optional association), doi.provider will be nil. Additionally, consortium_id can be nil even when a provider exists. Calling .downcase on nil will raise NoMethodError, causing an exception instead of gracefully denying access.

This same pattern appears in app/models/ability.rb:58 without nil guards, which is inconsistent with the guards properly used in lines 46–47 and 65–66 of the same file. The Doi.consortium_id method (lines 1899–1901) demonstrates the correct approach with explicit nil checks.

🛡️ Proposed fix
     if %w[consortium_admin].include?(user.role_id) &&
         user.provider_id.present? &&
+        doi.provider&.consortium_id&.present? &&
         user.provider_id.downcase == doi.provider.consortium_id.downcase
       return false
     end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/concerns/authenticable.rb` around lines 223 - 227, The conditional
that checks consortium access calls doi.provider.consortium_id.downcase without
nil guards, which can raise NoMethodError; update the condition in
authenticable.rb to first ensure doi.provider and doi.provider.consortium_id are
present (or use Doi.consortium_id helper) before calling downcase, e.g. add
presence checks around doi.provider and consortium_id in the existing if that
returns false, and mirror the nil-safe pattern used in Doi.consortium_id and in
app/models/ability.rb (line ~58) to prevent calling .downcase on nil.

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.

When logged in as a consortium, draft state records may not be accessible in Fabrica

1 participant