Module: OmniauthOpenidFederation::TasksHelper
- Defined in:
- lib/omniauth_openid_federation/tasks_helper.rb
Class Method Summary collapse
-
.detect_key_status(jwks) ⇒ Hash
Detect key configuration (single vs separate keys).
-
.fetch_entity_statement(url:, output_file:, fingerprint: nil) ⇒ Hash
Fetch entity statement and save to file.
-
.fetch_jwks(jwks_uri:, output_file:) ⇒ Hash
Fetch JWKS and save to file.
-
.parse_entity_statement(file_path:) ⇒ Hash
Parse entity statement and return metadata.
-
.prepare_client_keys(key_type:, output_dir:) ⇒ Hash
Generate client keys.
-
.process_callback_and_validate(callback_url:, base_url:, client_id:, redirect_uri:, private_key:, entity_statement_url: nil, entity_statement_path: nil, provider_acr: nil, client_entity_statement_url: nil, client_entity_statement_path: nil) ⇒ Hash
Process callback URL and complete authentication flow.
-
.resolve_path(file_path) ⇒ String
Resolve file path using configuration.
-
.test_authentication_flow(login_page_url:, base_url:, provider_acr: nil) ⇒ Hash
Test full OpenID Federation authentication flow.
-
.test_local_endpoint(base_url:) ⇒ Hash
Test local entity statement endpoint.
-
.validate_entity_statement(file_path:, expected_fingerprint: nil) ⇒ Hash
Validate entity statement file.
Class Method Details
.detect_key_status(jwks) ⇒ Hash
Detect key configuration (single vs separate keys)
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 336 def self.detect_key_status(jwks) return {type: :unknown, count: 0, recommendation: "No keys found in entity statement"} unless jwks keys = jwks.is_a?(Hash) ? (jwks["keys"] || jwks[:keys] || []) : [] return {type: :unknown, count: 0, recommendation: "No keys found in entity statement"} if keys.empty? # Check for duplicate kids (indicates single key used for both signing and encryption) kids = keys.map { |k| k["kid"] || k[:kid] }.compact duplicate_kids = kids.length != kids.uniq.length # Check use fields uses = keys.map { |k| k["use"] || k[:use] }.compact.uniq has_sig = uses.include?("sig") has_enc = uses.include?("enc") has_both_uses = has_sig && has_enc if duplicate_kids { type: :single, count: keys.length, recommendation: "⚠️ Single key detected (duplicate Key IDs). This is NOT RECOMMENDED for production. Use separate signing and encryption keys for better security. Generate with: rake openid_federation:prepare_client_keys[separate]" } elsif has_both_uses && keys.length >= 2 { type: :separate, count: keys.length, recommendation: "✅ Separate keys detected (recommended for production)" } elsif keys.length == 1 { type: :single, count: 1, recommendation: "⚠️ Single key detected. This is NOT RECOMMENDED for production. Use separate signing and encryption keys for better security. Generate with: rake openid_federation:prepare_client_keys[separate]" } else { type: :unknown, count: keys.length, recommendation: "Key configuration unclear. Ensure keys have unique Key IDs and proper 'use' fields ('sig' for signing, 'enc' for encryption)" } end end |
.fetch_entity_statement(url:, output_file:, fingerprint: nil) ⇒ Hash
Fetch entity statement and save to file
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 45 def self.fetch_entity_statement(url:, output_file:, fingerprint: nil) output_path = resolve_path(output_file) entity_statement = Federation::EntityStatement.fetch!( url, fingerprint: fingerprint ) entity_statement.save_to_file(output_path) = entity_statement.parse { success: true, entity_statement: entity_statement, output_path: output_path, fingerprint: entity_statement.fingerprint, metadata: } end |
.fetch_jwks(jwks_uri:, output_file:) ⇒ Hash
Fetch JWKS and save to file
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 106 def self.fetch_jwks(jwks_uri:, output_file:) output_path = resolve_path(output_file) jwks = Jwks::Fetch.run(jwks_uri) File.write(output_path, JSON.pretty_generate(jwks)) { success: true, jwks: jwks, output_path: output_path } end |
.parse_entity_statement(file_path:) ⇒ Hash
Parse entity statement and return metadata
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 126 def self.parse_entity_statement(file_path:) resolved_path = resolve_path(file_path) unless File.exist?(resolved_path) raise ConfigurationError, "Entity statement file not found: #{resolved_path}" end = EntityStatementReader.( entity_statement_path: resolved_path ) unless raise Federation::EntityStatement::ValidationError, "Failed to parse entity statement" end end |
.prepare_client_keys(key_type:, output_dir:) ⇒ Hash
Generate client keys
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 150 def self.prepare_client_keys(key_type:, output_dir:) unless %w[single separate].include?(key_type) raise ArgumentError, "Invalid key_type: #{key_type}. Valid options: 'single' or 'separate'" end output_path = resolve_path(output_dir) # Create output directory if it doesn't exist FileUtils.mkdir_p(output_path) unless File.directory?(output_path) result = if key_type == "single" generate_single_key(output_path) else generate_separate_keys(output_path) end { success: true, output_path: output_path, **result } end |
.process_callback_and_validate(callback_url:, base_url:, client_id:, redirect_uri:, private_key:, entity_statement_url: nil, entity_statement_path: nil, provider_acr: nil, client_entity_statement_url: nil, client_entity_statement_path: nil) ⇒ Hash
Process callback URL and complete authentication flow
This method processes the callback from the provider and validates the authentication:
-
Parses callback URL and extracts authorization code
-
Exchanges authorization code for tokens
-
Decrypts and validates ID token
-
Validates OpenID Federation compliance
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 700 def self.process_callback_and_validate( callback_url:, base_url:, client_id:, redirect_uri:, private_key:, entity_statement_url: nil, entity_statement_path: nil, provider_acr: nil, client_entity_statement_url: nil, client_entity_statement_path: nil ) require "uri" require "cgi" require "json" require "base64" require_relative "strategy" results = { steps_completed: [], errors: [], warnings: [], compliance_checks: {}, token_info: {}, id_token_claims: {} } # Parse callback URL begin # Note: Rake tasks are developer tools, no security validation needed begin uri = URI.parse(callback_url) rescue URI::InvalidURIError => e raise "Invalid callback URL: #{e.}" end params = CGI.parse(uri.query || "") auth_code = params["code"]&.first state = params["state"]&.first error = params["error"]&.first error_description = params["error_description"]&.first if error raise "Authorization error: #{error}#{" - #{error_description}" if error_description}" end unless auth_code raise "No authorization code found in callback URL" end results[:authorization_code] = auth_code results[:state] = state results[:steps_completed] << "parse_callback" rescue => e results[:errors] << "Callback parsing: #{e.}" raise end # Build strategy options from provided parameters begin # Resolve entity statement URL if only path provided resolved_entity_statement_url = entity_statement_url if resolved_entity_statement_url.nil? && entity_statement_path # If only path provided, try to resolve from base_url resolved_entity_statement_url = "#{base_url}/.well-known/openid-federation" end # Resolve client entity statement URL if only path provided resolved_client_entity_statement_url = client_entity_statement_url if resolved_client_entity_statement_url.nil? && client_entity_statement_path resolved_client_entity_statement_url = "#{base_url}/.well-known/openid-federation" end # Build strategy options = { discovery: true, scope: [:openid], response_type: "code", client_auth_method: :jwt_bearer, client_signing_alg: :RS256, always_encrypt_request_object: true, entity_statement_url: resolved_entity_statement_url, entity_statement_path: entity_statement_path, client_entity_statement_url: resolved_client_entity_statement_url, client_entity_statement_path: client_entity_statement_path, client_options: { identifier: client_id, redirect_uri: redirect_uri, private_key: private_key } } # Store client_auth_method before filtering nil values client_auth_method = [:client_auth_method] || :jwt_bearer # Remove nil values = .reject { |_k, v| v.nil? } [:client_options] = [:client_options].reject { |_k, v| v.nil? } strategy = OmniAuth::Strategies::OpenIDFederation.new(nil, ) oidc_client = strategy.client unless oidc_client raise "Failed to initialize OpenID Connect client" end unless oidc_client.private_key raise "Private key not set on OpenID Connect client (required for private_key_jwt)" end results[:steps_completed] << "initialize_strategy" rescue => e results[:errors] << "Strategy initialization: #{e.}" raise end # Exchange authorization code for tokens begin oidc_client. = auth_code oidc_client.redirect_uri = redirect_uri access_token = oidc_client.access_token!(client_auth_method) id_token_raw = access_token.id_token access_token_value = access_token.access_token refresh_token = access_token.refresh_token results[:token_info] = { access_token: access_token_value ? "#{access_token_value[0..30]}..." : nil, refresh_token: refresh_token ? "Present" : "Not provided", id_token_encrypted: id_token_raw ? "#{id_token_raw[0..50]}..." : nil } results[:steps_completed] << "token_exchange" rescue => e results[:errors] << "Token exchange: #{e.}" raise end # Decrypt and validate ID token begin id_token = strategy.send(:decode_id_token, id_token_raw) results[:id_token_claims] = { iss: id_token.iss, sub: id_token.sub, aud: id_token.aud, exp: id_token.exp, iat: id_token.iat, nonce: id_token.nonce, acr: id_token.acr, auth_time: id_token.auth_time, amr: id_token.amr } # Validate required claims required_claims = { iss: id_token.iss, sub: id_token.sub, aud: id_token.aud, exp: id_token.exp, iat: id_token.iat } missing_claims = required_claims.select { |_k, v| v.nil? } if missing_claims.empty? results[:id_token_valid] = true else results[:errors] << "Missing required ID token claims: #{missing_claims.keys.join(", ")}" end results[:steps_completed] << "id_token_validation" rescue => e results[:errors] << "ID token validation: #{e.}" raise end # Validate OpenID Federation compliance results[:compliance_checks] = { "Signed Request Objects" => { status: "✅ MANDATORY", description: "All requests use signed request objects (RFC 9101)", verified: true }, "ID Token Encryption" => { status: "✅ MANDATORY", description: "ID tokens are encrypted (RSA-OAEP + A128CBC-HS256)", verified: id_token_raw.split(".").length == 5 # JWE has 5 parts }, "Client Assertion (private_key_jwt)" => { status: "✅ MANDATORY", description: "Token endpoint uses private_key_jwt authentication", verified: true }, "Entity Statement JWKS" => { status: "✅ MANDATORY", description: "JWKS extracted from entity statement", verified: StringHelpers.present?(entity_statement_path) || StringHelpers.present?(entity_statement_url) }, "Signed JWKS Support" => { status: "✅ MANDATORY", description: "Supports OpenID Federation signed JWKS for key rotation", verified: true } } # Check for client entity statement (optional but recommended) if StringHelpers.present?(client_entity_statement_path) || StringHelpers.present?(client_entity_statement_url) results[:compliance_checks]["Client Entity Statement"] = { status: "✅ RECOMMENDED", description: "Client entity statement for federation-based key management", verified: true } end # Check registration type (automatic if client entity statement is provided) if StringHelpers.present?(client_entity_statement_path) || StringHelpers.present?(client_entity_statement_url) results[:compliance_checks]["Automatic Registration"] = { status: "✅ ENABLED", description: "Automatic client registration using entity statement", verified: true } end results[:all_compliance_verified] = results[:compliance_checks].all? { |_k, v| v[:verified] } results end |
.resolve_path(file_path) ⇒ String
Resolve file path using configuration
24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 24 def self.resolve_path(file_path) return file_path if file_path.start_with?("/") config = Configuration.config if defined?(Rails) && Rails.root Rails.root.join(file_path).to_s elsif config.root_path File.join(config.root_path, file_path) else File.(file_path) end end |
.test_authentication_flow(login_page_url:, base_url:, provider_acr: nil) ⇒ Hash
Test full OpenID Federation authentication flow
This method tests the complete authentication flow with a real provider:
-
Fetches CSRF token and cookies from login page URL
-
Finds authorization form/button in HTML
-
Makes authorization request with signed request object
-
Returns authorization URL for user interaction
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 458 def self.test_authentication_flow( login_page_url:, base_url:, provider_acr: nil ) require "uri" require "cgi" require "json" require "base64" require "http" require "openssl" results = { steps_completed: [], errors: [], warnings: [], csrf_token: nil, cookies: [], authorization_url: nil, instructions: [] } # HTTP client helper for custom requests build_http_client = lambda do |connect_timeout: 10, read_timeout: 10| HTTP.timeout(connect: connect_timeout, read: read_timeout) end # Step 1: Fetch login page for CSRF token and cookies results[:steps_completed] << "fetch_csrf_token" html_body = nil = nil csrf_token = nil = [] begin http_client = build_http_client.call(connect_timeout: 10, read_timeout: 10) login_response = http_client.get(login_page_url) unless login_response.status.success? raise "Failed to fetch login page: #{login_response.status.code} #{login_response.status.reason}" end # Extract cookies = login_response.headers["Set-Cookie"] if = .is_a?(Array) ? : [] .each do || = .to_s # Security: Limit cookie header size to prevent DoS attacks (max 4KB per cookie) next if .length > 4096 # Security: Use non-greedy matching with length limits to prevent ReDoS = .match(/^([^=]{1,256})=([^;]{1,4096})/) << "#{[1]}=#{[2]}" if end end = .join("; ") # Extract CSRF token from HTML html_body = login_response.body.to_s # Security: Limit HTML body size to prevent DoS attacks (max 1MB) if html_body.bytesize > 1_048_576 raise "HTML response too large (#{html_body.bytesize} bytes), possible DoS attack" end # Try meta tag first # Security: Use non-greedy matching and limit capture group to prevent ReDoS = html_body.match(/<meta\s+name=["']csrf-token["']\s+content=["']([^"']{1,256})["']/i) csrf_token = [1] if # Try form input if not found # Security: Use non-greedy matching and limit capture group to prevent ReDoS unless csrf_token csrf_input_match = html_body.match(/<input[^>]*name=["']authenticity_token["'][^>]*value=["']([^"']{1,256})["']/i) csrf_token = csrf_input_match[1] if csrf_input_match end unless csrf_token raise "Failed to extract CSRF token from login page" end results[:csrf_token] = csrf_token results[:cookies] = results[:steps_completed] << "extract_csrf_and_cookies" rescue => e results[:errors] << "Step 1 (CSRF token): #{e.}" raise end # Step 2: Find authorization form/button in HTML results[:steps_completed] << "find_authorization_form" begin # Try to find form with action containing "openid_federation" # Security: Use non-greedy matching and limit capture group to prevent ReDoS form_match = html_body.match(/<form[^>]*action=["']([^"']{0,2048}openid[_-]?federation[^"']{0,2048})["'][^>]*>/i) auth_endpoint = nil if form_match form_action = form_match[1] # Note: Rake tasks are developer tools, no security validation needed begin auth_endpoint = if form_action.start_with?("http://", "https://") URI.parse(form_action).to_s else URI.join(base_url, form_action).to_s end rescue URI::InvalidURIError => e raise "Invalid form action URI: #{e.}" end else # Try to find button/link with href containing "openid_federation" # Security: Use non-greedy matching and limit capture group to prevent ReDoS = html_body.match(/<a[^>]*href=["']([^"']{0,2048}openid[_-]?federation[^"']{0,2048})["'][^>]*>/i) if = [1] # Note: Rake tasks are developer tools, no security validation needed begin auth_endpoint = if .start_with?("http://", "https://") URI.parse().to_s else URI.join(base_url, ).to_s end rescue URI::InvalidURIError => e raise "Invalid button href URI: #{e.}" end else # Fallback: try common paths common_paths = [ "/users/auth/openid_federation", "/auth/openid_federation", "/openid_federation" ] auth_endpoint = nil common_paths.each do |path| test_url = URI.join(base_url, path).to_s begin http_client = build_http_client.call(connect_timeout: 5, read_timeout: 5) test_response = http_client.get(test_url) if test_response.status.code >= 300 && test_response.status.code < 400 auth_endpoint = test_url break end rescue # Continue to next path end end auth_endpoint ||= URI.join(base_url, "/users/auth/openid_federation").to_s end end results[:auth_endpoint] = auth_endpoint results[:steps_completed] << "resolve_auth_endpoint" rescue => e results[:errors] << "Step 2 (Find authorization form): #{e.}" raise end # Step 3: Request authorization URL results[:steps_completed] << "request_authorization" begin headers = { "X-CSRF-Token" => csrf_token, "X-Requested-With" => "XMLHttpRequest", "Referer" => login_page_url } headers["Cookie"] = unless .empty? form_data = {} # Include acr_values if provided (must be configured in request_object_params to be included in JWT) form_data[:acr_values] = provider_acr if StringHelpers.present?(provider_acr) http_client = build_http_client.call(connect_timeout: 10, read_timeout: 10) auth_response = http_client .headers(headers) .post(auth_endpoint, form: form_data) = nil if auth_response.status.code >= 300 && auth_response.status.code < 400 location = auth_response.headers["Location"] if location # Security: Validate location header if location.length > 2048 raise "Location header exceeds maximum length" end = if location.start_with?("http://", "https://") # Note: Rake tasks are developer tools, no security validation needed location else URI.join(base_url, location).to_s end end elsif auth_response.status.code == 200 = auth_response.headers["Location"] || auth_response.body.to_s = nil unless &.start_with?("http") end unless raise "Failed to get authorization URL: #{auth_response.status.code} #{auth_response.status.reason}" end results[:authorization_url] = results[:steps_completed] << "authorization_url_received" rescue => e results[:errors] << "Step 3 (Authorization request): #{e.}" raise end # Return results with instructions results[:instructions] = [ "1. Copy the authorization URL and open it in your browser", "2. Complete the authentication with your provider", "3. After authentication, you'll be redirected to a callback URL", "4. Copy the ENTIRE callback URL (including all parameters) and provide it when prompted" ] results end |
.test_local_endpoint(base_url:) ⇒ Hash
Test local entity statement endpoint
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 178 def self.test_local_endpoint(base_url:) entity_statement_url = "#{base_url}/.well-known/openid-federation" validation_warnings = [] # Fetch and parse entity statement begin entity_statement = Federation::EntityStatement.fetch!( entity_statement_url, fingerprint: nil # Don't validate fingerprint for local testing ) rescue ValidationError => e # Don't block execution - treat validation errors as warnings validation_warnings << e. # Try to parse anyway for diagnostic purposes begin require "json" require "base64" response = HttpClient.get(entity_statement_url) entity_statement = Federation::EntityStatement.new(response.body.to_s) rescue raise FetchError, "Failed to fetch entity statement: #{e.}" end end begin = entity_statement.parse rescue ValidationError => e validation_warnings << e. # Try to extract basic info even if validation fails begin require "json" require "base64" jwt_parts = entity_statement.entity_statement.split(".") payload = JSON.parse(Base64.urlsafe_decode64(jwt_parts[1])) # Preserve original structure (string keys from JSON) = { issuer: payload["iss"], sub: payload["sub"], exp: payload["exp"], iat: payload["iat"], jwks: payload["jwks"], metadata: payload["metadata"] || {} } rescue raise FetchError, "Failed to parse entity statement: #{e.}" end end # Extract endpoints - handle both provider and relying party entity types = [:metadata] || {} = [:openid_provider] || ["openid_provider"] || {} = [:openid_relying_party] || ["openid_relying_party"] || {} endpoints = {} # Provider endpoints if .any? endpoints["Authorization Endpoint"] = [:authorization_endpoint] || ["authorization_endpoint"] endpoints["Token Endpoint"] = [:token_endpoint] || ["token_endpoint"] endpoints["UserInfo Endpoint"] = [:userinfo_endpoint] || ["userinfo_endpoint"] endpoints["JWKS URI"] = [:jwks_uri] || ["jwks_uri"] endpoints["Signed JWKS URI"] = [:signed_jwks_uri] || ["signed_jwks_uri"] endpoints["End Session Endpoint"] = [:end_session_endpoint] || ["end_session_endpoint"] end # Relying Party endpoints (JWKS only) if .any? endpoints["JWKS URI"] ||= [:jwks_uri] || ["jwks_uri"] endpoints["Signed JWKS URI"] ||= [:signed_jwks_uri] || ["signed_jwks_uri"] end endpoints.compact! # Detect key configuration status key_status = detect_key_status([:jwks]) # Test endpoints results = {} entity_jwks = [:jwks] endpoints.each do |name, url| next unless url begin case name when "JWKS URI" jwks = Jwks::Fetch.run(url) key_count = jwks["keys"]&.length || 0 results[name] = {status: :success, keys: key_count} when "Signed JWKS URI" signed_jwks = Federation::SignedJWKS.fetch!( url, entity_jwks, force_refresh: true ) key_count = signed_jwks["keys"]&.length || 0 results[name] = {status: :success, keys: key_count} else # Test other endpoints with simple HTTP GET # Note: Rake tasks are developer tools, no security validation needed begin uri = URI.parse(url) rescue URI::InvalidURIError => e results[name] = {status: :error, message: "Invalid URL: #{e.}"} next end http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = (uri.scheme == "https") if uri.scheme == "https" http.verify_mode = OpenSSL::SSL::VERIFY_PEER # Set ca_file directly - this is the simplest and most reliable approach # Try SSL_CERT_FILE first, then default cert file ca_file = if ENV["SSL_CERT_FILE"] && File.file?(ENV["SSL_CERT_FILE"]) ENV["SSL_CERT_FILE"] elsif File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE) OpenSSL::X509::DEFAULT_CERT_FILE end http.ca_file = ca_file if ca_file end request_path = uri.path request_path += "?#{uri.query}" if uri.query request = Net::HTTP::Get.new(request_path) response = http.request(request) results[name] = if response.code.to_i < 400 {status: :success, code: response.code} else {status: :warning, code: response.code, body: response.body} end end rescue FetchError, Federation::SignedJWKS::FetchError => e results[name] = {status: :error, message: e.} rescue Federation::SignedJWKS::ValidationError => e results[name] = {status: :error, message: e.} rescue => e results[name] = {status: :error, message: e.} end end { success: true, entity_statement: entity_statement, metadata: , results: results, key_status: key_status, validation_warnings: validation_warnings } end |
.validate_entity_statement(file_path:, expected_fingerprint: nil) ⇒ Hash
Validate entity statement file
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/omniauth_openid_federation/tasks_helper.rb', line 72 def self.validate_entity_statement(file_path:, expected_fingerprint: nil) resolved_path = resolve_path(file_path) unless File.exist?(resolved_path) raise ConfigurationError, "Entity statement file not found: #{resolved_path}" end entity_statement_content = File.read(resolved_path) entity_statement = Federation::EntityStatement.new( entity_statement_content, fingerprint: expected_fingerprint ) if expected_fingerprint unless entity_statement.validate_fingerprint(expected_fingerprint) raise Federation::EntityStatement::ValidationError, "Fingerprint mismatch: expected #{expected_fingerprint}, got #{entity_statement.fingerprint}" end end = entity_statement.parse { success: true, fingerprint: entity_statement.fingerprint, metadata: } end |