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 969dceae..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) @@ -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) @@ -272,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) @@ -284,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.9) + ruby-lsp (0.17.11) language_server-protocol (~> 3.17.0) prism (>= 0.29.0, < 0.31) rbs (>= 3, < 4) @@ -299,12 +303,12 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - simplecov (0.13.0) - docile (~> 1.1.0) + simplecov (0.17.1) + docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - sorbet-runtime (0.5.11492) + sorbet-runtime (0.5.11511) stimulus-rails (1.3.3) railties (>= 6.0.0) stringio (3.1.1) @@ -335,14 +339,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 @@ -371,7 +375,7 @@ DEPENDENCIES rubocop-rspec ruby-lsp selenium-webdriver - simplecov + simplecov (~> 0.17.0) stimulus-rails turbo-rails tzinfo-data @@ -382,4 +386,4 @@ RUBY VERSION ruby 3.2.4p170 BUNDLED WITH - 2.4.6 + 2.5.9 diff --git a/Rakefile b/Rakefile index 84b021d0..f925e9ee 100644 --- a/Rakefile +++ b/Rakefile @@ -9,8 +9,12 @@ 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 - exit(0) unless instance_index == 0 + 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.zero? end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7944f9f9..34c3a93f 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 + 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? + !!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") + return unless logged_in? + + redirect_to path, notice: I18n.t("already_logged_in_notice") + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb new file mode 100644 index 00000000..6049155d --- /dev/null +++ b/app/controllers/dashboard_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class DashboardController < ApplicationController + def index; end +end 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..4e9fa68d 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,4 +2,14 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class + + attribute :inserted_at, :datetime, precision: 6 + attribute :updated_at, :datetime, precision: 6 + + # 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 33049ec8..eccb03a5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -80,7 +80,55 @@ class User < ApplicationRecord attribute :renewal_request, :string - 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:)) + user + elsif (user = find_by(email:)) + update_admin_added_user(user, userinfo) + else + create_user_from_userinfo(userinfo) + end + end + + 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, default_status = default_role_and_status_for_email(email) + + 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] + else + %w[solver active] + end + end + + def self.default_challenge_manager?(email) + /\.(gov|mil)$/.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 @@ +