Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[158] Add role-based permission check to routes #174

Merged
merged 13 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ jobs:
rspec:
executor:
name: test_executor

environment:
PHOENIX_URI: http://localhost:4000

parallelism: 1

Expand Down
3 changes: 2 additions & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ exclude_patterns:
- db/
- vendor/
- "**/vendor/**/*"
- app/assets/images/
- app/assets/images/
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
- spec/**/*
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Metrics/BlockLength:
# By default, exclude the `#refine` method, as it tends to have larger
# associated blocks.
- refine
- dashboard_cards_by_role
Exclude:
# Specs by nature tend to have lengthy, nested blocks
- '*.gemspec'
Expand Down
13 changes: 13 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base
helper_method :current_user, :logged_in?

before_action :check_session_expiration, except: [:sign_out]
before_action :redirect_solvers_to_phoenix

def current_user
return unless session[:userinfo]
Expand All @@ -16,6 +17,18 @@ def logged_in?
!!current_user
end

def authorize_user(role)
return if current_user&.role == role || %w[super_admin admin].include?(current_user&.role)

redirect_to dashboard_path, alert: I18n.t("access_denied")
end

def redirect_solvers_to_phoenix
return unless current_user&.role == 'solver'

redirect_to Rails.configuration.phx_interop[:phx_uri], allow_other_host: true
end

def sign_in(login_userinfo)
user = User.user_from_userinfo(login_userinfo)

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/evaluation_forms_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class EvaluationFormsController < ApplicationController

before_action -> { authorize_user('challenge_manager') }
before_action :set_evaluation_form, only: %i[show edit update destroy]
before_action :set_evaluation_forms, only: %i[index]

Expand Down
1 change: 1 addition & 0 deletions app/controllers/evaluations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

class EvaluationsController < ApplicationController
before_action -> { authorize_user('evaluator') }
def index; end
end
1 change: 1 addition & 0 deletions app/controllers/manage_submissions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

