From 5ccdbdee0929ec73c2ff4ca21643a57c03c72f29 Mon Sep 17 00:00:00 2001 From: Bilal Hankins Date: Wed, 31 Jul 2024 15:49:35 -0500 Subject: [PATCH 01/21] Created rails security log model and basic spec test --- Gemfile.lock | 1 + app/models/security_log.rb | 48 ++++++++++++++++++++++++++++++++ spec/models/security_log_spec.rb | 11 ++++++++ 3 files changed, 60 insertions(+) create mode 100644 app/models/security_log.rb create mode 100644 spec/models/security_log_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index e0556ef1..3739e48d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -315,6 +315,7 @@ PLATFORMS aarch64-linux arm64-darwin-21 arm64-darwin-22 + arm64-darwin-23 x86_64-darwin-22 x86_64-linux diff --git a/app/models/security_log.rb b/app/models/security_log.rb new file mode 100644 index 00000000..8a5cf24c --- /dev/null +++ b/app/models/security_log.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: security_log +# +# id :bigint not null, primary key +# action :string not null +# details :jsonb +# originator_id :bigint +# originator_role :string +# originator_identifier :string +# originator_remote_ip :string +# target_id :integer +# target_type :string +# target_identifier :string +# logged_at :datetime not null +# +class SecurityLog < ApplicationRecord + self.table_name = 'security_log' + + belongs_to :originator, class_name: 'User', optional: true + + validates :action, presence: true, inclusion: { in: %w[ + status_change account_update role_change accessed_site session_duration + create read update delete submit renewal_request + ] } + + before_validation :set_logged_at, on: :create + + # Attributes + attribute :action, :string + attribute :details, :jsonb + attribute :originator_id, :integer + attribute :originator_role, :string + attribute :originator_identifier, :string + attribute :originator_remote_ip, :string + attribute :target_id, :integer + attribute :target_type, :string + attribute :target_identifier, :string + attribute :logged_at, :datetime + + private + + def set_logged_at + self.logged_at ||= DateTime.now + end +end diff --git a/spec/models/security_log_spec.rb b/spec/models/security_log_spec.rb new file mode 100644 index 00000000..6f98c39c --- /dev/null +++ b/spec/models/security_log_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +RSpec.describe SecurityLog, type: :model do + describe 'validations' do + it 'validates presence of action' do + security_log = described_class.new(action: nil) + expect(security_log).not_to be_valid + expect(security_log.errors[:action]).to include("can't be blank") + end + end +end From 5e3241e1c32cffeee56e6d08a2d86c1956776381 Mon Sep 17 00:00:00 2001 From: Bilal Hankins Date: Thu, 1 Aug 2024 11:22:20 -0500 Subject: [PATCH 02/21] linting and annotations --- app/models/security_log.rb | 12 ++++++------ spec/models/security_log_spec.rb | 21 +++++++++++++++++++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/models/security_log.rb b/app/models/security_log.rb index 8a5cf24c..ee984b12 100644 --- a/app/models/security_log.rb +++ b/app/models/security_log.rb @@ -5,16 +5,16 @@ # Table name: security_log # # id :bigint not null, primary key -# action :string not null +# action :string(255) not null # details :jsonb # originator_id :bigint -# originator_role :string -# originator_identifier :string -# originator_remote_ip :string +# originator_role :string(255) +# originator_identifier :string(255) # target_id :integer -# target_type :string -# target_identifier :string +# target_type :string(255) +# target_identifier :string(255) # logged_at :datetime not null +# originator_remote_ip :string(255) # class SecurityLog < ApplicationRecord self.table_name = 'security_log' diff --git a/spec/models/security_log_spec.rb b/spec/models/security_log_spec.rb index 6f98c39c..e5b03064 100644 --- a/spec/models/security_log_spec.rb +++ b/spec/models/security_log_spec.rb @@ -1,11 +1,28 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: security_log +# +# id :bigint not null, primary key +# action :string(255) not null +# details :jsonb +# originator_id :bigint +# originator_role :string(255) +# originator_identifier :string(255) +# target_id :integer +# target_type :string(255) +# target_identifier :string(255) +# logged_at :datetime not null +# originator_remote_ip :string(255) +# require 'rails_helper' RSpec.describe SecurityLog, type: :model do - describe 'validations' do + describe 'Security Log validations' do it 'validates presence of action' do security_log = described_class.new(action: nil) expect(security_log).not_to be_valid - expect(security_log.errors[:action]).to include("can't be blank") + expect(security_log.errors[:action]).to include("can not be blank") end end end From 837b90fcddf01dc71df41b6c6e1f40fd72b9b732 Mon Sep 17 00:00:00 2001 From: Bilal Hankins Date: Thu, 1 Aug 2024 11:23:42 -0500 Subject: [PATCH 03/21] fix typo --- spec/models/security_log_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/security_log_spec.rb b/spec/models/security_log_spec.rb index e5b03064..baec690d 100644 --- a/spec/models/security_log_spec.rb +++ b/spec/models/security_log_spec.rb @@ -22,7 +22,7 @@ it 'validates presence of action' do security_log = described_class.new(action: nil) expect(security_log).not_to be_valid - expect(security_log.errors[:action]).to include("can not be blank") + expect(security_log.errors[:action]).to include("can't be blank") end end end From e938106242ebb4846c0f7a2413c43bec90c45f60 Mon Sep 17 00:00:00 2001 From: Stephen Chudleigh Date: Thu, 1 Aug 2024 16:57:51 -0700 Subject: [PATCH 04/21] add login.gov certs and configuration --- .env_login | 4 ++++ .envrc | 3 +++ DEVCONFIG.md | 3 ++- config/application.rb | 12 ++++++++++++ config/environments/development.rb | 11 ----------- manifest.yml | 8 ++++---- 6 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 .env_login diff --git a/.env_login b/.env_login new file mode 100644 index 00000000..de4aac84 --- /dev/null +++ b/.env_login @@ -0,0 +1,4 @@ +# local dev env vars for login.gov +export LOGIN_CLIENT_ID=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:challenge_gov_portal_eval_dev +export LOGIN_REDIRECT_EVAL_URL=http://localhost:3000/auth/result +export LOGOUT_REDIRECT_EVAL_URL=http://localhost:3000/ \ No newline at end of file diff --git a/.envrc b/.envrc index 3e088573..4fbb075a 100644 --- a/.envrc +++ b/.envrc @@ -4,3 +4,6 @@ use nix mkdir -p .nix-bundler export BUNDLE_PATH=./.nix-bundler + +# Login Env Vars +source .env_login diff --git a/DEVCONFIG.md b/DEVCONFIG.md index 287a87dd..cc7dc3ac 100644 --- a/DEVCONFIG.md +++ b/DEVCONFIG.md @@ -63,7 +63,8 @@ Once direnv is installed and your shell is restarted, clone the project and `cd` 1. Set up your uswds files in the build directory `npx gulp copyAssets` 1. Setup the database `rake db:create`, note that postgres must be running for this to work 1. Boot the system, this will run the sass, esbuild, and uswds watchers along with the rails server - 1. `./bin/dev` + 1. `./bin/dev` + 1. NOTE for login.gov environment: if you are not using direnv/nix to eval .envrc, you can run `source .env_login` in your terminal before starting the server or add the env vars in that file to your local environment directly. Now you can visit [`localhost:3000`](http://localhost:3000) from your browser. diff --git a/config/application.rb b/config/application.rb index 0f48164e..82ebc69d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -28,5 +28,17 @@ class Application < Rails::Application # Use the Postgresql-specific syntax for DB dumps config.active_record.schema_format = :sql + + # Shared login.gov config with ENV overrides + config.login_gov_oidc = { + idp_host: ENV.fetch("LOGIN_IDP_HOST", "https://idp.int.identitysandbox.gov"), + login_redirect_uri: ENV.fetch("LOGIN_REDIRECT_EVAL_URL"), + logout_redirect_uri: ENV.fetch("LOGOUT_REDIRECT_EVAL_URL"), + acr_value: "http://idmanagement.gov/ns/assurance/loa/1", + client_id: ENV.fetch("LOGIN_CLIENT_ID"), # determines the login.gov IdP application + private_key_password: ENV.fetch("LOGIN_PRIVATE_KEY_PASSWORD", nil), # optional + public_key_path: ENV.fetch("LOGIN_PUBLIC_KEY_PATH", "config/public.crt"), + private_key_path: ENV.fetch("LOGIN_PRIVATE_KEY_PATH", "config/private.pem"), + } end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 4ffc19a7..a10ac8b1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -75,15 +75,4 @@ # Raise error when a before_action's only/except options reference missing actions config.action_controller.raise_on_missing_callback_actions = true - - config.login_gov_oidc = { - idp_host: "https://idp.int.identitysandbox.gov", - login_redirect_uri: "http://localhost:3000/auth/result", - logout_redirect_uri: "https://www.challenge.gov/", - acr_value: "http://idmanagement.gov/ns/assurance/loa/1", - client_id: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:challenge_gov_platform_dev", - private_key_password: nil, - private_key_path: "config/private.pem", - public_key_path: "config/public.crt", - } end diff --git a/manifest.yml b/manifest.yml index 98a99852..9ae01e7d 100644 --- a/manifest.yml +++ b/manifest.yml @@ -20,9 +20,9 @@ applications: RAILS_LOG_TO_STDOUT: true RAILS_SERVE_STATIC_FILES: true HOST: challenge-dev.app.cloud.gov + LOGIN_CLIENT_ID: urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:challenge_gov_portal_eval_dev + LOGIN_IDP_HOST: https://idp.int.identitysandbox.gov LOGIN_PRIVATE_KEY_PATH: dev_key.pem LOGIN_PUBLIC_KEY_PATH: dev_cert.pem - LOGIN_REDIRECT_URL: https://challenge-portal-dev.app.cloud.gov/auth/result - LOGIN_IDP_AUTHORIZE_URL: https://idp.int.identitysandbox.gov/openid_connect/authorize - LOGIN_TOKEN_ENDPOINT: https://idp.int.identitysandbox.gov/api/openid_connect/token - LOGIN_CLIENT_ID: urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:challenge_gov_portal_dev + LOGIN_REDIRECT_EVAL_URL: https://challenge-dev.app.cloud.gov/auth/result + LOGOUT_REDIRECT_EVAL_URL: https://challenge-dev.app.cloud.gov/ \ No newline at end of file From 32db19267191a418b598bf349f5d1b6692939af6 Mon Sep 17 00:00:00 2001 From: Stephen Chudleigh Date: Thu, 1 Aug 2024 17:10:07 -0700 Subject: [PATCH 05/21] make CircleCI happier --- config/application.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index 82ebc69d..a97f4d90 100644 --- a/config/application.rb +++ b/config/application.rb @@ -32,10 +32,10 @@ class Application < Rails::Application # Shared login.gov config with ENV overrides config.login_gov_oidc = { idp_host: ENV.fetch("LOGIN_IDP_HOST", "https://idp.int.identitysandbox.gov"), - login_redirect_uri: ENV.fetch("LOGIN_REDIRECT_EVAL_URL"), - logout_redirect_uri: ENV.fetch("LOGOUT_REDIRECT_EVAL_URL"), + login_redirect_uri: ENV.fetch("LOGIN_REDIRECT_EVAL_URL", "https://challenge-dev.app.cloud.gov/auth/result"), + logout_redirect_uri: ENV.fetch("LOGOUT_REDIRECT_EVAL_URL", "https://challenge-dev.app.cloud.gov/"), acr_value: "http://idmanagement.gov/ns/assurance/loa/1", - client_id: ENV.fetch("LOGIN_CLIENT_ID"), # determines the login.gov IdP application + client_id: ENV.fetch("LOGIN_CLIENT_ID", "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:_client_id"), # default fake ID for CI private_key_password: ENV.fetch("LOGIN_PRIVATE_KEY_PASSWORD", nil), # optional public_key_path: ENV.fetch("LOGIN_PUBLIC_KEY_PATH", "config/public.crt"), private_key_path: ENV.fetch("LOGIN_PRIVATE_KEY_PATH", "config/private.pem"), From 81076d1bdf8ab5eb43a3aa091996b3ef640608de Mon Sep 17 00:00:00 2001 From: Stephen Chudleigh Date: Thu, 1 Aug 2024 17:20:04 -0700 Subject: [PATCH 06/21] Update DEVCONFIG.md --- DEVCONFIG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DEVCONFIG.md b/DEVCONFIG.md index cc7dc3ac..c9b2a296 100644 --- a/DEVCONFIG.md +++ b/DEVCONFIG.md @@ -63,8 +63,10 @@ Once direnv is installed and your shell is restarted, clone the project and `cd` 1. Set up your uswds files in the build directory `npx gulp copyAssets` 1. Setup the database `rake db:create`, note that postgres must be running for this to work 1. Boot the system, this will run the sass, esbuild, and uswds watchers along with the rails server - 1. `./bin/dev` - 1. NOTE for login.gov environment: if you are not using direnv/nix to eval .envrc, you can run `source .env_login` in your terminal before starting the server or add the env vars in that file to your local environment directly. + ``` + ./bin/dev + ``` + > _NOTE for login.gov configuration_ -- if you are **not** using direnv/nix to eval `.envrc`, you can run `source .env_login` in your terminal before starting the server or add the env vars in that file to your local environment directly. Now you can visit [`localhost:3000`](http://localhost:3000) from your browser. From 20e41ff764ac319b7f203a2a563de7a6a51a73ad Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Sun, 4 Aug 2024 23:59:03 -0400 Subject: [PATCH 07/21] 22|24 Initial layout, user updates, login/logout --- Gemfile.lock | 55 +++---- app/controllers/application_controller.rb | 30 ++++ app/controllers/dashboard_controller.rb | 5 + app/controllers/sessions_controller.rb | 11 +- app/models/application_record.rb | 17 +++ app/models/user.rb | 54 ++++++- app/views/dashboard/index.html.erb | 9 ++ app/views/layouts/_header.html.erb | 18 +++ app/views/layouts/application.html.erb | 1 + config/environments/development.rb | 2 +- config/routes.rb | 3 + db/structure.sql | 173 ++++++++++++++++++---- spec/models/user_spec.rb | 67 ++++++++- spec/spec_helper.rb | 1 + yarn.lock | 74 ++++----- 15 files changed, 419 insertions(+), 101 deletions(-) create mode 100644 app/controllers/dashboard_controller.rb create mode 100644 app/views/dashboard/index.html.erb create mode 100644 app/views/layouts/_header.html.erb diff --git a/Gemfile.lock b/Gemfile.lock index 969dceae..d8f1e2f8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,8 +96,8 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - codeclimate-test-reporter (1.0.9) - simplecov (<= 0.13) + codeclimate-test-reporter (1.0.7) + simplecov concurrent-ruby (1.3.3) connection_pool (2.4.1) crack (1.0.0) @@ -111,18 +111,18 @@ GEM irb (~> 1.10) reline (>= 0.3.8) diff-lcs (1.5.1) - docile (1.1.5) + docile (1.4.1) drb (2.2.1) erubi (1.13.0) - faraday (2.10.0) + faraday (2.10.1) faraday-net_http (>= 2.0, < 3.2) logger - faraday-net_http (3.1.0) + faraday-net_http (3.1.1) net-http foreman (0.88.1) globalid (1.2.1) activesupport (>= 6.1) - hashdiff (1.1.0) + hashdiff (1.1.1) i18n (1.14.5) concurrent-ruby (~> 1.0) io-console (0.7.2) @@ -165,19 +165,23 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.16.6-aarch64-linux) + nokogiri (1.16.7-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.6-arm64-darwin) + nokogiri (1.16.7-arm-linux) racc (~> 1.4) - nokogiri (1.16.6-x86_64-darwin) + nokogiri (1.16.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86-linux) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) parallel (1.25.1) parser (3.3.4.0) ast (~> 2.4.1) racc - pg (1.5.6) + pg (1.5.7) prism (0.30.0) propshaft (0.9.0) actionpack (>= 7.0.0) @@ -186,7 +190,7 @@ GEM railties (>= 7.0.0) psych (5.1.2) stringio - public_suffix (6.0.0) + public_suffix (6.0.1) puma (6.4.2) nio4r (~> 2.0) racc (1.8.1) @@ -286,7 +290,7 @@ GEM rubocop (~> 1.0) rubocop-rspec (3.0.3) rubocop (~> 1.61) - ruby-lsp (0.17.9) + ruby-lsp (0.17.10) language_server-protocol (~> 3.17.0) prism (>= 0.29.0, < 0.31) rbs (>= 3, < 4) @@ -299,12 +303,13 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - simplecov (0.13.0) - docile (~> 1.1.0) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) - sorbet-runtime (0.5.11492) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) + sorbet-runtime (0.5.11504) stimulus-rails (1.3.3) railties (>= 6.0.0) stringio (3.1.1) @@ -335,14 +340,14 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.16) + zeitwerk (2.6.17) PLATFORMS aarch64-linux - arm64-darwin-21 - arm64-darwin-22 - arm64-darwin-23 - x86_64-darwin-22 + arm-linux + arm64-darwin + x86-linux + x86_64-darwin x86_64-linux DEPENDENCIES @@ -382,4 +387,4 @@ RUBY VERSION ruby 3.2.4p170 BUNDLED WITH - 2.4.6 + 2.5.9 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7944f9f9..46ac30fe 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,34 @@ # frozen_string_literal: true class ApplicationController < ActionController::Base + helper_method :current_user, :logged_in? + + def current_user + if session[:userinfo] + user_token = session["userinfo"][0]["sub"] + @current_user ||= User.find_by(token: user_token) if user_token + end + end + + def logged_in? + !!current_user + end + + def sign_in(login_userinfo) + user = User.user_from_userinfo(login_userinfo) + + @current_user = user + session[:userinfo] = login_userinfo + end + + def sign_out + @current_user = nil + session.delete(:userinfo) + end + + def redirect_if_logged_in(path = "/dashboard") + if logged_in? + redirect_to path, notice: "You are already logged in." + end + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb new file mode 100644 index 00000000..2d803889 --- /dev/null +++ b/app/controllers/dashboard_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class DashboardController < ApplicationController + def index; end +end \ No newline at end of file diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8795a792..e64fade8 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -12,17 +12,16 @@ def create redirect_to(login_gov.authorization_url, allow_other_host: true) end - def delete + def destroy login_gov = LoginGov.new - # TODO: update user session status, clear out JWT # TODO: add session duration to the security log - # TODO: delete session locally and Phoenix - redirect_to(login_gov.logout_url) + sign_out + redirect_to(login_gov.logout_url, allow_other_host: true) end def result - # TODO: store the user_info in the session - # session[:user_info] = @login_userinfo + sign_in(@login_userinfo) + redirect_to dashboard_path end private diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 08dc5379..011fa12c 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,4 +2,21 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class + + before_create :set_inserted_at + before_save :set_updated_at + + self.record_timestamps = false + + attribute :inserted_at, :datetime, precision: 6 + attribute :updated_at, :datetime, precision: 6 + + private + def set_inserted_at + self.inserted_at ||= Time.current + end + + def set_updated_at + self.updated_at ||= Time.current + end end diff --git a/app/models/user.rb b/app/models/user.rb index 7bd1bd84..30c8840c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,7 +47,7 @@ class User < ApplicationRecord has_many :submission_documents, class_name: 'Submissions::Document', dependent: :destroy has_many :message_context_statuses, dependent: :destroy - attribute :role, :string, default: -> { self[:role] } + attribute :role, :string attribute :status, :string, default: 'pending' attribute :finalized, :boolean, default: true attribute :display, :boolean, default: true @@ -80,8 +80,54 @@ class User < ApplicationRecord attribute :renewal_request, :string - attribute :created_at, :datetime, precision: 6 - attribute :updated_at, :datetime, precision: 6 - validates :email, presence: true + + # Finds, creates, or updates user from userinfo + # Find in case of user with existing token matching userinfo["sub"] + # Create in case of no token or email matching in userinfo + # Update in case of matching email to userinfo["email"] but no token set + # TODO: Add relevant security log tracking here? + def self.user_from_userinfo(userinfo) + email = userinfo[0]["email"] + token = userinfo[0]["sub"] + + if user = find_by(token: token) + user + elsif user = find_by(email: email) + update_admin_added_user(user, userinfo) + else + default_role_and_status = default_role_and_status_for_email(email) + default_role = default_role_and_status[0] + default_status = default_role_and_status[1] + + user = create({ + email: email, + role: default_role, + token: token, + terms_of_use: nil, + privacy_guidelines: nil, + status: default_status + }) + end + end + + private + def self.update_admin_added_user(user, userinfo) + update(user.id, {token: userinfo[0]["sub"]}) + end + + def self.default_role_and_status_for_email(email) + if default_challenge_manager?(email) + ["challenge_manager", "pending"] + else + ["solver", "active"] + end + end + + def self.default_challenge_manager?(email) + escaped_gov_tld = Regexp.escape('.gov') + matching_gov_string = ".*#{escaped_gov_tld}$" + gov_regex = Regexp.new(matching_gov_string) + gov_regex.match?(email) + end end diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb new file mode 100644 index 00000000..576f6fed --- /dev/null +++ b/app/views/dashboard/index.html.erb @@ -0,0 +1,9 @@ +
+
+ <% if logged_in? %> + Logged In Dashboard Index + <% else %> + Logged Out Dashboard Index + <% end %> +
+
\ No newline at end of file diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..fdc90313 --- /dev/null +++ b/app/views/layouts/_header.html.erb @@ -0,0 +1,18 @@ +
+
+
+ +
+ +
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index e00cfa64..a9dfbf59 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,6 +14,7 @@ + <%= render "layouts/header" %> <%= yield %> diff --git a/config/environments/development.rb b/config/environments/development.rb index 4ffc19a7..5220eed1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -79,7 +79,7 @@ config.login_gov_oidc = { idp_host: "https://idp.int.identitysandbox.gov", login_redirect_uri: "http://localhost:3000/auth/result", - logout_redirect_uri: "https://www.challenge.gov/", + logout_redirect_uri: "http://localhost:3000/", acr_value: "http://idmanagement.gov/ns/assurance/loa/1", client_id: "urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:challenge_gov_platform_dev", private_key_password: nil, diff --git a/config/routes.rb b/config/routes.rb index 12c114e8..7a1763af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,9 @@ get 'auth/result', to: 'sessions#result' resource 'session', only: [:new, :create, :destroy] + get '/', to: "dashboard#index" + get '/dashboard', to: "dashboard#index" + # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check diff --git a/db/structure.sql b/db/structure.sql index 379e3719..b6ebe855 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -24,29 +24,6 @@ CREATE TYPE public.oban_job_state AS ENUM ( ); --- --- Name: oban_jobs_notify(); Type: FUNCTION; Schema: public; Owner: - --- - -CREATE FUNCTION public.oban_jobs_notify() RETURNS trigger - LANGUAGE plpgsql - AS $$ -DECLARE - channel text; - notice json; -BEGIN - IF NEW.state = 'available' THEN - channel = 'public.oban_insert'; - notice = json_build_object('queue', NEW.queue); - - PERFORM pg_notify(channel, notice::text); - END IF; - - RETURN NULL; -END; -$$; - - SET default_tablespace = ''; SET default_table_access_method = heap; @@ -175,6 +152,37 @@ CREATE SEQUENCE public.certification_log_id_seq ALTER SEQUENCE public.certification_log_id_seq OWNED BY public.certification_log.id; +-- +-- Name: challenge_managers; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.challenge_managers ( + id bigint NOT NULL, + challenge_id bigint, + user_id bigint, + revoked_at timestamp(0) without time zone +); + + +-- +-- Name: challenge_owners_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.challenge_owners_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: challenge_owners_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.challenge_owners_id_seq OWNED BY public.challenge_managers.id; + + -- -- Name: challenges; Type: TABLE; Schema: public; Owner: - -- @@ -505,7 +513,6 @@ CREATE TABLE public.oban_jobs ( cancelled_at timestamp without time zone, CONSTRAINT attempt_range CHECK (((attempt >= 0) AND (attempt <= max_attempts))), CONSTRAINT positive_max_attempts CHECK ((max_attempts > 0)), - CONSTRAINT priority_range CHECK (((priority >= 0) AND (priority <= 3))), CONSTRAINT queue_length CHECK (((char_length(queue) > 0) AND (char_length(queue) < 128))), CONSTRAINT worker_length CHECK (((char_length(worker) > 0) AND (char_length(worker) < 128))) ); @@ -515,7 +522,7 @@ CREATE TABLE public.oban_jobs ( -- Name: TABLE oban_jobs; Type: COMMENT; Schema: public; Owner: - -- -COMMENT ON TABLE public.oban_jobs IS '11'; +COMMENT ON TABLE public.oban_jobs IS '12'; -- @@ -1072,6 +1079,13 @@ ALTER TABLE ONLY public.agency_members ALTER COLUMN id SET DEFAULT nextval('publ ALTER TABLE ONLY public.certification_log ALTER COLUMN id SET DEFAULT nextval('public.certification_log_id_seq'::regclass); +-- +-- Name: challenge_managers id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.challenge_managers ALTER COLUMN id SET DEFAULT nextval('public.challenge_owners_id_seq'::regclass); + + -- -- Name: challenges id; Type: DEFAULT; Schema: public; Owner: - -- @@ -1251,6 +1265,14 @@ ALTER TABLE ONLY public.certification_log ADD CONSTRAINT certification_log_pkey PRIMARY KEY (id); +-- +-- Name: challenge_managers challenge_owners_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.challenge_managers + ADD CONSTRAINT challenge_owners_pkey PRIMARY KEY (id); + + -- -- Name: challenges challenges_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1307,6 +1329,14 @@ ALTER TABLE ONLY public.non_federal_partners ADD CONSTRAINT non_federal_partners_pkey PRIMARY KEY (id); +-- +-- Name: oban_jobs non_negative_priority; Type: CHECK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE public.oban_jobs + ADD CONSTRAINT non_negative_priority CHECK ((priority >= 0)) NOT VALID; + + -- -- Name: oban_jobs oban_jobs_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1506,10 +1536,75 @@ CREATE UNIQUE INDEX winners_phase_id_index ON public.phase_winners USING btree ( -- --- Name: oban_jobs oban_notify; Type: TRIGGER; Schema: public; Owner: - +-- Name: agencies agencies_parent_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.agencies + ADD CONSTRAINT agencies_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES public.agencies(id); + + +-- +-- Name: agency_members agency_members_agency_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.agency_members + ADD CONSTRAINT agency_members_agency_id_fkey FOREIGN KEY (agency_id) REFERENCES public.agencies(id); + + +-- +-- Name: agency_members agency_members_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.agency_members + ADD CONSTRAINT agency_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: certification_log certification_log_approver_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.certification_log + ADD CONSTRAINT certification_log_approver_id_fkey FOREIGN KEY (approver_id) REFERENCES public.users(id); + + +-- +-- Name: certification_log certification_log_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- -CREATE TRIGGER oban_notify AFTER INSERT ON public.oban_jobs FOR EACH ROW EXECUTE FUNCTION public.oban_jobs_notify(); +ALTER TABLE ONLY public.certification_log + ADD CONSTRAINT certification_log_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: challenge_managers challenge_owners_challenge_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.challenge_managers + ADD CONSTRAINT challenge_owners_challenge_id_fkey FOREIGN KEY (challenge_id) REFERENCES public.challenges(id); + + +-- +-- Name: challenge_managers challenge_owners_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.challenge_managers + ADD CONSTRAINT challenge_owners_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: challenges challenges_agency_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.challenges + ADD CONSTRAINT challenges_agency_id_fkey FOREIGN KEY (agency_id) REFERENCES public.agencies(id); + + +-- +-- Name: challenges challenges_sub_agency_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.challenges + ADD CONSTRAINT challenges_sub_agency_id_fkey FOREIGN KEY (sub_agency_id) REFERENCES public.agencies(id); -- @@ -1520,6 +1615,14 @@ ALTER TABLE ONLY public.challenges ADD CONSTRAINT challenges_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); +-- +-- Name: federal_partners federal_partners_agency_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.federal_partners + ADD CONSTRAINT federal_partners_agency_id_fkey FOREIGN KEY (agency_id) REFERENCES public.agencies(id); + + -- -- Name: federal_partners federal_partners_challenge_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -1528,6 +1631,14 @@ ALTER TABLE ONLY public.federal_partners ADD CONSTRAINT federal_partners_challenge_id_fkey FOREIGN KEY (challenge_id) REFERENCES public.challenges(id); +-- +-- Name: federal_partners federal_partners_sub_agency_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.federal_partners + ADD CONSTRAINT federal_partners_sub_agency_id_fkey FOREIGN KEY (sub_agency_id) REFERENCES public.agencies(id); + + -- -- Name: message_context_statuses message_context_statuses_message_context_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -1704,6 +1815,14 @@ ALTER TABLE ONLY public.timeline_events ADD CONSTRAINT timeline_events_challenge_id_fkey FOREIGN KEY (challenge_id) REFERENCES public.challenges(id); +-- +-- Name: users users_agency_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_agency_id_fkey FOREIGN KEY (agency_id) REFERENCES public.agencies(id); + + -- -- Name: phase_winners winners_phase_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e1550e7a..a9ae61ce 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -34,7 +34,21 @@ # require 'rails_helper' -RSpec.describe User do +RSpec.describe User, type: :model do + let(:gov_userinfo) do + [{ + "email" => "test@example.gov", + "sub" => SecureRandom.uuid + }] + end + + let(:non_gov_userinfo) do + [{ + "email" => "test@example.com", + "sub" => SecureRandom.uuid + }] + end + describe 'validations' do it 'validates presence of email' do user = described_class.new(email: nil) @@ -49,4 +63,55 @@ expect(user.active_session).to be_falsey end end + + describe 'user_from_userinfo' do + it 'finds user if one matches token' do + email = gov_userinfo[0]["email"] + token = gov_userinfo[0]["sub"] + + user = described_class.create!(email: email, token: token) + + found_user = User.user_from_userinfo(gov_userinfo) + + expect(user).to eq(found_user) + end + + it 'creates pending challenge_manager user if no matching token or email and .gov email' do + email = gov_userinfo[0]["email"] + token = gov_userinfo[0]["sub"] + + created_user = User.user_from_userinfo(gov_userinfo) + + expect(created_user.email).to eq(email) + expect(created_user.token).to eq(token) + expect(created_user.role).to eq("challenge_manager") + expect(created_user.status).to eq("pending") + end + + it 'creates active solver user if no matching token or email and non .gov email' do + email = non_gov_userinfo[0]["email"] + token = non_gov_userinfo[0]["sub"] + + created_user = User.user_from_userinfo(non_gov_userinfo) + + expect(created_user.email).to eq(email) + expect(created_user.token).to eq(token) + expect(created_user.role).to eq("solver") + expect(created_user.status).to eq("active") + end + + it 'update user with token if matching email but no token set (from admin creation)' do + email = gov_userinfo[0]["email"] + token = gov_userinfo[0]["sub"] + + user = described_class.create!(email: email) + expect(user.token).to eq(nil) + + updated_user = User.user_from_userinfo(gov_userinfo) + + expect(updated_user.id).to eq(user.id) + expect(updated_user.email).to eq(email) + expect(updated_user.token).to eq(token) + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f528642d..9a0bfd4a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'simplecov' require 'webmock/rspec' +require 'securerandom' SimpleCov.command_name 'RSpec' diff --git a/yarn.lock b/yarn.lock index 75fed022..cb0ba9d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -173,11 +173,11 @@ integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== "@types/node@*": - version "20.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" - integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== + version "22.1.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.1.0.tgz#6d6adc648b5e03f0e83c78dc788c2b037d0ad94b" + integrity sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw== dependencies: - undici-types "~5.26.4" + undici-types "~6.13.0" "@types/vinyl@^2.0.4": version "2.0.12" @@ -494,14 +494,14 @@ braces@^3.0.3, braces@~3.0.2: fill-range "^7.1.1" browserslist@^4.21.10: - version "4.23.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.1.tgz#ce4af0534b3d37db5c1a4ca98b9080f985041e96" - integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw== + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== dependencies: - caniuse-lite "^1.0.30001629" - electron-to-chromium "^1.4.796" - node-releases "^2.0.14" - update-browserslist-db "^1.0.16" + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" buffer-builder@^0.2.0: version "0.2.0" @@ -549,10 +549,10 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== -caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001629: - version "1.0.30001637" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001637.tgz#d9fab531493d9ef46a8ff305e9812190ac463f21" - integrity sha512-1x0qRI1mD1o9e+7mBI7XtzFAP4XszbHaVWsMiGbSPLYekKTJF7K+FNk6AsXH4sUpc+qrsI3pVgf1Jdl/uGkuSQ== +caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001646: + version "1.0.30001646" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz#d472f2882259ba032dd73ee069ff01bfd059b25d" + integrity sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw== cheerio-select@^2.1.0: version "2.1.0" @@ -948,10 +948,10 @@ each-props@^1.3.2: is-plain-object "^2.0.1" object.defaults "^1.1.0" -electron-to-chromium@^1.4.796: - version "1.4.812" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz#21b78709c5a13af5d5c688d135a22dcea7617acf" - integrity sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg== +electron-to-chromium@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz#cd477c830dd6fca41fbd5465c1ff6ce08ac22343" + integrity sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA== element-closest@^2.0.1: version "2.0.2" @@ -1629,9 +1629,9 @@ ignore@^5.2.0: integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== immutable@^4.0.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.6.tgz#6a05f7858213238e587fb83586ffa3b4b27f0447" - integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ== + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== indent-string@^4.0.0: version "4.0.0" @@ -1706,9 +1706,9 @@ is-buffer@^1.1.5: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-core-module@^2.13.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" - integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== dependencies: hasown "^2.0.2" @@ -2138,10 +2138,10 @@ next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== normalize-package-data@^2.3.2: version "2.5.0" @@ -3177,10 +3177,10 @@ undertaker@^1.2.1: object.reduce "^1.0.0" undertaker-registry "^1.0.0" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.13.0.tgz#e3e79220ab8c81ed1496b5812471afd7cf075ea5" + integrity sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg== union-value@^1.0.0: version "1.0.1" @@ -3213,10 +3213,10 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-browserslist-db@^1.0.16: - version "1.0.16" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" - integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: escalade "^3.1.2" picocolors "^1.0.1" From 2b8dff4cea5a77999dd49c840c635cfcf20340f1 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Mon, 5 Aug 2024 00:07:03 -0400 Subject: [PATCH 08/21] 22|24 Rubocop autofixes --- Rakefile | 6 +++++- app/controllers/application_controller.rb | 14 ++++++------- app/controllers/dashboard_controller.rb | 2 +- app/models/application_record.rb | 3 ++- app/models/user.rb | 25 +++++++++++------------ spec/models/user_spec.rb | 4 ++-- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Rakefile b/Rakefile index 84b021d0..786d01de 100644 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,11 @@ Rails.application.load_tasks namespace :cf do desc "Only run on the first application instance" task :on_first_instance do - instance_index = JSON.parse(ENV["VCAP_APPLICATION"])["instance_index"] rescue nil + instance_index = begin + JSON.parse(ENV.fetch("VCAP_APPLICATION", nil))["instance_index"] + rescue + nil + end exit(0) unless instance_index == 0 end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 46ac30fe..f53a6777 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,10 +4,10 @@ class ApplicationController < ActionController::Base helper_method :current_user, :logged_in? def current_user - if session[:userinfo] - user_token = session["userinfo"][0]["sub"] - @current_user ||= User.find_by(token: user_token) if user_token - end + return unless session[:userinfo] + + user_token = session["userinfo"][0]["sub"] + @current_user ||= User.find_by(token: user_token) if user_token end def logged_in? @@ -27,8 +27,8 @@ def sign_out end def redirect_if_logged_in(path = "/dashboard") - if logged_in? - redirect_to path, notice: "You are already logged in." - end + return unless logged_in? + + redirect_to path, notice: "You are already logged in." end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 2d803889..6049155d 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -2,4 +2,4 @@ class DashboardController < ApplicationController def index; end -end \ No newline at end of file +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 011fa12c..2dc662cc 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -3,8 +3,8 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class - before_create :set_inserted_at before_save :set_updated_at + before_create :set_inserted_at self.record_timestamps = false @@ -12,6 +12,7 @@ class ApplicationRecord < ActiveRecord::Base attribute :updated_at, :datetime, precision: 6 private + def set_inserted_at self.inserted_at ||= Time.current end diff --git a/app/models/user.rb b/app/models/user.rb index 30c8840c..9eeb9827 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -91,9 +91,9 @@ def self.user_from_userinfo(userinfo) email = userinfo[0]["email"] token = userinfo[0]["sub"] - if user = find_by(token: token) + if user = find_by(token:) user - elsif user = find_by(email: email) + elsif user = find_by(email:) update_admin_added_user(user, userinfo) else default_role_and_status = default_role_and_status_for_email(email) @@ -101,26 +101,25 @@ def self.user_from_userinfo(userinfo) default_status = default_role_and_status[1] user = create({ - email: email, - role: default_role, - token: token, - terms_of_use: nil, - privacy_guidelines: nil, - status: default_status - }) + email:, + role: default_role, + token:, + terms_of_use: nil, + privacy_guidelines: nil, + status: default_status + }) end end - private def self.update_admin_added_user(user, userinfo) - update(user.id, {token: userinfo[0]["sub"]}) + update(user.id, { token: userinfo[0]["sub"] }) end def self.default_role_and_status_for_email(email) if default_challenge_manager?(email) - ["challenge_manager", "pending"] + %w[challenge_manager pending] else - ["solver", "active"] + %w[solver active] end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a9ae61ce..95230d96 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -69,7 +69,7 @@ email = gov_userinfo[0]["email"] token = gov_userinfo[0]["sub"] - user = described_class.create!(email: email, token: token) + user = described_class.create!(email:, token:) found_user = User.user_from_userinfo(gov_userinfo) @@ -104,7 +104,7 @@ email = gov_userinfo[0]["email"] token = gov_userinfo[0]["sub"] - user = described_class.create!(email: email) + user = described_class.create!(email:) expect(user.token).to eq(nil) updated_user = User.user_from_userinfo(gov_userinfo) From 9ac306134937686dcd1e2c6bfa6f5ccc12b93ce8 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Mon, 5 Aug 2024 00:50:52 -0400 Subject: [PATCH 09/21] 22|24 Remaining Rubocop adjustments --- Rakefile | 4 +-- app/controllers/application_controller.rb | 2 +- app/models/user.rb | 35 ++++++++++++++--------- config/locales/en.yml | 1 + spec/models/user_spec.rb | 10 +++---- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Rakefile b/Rakefile index 786d01de..f925e9ee 100644 --- a/Rakefile +++ b/Rakefile @@ -9,12 +9,12 @@ Rails.application.load_tasks namespace :cf do desc "Only run on the first application instance" - task :on_first_instance do + task on_first_instance: :environment do instance_index = begin JSON.parse(ENV.fetch("VCAP_APPLICATION", nil))["instance_index"] rescue nil end - exit(0) unless instance_index == 0 + exit(0) unless instance_index.zero? end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f53a6777..34c3a93f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,6 +29,6 @@ def sign_out def redirect_if_logged_in(path = "/dashboard") return unless logged_in? - redirect_to path, notice: "You are already logged in." + redirect_to path, notice: I18n.t("already_logged_in_notice") end end diff --git a/app/models/user.rb b/app/models/user.rb index 9eeb9827..3e49593b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -91,23 +91,12 @@ def self.user_from_userinfo(userinfo) email = userinfo[0]["email"] token = userinfo[0]["sub"] - if user = find_by(token:) + if (user = find_by(token:)) user - elsif user = find_by(email:) + elsif (user = find_by(email:)) update_admin_added_user(user, userinfo) else - default_role_and_status = default_role_and_status_for_email(email) - default_role = default_role_and_status[0] - default_status = default_role_and_status[1] - - user = create({ - email:, - role: default_role, - token:, - terms_of_use: nil, - privacy_guidelines: nil, - status: default_status - }) + create_user_from_userinfo(userinfo) end end @@ -115,6 +104,24 @@ def self.update_admin_added_user(user, userinfo) update(user.id, { token: userinfo[0]["sub"] }) end + def self.create_user_from_userinfo(userinfo) + email = userinfo[0]["email"] + token = userinfo[0]["sub"] + + default_role_and_status = default_role_and_status_for_email(email) + default_role = default_role_and_status[0] + default_status = default_role_and_status[1] + + create({ + email:, + role: default_role, + token:, + terms_of_use: nil, + privacy_guidelines: nil, + status: default_status + }) + end + def self.default_role_and_status_for_email(email) if default_challenge_manager?(email) %w[challenge_manager pending] diff --git a/config/locales/en.yml b/config/locales/en.yml index e0a9572e..86bbf980 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -31,3 +31,4 @@ en: hello: "Hello world" please_try_again: "Please try again." login_error: "There was an issue with logging in. Please try again." + already_logged_in_notice: "You are already logged in." diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 95230d96..5c41693e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -71,7 +71,7 @@ user = described_class.create!(email:, token:) - found_user = User.user_from_userinfo(gov_userinfo) + found_user = described_class.user_from_userinfo(gov_userinfo) expect(user).to eq(found_user) end @@ -80,7 +80,7 @@ email = gov_userinfo[0]["email"] token = gov_userinfo[0]["sub"] - created_user = User.user_from_userinfo(gov_userinfo) + created_user = described_class.user_from_userinfo(gov_userinfo) expect(created_user.email).to eq(email) expect(created_user.token).to eq(token) @@ -92,7 +92,7 @@ email = non_gov_userinfo[0]["email"] token = non_gov_userinfo[0]["sub"] - created_user = User.user_from_userinfo(non_gov_userinfo) + created_user = described_class.user_from_userinfo(non_gov_userinfo) expect(created_user.email).to eq(email) expect(created_user.token).to eq(token) @@ -105,9 +105,9 @@ token = gov_userinfo[0]["sub"] user = described_class.create!(email:) - expect(user.token).to eq(nil) + expect(user.token).to be_nil - updated_user = User.user_from_userinfo(gov_userinfo) + updated_user = described_class.user_from_userinfo(gov_userinfo) expect(updated_user.id).to eq(user.id) expect(updated_user.email).to eq(email) From f645e0e2913731c7fd002d2dec45ad729fe59e5e Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Mon, 5 Aug 2024 20:10:49 -0400 Subject: [PATCH 10/21] 22|24 Fix rubocop issues --- app/models/login_gov.rb | 1 - spec/models/user_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/login_gov.rb b/app/models/login_gov.rb index ac88f861..7d4e398e 100644 --- a/app/models/login_gov.rb +++ b/app/models/login_gov.rb @@ -21,7 +21,6 @@ def initialize(msg, code:, body:) attr_reader :config def initialize(config = Rails.configuration.login_gov_oidc) - puts config.inspect @config = config.freeze.dup end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5c41693e..4de05d55 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -34,7 +34,7 @@ # require 'rails_helper' -RSpec.describe User, type: :model do +RSpec.describe User do let(:gov_userinfo) do [{ "email" => "test@example.gov", From 29608f646abb2c911406f9d485342ab9e98077e9 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Tue, 6 Aug 2024 11:41:42 -0400 Subject: [PATCH 11/21] 22|24 Fix failing tests --- spec/requests/sessions_request_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/requests/sessions_request_spec.rb b/spec/requests/sessions_request_spec.rb index 0e752824..34220665 100644 --- a/spec/requests/sessions_request_spec.rb +++ b/spec/requests/sessions_request_spec.rb @@ -13,9 +13,9 @@ end it "delete session logs the user out" do - skip "not implemented" - delete "/session/:id" - assert_response :success + # skip "not implemented" + delete "/session" + assert_response :redirect end it "get /auth/result without params redirects to login" do @@ -34,9 +34,11 @@ code = "ABC123" login_gov = instance_double(LoginGov) allow(LoginGov).to receive(:new).and_return(login_gov) - allow(login_gov).to receive(:exchange_token_from_auth_result).with(code).and_return({ email: "test@example.com" }) + allow(login_gov).to receive(:exchange_token_from_auth_result).with(code).and_return( + [{ email: "test@example.com", sub: "sub" }] + ) get "/auth/result", params: { code: } - expect(response).to have_http_status(:ok) - expect(response).to render_template(:result) + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to("/dashboard") end end From 1715b88fd1577633c433066ed9731ed42d280e28 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Tue, 6 Aug 2024 11:42:44 -0400 Subject: [PATCH 12/21] 22|24 Revert tool-versions system change --- .tool-versions | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.tool-versions b/.tool-versions index 70b96cf5..e87d7b95 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -ruby system -nodejs system -yarn system +ruby 3.2.4 +nodejs 20.15.1 +yarn 1.22.22 From 006c02ad1dd12ef77d15876fdc633717d809979e Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Tue, 6 Aug 2024 13:37:22 -0400 Subject: [PATCH 13/21] 22|24 Add back development.rb --- config/environments/development.rb | 78 ++++++++++++++++++++++++++ spec/requests/sessions_request_spec.rb | 1 - 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 config/environments/development.rb diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 00000000..cd4e78a2 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions + config.action_controller.raise_on_missing_callback_actions = true +end \ No newline at end of file diff --git a/spec/requests/sessions_request_spec.rb b/spec/requests/sessions_request_spec.rb index 34220665..8466e041 100644 --- a/spec/requests/sessions_request_spec.rb +++ b/spec/requests/sessions_request_spec.rb @@ -13,7 +13,6 @@ end it "delete session logs the user out" do - # skip "not implemented" delete "/session" assert_response :redirect end From 830b6353d91e6cafc5fbc6916b5a1f6492405520 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Tue, 6 Aug 2024 14:10:32 -0400 Subject: [PATCH 14/21] Locked simplecov version for codeclimate error --- Gemfile | 2 +- Gemfile.lock | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 5024a7d0..d3e33ea2 100644 --- a/Gemfile +++ b/Gemfile @@ -90,6 +90,6 @@ group :test do gem "capybara" gem "selenium-webdriver" gem "rspec_junit_formatter" - gem "simplecov" + gem 'simplecov', '~> 0.17.0', require: false gem "rails-controller-testing" end diff --git a/Gemfile.lock b/Gemfile.lock index d8f1e2f8..b7752961 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,7 +84,7 @@ GEM base64 (0.2.0) bigdecimal (3.1.8) bindex (0.8.1) - bootsnap (1.18.3) + bootsnap (1.18.4) msgpack (~> 1.2) builder (3.3.0) capybara (3.40.0) @@ -276,7 +276,7 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) + rubocop-ast (1.32.0) parser (>= 3.3.1.0) rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) @@ -288,9 +288,9 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) rubocop-rake (0.6.0) rubocop (~> 1.0) - rubocop-rspec (3.0.3) + rubocop-rspec (3.0.4) rubocop (~> 1.61) - ruby-lsp (0.17.10) + ruby-lsp (0.17.11) language_server-protocol (~> 3.17.0) prism (>= 0.29.0, < 0.31) rbs (>= 3, < 4) @@ -303,13 +303,12 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - simplecov (0.22.0) + simplecov (0.17.1) docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.4) - sorbet-runtime (0.5.11504) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + sorbet-runtime (0.5.11511) stimulus-rails (1.3.3) railties (>= 6.0.0) stringio (3.1.1) @@ -376,7 +375,7 @@ DEPENDENCIES rubocop-rspec ruby-lsp selenium-webdriver - simplecov + simplecov (~> 0.17.0) stimulus-rails turbo-rails tzinfo-data From 5ba185495f3937a5947e79a3e0d051cca0bfb128 Mon Sep 17 00:00:00 2001 From: Bilal Hankins Date: Wed, 7 Aug 2024 12:02:28 -0500 Subject: [PATCH 15/21] created shared_examples for redundant tests --- app/models/security_log.rb | 1 + spec/models/security_log_spec.rb | 3 ++- spec/models/user_spec.rb | 6 +----- spec/rails_helper.rb | 2 +- spec/support/shared_examples/validations.rb | 10 ++++++++++ 5 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 spec/support/shared_examples/validations.rb diff --git a/app/models/security_log.rb b/app/models/security_log.rb index ee984b12..860c4e69 100644 --- a/app/models/security_log.rb +++ b/app/models/security_log.rb @@ -25,6 +25,7 @@ class SecurityLog < ApplicationRecord status_change account_update role_change accessed_site session_duration create read update delete submit renewal_request ] } + validates :logged_at, presence: true before_validation :set_logged_at, on: :create diff --git a/spec/models/security_log_spec.rb b/spec/models/security_log_spec.rb index baec690d..c520b5c6 100644 --- a/spec/models/security_log_spec.rb +++ b/spec/models/security_log_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # == Schema Information # # Table name: security_log @@ -17,7 +18,7 @@ # require 'rails_helper' -RSpec.describe SecurityLog, type: :model do +RSpec.describe SecurityLog do describe 'Security Log validations' do it 'validates presence of action' do security_log = described_class.new(action: nil) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e1550e7a..f2694166 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -36,11 +36,7 @@ RSpec.describe User do describe 'validations' do - it 'validates presence of email' do - user = described_class.new(email: nil) - expect(user).not_to be_valid - expect(user.errors[:email]).to include("can't be blank") - end + it_behaves_like 'a model with required attributes', [:email] end describe 'default values' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index a15455f3..caa1f429 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -20,7 +20,7 @@ # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. # -# Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f } +Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f } # Checks for pending migrations and applies them before tests are run. # If you are not using ActiveRecord, you can remove these lines. diff --git a/spec/support/shared_examples/validations.rb b/spec/support/shared_examples/validations.rb new file mode 100644 index 00000000..7ca9cb5c --- /dev/null +++ b/spec/support/shared_examples/validations.rb @@ -0,0 +1,10 @@ +# spec/support/shared_examples/validations.rb +RSpec.shared_examples 'a model with required attributes' do |attributes| + attributes.each do |attribute| + it "validates presence of #{attribute}" do + model = described_class.new(attribute => nil) + expect(model).not_to be_valid + expect(model.errors[attribute]).to include("can't be blank") + end + end +end From dad26236e424dbffa300b513ae0b3e00b443cfa9 Mon Sep 17 00:00:00 2001 From: Bilal Hankins Date: Thu, 8 Aug 2024 09:34:37 -0500 Subject: [PATCH 16/21] Update app/models/security_log.rb Co-authored-by: Stephen Chudleigh --- app/models/security_log.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/security_log.rb b/app/models/security_log.rb index 860c4e69..8708d941 100644 --- a/app/models/security_log.rb +++ b/app/models/security_log.rb @@ -21,10 +21,11 @@ class SecurityLog < ApplicationRecord belongs_to :originator, class_name: 'User', optional: true - validates :action, presence: true, inclusion: { in: %w[ + ROLES = %w[ status_change account_update role_change accessed_site session_duration create read update delete submit renewal_request - ] } + ].freeze + validates :action, presence: true, inclusion: { in: ROLES } validates :logged_at, presence: true before_validation :set_logged_at, on: :create From c40f9f97270fa6fc81c9b1ea0b956b91d3d26de1 Mon Sep 17 00:00:00 2001 From: Bilal Hankins Date: Thu, 8 Aug 2024 11:04:44 -0500 Subject: [PATCH 17/21] updated tests for logged_at validation --- app/models/security_log.rb | 5 +++++ spec/models/security_log_spec.rb | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/models/security_log.rb b/app/models/security_log.rb index 8708d941..fb55cd57 100644 --- a/app/models/security_log.rb +++ b/app/models/security_log.rb @@ -25,6 +25,7 @@ class SecurityLog < ApplicationRecord status_change account_update role_change accessed_site session_duration create read update delete submit renewal_request ].freeze + validates :action, presence: true, inclusion: { in: ROLES } validates :logged_at, presence: true @@ -42,6 +43,10 @@ class SecurityLog < ApplicationRecord attribute :target_identifier, :string attribute :logged_at, :datetime + def self.timestamp_attributes_for_create + super + %w[logged_at] + end + private def set_logged_at diff --git a/spec/models/security_log_spec.rb b/spec/models/security_log_spec.rb index c520b5c6..d1cece64 100644 --- a/spec/models/security_log_spec.rb +++ b/spec/models/security_log_spec.rb @@ -20,10 +20,10 @@ RSpec.describe SecurityLog do describe 'Security Log validations' do - it 'validates presence of action' do - security_log = described_class.new(action: nil) - expect(security_log).not_to be_valid - expect(security_log.errors[:action]).to include("can't be blank") + it 'sets logged_at before validation on create' do + security_log = described_class.new(action: 'create') + expect(security_log).to be_valid + expect(security_log.logged_at).not_to be_nil end end end From 924e72d4666729488017d150370adafea8101cd5 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Fri, 9 Aug 2024 14:11:39 -0400 Subject: [PATCH 18/21] 22 PR feedback. Mil emails. Timestamp changes --- app/models/application_record.rb | 19 +++++--------- app/models/user.rb | 9 ++----- spec/models/user_spec.rb | 45 ++++++++++++++++++++++++++++++++ spec/rails_helper.rb | 2 ++ 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 2dc662cc..caea3da0 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -3,21 +3,14 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class - before_save :set_updated_at - before_create :set_inserted_at - - self.record_timestamps = false - attribute :inserted_at, :datetime, precision: 6 attribute :updated_at, :datetime, precision: 6 - private - - def set_inserted_at - self.inserted_at ||= Time.current - end - - def set_updated_at - self.updated_at ||= Time.current + private_class_method + # created_at timestamp is currently overridden to inserted_at due to shared Phoenix database + def self.timestamp_attributes_for_create + # only strings allowed here, symbols won't work, see below commit for more details + # https://github.com/rails/rails/commit/2b5dacb43dd92e98e1fd240a80c2a540ed380257 + super << 'inserted_at' end end diff --git a/app/models/user.rb b/app/models/user.rb index 3e49593b..eccb03a5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -108,9 +108,7 @@ def self.create_user_from_userinfo(userinfo) email = userinfo[0]["email"] token = userinfo[0]["sub"] - default_role_and_status = default_role_and_status_for_email(email) - default_role = default_role_and_status[0] - default_status = default_role_and_status[1] + default_role, default_status = default_role_and_status_for_email(email) create({ email:, @@ -131,9 +129,6 @@ def self.default_role_and_status_for_email(email) end def self.default_challenge_manager?(email) - escaped_gov_tld = Regexp.escape('.gov') - matching_gov_string = ".*#{escaped_gov_tld}$" - gov_regex = Regexp.new(matching_gov_string) - gov_regex.match?(email) + /\.(gov|mil)$/.match?(email) end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4de05d55..46bd772f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -42,6 +42,13 @@ }] end + let(:mil_userinfo) do + [{ + "email" => "test@example.mil", + "sub" => SecureRandom.uuid + }] + end + let(:non_gov_userinfo) do [{ "email" => "test@example.com", @@ -64,6 +71,32 @@ end end + describe 'timestamps' do + it 'properly sets inserted_at and updated_at' do + email = gov_userinfo[0]["email"] + token = gov_userinfo[0]["sub"] + + user = described_class.create!(email:, token:) + + expect(user.inserted_at).not_to be_nil + expect(user.updated_at).not_to be_nil + + expect(user.inserted_at).to be_within(1.second).of(Time.current) + expect(user.updated_at).to be_within(1.second).of(Time.current) + + original_inserted_at = user.inserted_at + original_updated_at = user.updated_at + + travel_to 1.hour.from_now do + user.update!(email: 'new-email@example.com') + + expect(user.inserted_at).to eq(original_inserted_at) + expect(user.updated_at).to be > original_updated_at + expect(user.updated_at).to be_within(1.second).of(Time.current) + end + end + end + describe 'user_from_userinfo' do it 'finds user if one matches token' do email = gov_userinfo[0]["email"] @@ -88,6 +121,18 @@ expect(created_user.status).to eq("pending") end + it 'creates pending challenge_manager user if no matching token or email and .mil email' do + email = mil_userinfo[0]["email"] + token = mil_userinfo[0]["sub"] + + created_user = described_class.user_from_userinfo(mil_userinfo) + + expect(created_user.email).to eq(email) + expect(created_user.token).to eq(token) + expect(created_user.role).to eq("challenge_manager") + expect(created_user.status).to eq("pending") + end + it 'creates active solver user if no matching token or email and non .gov email' do email = non_gov_userinfo[0]["email"] token = non_gov_userinfo[0]["sub"] diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index ea28bc2b..0dad5f5b 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -66,4 +66,6 @@ config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") + + config.include ActiveSupport::Testing::TimeHelpers end From d46abdbae6115fa8c63f62a19479f2b4c43b87af Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Fri, 9 Aug 2024 14:15:45 -0400 Subject: [PATCH 19/21] 22 Fix trailing whitespace errors --- app/models/application_record.rb | 5 ++--- spec/models/user_spec.rb | 6 +++--- spec/rails_helper.rb | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/models/application_record.rb b/app/models/application_record.rb index caea3da0..4e9fa68d 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -6,11 +6,10 @@ class ApplicationRecord < ActiveRecord::Base attribute :inserted_at, :datetime, precision: 6 attribute :updated_at, :datetime, precision: 6 - private_class_method # created_at timestamp is currently overridden to inserted_at due to shared Phoenix database def self.timestamp_attributes_for_create # only strings allowed here, symbols won't work, see below commit for more details - # https://github.com/rails/rails/commit/2b5dacb43dd92e98e1fd240a80c2a540ed380257 - super << 'inserted_at' + # https://github.com/rails/rails/commit/2b5dacb43dd92e98e1fd240a80c2a540ed380257 + super << 'inserted_at' end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 46bd772f..6beee664 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -80,16 +80,16 @@ expect(user.inserted_at).not_to be_nil expect(user.updated_at).not_to be_nil - + expect(user.inserted_at).to be_within(1.second).of(Time.current) - expect(user.updated_at).to be_within(1.second).of(Time.current) + expect(user.updated_at).to be_within(1.second).of(Time.current) original_inserted_at = user.inserted_at original_updated_at = user.updated_at travel_to 1.hour.from_now do user.update!(email: 'new-email@example.com') - + expect(user.inserted_at).to eq(original_inserted_at) expect(user.updated_at).to be > original_updated_at expect(user.updated_at).to be_within(1.second).of(Time.current) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 0dad5f5b..be870792 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -66,6 +66,6 @@ config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") - + config.include ActiveSupport::Testing::TimeHelpers end From 058e338ce0a506846892758830988ae0dc9fd37a Mon Sep 17 00:00:00 2001 From: Stephen Chudleigh Date: Fri, 9 Aug 2024 11:15:50 -0700 Subject: [PATCH 20/21] fix User attributes, add shared example spec --- app/models/user.rb | 3 +-- spec/models/security_log_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7bd1bd84..33049ec8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,7 +47,7 @@ class User < ApplicationRecord has_many :submission_documents, class_name: 'Submissions::Document', dependent: :destroy has_many :message_context_statuses, dependent: :destroy - attribute :role, :string, default: -> { self[:role] } + attribute :role, :string attribute :status, :string, default: 'pending' attribute :finalized, :boolean, default: true attribute :display, :boolean, default: true @@ -80,7 +80,6 @@ class User < ApplicationRecord attribute :renewal_request, :string - attribute :created_at, :datetime, precision: 6 attribute :updated_at, :datetime, precision: 6 validates :email, presence: true diff --git a/spec/models/security_log_spec.rb b/spec/models/security_log_spec.rb index d1cece64..b2a3b39d 100644 --- a/spec/models/security_log_spec.rb +++ b/spec/models/security_log_spec.rb @@ -20,6 +20,8 @@ RSpec.describe SecurityLog do describe 'Security Log validations' do + it_behaves_like 'a model with required attributes', [:action] + it 'sets logged_at before validation on create' do security_log = described_class.new(action: 'create') expect(security_log).to be_valid From b68d881684f4f1623605c3a27341fdddd7b50f07 Mon Sep 17 00:00:00 2001 From: Chris Preisinger Date: Fri, 9 Aug 2024 14:40:10 -0400 Subject: [PATCH 21/21] 22 Reverted structure.sql22 Reverted structure.sql --- db/structure.sql | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/db/structure.sql b/db/structure.sql index b6ebe855..65453f93 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -98,18 +98,6 @@ CREATE SEQUENCE public.agency_members_id_seq ALTER SEQUENCE public.agency_members_id_seq OWNED BY public.agency_members.id; --- --- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.ar_internal_metadata ( - key character varying NOT NULL, - value character varying, - created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL -); - - -- -- Name: certification_log; Type: TABLE; Schema: public; Owner: - -- @@ -1249,14 +1237,6 @@ ALTER TABLE ONLY public.agency_members ADD CONSTRAINT agency_members_pkey PRIMARY KEY (id); --- --- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.ar_internal_metadata - ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key); - - -- -- Name: certification_log certification_log_pkey; Type: CONSTRAINT; Schema: public; Owner: - --