diff --git a/Gemfile b/Gemfile index a015e6bc1..371716f3c 100644 --- a/Gemfile +++ b/Gemfile @@ -5,11 +5,11 @@ source 'https://rubygems.org' ruby '3.4.10' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 8.0.0' +gem 'rails', '~> 8.1.0' # Use postgresql as the database for Active Record gem 'pg', '>= 0.18', '< 2.0' # Use Puma as the app server -gem 'puma', '~> 7.0' +gem 'puma', '~> 8.0' # Asset pipeline for JavaScript bundling gem 'jsbundling-rails' # Asset pipeline for Rails @@ -19,7 +19,7 @@ gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.7' # Use HAML instead of ERB -gem 'haml-rails', '~> 2.0' +gem 'haml-rails', '~> 3.1' # Add Devise for authentication gem 'devise', '~> 5.0' # PBKDF2-SHA512 password hashing for FIPS 140-2 compliance @@ -32,13 +32,13 @@ gem 'omniauth', '~> 2.1' # LDAP Auth — original omniauth-ldap (actively maintained, no nkf/kconv dependency). # Replaces gitlab_omniauth-ldap which had a dead `require 'kconv'` that triggered # Ruby VM crash bugs.ruby-lang.org/issues/21967 on parallel_tests forking. -gem 'omniauth-ldap', '~> 2.3', require: false +gem 'omniauth-ldap', '~> 3.0', require: false # Allow users to sign in with GitHub gem 'omniauth-github' # https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284 -gem 'omniauth-rails_csrf_protection', '~> 1.0' +gem 'omniauth-rails_csrf_protection', '~> 2.0' # Allow users to sign in with OIDC providers -gem 'omniauth_openid_connect', '~> 0.6.0' +gem 'omniauth_openid_connect', '~> 0.8' # Vulcan settings - upgraded for Ruby 3.1+ compatibility gem 'mitre-settingslogic', '~> 3.0' # Use Redis adapter to run Action Cable in production @@ -79,7 +79,7 @@ gem 'amoeba' # For writing excel files with xml:space="preserve" (fixes whitespace round-trip) gem 'caxlsx' # For reading excel files -gem 'ruh-roo', '~> 3.0.0', require: 'roo' +gem 'roo', '~> 3.0' # REXML - required explicitly in Ruby 3.0+ gem 'rexml' @@ -95,6 +95,12 @@ gem 'commonmarker' gem 'rack-attack' gem 'mitre-inspec-objects' +# inspec-core is only consumed as a library (control-code generation via +# mitre-inspec-objects). It resolves from the Cinc Project's community gem +# server, which builds current InSpec releases from source. +source 'https://rubygems.cinc.sh' do + gem 'inspec-core', '~> 7.1' +end gem 'rest-client' group :development do @@ -113,6 +119,8 @@ end group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 2.15' + # Parallel-safe ENV mutation for specs (never bare ENV assignment) + gem 'climate_control', '~> 1.2' gem 'selenium-webdriver' # Easy installation and use of web drivers to run system tests with browsers # gem 'webdrivers' @@ -125,7 +133,7 @@ group :test do gem 'rubocop-rails' gem 'rubocop-rspec' gem 'rubocop-rspec_rails' - gem 'shoulda-matchers', '~> 7.0' + gem 'shoulda-matchers', '~> 8.0' gem 'simplecov', require: false gem 'test-prof', '~> 1.5' gem 'webmock' @@ -137,7 +145,7 @@ group :development, :test do gem 'factory_bot_rails', '~> 6.5.0' gem 'parallel_tests' gem 'rspec-mocks' - gem 'rspec-rails', '~> 6.0' + gem 'rspec-rails', '~> 8.0' # Load environment variables from .env files in development and test gem 'dotenv-rails' end @@ -147,7 +155,7 @@ gem 'tzinfo-data' gem 'highline', '~> 2.0' # Ruby wrapper around slack API -gem 'slack-ruby-client', '1.0.0' +gem 'slack-ruby-client', '~> 3.2' # Slack notification formatting gem 'slack_block_kit', '0.3.3' @@ -157,4 +165,4 @@ gem 'blueprinter-activerecord', '~> 1.3' gem 'oj', '~> 3.16' -gem 'faraday', '~> 1.10', '>= 1.10.6' +gem 'faraday', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index a63e10ed3..542b8db5b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,33 +6,67 @@ GIT devise-security (0.18.0) devise (>= 4.8.1) +GEM + remote: https://rubygems.cinc.sh/ + specs: + inspec-core (7.1.7) + addressable (~> 2.9) + chef-licensing (>= 1.2.0) + chef-telemetry (~> 1.0, >= 1.0.8) + cookstyle + csv (~> 3.0) + faraday (>= 1, < 3) + faraday-follow_redirects (~> 0.3) + hashie (>= 3.4, < 6.0) + license-acceptance (>= 0.2.13, < 3.0) + method_source (>= 0.8, < 2.0) + mixlib-log (~> 3.0) + multipart-post (~> 2.0) + ostruct (>= 0.1, < 0.7) + parallel (~> 1.9) + parslet (>= 1.5, < 3.0) + pry (~> 0.13) + rspec (>= 3.9, <= 3.14) + rspec-its (>= 1.2, < 3.0) + rubyzip (>= 1.2.2, < 4.0) + semverse (~> 3.0) + sslshake (~> 1.2) + syslog (~> 0.1) + thor (>= 0.20, < 1.5.0) + tomlrb (>= 1.3, < 2.1) + train-core (~> 3.16, >= 3.16.1) + tty-prompt (~> 0.17) + tty-table (~> 0.10) + GEM remote: https://rubygems.org/ specs: abbrev (0.1.2) - actioncable (8.0.5.1) - actionpack (= 8.0.5.1) - activesupport (= 8.0.5.1) + action_text-trix (2.1.19) + railties + actioncable (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.5.1) - actionpack (= 8.0.5.1) - activejob (= 8.0.5.1) - activerecord (= 8.0.5.1) - activestorage (= 8.0.5.1) - activesupport (= 8.0.5.1) + actionmailbox (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) - actionmailer (8.0.5.1) - actionpack (= 8.0.5.1) - actionview (= 8.0.5.1) - activejob (= 8.0.5.1) - activesupport (= 8.0.5.1) + actionmailer (8.1.3.1) + actionpack (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.5.1) - actionview (= 8.0.5.1) - activesupport (= 8.0.5.1) + actionpack (8.1.3.1) + actionview (= 8.1.3.1) + activesupport (= 8.1.3.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -40,50 +74,51 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.5.1) - actionpack (= 8.0.5.1) - activerecord (= 8.0.5.1) - activestorage (= 8.0.5.1) - activesupport (= 8.0.5.1) + actiontext (8.1.3.1) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.5.1) - activesupport (= 8.0.5.1) + actionview (8.1.3.1) + activesupport (= 8.1.3.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.0.5.1) - activesupport (= 8.0.5.1) + activejob (8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.3.6) - activemodel (8.0.5.1) - activesupport (= 8.0.5.1) - activerecord (8.0.5.1) - activemodel (= 8.0.5.1) - activesupport (= 8.0.5.1) + activemodel (8.1.3.1) + activesupport (= 8.1.3.1) + activerecord (8.1.3.1) + activemodel (= 8.1.3.1) + activesupport (= 8.1.3.1) timeout (>= 0.4.0) activerecord-import (2.2.0) activerecord (>= 4.2) - activerecord-session_store (2.2.0) - actionpack (>= 7.0) - activerecord (>= 7.0) + activerecord-session_store (2.3.0) + actionpack (>= 7.1) + activerecord (>= 7.1) cgi (>= 0.3.6) rack (>= 2.0.8, < 4) - railties (>= 7.0) - activestorage (8.0.5.1) - actionpack (= 8.0.5.1) - activejob (= 8.0.5.1) - activerecord (= 8.0.5.1) - activesupport (= 8.0.5.1) + railties (>= 7.1) + activestorage (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activesupport (= 8.1.3.1) marcel (~> 1.0) - activesupport (8.0.5.1) + activesupport (8.1.3.1) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) @@ -94,34 +129,34 @@ GEM aes_key_wrap (1.1.0) amoeba (3.3.0) activerecord (>= 5.2.0) - anonymous_loader (0.1.1) - version_gem (~> 1.1, >= 1.1.12) + anonymous_loader (0.1.3) + version_gem (~> 1.1, >= 1.1.14) ast (2.4.3) attr_required (1.0.2) audited (5.8.0) activerecord (>= 5.2, < 8.2) activesupport (>= 5.2, < 8.2) - auth-sanitizer (0.2.2) - version_gem (~> 1.1, >= 1.1.10) + auth-sanitizer (0.2.3) + version_gem (~> 1.1, >= 1.1.14) base64 (0.3.0) bcrypt (3.1.22) - benchmark (0.5.0) bigdecimal (3.3.1) - bindata (2.5.1) + bindata (3.0.0) bindex (0.8.1) - blueprinter (1.2.1) + blueprinter (1.3.0) blueprinter-activerecord (1.3.0) activerecord (>= 6.0) blueprinter (~> 1.0) - bootsnap (1.24.6) - msgpack (~> 1.2) - brakeman (7.1.0) + bootsnap (1.25.0) + msgpack (~> 1.5) + brakeman (8.0.5) racc builder (3.3.0) bundler-audit (0.9.3) bundler (>= 1.2.0) thor (~> 1.0) - byebug (12.0.0) + byebug (13.0.0) + reline (>= 0.6.0) capybara (3.40.0) addressable matrix @@ -131,46 +166,55 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - caxlsx (4.4.1) + caxlsx (4.5.0) htmlentities (~> 4.3, >= 4.3.4) marcel (~> 1.0) nokogiri (~> 1.10, >= 1.10.4) rubyzip (>= 2.4, < 4) - cgi (0.5.1) - chef-config (18.8.11) + cgi (0.5.2) + chef-config (19.3.15) addressable - chef-utils (= 18.8.11) + chef-utils (= 19.3.15) fuzzyurl mixlib-config (>= 2.2.12, < 4.0) mixlib-shellout (>= 2.0, < 4.0) - tomlrb (~> 1.2) + racc + tomlrb (>= 1.2, < 3.0) + chef-licensing (1.4.1) + chef-config (>= 15) + faraday (>= 1, < 3) + faraday-http-cache + mixlib-log (~> 3.0) + ostruct (~> 0.6.0) + pstore (~> 0.1.1) + tty-prompt (~> 0.23) + tty-spinner (~> 0.9.3) chef-telemetry (1.1.1) chef-config concurrent-ruby (~> 1.0) - chef-utils (18.8.11) + chef-utils (19.3.15) concurrent-ruby childprocess (5.1.0) logger (~> 1.5) + climate_control (1.2.0) coderay (1.1.3) - commonmarker (2.7.0) - rb_sys (~> 0.9) - commonmarker (2.7.0-aarch64-linux) - commonmarker (2.7.0-arm64-darwin) - commonmarker (2.7.0-x86_64-linux) - commonmarker (2.7.0-x86_64-linux-musl) + commonmarker (2.9.0-aarch64-linux) + commonmarker (2.9.0-arm64-darwin) + commonmarker (2.9.0-x86_64-linux) + commonmarker (2.9.0-x86_64-linux-musl) concurrent-ruby (1.3.8) connection_pool (3.0.2) - cookstyle (8.4.0) - rubocop (= 1.79.2) - crack (1.0.0) + cookstyle (8.7.6) + rubocop (= 1.86.1) + crack (1.0.1) bigdecimal rexml crass (1.0.7) - csv (3.3.5) + csv (3.3.6) database_cleaner-active_record (2.2.2) activerecord (>= 5.a) database_cleaner-core (~> 2.0) - database_cleaner-core (2.0.1) + database_cleaner-core (2.1.0) date (3.5.1) devise (5.0.4) bcrypt (~> 3.0) @@ -178,129 +222,94 @@ GEM railties (>= 7.0) responders warden (~> 1.2.3) - devise-encryptable (0.2.0) + devise-encryptable (0.3.0) devise (>= 2.1.0) diff-lcs (1.6.2) - docile (1.4.1) domain_name (0.6.20240107) dotenv (3.2.0) dotenv-rails (3.2.0) dotenv (= 3.2.0) railties (>= 6.1) drb (2.2.3) - erb (6.0.4) + erb (6.0.7) erubi (1.13.1) - factory_bot (6.5.6) + factory_bot (6.6.0) activesupport (>= 6.1.0) factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faraday (1.10.6) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.1) - faraday-excon (1.1.0) - faraday-follow_redirects (0.3.0) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-follow_redirects (0.5.0) faraday (>= 1, < 3) - faraday-httpclient (1.0.1) + faraday-http-cache (2.7.0) + faraday (>= 0.8) + faraday-mashify (1.0.2) + faraday (~> 2.0) + hashie faraday-multipart (1.2.0) multipart-post (~> 2.0) - faraday-net_http (1.0.2) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.4) - faraday_middleware (1.2.1) - faraday (~> 1.0) - ffaker (2.24.0) - ffi (1.17.2) - ffi (1.17.2-aarch64-linux-gnu) - ffi (1.17.2-arm64-darwin) - ffi (1.17.2-x86_64-linux-gnu) - ffi (1.17.2-x86_64-linux-musl) + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffaker (2.25.0) + ffi (1.17.4) + ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-arm64-darwin) + ffi (1.17.4-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-musl) foreman (0.90.0) thor (~> 1.4) fuzzyurl (0.9.0) gli (2.22.2) ostruct - globalid (1.3.0) + globalid (1.4.0) activesupport (>= 6.1) - haml (6.3.0) + haml (7.3.0) + prism (>= 1.1.0) temple (>= 0.8.2) thor tilt - haml-rails (2.1.0) + haml-rails (3.1.0) actionpack (>= 5.1) activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - hashdiff (1.2.0) - hashie (5.0.0) + hashdiff (1.2.1) + hashie (5.1.0) + logger health_check (3.1.0) railties (>= 5.0) highline (2.1.0) htmlentities (4.4.2) http-accept (1.7.0) - http-cookie (1.0.8) + http-cookie (1.1.6) domain_name (~> 0.5) - httpclient (2.9.0) - mutex_m - i18n (1.14.8) + i18n (1.15.2) concurrent-ruby (~> 1.0) - inspec-core (5.22.95) - addressable (~> 2.4) - chef-telemetry (~> 1.0, >= 1.0.8) - cookstyle - faraday (>= 1, < 3) - faraday-follow_redirects (~> 0.3) - hashie (>= 3.4, < 6.0) - license-acceptance (>= 0.2.13, < 3.0) - method_source (>= 0.8, < 2.0) - mixlib-log (~> 3.0, < 3.2) - multipart-post (~> 2.0) - parallel (~> 1.9) - parslet (>= 1.5, < 3.0) - pry (~> 0.13) - rspec (>= 3.9, <= 3.14) - rspec-its (>= 1.2, < 3.0) - rubyzip (>= 1.2.2, < 4.0) - semverse (~> 3.0) - sslshake (~> 1.2) - thor (>= 0.20, < 1.5.0) - tomlrb (>= 1.2, < 2.1) - train-core (~> 3.12.13) - tty-prompt (~> 0.17) - tty-table (~> 0.10) - io-console (0.8.2) - irb (1.17.0) + io-console (0.9.0) + irb (1.18.0) pp (>= 0.6.0) prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - jbuilder (2.14.1) + jbuilder (2.15.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) jsbundling-rails (1.3.1) railties (>= 6.0.0) - json (2.21.1) - json-jwt (1.15.3.1) + json (2.21.2) + json-jwt (1.17.2) activesupport (>= 4.2) aes_key_wrap + base64 bindata - httpclient + faraday (~> 2.0) + faraday-follow_redirects jwt (3.2.0) base64 - language_server-protocol (3.17.0.5) + language_server-protocol (3.17.0.6) launchy (3.1.1) addressable (~> 2.8) childprocess (~> 5.0) @@ -313,29 +322,29 @@ GEM tty-box (~> 0.6) tty-prompt (~> 0.20) lint_roller (1.1.0) - listen (3.9.0) + listen (3.10.0) + logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) loofah (2.25.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.9.0) + mail (2.9.1) logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.1.0) + marcel (1.2.1) matrix (0.4.3) method_source (1.1.0) mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0812) + mime-types-data (3.2026.0701) mini_mime (1.1.5) - mini_portile2 (2.8.9) - minitest (6.0.3) + minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) mitre-inspec-objects (0.3.3) @@ -344,15 +353,17 @@ GEM bigdecimal (~> 3.1) mixlib-config (3.0.27) tomlrb - mixlib-log (3.0.9) - mixlib-shellout (3.3.9) + mixlib-log (3.2.3) + ffi (>= 1.15.5) + mixlib-shellout (3.4.10) chef-utils - msgpack (1.8.3) - multi_xml (0.7.2) - bigdecimal (~> 3.1) + msgpack (1.8.4) + multi_xml (0.9.1) + bigdecimal (>= 3.1, < 5) multipart-post (2.4.1) - mutex_m (0.3.0) - net-imap (0.6.4.1) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.6.6) date net-protocol net-ldap (0.20.0) @@ -366,12 +377,9 @@ GEM net-ssh (>= 2.6.5, < 8.0.0) net-smtp (0.5.1) net-protocol - net-ssh (7.3.0) + net-ssh (7.3.3) netrc (0.11.0) nio4r (2.7.5) - nokogiri (1.19.4) - mini_portile2 (~> 2.8.2) - racc (~> 1.4) nokogiri (1.19.4-aarch64-linux-gnu) racc (~> 1.4) nokogiri (1.19.4-arm64-darwin) @@ -380,63 +388,67 @@ GEM racc (~> 1.4) nokogiri (1.19.4-x86_64-linux-musl) racc (~> 1.4) - nokogiri-happymapper (0.10.0) + nokogiri-happymapper (0.10.1) nokogiri (~> 1.5) - oauth2 (2.0.24) - anonymous_loader (~> 0.1, >= 0.1.1) - auth-sanitizer (~> 0.2, >= 0.2.2) + oauth2 (2.0.25) + anonymous_loader (~> 0.1, >= 0.1.3) + auth-sanitizer (~> 0.2, >= 0.2.3) faraday (>= 0.17.3, < 4.0) jwt (>= 1.0, < 4.0) logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0, >= 2.0.6) - version_gem (~> 1.1, >= 1.1.12) - oj (3.17.4) + snaky_hash (~> 2.0, >= 2.0.7) + version_gem (~> 1.1, >= 1.1.14) + oj (3.17.5) bigdecimal (>= 3.0) ostruct (>= 0.2) - omniauth (2.1.3) + omniauth (2.1.4) hashie (>= 3.4.6) + logger rack (>= 2.2.3) rack-protection omniauth-github (2.0.1) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) - omniauth-ldap (2.3.3) + omniauth-ldap (3.0.2) + anonymous_loader (~> 0.1, >= 0.1.2) + auth-sanitizer (~> 0.2, >= 0.2.2) net-ldap (~> 0.16, < 1) omniauth (>= 1.2, < 3) pyu-ruby-sasl (>= 0.0.3.3, < 0.1) rack (>= 1, < 4) rubyntlm (~> 0.6.2, < 1) - version_gem (~> 1.1, >= 1.1.9) + version_gem (~> 1.1, >= 1.1.14) omniauth-oauth2 (1.9.0) oauth2 (>= 2.0.2, < 3) omniauth (~> 2.0) - omniauth-rails_csrf_protection (1.0.2) + omniauth-rails_csrf_protection (2.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - omniauth_openid_connect (0.6.1) + omniauth_openid_connect (0.8.0) omniauth (>= 1.9, < 3) - openid_connect (~> 1.1) - openid_connect (1.4.2) + openid_connect (~> 2.2) + openid_connect (2.5.0) activemodel attr_required (>= 1.0.0) - json-jwt (>= 1.15.0) - net-smtp - rack-oauth2 (~> 1.21) - swd (~> 1.3) + faraday (~> 2.0) + faraday-follow_redirects + json-jwt (>= 1.16) + mail + rack-oauth2 (~> 2.2) + swd (~> 2.0) tzinfo - validate_email validate_url - webfinger (~> 1.2) + webfinger (~> 2.0) orm_adapter (0.5.0) ostruct (0.6.3) - ox (2.14.23) + ox (2.14.28) bigdecimal (>= 3.0) - parallel (1.27.0) - parallel_tests (5.6.0) + parallel (1.28.0) + parallel_tests (5.7.0) parallel - parser (3.3.9.0) + parser (3.3.12.0) ast (~> 2.4.1) racc parslet (2.0.0) @@ -450,58 +462,59 @@ GEM pg_search (2.3.7) activerecord (>= 6.1) activesupport (>= 6.1) - pp (0.6.3) + pp (0.6.4) prettyprint prettyprint (0.2.0) prism (1.9.0) - propshaft (1.2.1) + propshaft (1.3.2) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack - pry (0.15.2) + pry (0.16.0) coderay (~> 1.1) method_source (~> 1.0) - psych (5.3.1) - date - stringio - public_suffix (7.0.2) - puma (7.2.1) + reline (>= 0.6.0) + pstore (0.1.4) + public_suffix (7.0.5) + puma (8.0.2) nio4r (~> 2.0) pyu-ruby-sasl (0.0.3.3) racc (1.8.1) - rack (2.2.23) + rack (3.2.6) rack-attack (6.8.0) rack (>= 1.0, < 4) - rack-oauth2 (1.21.3) + rack-oauth2 (2.3.0) activesupport attr_required - httpclient + faraday (~> 2.0) + faraday-follow_redirects json-jwt (>= 1.11.0) rack (>= 2.1.0) - rack-protection (3.2.0) + rack-protection (4.2.1) base64 (>= 0.1.0) - rack (~> 2.2, >= 2.2.4) - rack-session (1.0.2) - rack (< 3) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) + rack-session (2.1.2) + base64 (>= 0.1.0) + rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (1.0.1) - rack (< 3) - webrick - rails (8.0.5.1) - actioncable (= 8.0.5.1) - actionmailbox (= 8.0.5.1) - actionmailer (= 8.0.5.1) - actionpack (= 8.0.5.1) - actiontext (= 8.0.5.1) - actionview (= 8.0.5.1) - activejob (= 8.0.5.1) - activemodel (= 8.0.5.1) - activerecord (= 8.0.5.1) - activestorage (= 8.0.5.1) - activesupport (= 8.0.5.1) + rackup (2.3.1) + rack (>= 3) + rails (8.1.3.1) + actioncable (= 8.1.3.1) + actionmailbox (= 8.1.3.1) + actionmailer (= 8.1.3.1) + actionpack (= 8.1.3.1) + actiontext (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activemodel (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) bundler (>= 1.15.0) - railties (= 8.0.5.1) + railties (= 8.1.3.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -509,9 +522,9 @@ GEM rails-html-sanitizer (1.7.1) loofah (~> 2.25, >= 2.25.2) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.0.5.1) - actionpack (= 8.0.5.1) - activesupport (= 8.0.5.1) + railties (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -519,19 +532,21 @@ GEM tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.1) - rake-compiler-dock (1.11.0) + rake (13.4.2) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rb_sys (0.9.124) - rake-compiler-dock (= 1.11.0) - rdoc (7.2.0) + rbs (4.1.2) + logger + prism (>= 1.6.0) + tsort + rdoc (8.0.0) erb - psych (>= 4.0.0) + prism (>= 1.6.0) + rbs (>= 4.0.0) tsort - regexp_parser (2.11.2) - reline (0.6.3) + regexp_parser (2.12.0) + reline (0.7.0) io-console (~> 0.5) responders (3.2.0) actionpack (>= 7.0) @@ -542,11 +557,17 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.4.4) - rspec (3.13.1) + roo (3.0.0) + base64 (~> 0.2) + csv (~> 3) + logger (~> 1) + nokogiri (~> 1) + rubyzip (>= 3.0.0, < 4.0.0) + rspec (3.13.2) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.5) + rspec-core (3.13.6) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) @@ -554,110 +575,105 @@ GEM rspec-its (2.0.0) rspec-core (>= 3.13.0) rspec-expectations (>= 3.13.0) - rspec-mocks (3.13.5) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.5) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) - rspec-core (~> 3.13) - rspec-expectations (~> 3.13) - rspec-mocks (~> 3.13) - rspec-support (~> 3.13) - rspec-support (3.13.4) - rubocop (1.79.2) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) + rubocop (1.86.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - parallel (~> 1.10) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.50.0) parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-capybara (2.22.1) + prism (~> 1.7) + rubocop-capybara (3.0.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) + rubocop (~> 1.81) rubocop-factory_bot (2.28.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) - rubocop-performance (1.25.0) + rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.33.3) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.36.0) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.6.0) + rubocop-rspec (3.9.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) + rubocop (~> 1.81) rubocop-rspec_rails (2.32.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (~> 3.5) ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) rubyntlm (0.6.5) base64 - rubyzip (2.4.1) - ruh-roo (3.0.1) - nokogiri (~> 1) - rubyzip (>= 1.3.0, < 3.0.0) + rubyzip (3.4.1) securerandom (0.4.1) - selenium-webdriver (4.35.0) + selenium-webdriver (4.46.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) semverse (3.0.2) - shoulda-matchers (7.0.1) - activesupport (>= 7.1) - simplecov (0.22.0) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-html (0.13.2) - simplecov_json_formatter (0.1.4) - slack-ruby-client (1.0.0) - faraday (>= 1.0) - faraday_middleware + shoulda-matchers (8.0.1) + activesupport (>= 7.2) + simplecov (1.0.3) + slack-ruby-client (3.2.0) + faraday (>= 2.0.1) + faraday-mashify + faraday-multipart gli hashie - websocket-driver + logger slack_block_kit (0.3.3) - snaky_hash (2.0.6) + snaky_hash (2.0.7) hashie (>= 0.1.0, < 6) - version_gem (>= 1.1.8, < 3) + version_gem (~> 1.1, >= 1.1.14) sslshake (1.3.1) - stringio (3.2.0) strings (0.2.1) strings-ansi (~> 0.2) unicode-display_width (>= 1.5, < 3.0) unicode_utils (~> 1.4) strings-ansi (0.2.0) - swd (1.3.0) + swd (2.0.3) activesupport (>= 3) attr_required (>= 0.0.5) - httpclient (>= 2.4) - temple (0.10.4) - test-prof (1.5.2) + faraday (~> 2.0) + faraday-follow_redirects + syslog (0.4.0) + logger + temple (0.10.6) + test-prof (1.6.3) + logger thor (1.4.0) - tilt (2.6.1) + tilt (2.8.0) timeout (0.6.1) - tomlrb (1.3.0) - train-core (3.12.13) + tomlrb (2.0.4) + train-core (3.16.5) addressable (~> 2.5) - ffi (!= 1.13.0) - json (>= 1.8, < 3.0) + ffi (>= 1.16.0, < 1.18) + json (>= 2.19.2, < 3.0) mixlib-shellout (>= 2.0, < 4.0) net-scp (>= 1.2, < 5.0) net-ssh (>= 2.9, < 8.0) @@ -676,6 +692,8 @@ GEM tty-screen (~> 0.8) wisper (~> 2.0) tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) tty-table (0.12.0) pastel (~> 0.8) strings (~> 0.2.0) @@ -685,34 +703,30 @@ GEM turbolinks-source (5.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2025.2) + tzinfo-data (1.2026.3) tzinfo (>= 1.0.0) unicode-display_width (2.6.0) unicode_utils (1.4.0) uri (1.1.1) useragent (0.16.11) - validate_email (0.1.6) - activemodel (>= 3.0) - mail (>= 2.2.5) validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix - version_gem (1.1.12) + version_gem (1.1.15) warden (1.2.9) rack (>= 2.0.9) - web-console (4.2.1) - actionview (>= 6.0.0) - activemodel (>= 6.0.0) + web-console (4.3.0) + actionview (>= 8.0.0) bindex (>= 0.4.0) - railties (>= 6.0.0) - webfinger (1.2.0) + railties (>= 8.0.0) + webfinger (2.1.3) activesupport - httpclient (>= 2.4) - webmock (3.25.1) + faraday (~> 2.0) + faraday-follow_redirects + webmock (3.26.2) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.9.2) websocket (1.2.11) websocket-driver (0.8.2) base64 @@ -724,12 +738,12 @@ GEM zeitwerk (>= 2.6) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.7.5) + zeitwerk (2.8.3) PLATFORMS aarch64-linux arm64-darwin-24 - ruby + arm64-darwin-25 x86_64-linux x86_64-linux-musl @@ -747,6 +761,7 @@ DEPENDENCIES byebug capybara (>= 2.15) caxlsx + climate_control (~> 1.2) commonmarker csv database_cleaner-active_record @@ -755,12 +770,13 @@ DEPENDENCIES devise-security! dotenv-rails factory_bot_rails (~> 6.5.0) - faraday (~> 1.10, >= 1.10.6) + faraday (~> 2.0) ffaker (~> 2.10) foreman - haml-rails (~> 2.0) + haml-rails (~> 3.1) health_check (~> 3.1) highline (~> 2.0) + inspec-core (~> 7.1)! jbuilder (~> 2.7) jsbundling-rails letter_opener @@ -772,21 +788,22 @@ DEPENDENCIES oj (~> 3.16) omniauth (~> 2.1) omniauth-github - omniauth-ldap (~> 2.3) - omniauth-rails_csrf_protection (~> 1.0) - omniauth_openid_connect (~> 0.6.0) + omniauth-ldap (~> 3.0) + omniauth-rails_csrf_protection (~> 2.0) + omniauth_openid_connect (~> 0.8) ox parallel_tests pg (>= 0.18, < 2.0) pg_search propshaft - puma (~> 7.0) + puma (~> 8.0) rack-attack - rails (~> 8.0.0) + rails (~> 8.1.0) rest-client rexml + roo (~> 3.0) rspec-mocks - rspec-rails (~> 6.0) + rspec-rails (~> 8.0) rubocop rubocop-capybara rubocop-factory_bot @@ -795,11 +812,10 @@ DEPENDENCIES rubocop-rspec rubocop-rspec_rails rubyzip - ruh-roo (~> 3.0.0) selenium-webdriver - shoulda-matchers (~> 7.0) + shoulda-matchers (~> 8.0) simplecov - slack-ruby-client (= 1.0.0) + slack-ruby-client (~> 3.2) slack_block_kit (= 0.3.3) test-prof (~> 1.5) turbolinks (~> 5) diff --git a/app/controllers/api/user_search_controller.rb b/app/controllers/api/user_search_controller.rb index 70ccd5e45..b2048246b 100644 --- a/app/controllers/api/user_search_controller.rb +++ b/app/controllers/api/user_search_controller.rb @@ -33,9 +33,9 @@ def index def set_target @target = case params[:membership_type] when 'Project' - Project.find(params[:membership_id]) + Project.find(params.expect(:membership_id)) when 'Component' - Component.find(params[:membership_id]) + Component.find(params.expect(:membership_id)) else raise ActiveRecord::RecordNotFound end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ab45b8655..03bb66a1e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -305,7 +305,7 @@ def helpful_errors(exception) respond_to do |format| format.html do flash.alert = message - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end format.json do render json: { @@ -325,7 +325,7 @@ def not_authorized(exception) respond_to do |format| format.html do flash.alert = not_authorized_html_message(exception) - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end format.json do render json: permission_denied_payload(exception), status: :forbidden diff --git a/app/controllers/components_controller.rb b/app/controllers/components_controller.rb index e80913f6c..1cd9d80f4 100644 --- a/app/controllers/components_controller.rb +++ b/app/controllers/components_controller.rb @@ -114,7 +114,7 @@ def create message: component.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end ensure Audited.auditing_enabled = true @@ -133,7 +133,7 @@ def update message: @component.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -171,7 +171,7 @@ def destroy message: 'Could not remove component from project.', variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end def export @@ -281,7 +281,7 @@ def bulk_export zip_filename: 'components_inspec.zip' ) else - head :unprocessable_entity + head :unprocessable_content end end format.json { render json: { status: :ok } } @@ -363,10 +363,10 @@ def based_on_same_srg srg_title = @component.based_on.title accessible_project_ids = current_user.available_projects.ids render json: Component.where(based_on: SecurityRequirementsGuide.where(title: srg_title)) - .where.not(id: params[:id]) + .where.not(id: @component.id) .where(project_id: accessible_project_ids) .or(Component.where(based_on: SecurityRequirementsGuide.where(title: srg_title)) - .where.not(id: params[:id]) + .where.not(id: @component.id) .where(released: true)) .distinct .order(:project_id) @@ -435,13 +435,13 @@ def history def detect_srg file = params[:file] unless file - render json: { error: NO_FILE_PROVIDED }, status: :unprocessable_entity + render json: { error: NO_FILE_PROVIDED }, status: :unprocessable_content return end srg_ids = SpreadsheetParser.peek_srg_ids(file) if srg_ids.empty? - render json: { error: 'No SRG IDs found in spreadsheet' }, status: :unprocessable_entity + render json: { error: 'No SRG IDs found in spreadsheet' }, status: :unprocessable_content return end @@ -451,11 +451,11 @@ def detect_srg if srgs.empty? render json: { error: 'Could not identify a matching SRG for the IDs in this spreadsheet' }, - status: :unprocessable_entity + status: :unprocessable_content elsif srgs.size > 1 names = srgs.map { |s| "#{s.title} (#{s.version})" }.join(', ') render json: { error: "SRG IDs map to multiple SRGs: #{names}. Please select manually." }, - status: :unprocessable_entity + status: :unprocessable_content else srg = srgs.first render json: { id: srg.id, srg_id: srg.srg_id, title: srg.title, version: srg.version } @@ -465,13 +465,13 @@ def detect_srg def preview_spreadsheet_update file = params[:file] unless file - render json: { error: NO_FILE_PROVIDED }, status: :unprocessable_entity + render json: { error: NO_FILE_PROVIDED }, status: :unprocessable_content return end result = @component.update_from_spreadsheet(file) if result[:error] - render json: { error: result[:error] }, status: :unprocessable_entity + render json: { error: result[:error] }, status: :unprocessable_content else render json: result end @@ -480,7 +480,7 @@ def preview_spreadsheet_update def apply_spreadsheet_update file = params[:file] unless file - render json: { error: NO_FILE_PROVIDED }, status: :unprocessable_entity + render json: { error: NO_FILE_PROVIDED }, status: :unprocessable_content return end @@ -490,7 +490,7 @@ def apply_spreadsheet_update message: "Successfully updated #{result[:count]} rules from spreadsheet.", variant: 'success', status: :ok) elsif result[:error] - render json: { error: result[:error] }, status: :unprocessable_entity + render json: { error: result[:error] }, status: :unprocessable_content end end @@ -601,7 +601,7 @@ def set_component # Return an HTML response with an alert flash message if request format is HTML. format.html do flash.alert = message - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end # Return a JSON response with a toast message if request formt is JSON. format.json do @@ -639,7 +639,7 @@ def set_rule # If html format is requested, redirect back to default page format.html do - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end format.json do # Render a json response in a toast message format @@ -669,7 +669,7 @@ def set_component_basic respond_to do |format| format.html do flash.alert = message - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end format.json do render json: { diff --git a/app/controllers/concerns/oidc_discovery_helper.rb b/app/controllers/concerns/oidc_discovery_helper.rb index 8443236b2..2fd68c899 100644 --- a/app/controllers/concerns/oidc_discovery_helper.rb +++ b/app/controllers/concerns/oidc_discovery_helper.rb @@ -200,7 +200,6 @@ def cache_discovery_document(config, cache_key, normalized_issuer) Rails.logger.debug { "Cached OIDC discovery for #{normalized_issuer}, expires in 1 hour" } end - # rubocop:disable Naming/PredicateMethod def validate_discovery_document(config, expected_issuer) # Security: Validate issuer matches expected value (prevents man-in-the-middle attacks) actual_issuer = config['issuer'] @@ -291,7 +290,6 @@ def validate_discovery_document(config, expected_issuer) true end - # rubocop:enable Naming/PredicateMethod def fetch_oidc_endpoint(endpoint_name, fallback_url = nil) return fallback_url unless Settings.oidc.discovery diff --git a/app/controllers/concerns/upload_validatable.rb b/app/controllers/concerns/upload_validatable.rb index 52babeaaa..e618c234a 100644 --- a/app/controllers/concerns/upload_validatable.rb +++ b/app/controllers/concerns/upload_validatable.rb @@ -30,7 +30,7 @@ def validate_upload_size(file, max_size) # rubocop:disable Naming/PredicateMetho message: "File exceeds maximum size of #{ActiveSupport::NumberHelper.number_to_human_size(max_size)}.", variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content false end @@ -46,7 +46,7 @@ def validate_upload_type(file, allowed_types) # rubocop:disable Naming/Predicate message: "Invalid file type '#{ext}'. Allowed: #{allowed_types.join(', ')}.", variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content false end end diff --git a/app/controllers/memberships_controller.rb b/app/controllers/memberships_controller.rb index b141e0c70..1eb707f0b 100644 --- a/app/controllers/memberships_controller.rb +++ b/app/controllers/memberships_controller.rb @@ -28,7 +28,7 @@ def create respond_to do |format| format.html do flash.alert = "Unable to create membership. #{membership.errors.full_messages}" - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end format.json do render json: { @@ -37,7 +37,7 @@ def create message: membership.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -71,7 +71,7 @@ def update message: @membership.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -105,7 +105,7 @@ def destroy message: @membership.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -130,7 +130,7 @@ def notify_membership_change(membership, smtp_action:, inapp_verb:) end def set_membership - @membership = Membership.find(params[:id]) + @membership = Membership.find(params.expect(:id)) end # This isn't in the application controller because it is specific to the membership controller diff --git a/app/controllers/project_access_requests_controller.rb b/app/controllers/project_access_requests_controller.rb index aed88b887..8379a9066 100644 --- a/app/controllers/project_access_requests_controller.rb +++ b/app/controllers/project_access_requests_controller.rb @@ -8,7 +8,7 @@ class ProjectAccessRequestsController < ApplicationController before_action :set_and_authorize_access_request, only: %i[destroy] def create - @project = Project.find(params[:project_id]) + @project = Project.find(params.expect(:project_id)) @access_request = ProjectAccessRequest.new(user: current_user, project: @project) if @access_request.save @@ -40,7 +40,7 @@ def destroy respond_to do |format| format.html do flash.notice = toast - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end # multi-key response (toast + id). format.json do @@ -54,9 +54,9 @@ def destroy respond_to do |format| format.html do flash.alert = @access_request.errors.full_messages.to_sentence - redirect_back(fallback_location: root_path) + redirect_back_or_to(root_path) end - format.json { render json: { error: @access_request.errors.full_messages.to_sentence }, status: :unprocessable_entity } + format.json { render json: { error: @access_request.errors.full_messages.to_sentence }, status: :unprocessable_content } end end end @@ -64,7 +64,7 @@ def destroy private def set_and_authorize_access_request - @access_request = ProjectAccessRequest.find(params[:id]) + @access_request = ProjectAccessRequest.find(params.expect(:id)) return if @access_request.user == current_user || current_user.can_admin_project?(@access_request.project) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 988b6571d..f0307f076 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -166,7 +166,7 @@ def create message: project.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -193,7 +193,7 @@ def update message: @project.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -224,7 +224,7 @@ def destroy message: @project.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -366,7 +366,7 @@ def import_backup variant: 'danger' }, warnings: result.warnings - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -425,7 +425,7 @@ def perform_create_from_backup_dry_run(file, include_reviews, include_membership }, warnings: result.warnings, project_defaults: project_defaults - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -434,7 +434,7 @@ def perform_create_from_backup(file, include_reviews, include_memberships, unless project_name render json: { toast: { title: IMPORT_ERROR_TITLE, message: 'Project name is required', variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content return end @@ -478,7 +478,7 @@ def perform_create_from_backup(file, include_reviews, include_memberships, variant: 'danger' }, warnings: result&.warnings || [] - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -491,7 +491,7 @@ def extract_project_defaults(file) Zip::File.open_buffer(file_data) do |zip| project_entry = zip.find_entry('project.json') if project_entry - project_json = JSON.parse(zip.read('project.json')) + project_json = JSON.parse(ZipEntryReader.read_capped(project_entry)) defaults[:name] = project_json['name'] if project_json['name'].present? defaults[:description] = project_json['description'] if project_json['description'].present? defaults[:visibility] = project_json['visibility'] if project_json['visibility'].present? @@ -504,7 +504,7 @@ def extract_project_defaults(file) end def set_project - @project = Project.find(params[:id]) + @project = Project.find(params.expect(:id)) end def new_project_params diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb index f154cd449..ac0320040 100644 --- a/app/controllers/reviews_controller.rb +++ b/app/controllers/reviews_controller.rb @@ -548,7 +548,7 @@ def lock_sections comment = params[:comment] invalid = sections - RuleConstants::LOCKABLE_SECTION_NAMES - return render json: { error: "Invalid sections: #{invalid.join(', ')}" }, status: :unprocessable_entity if invalid.any? + return render json: { error: "Invalid sections: #{invalid.join(', ')}" }, status: :unprocessable_content if invalid.any? rules = @component.rules.where(locked: false) count = 0 @@ -625,11 +625,11 @@ def move_review_subtree!(review, new_rule_id, audit_comment) end def set_rule - @rule = Rule.find(params[:rule_id]) + @rule = Rule.find(params.expect(:rule_id)) end def set_component - @component = Component.find(params[:component_id]) + @component = Component.find(params.expect(:component_id)) end # Picks rule (POST /rules/:rule_id/reviews) or component (POST /components/:component_id/reviews). diff --git a/app/controllers/rule_satisfactions_controller.rb b/app/controllers/rule_satisfactions_controller.rb index dc1049b17..a44e3f8fb 100644 --- a/app/controllers/rule_satisfactions_controller.rb +++ b/app/controllers/rule_satisfactions_controller.rb @@ -62,12 +62,12 @@ def render_satisfaction_failure(verb, message) message: message, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end def set_component_and_rules - @rule = Rule.find(params[:rule_id]) - @satisfied_by_rule = Rule.find(params[:satisfied_by_rule_id]) + @rule = Rule.find(params.expect(:rule_id)) + @satisfied_by_rule = Rule.find(params.expect(:satisfied_by_rule_id)) @component = @rule.component end end diff --git a/app/controllers/rules_controller.rb b/app/controllers/rules_controller.rb index 27aca783d..f79086c09 100644 --- a/app/controllers/rules_controller.rb +++ b/app/controllers/rules_controller.rb @@ -83,7 +83,7 @@ def create message: rule.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -99,7 +99,7 @@ def update message: @rule.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -137,7 +137,7 @@ def destroy message: 'A database error prevented the delete from completing. The control was not modified.', variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end def revert @@ -154,7 +154,7 @@ def revert message: e.message, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end def section_locks @@ -162,7 +162,7 @@ def section_locks locked = ActiveModel::Type::Boolean.new.cast(params[:locked]) comment = params[:comment] - return render json: { error: "Invalid section: #{section}" }, status: :unprocessable_entity unless RuleConstants::LOCKABLE_SECTION_NAMES.include?(section) + return render json: { error: "Invalid section: #{section}" }, status: :unprocessable_content unless RuleConstants::LOCKABLE_SECTION_NAMES.include?(section) fields = @rule.locked_fields.dup if locked @@ -189,7 +189,7 @@ def bulk_section_locks comment = params[:comment] invalid = sections - RuleConstants::LOCKABLE_SECTION_NAMES - return render json: { error: "Invalid sections: #{invalid.join(', ')}" }, status: :unprocessable_entity if invalid.any? + return render json: { error: "Invalid sections: #{invalid.join(', ')}" }, status: :unprocessable_content if invalid.any? fields = @rule.locked_fields.dup sections.each do |section| @@ -292,7 +292,7 @@ def revert_params end def set_rule - @rule = Rule.find(params[:id]) + @rule = Rule.find(params.expect(:id)) end def set_component diff --git a/app/controllers/security_requirements_guides_controller.rb b/app/controllers/security_requirements_guides_controller.rb index d7bda3d83..6fa7c4345 100644 --- a/app/controllers/security_requirements_guides_controller.rb +++ b/app/controllers/security_requirements_guides_controller.rb @@ -15,7 +15,7 @@ def index end def show - @srg = SecurityRequirementsGuide.includes(srg_rules: %i[disa_rule_descriptions checks]).find(params[:id]) + @srg = SecurityRequirementsGuide.includes(srg_rules: %i[disa_rule_descriptions checks]).find(params.expect(:id)) respond_to do |format| format.html { @srg_json = SrgBlueprint.render(@srg, view: :show) } @@ -108,7 +108,7 @@ def destroy message: @srg.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -117,7 +117,7 @@ def destroy private def security_requirements_guide - @srg = SecurityRequirementsGuide.find(params[:id]) + @srg = SecurityRequirementsGuide.find(params.expect(:id)) end def parse_csv_columns(columns_param) diff --git a/app/controllers/stigs_controller.rb b/app/controllers/stigs_controller.rb index b4ed7392f..563db6d10 100644 --- a/app/controllers/stigs_controller.rb +++ b/app/controllers/stigs_controller.rb @@ -15,7 +15,7 @@ def index end def show - @stig = Stig.includes(stig_rules: %i[disa_rule_descriptions checks]).find(params[:id]) + @stig = Stig.includes(stig_rules: %i[disa_rule_descriptions checks]).find(params.expect(:id)) respond_to do |format| format.html { @stig_json = StigBlueprint.render(@stig, view: :show) } @@ -104,7 +104,7 @@ def destroy message: @stig.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 3f3167f50..febab3958 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -5,6 +5,10 @@ module Users # that is hit. Currently we don't have any provider-specific code, since # both LDAP and Github return data in a similar enough manner. class OmniauthCallbacksController < Devise::OmniauthCallbacksController + # remember_me(resource) lives in this opt-in module, built for + # controllers that sign users in from a callback. + include Devise::Controllers::Rememberable + # Error handling for OmniAuth failure scenarios. # Rails checks rescue_from in REVERSE order (last-defined = first-checked). # StandardError must be FIRST so specific subclasses defined after it take priority. @@ -51,9 +55,11 @@ def all Rails.logger.warn "No ID token in OmniAuth credentials for user: #{user.email}" end - # Handle remember_me for OmniAuth logins - # The checkbox sends remember_me=1 - check both regular params and omniauth.params - # OmniAuth may pass form data via request.env['omniauth.params'] in some configurations + # Handle remember_me for OmniAuth logins. Two delivery paths: the LDAP + # form posts remember_me with the callback params directly; a checkbox + # that must survive an external provider redirect rides the authorize + # URL's query string, which OmniAuth stashes (request.GET only) into + # session['omniauth.params'] and restores at the callback. omniauth_params = request.env['omniauth.params'] || {} should_remember = params[:remember_me] == '1' || omniauth_params['remember_me'] == '1' remember_me(user) if should_remember @@ -66,6 +72,19 @@ def all alias github all alias oidc all + protected + + # Devise builds the failure flash reason by humanizing the raw OmniAuth + # error type, which surfaces strings like "Ldap error" to users. Known + # types get a readable reason from devise.omniauth_callbacks.reasons; + # anything unmapped keeps Devise's humanized fallback. + def failure_message + error_type = request.env['omniauth.error.type'].to_s + I18n.t(error_type, scope: 'devise.omniauth_callbacks.reasons', default: super) + end + + private + def oauth_error(exception) # Log full details server-side for debugging. Rails.logger.error "OAuth authentication error: #{exception.class} - #{exception.message}" diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index e5cc76fab..bc7834f69 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -39,7 +39,7 @@ def create if Settings.local_login.enabled super else - redirect_back(fallback_location: new_user_session_path, alert: I18n.t('devise.registrations.disabled')) + redirect_back_or_to(new_user_session_path, alert: I18n.t('devise.registrations.disabled')) end end @@ -77,7 +77,7 @@ def update message: resource.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ede59696b..6f6703368 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -47,7 +47,7 @@ def admin_create else render json: { toast: { title: 'Could not create user.', message: user.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end @@ -62,7 +62,7 @@ def update format.json do render json: { toast: { title: 'Cannot remove admin.', message: ['You are the only admin. Promote another user first.'], variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -106,7 +106,7 @@ def update message: @user.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -123,7 +123,7 @@ def destroy format.json do render json: { toast: { title: 'Cannot delete user.', message: ['This is the only admin. Promote another user first.'], variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -153,7 +153,7 @@ def destroy message: @user.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end end end @@ -164,7 +164,7 @@ def send_password_reset unless Settings.smtp.enabled return render json: { toast: { title: 'SMTP not configured.', message: ['Email delivery is not available. Use "Generate Reset Link" instead.'], variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end @user.send_reset_password_instructions @@ -197,7 +197,7 @@ def lock if @user == current_user return render json: { toast: { title: 'Cannot lock yourself.', message: ['You cannot lock your own account.'], variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end @user.lock_access!(send_instructions: false) @@ -232,7 +232,7 @@ def set_password if password_params[:password].blank? return render json: { toast: { title: 'Password required.', message: ['Password cannot be blank.'], variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end @user.password = @user.password_confirmation = password_params[:password] @@ -243,7 +243,7 @@ def set_password else render json: { toast: { title: 'Could not set password.', message: @user.errors.full_messages, variant: 'danger' } - }, status: :unprocessable_entity + }, status: :unprocessable_content end rescue StandardError => e Rails.logger.error "set_password failed for user #{@user.id}: #{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}" @@ -340,7 +340,7 @@ def comment_row_for(review, latest_response, responses_count, reaction_counts) end def set_user - @user = User.find(params[:id]) + @user = User.find(params.expect(:id)) end def user_create_params diff --git a/app/lib/zip_entry_reader.rb b/app/lib/zip_entry_reader.rb new file mode 100644 index 000000000..915414ca7 --- /dev/null +++ b/app/lib/zip_entry_reader.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Streaming zip-entry read with a hard cap at the entry's declared size. +# +# rubyzip validates entry sizes only on file extraction — streaming reads +# inflate without limit, so an archive whose central directory +# under-declares sizes can expand far past any accounting done from +# declared values. Capping each read at its declaration makes the central +# directory's numbers trustworthy for callers that budget against them, +# and turns a lying archive into a loud Zip::EntrySizeError instead of +# unbounded memory growth. +module ZipEntryReader + CHUNK_SIZE = 16_384 + + def self.read_capped(entry) + declared = entry.size + data = +'' + entry.get_input_stream do |is| + while (chunk = is.sysread(CHUNK_SIZE)) + data << chunk + raise Zip::EntrySizeError, entry if data.bytesize > declared + end + end + data + end +end diff --git a/app/models/project.rb b/app/models/project.rb index fbec02ec9..4848c4da3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -84,7 +84,7 @@ def self.pending_comment_counts(project_ids) sql = sanitize_sql_array([PENDING_COMMENT_COUNTS_SQL, project_ids, project_ids]) rows = connection.exec_query(sql) - rows.each_with_object({}) { |r, h| h[r['project_id']] = r['cnt'] } + rows.to_h { |r| [r['project_id'], r['cnt']] } end # Pending + total top-level comment counts per project. Sparse hash; @@ -94,9 +94,7 @@ def self.comment_counts(project_ids) sql = sanitize_sql_array([COMMENT_COUNTS_SQL, project_ids, project_ids]) rows = connection.exec_query(sql) - rows.each_with_object({}) do |r, h| - h[r['project_id']] = { pending: r['pending'], total: r['total'] } - end + rows.to_h { |r| [r['project_id'], { pending: r['pending'], total: r['total'] }] } end # Per-project deep-link target: the unique pending component_id when a @@ -107,7 +105,7 @@ def self.pending_comment_target_components(project_ids) sql = sanitize_sql_array([PENDING_COMMENT_TARGET_COMPONENTS_SQL, project_ids, project_ids]) rows = connection.exec_query(sql) - rows.each_with_object({}) { |r, h| h[r['project_id']] = r['component_id'] } + rows.to_h { |r| [r['project_id'], r['component_id']] } end # Backs GET /projects/:id/comments — same row shape as diff --git a/app/models/security_requirements_guide.rb b/app/models/security_requirements_guide.rb index 95e2aa491..59c39b6b8 100644 --- a/app/models/security_requirements_guide.rb +++ b/app/models/security_requirements_guide.rb @@ -61,7 +61,7 @@ def self.release_date(plaintext_mapping) end def self.latest - query = <<-SQL.squish + query = <<~SQL.squish SELECT id, title, version FROM security_requirements_guides WHERE version IN ( diff --git a/app/services/import/json_archive_importer.rb b/app/services/import/json_archive_importer.rb index d28884227..866cf1fe8 100644 --- a/app/services/import/json_archive_importer.rb +++ b/app/services/import/json_archive_importer.rb @@ -95,7 +95,7 @@ def parse_archive(result) return archive end - archive[:manifest] = JSON.parse(zip.read('manifest.json')) + archive[:manifest] = JSON.parse(ZipEntryReader.read_capped(manifest_entry)) archive[:project] = safe_parse_json(zip, 'project.json') # Parse SRG XML files from srgs/ directory @@ -103,11 +103,11 @@ def parse_archive(result) next unless entry.name.start_with?('srgs/') && entry.name.end_with?('.xml') filename = entry.name.sub('srgs/', '') - archive[:srg_files][filename] = zip.read(entry.name) + archive[:srg_files][filename] = ZipEntryReader.read_capped(entry) end # Detect archive structure: flat (single component) or nested (components/ directory) - archive[:components] = detect_and_parse_components(zip, archive[:manifest]) + archive[:components] = detect_and_parse_components(zip, archive[:manifest], result) end rescue Zip::Error => e result.add_error("Invalid ZIP file: #{e.message}") @@ -118,12 +118,18 @@ def parse_archive(result) archive end - def detect_and_parse_components(zip, manifest) + def detect_and_parse_components(zip, manifest, result) # Check for flat structure (single component export) return [parse_component_files(zip, '')] if zip.find_entry('component.json') # Nested structure: components/ComponentName-V1R1/ - manifest['components'].filter_map do |entry| + components = manifest['components'] + unless components.is_a?(Array) + result.add_error('Invalid backup archive: manifest.json has no components list') + return [] + end + + components.filter_map do |entry| dir = find_component_dir(zip, entry) next unless dir @@ -154,7 +160,7 @@ def safe_parse_json(zip, path) entry = zip.find_entry(path) return nil unless entry - JSON.parse(zip.read(path)) + JSON.parse(ZipEntryReader.read_capped(entry)) end def read_file_data diff --git a/config/application.rb b/config/application.rb index 841e74fb9..66553ad1c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,7 +12,7 @@ module VulcanVue # Main Rails application configuration for Vulcan class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 8.0 + config.load_defaults 8.1 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. diff --git a/config/environments/development.rb b/config/environments/development.rb index 7f54ec1b6..34957435b 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -57,6 +57,9 @@ # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true + # Highlight code that triggered redirect in logs. + config.action_dispatch.verbose_redirect_logs = true + # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb deleted file mode 100644 index a91b01427..000000000 --- a/config/initializers/new_framework_defaults_7_0.rb +++ /dev/null @@ -1,143 +0,0 @@ -# frozen_string_literal: true - -# Be sure to restart your server when you modify this file. -# -# This file eases your Rails 7.0 framework defaults upgrade. -# -# Uncomment each configuration one by one to switch to the new default. -# Once your application is ready to run with all new defaults, you can remove -# this file and set the `config.load_defaults` to `7.0`. -# -# Read the Guide for Upgrading Ruby on Rails for more info on each option. -# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html - -# `button_to` view helper will render `