Skip to content

Commit

Permalink
249 - Fix invalid access token error (#261)
Browse files Browse the repository at this point in the history
* handling InvalidAuthenticityToken errors

* expanded multi_concern_interaction test

* removed addition covered by another PR

* WIP - building up application access tests

* Tests now passing, including feature tests for authorization

* removed VSCode files

* added VSCode files to .gitignore

* added blank line to end of .gitignore

* removing extraneous comments

* removed accidental change to admin form

* removed accidental change to artworks show view
  • Loading branch information
Janell-Huyck authored Jan 23, 2024
1 parent 186e4f7 commit 04f93c8
Show file tree
Hide file tree
Showing 20 changed files with 452 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ yarn-debug.log*
# Ignore Vendor Local
/vendor/bundle --local
/vendor/bundle

# Ignore the files specific to VSCode
.vscode/*
1 change: 1 addition & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def login; end

def validate
if check_credentials(params[:username], params[:password])
reset_session
session[:admin] = true
redirect_to publications_path
else
Expand Down
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class ApplicationController < ActionController::Base
include Pagy::Backend
include ExceptionHandlingManager
include UserAuthentication

prepend_before_action :check_date
Expand Down
36 changes: 36 additions & 0 deletions app/controllers/concerns/exception_handling_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

# ExceptionHandlingManager is a concern designed to centralize and manage
# the handling of various application-specific exceptions.
#
# Currently, it includes handling for:
# - ActionController::InvalidAuthenticityToken
#
# Future additions may include but are not limited to:
# - RecordNotFound
# - TimeoutErrors
# - CustomApplicationErrors
#
# This concern is included in the ApplicationController, so all controllers inherit
module ExceptionHandlingManager
extend ActiveSupport::Concern

included do
rescue_from ActionController::InvalidAuthenticityToken, with: :handle_invalid_token
end

private

def handle_invalid_token(exception)
Rails.logger.warn("InvalidAuthenticityToken occurred: #{exception}")
user_was_admin = session[:admin]
reset_session
flash.keep[:danger] = 'Your session has expired. Please log in again.'

if user_was_admin
redirect_to manage_path
else
redirect_to root_path
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# app/controllers/pages_controller.rb
class PagesController < ApplicationController
skip_before_action :check_date
skip_before_action :check_date, :require_authenticated_user
ALLOWED_PAGES = %w[closed finished].freeze

def show
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/submitters_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def create
if @submitter.save
reset_session
session[:submitter_id] = @submitter.id
# Change to home page

flash.keep[:success] = 'Your account was successfully created.'
format.html { redirect_to publications_path }
format.json { render :show, status: :created, location: @submitter }
Expand Down
69 changes: 69 additions & 0 deletions spec/controllers/concerns/exception_handling_manager_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe ExceptionHandlingManager, type: :controller do
controller(ApplicationController) do
include ExceptionHandlingManager

def index
render plain: 'Hello, world!'
end
end

let(:specific_exception) { ActionController::InvalidAuthenticityToken.new('Test Exception Message') }
let(:submitter) { FactoryBot.create(:submitter) }
let(:submitter_session) { { submitter_id: submitter.id } }
let(:admin_session) { { admin: true } }

before do
routes.draw { get 'index' => 'anonymous#index' }
allow(controller).to receive(:index).and_raise(specific_exception)
end

context 'as a submitter' do
let(:session) { submitter_session }

it 'logs the exception to the Rails logger' do
allow(Rails.logger).to receive(:warn)
get(:index, session:)
expect(Rails.logger).to have_received(:warn).with('InvalidAuthenticityToken occurred: Test Exception Message')
end

it 'resets the session' do
get(:index, session:)
expect(controller.session[:admin]).to be_nil
expect(controller.session[:submitter_id]).to be_nil
end

it 'sets a flash message and redirects to the root path' do
get(:index, session:)
expect(response.status).to eq(302)
expect(response).to redirect_to(root_path)
expect(flash[:danger]).to eq('Your session has expired. Please log in again.')
end
end

context 'as an admin' do
let(:session) { admin_session }

it 'logs the exception to the Rails logger' do
allow(Rails.logger).to receive(:warn)
get(:index, session:)
expect(Rails.logger).to have_received(:warn).with('InvalidAuthenticityToken occurred: Test Exception Message')
end

it 'resets the session' do
get(:index, session:)
expect(controller.session[:admin]).to be_nil
expect(controller.session[:submitter_id]).to be_nil
end

it 'sets a flash message and redirects to the manage path' do
get(:index, session:)
expect(response.status).to eq(302)
expect(response).to redirect_to(manage_path)
expect(flash[:danger]).to eq('Your session has expired. Please log in again.')
end
end
end
6 changes: 4 additions & 2 deletions spec/controllers/submitters_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
context 'with valid params' do
it 'clears the old session' do
post :create, params: { submitter: valid_attributes }, session: old_session
expect(session[:submitter_id]).not_to be_nil
expect(session[:submitter_id]).to eq(Submitter.last.id)
expect(session[:submitter_id]).to_not be(old_submitter.id)
expect(session[:submitter_id]).to_not be_nil
expect(session[:some_old_key]).to be_nil
end

Expand All @@ -63,6 +63,8 @@
it 'redirects to the publications show page' do
post :create, params: { submitter: valid_attributes }, session: {}
expect(response).to redirect_to(publications_path)
expect(flash[:success]).to eql 'Your account was successfully created.'
expect(flash[:error]).to be_nil
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature do
let(:submitter) { FactoryBot.build(:submitter) }

before do
ActionController::Base.allow_forgery_protection = true
end

after do
ActionController::Base.allow_forgery_protection = false
end

context 'when an invalid authenticity token is provided along with a missing or invalid session ID' do
it 'redirects to the root page with an error message' do
# The user never tries to log in, otherwise they would have a session.
# The user never gets to fill in information, so there is no authenticity token to make invalid.
visit publications_path
expect_to_be_on_root_page_with_login_message
end

it 'allows the user to log in after having been redirected to the root page with an error message' do
visit publications_path
visit_publications_page_as_submitter(submitter)
expect(page).to have_current_path(publications_path)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Handling of invalid authenticity token', type: :feature, js: true do
let(:submitter) { FactoryBot.create(:submitter) }

before do
ActionController::Base.allow_forgery_protection = true
end

after do
ActionController::Base.allow_forgery_protection = false
end

context 'when trying to log in' do
context 'as a submitter' do
it 'triggers an inauthentic token error and redirects to the root path' do
visit root_path
fill_in('submitter[first_name]', with: submitter.first_name)
fill_in('submitter[last_name]', with: submitter.last_name)
find_by_id('submitter_college').find(:xpath, "option[#{submitter.college}]").select_option
fill_in('submitter[department]', with: submitter.department)
fill_in('submitter[mailing_address]', with: submitter.mailing_address)
fill_in('submitter[phone_number]', with: submitter.phone_number)
fill_in('submitter[email_address]', with: submitter.email_address)
make_authenticity_token_invalid
click_on('Next')
expect_to_be_on_root_page_with_expired_error
end
end

context 'as an admin' do
before do
allow(ENV).to receive(:fetch).and_call_original
end
context 'when the user was previously in an admin session' do
# We are assuming that the previous session was an admin session.
# If it were not, then the user would be redirected to the root path.
it 'triggers an inauthentic token error and redirects to the manage path' do
visit_publications_page_as_admin
visit manage_path
fill_in('username', with: ENV.fetch('ADMIN_USERNAME', nil))
fill_in('password', with: ENV.fetch('ADMIN_PASSWORD', nil))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_manage_page_with_expired_error
end
end

context 'when the user was not previously in an admin session' do
it 'triggers an inauthentic token error and redirects to the root path' do
# We are assuming that the session[:admin] is not already set.
# If it were, then the user would be redirected to the manage path.
visit manage_path
fill_in('username', with: ENV.fetch('ADMIN_USERNAME', nil))
fill_in('password', with: ENV.fetch('ADMIN_PASSWORD', nil))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_root_page_with_expired_error
end
end
end
end

context 'when trying to add a publication' do
context 'as a submitter' do
it 'triggers an inauthentic token error and redirects to the root path' do
visit_publications_page_as_submitter(submitter)
first('a', text: 'New').click # There is no authenticity check for this button
expect(page).to have_current_path(new_artwork_path)
make_authenticity_token_invalid # New artwork will submit an authenticity token.

fill_in('artwork[author_first_name][]', with: 'Test')
fill_in('artwork[author_last_name][]', with: 'Artist')
fill_in('artwork[work_title]', with: 'Test Artwork')
click_on('Submit')
expect_to_be_on_root_page_with_expired_error
end
end

context 'as an admin' do
before do
FactoryBot.create(:artwork) # create an artwork to edit
end
it 'triggers an inauthentic token error and redirects to the manage path' do
visit_publications_page_as_admin
expect(page).to have_current_path(publications_path)
find('i.fas.fa-edit', match: :first).click
expect(page).to have_current_path(edit_artwork_path(Artwork.first))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_manage_page_with_expired_error
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature do
context 'when a user is not logged in' do
# No submitter_id or admin=true
it 'redirects to a login page with a message to submit your information' do
visit publications_path
expect(current_path).to eq(root_path)
expect(page).to have_content('You must submit your information first.')
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature, js: true do
before do
ActionController::Base.allow_forgery_protection = true
allow(ENV).to receive(:fetch).and_call_original
allow(ENV).to receive(:fetch).with('EXPIRATION_DATE').and_return('Jan 01 2000')
end

after do
ActionController::Base.allow_forgery_protection = false
end

context 'when accessing outside open dates and with an invalid authenticity token' do
context 'as an admin' do
before do
FactoryBot.create(:artwork) # create an artwork to edit
end
it 'triggers an inauthentic token error and redirects to the root path' do
visit_publications_page_as_admin
expect(page).to have_current_path(publications_path)
find('i.fas.fa-edit', match: :first).click
expect(page).to have_current_path(edit_artwork_path(Artwork.first))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_manage_page_with_expired_error
end
end

context 'as a submitter' do
it 'redirects to the closed page' do
visit root_path
# The user never gets to fill in information, so there is no authenticity token to make invalid.
expect(page).to have_content('The deadline for submissions has passed.')
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature do
let(:submitter) { FactoryBot.build(:submitter) }

before do
allow(ENV).to receive(:fetch).with('EXPIRATION_DATE').and_return('Jan 01 2000')
end

context 'when accessing outside open dates and with a missing or invalid session ID' do
it 'redirects to the closed page' do
visit publications_path
expect(page).to have_content('The deadline for submissions has passed.')
end
end
end
Loading

0 comments on commit 04f93c8

Please sign in to comment.