From 444ae6291c758d328b6563fa083da58b6a4945df Mon Sep 17 00:00:00 2001 From: Dan Milne Date: Mon, 5 Jan 2026 23:34:11 +1100 Subject: [PATCH] Add missing files, fix formatting --- app/controllers/oidc_controller.rb | 16 ++++--- app/lib/duration_parser.rb | 45 +++++++++++++++++++ config/environments/production.rb | 2 +- test/controllers/oidc_claims_security_test.rb | 2 +- test/controllers/oidc_prompt_login_test.rb | 4 +- 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 app/lib/duration_parser.rb diff --git a/app/controllers/oidc_controller.rb b/app/controllers/oidc_controller.rb index 3e461a5..cc18974 100644 --- a/app/controllers/oidc_controller.rb +++ b/app/controllers/oidc_controller.rb @@ -422,7 +422,11 @@ class OidcController < ApplicationController # Record user consent requested_scopes = oauth_params["scope"].split(" ") - parsed_claims = JSON.parse(oauth_params["claims_requests"]) rescue {} + parsed_claims = begin + JSON.parse(oauth_params["claims_requests"]) + rescue + {} + end consent = OidcUserConsent.find_or_initialize_by(user: user, application: application) consent.scopes_granted = requested_scopes.join(" ") @@ -780,10 +784,10 @@ class OidcController < ApplicationController # Extract access token from Authorization header or POST body # RFC 6750: Bearer token can be in Authorization header, request body, or query string token = if request.headers["Authorization"]&.start_with?("Bearer ") - request.headers["Authorization"].sub("Bearer ", "") - elsif request.params["access_token"].present? - request.params["access_token"] - end + request.headers["Authorization"].sub("Bearer ", "") + elsif request.params["access_token"].present? + request.params["access_token"] + end unless token head :unauthorized @@ -1026,7 +1030,7 @@ class OidcController < ApplicationController end # Validate code verifier format (per RFC 7636: [A-Za-z0-9\-._~], 43-128 characters) - unless code_verifier.match?(/\A[A-Za-z0-9\.\-_~]{43,128}\z/) + unless code_verifier.match?(/\A[A-Za-z0-9.\-_~]{43,128}\z/) return { valid: false, error: "invalid_request", diff --git a/app/lib/duration_parser.rb b/app/lib/duration_parser.rb new file mode 100644 index 0000000..966faf8 --- /dev/null +++ b/app/lib/duration_parser.rb @@ -0,0 +1,45 @@ +class DurationParser + UNITS = { + "s" => 1, # seconds + "m" => 60, # minutes + "h" => 3600, # hours + "d" => 86400, # days + "w" => 604800, # weeks + "M" => 2592000, # months (30 days) + "y" => 31536000 # years (365 days) + } + + # Parse a duration string into seconds + # Accepts formats: "1h", "30m", "1d", "1M" (month), "3600" (plain number) + # Returns integer seconds or nil if invalid + # Case-sensitive: 1s, 1m, 1h, 1d, 1w, 1M (month), 1y + def self.parse(input) + # Handle integers directly + return input if input.is_a?(Integer) + + # Convert to string and strip whitespace + str = input.to_s.strip + + # Return nil for blank input + return nil if str.blank? + + # Try to parse as plain number (already in seconds) + if str.match?(/^\d+$/) + return str.to_i + end + + # Try to parse with unit (e.g., "1h", "30m", "1M") + # Allow optional space between number and unit + # Case-sensitive to avoid confusion (1m = minute, 1M = month) + match = str.match(/^(\d+)\s*([smhdwMy])$/) + return nil unless match + + number = match[1].to_i + unit = match[2] + + multiplier = UNITS[unit] + return nil unless multiplier + + number * multiplier + end +end diff --git a/config/environments/production.rb b/config/environments/production.rb index 623c55f..6faba40 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -59,7 +59,7 @@ Rails.application.configure do # Use Solid Queue for background jobs config.active_job.queue_adapter = :solid_queue - config.solid_queue.connects_to = { database: { writing: :queue } } + config.solid_queue.connects_to = {database: {writing: :queue}} # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. diff --git a/test/controllers/oidc_claims_security_test.rb b/test/controllers/oidc_claims_security_test.rb index 20d9965..16129f5 100644 --- a/test/controllers/oidc_claims_security_test.rb +++ b/test/controllers/oidc_claims_security_test.rb @@ -271,7 +271,7 @@ class OidcClaimsSecurityTest < ActionDispatch::IntegrationTest '{"id_token":{"email":{"essential":true}}}', '{"userinfo":{"groups":{"values":["admin"]}}}', '{"id_token":{"custom_claim":"custom_value"}}', - 'invalid-json' + "invalid-json" ] malicious_claims.each do |claims_value| diff --git a/test/controllers/oidc_prompt_login_test.rb b/test/controllers/oidc_prompt_login_test.rb index 58ad427..11b3ab6 100644 --- a/test/controllers/oidc_prompt_login_test.rb +++ b/test/controllers/oidc_prompt_login_test.rb @@ -76,7 +76,7 @@ class OidcPromptLoginTest < ActionDispatch::IntegrationTest # Should redirect to sign in because session is too old assert_response :redirect - assert_redirected_to /signin/ + assert_redirected_to(/signin/) # Sign in again post "/signin", params: { @@ -194,7 +194,7 @@ class OidcPromptLoginTest < ActionDispatch::IntegrationTest # Should redirect to sign in assert_response :redirect - assert_redirected_to /signin/ + assert_redirected_to(/signin/) # Sign in again (simulating user re-authentication) post "/signin", params: {