class ManageSubmissionsController < ApplicationController
before_action -> { authorize_user('challenge_manager') }
def index; end
end
12 changes: 10 additions & 2 deletions app/helpers/dashboard_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
module DashboardHelper
def dashboard_cards_by_role
{
super_admin: [
{ image_path: 'emoji_events', href: "",
alt: '', title: 'Placeholder', subtitle: 'Placeholder super admin dashboard.' }
],
admin: [
{ image_path: 'emoji_events', href: "",
alt: '', title: 'Placeholder', subtitle: 'Placeholder admin dashboard.' }
],
challenge_manager: [
{ image_path: 'emoji_events', href: Rails.configuration.phx_interop[:phx_uri],
alt: 'challenges', title: 'Challenges', subtitle: 'Create and manage challenges.' },
Expand All @@ -16,8 +24,8 @@ def dashboard_cards_by_role
alt: 'help', title: 'Help', subtitle: 'Get support on the Challenge.Gov platform.' }
],
evaluator: [
{ image_path: 'content_copy', href: 'manage_submissions',
alt: 'submissions', title: 'Submissions', subtitle: 'Evaluate my assigned submissions.' },
{ image_path: 'content_copy', href: 'evaluations',
alt: 'evaluations', title: 'Evaluations', subtitle: 'Evaluate my assigned submissions.' },
{ image_path: 'map', href: 'user_guide',
alt: 'user guides', title: 'Resources', subtitle: 'Learn how to make the most of the platform.' },
{ image_path: 'support_agent', href: 'federal-agency-faqs',
Expand Down
3 changes: 3 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class User < ApplicationRecord

validates :email, presence: true

ROLES = %w[super_admin admin challenge_manager evaluator solver].freeze
validates :role, inclusion: { in: ROLES }

# 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
Expand Down
10 changes: 5 additions & 5 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
# enabled: "ON"

en:
hello: "Hello world"
please_try_again: "Please try again."
login_error: "There was an issue with logging in. Please try again."
access_denied: "You do not have access to this section of Challenge.gov."
already_logged_in_notice: "You are already logged in."
session_expired_alert: "Your session has expired. Please log in again."
evaluation_form_saved: "Evaluation form was saved successfully."
evaluation_form_destroyed: "Evaluation form was successfully destroyed."
evaluation_form_saved: "Evaluation form was saved successfully."
login_error: "There was an issue with logging in. Please try again."
please_try_again: "Please try again."
session_expired_alert: "Your session has expired. Please log in again."
31 changes: 25 additions & 6 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,42 @@
let(:gov_userinfo) do
[{
"email" => "[email protected]",
"sub" => SecureRandom.uuid
"sub" => SecureRandom.uuid,
"role" => "challenge_manager"
}]
end

let(:mil_userinfo) do
[{
"email" => "[email protected]",
"sub" => SecureRandom.uuid
"sub" => SecureRandom.uuid,
"role" => "challenge_manager"
}]
end

let(:non_gov_userinfo) do
[{
"email" => "[email protected]",
"sub" => SecureRandom.uuid
"sub" => SecureRandom.uuid,
"role" => "challenge_manager"
}]
end

describe 'validations' do
it_behaves_like 'a model with required attributes', [:email]

it "is valid with a valid role" do
User::ROLES.each do |role|
user = User.new(email: "[email protected]", role:)
expect(user).to be_valid
end
end

it "is invalid with an invalid role" do
user = described_class.new(email: "[email protected]", role: "invalid_role")
expect(user).not_to be_valid
expect(user.errors[:role]).to include("is not included in the list")
end
end

describe 'default values' do
Expand All @@ -71,8 +87,9 @@
it 'properly sets inserted_at and updated_at' do
email = gov_userinfo[0]["email"]
token = gov_userinfo[0]["sub"]
role = gov_userinfo[0]["role"]

user = described_class.create!(email:, token:)
user = described_class.create!(email:, token:, role:)

expect(user.inserted_at).not_to be_nil
expect(user.updated_at).not_to be_nil
Expand All @@ -97,8 +114,9 @@
it 'finds user if one matches token' do
email = gov_userinfo[0]["email"]
token = gov_userinfo[0]["sub"]
role = gov_userinfo[0]["role"]

user = described_class.create!(email:, token:)
user = described_class.create!(email:, token:, role:)

found_user = described_class.user_from_userinfo(gov_userinfo)

Expand Down Expand Up @@ -144,8 +162,9 @@
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"]
role = gov_userinfo[0]["role"]

user = described_class.create!(email:)
user = described_class.create!(email:, role:)
expect(user.token).to be_nil

updated_user = described_class.user_from_userinfo(gov_userinfo)
Expand Down
42 changes: 38 additions & 4 deletions spec/requests/dashboard_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,31 @@
it_behaves_like "a page with footer content"
it_behaves_like "a page with header content"

context "when logged in as super admin on the root url" do
before do
user.update(role: "super_admin")
get "/"
end

it_behaves_like "a page with dashboard content for a super admin"
end

context "when logged in as admin on the root url" do
before do
user.update(role: "admin")
get "/"
end

it_behaves_like "a page with dashboard content for an admin"
end

context "when logged in as public solver on the root url" do
before do
user.update(role: "solver")
get "/"
end

it_behaves_like "a page with utility menu links for all users"
stepchud marked this conversation as resolved.
Show resolved Hide resolved
it_behaves_like "a page with utility menu links for a public solver"
it_behaves_like "a page with dashboard content for a public solver"
end

context "when logged in as a challenge manager on the root url" do
Expand Down Expand Up @@ -48,14 +65,31 @@
it_behaves_like "a page with footer content"
it_behaves_like "a page with header content"

context "when logged in as a super admin on the dashboard" do
before do
user.update(role: "super_admin")
get "/dashboard"
end

it_behaves_like "a page with dashboard content for a super admin"
end

context "when logged in as an admin on the dashboard" do
before do
user.update(role: "admin")
get "/dashboard"
end

it_behaves_like "a page with dashboard content for an admin"
end

context "when logged in as a public solver on the dashboard" do
before do
user.update(role: "solver")
get "/dashboard"
end

it_behaves_like "a page with utility menu links for all users"
it_behaves_like "a page with utility menu links for a public solver"
it_behaves_like "a page with dashboard content for a public solver"
end

context "when logged in as a challenge manager on the dashboard" do
Expand Down
57 changes: 53 additions & 4 deletions spec/requests/evaluation_forms_spec.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
require 'rails_helper'

RSpec.describe "EvaluationForms" do
let(:user) { create_user(role: "challenge_manager") }
describe "GET /evaluation_forms" do
context "when logged in as a super admin" do
before do
create_and_log_in_user(role: "super_admin")
end

context "when a challenge_manager is logged in" do
before { log_in_user(user) }
it "renders the index view with the correct header" do
get evaluation_forms_path

expect(response).to have_http_status(:success)
expect(response.body).to include("Evaluation Forms")
end
end

context "when logged in as a admin" do
before do
create_and_log_in_user(role: "admin")
end

describe "GET /evaluation_forms" do
it "renders the index view with the correct header" do
get evaluation_forms_path

expect(response).to have_http_status(:success)
expect(response.body).to include("Evaluation Forms")
end
end

context "when logged in as a challenge manager" do
let(:user) { create_user(role: "challenge_manager") }
before { log_in_user(user) }

it "renders the index view with the correct header" do
get evaluation_forms_path

expect(response).to have_http_status(:success)
expect(response.body).to include("Evaluation Forms")
end
it "renders an empty list" do
get evaluation_forms_path
expect(response.body).to include("You currently do not have any evaluation forms.")
Expand Down Expand Up @@ -51,5 +76,29 @@
expect(response.body).not_to include("Farquad")
end
end

context "when logged in as an evaluator" do
before do
create_and_log_in_user(role: "evaluator")
end

it "redirects to the dashboard" do
get evaluation_forms_path

expect(response).to redirect_to(dashboard_path)
end
end

context "when logged in as a solver" do
before do
create_and_log_in_user(role: "solver")
end

it "redirects to the phoenix app" do
get evaluation_forms_path

expect(response).to redirect_to(ENV.fetch("PHOENIX_URI", nil))
end
end
end
end
Loading
Loading