Extract OidcScopes::SUPPORTED as the shared scope list

The supported-scope list lived on OidcController, so the device authorization
controller (and a test) had to reach across into OidcController::SUPPORTED_SCOPES.
Move it to a shared OidcScopes::SUPPORTED module — one source of truth for the
OIDC controller, the device flow, and consent handling — and update every
reference. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Q4ATZHoMCWqvSpE2yYoie
This commit is contained in:
Dan Milne
2026-07-19 14:42:39 +10:00
co-authored by Claude Opus 4.8
parent f65f8da2e7
commit 6c4be7199c
4 changed files with 13 additions and 7 deletions
@@ -80,7 +80,7 @@ class DeviceAuthorizationsController < ApplicationController
end end
def granted_scopes(device_code) def granted_scopes(device_code)
device_code.scope.to_s.split & OidcController::SUPPORTED_SCOPES device_code.scope.to_s.split & OidcScopes::SUPPORTED
end end
def record_consent(device_code, user) def record_consent(device_code, user)
+4 -5
View File
@@ -1,5 +1,4 @@
class OidcController < ApplicationController class OidcController < ApplicationController
SUPPORTED_SCOPES = %w[openid profile email groups offline_access].freeze
# Grant types this authorization server supports. Single source of truth: # Grant types this authorization server supports. Single source of truth:
# advertised in discovery (grant_types_supported), accepted at dynamic client # advertised in discovery (grant_types_supported), accepted at dynamic client
@@ -69,7 +68,7 @@ class OidcController < ApplicationController
grant_types_supported: SUPPORTED_GRANT_TYPES, grant_types_supported: SUPPORTED_GRANT_TYPES,
subject_types_supported: ["pairwise"], subject_types_supported: ["pairwise"],
id_token_signing_alg_values_supported: ["RS256"], id_token_signing_alg_values_supported: ["RS256"],
scopes_supported: SUPPORTED_SCOPES, scopes_supported: OidcScopes::SUPPORTED,
token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"], token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"],
claims_supported: [ claims_supported: [
"sub", # Always included "sub", # Always included
@@ -127,7 +126,7 @@ class OidcController < ApplicationController
end end
# Only accept scopes we support (mirrors the authorize endpoint). # Only accept scopes we support (mirrors the authorize endpoint).
requested_scope = (params[:scope].to_s.split & SUPPORTED_SCOPES).join(" ") requested_scope = (params[:scope].to_s.split & OidcScopes::SUPPORTED).join(" ")
requested_scope = "openid" if requested_scope.blank? requested_scope = "openid" if requested_scope.blank?
# PKCE is optional but recommended for device flow (RFC 8628 §5.5). If the # PKCE is optional but recommended for device flow (RFC 8628 §5.5). If the
@@ -240,7 +239,7 @@ class OidcController < ApplicationController
# Normalize requested scopes to the set we support. Needed here so claims # Normalize requested scopes to the set we support. Needed here so claims
# validation below can check claim→scope coverage against what will actually # validation below can check claim→scope coverage against what will actually
# be granted. # be granted.
requested_scopes = scope.split(" ") & SUPPORTED_SCOPES requested_scopes = scope.split(" ") & OidcScopes::SUPPORTED
scope = requested_scopes.join(" ") scope = requested_scopes.join(" ")
# Parse claims parameter (JSON string) for OIDC claims request # Parse claims parameter (JSON string) for OIDC claims request
@@ -490,7 +489,7 @@ class OidcController < ApplicationController
user = Current.session.user user = Current.session.user
requested_scopes = oauth_params["scope"].split(" ") & SUPPORTED_SCOPES requested_scopes = oauth_params["scope"].split(" ") & OidcScopes::SUPPORTED
parsed_claims = begin parsed_claims = begin
JSON.parse(oauth_params["claims_requests"]) JSON.parse(oauth_params["claims_requests"])
rescue rescue
+7
View File
@@ -0,0 +1,7 @@
# Single source of truth for the OAuth/OIDC scopes this IdP supports. Shared by
# the OIDC controller (discovery + authorize/consent), the device authorization
# flow, and consent handling, so no controller has to reach into another for the
# list.
module OidcScopes
SUPPORTED = %w[openid profile email groups offline_access].freeze
end
+1 -1
View File
@@ -12,7 +12,7 @@ class OidcHelperTest < ActionView::TestCase
test "scope_description covers every SUPPORTED_SCOPE (so the consent screens can't silently drop one)" do test "scope_description covers every SUPPORTED_SCOPE (so the consent screens can't silently drop one)" do
user = User.new(email_address: "person@example.com") user = User.new(email_address: "person@example.com")
OidcController::SUPPORTED_SCOPES.each do |scope| OidcScopes::SUPPORTED.each do |scope|
assert_not_equal scope, scope_description(scope, user: user), assert_not_equal scope, scope_description(scope, user: user),
"#{scope} has no description and would render as its raw name" "#{scope} has no description and would render as its raw name"
end end