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

Art/98712/enhance pundit usage *DRAFT* #19963

Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions app/controllers/concerns/exception_handling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def skip_sentry_exception?(exception)
rescue_from 'Exception' do |exception|
va_exception =
case exception
when Pundit::AuthorizationNotPerformedError
Common::Exceptions::AuthorizationNotPerformedError.new(detail: 'Controller has not performed authorization')
when Pundit::NotAuthorizedError
Common::Exceptions::Forbidden.new(detail: 'User does not have access to the requested resource')
when ActionController::InvalidAuthenticityToken
Expand Down Expand Up @@ -63,6 +65,8 @@ def render_errors(va_exception)
case va_exception
when JsonSchema::JsonApiMissingAttribute
render json: va_exception.to_json_api, status: va_exception.code
when Common::Exceptions::AuthorizationNotPerformedError
# Pundit renders it's own error
else
render json: { errors: va_exception.errors }, status: va_exception.status_code
end
Expand Down
8 changes: 8 additions & 0 deletions config/locales/exceptions.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ en:
detail: "No query params are allowed for this route"
code: 400
status: 400
authorization_not_performed_error:
<<: *defaults
title: Authorization not performed
detail: "Authorization not performed by controller"
code: 500
status: 500
detailed_schema_errors:
<<: *validation_errors # Fallbacks for missing translations
data_type:
Expand Down Expand Up @@ -2461,3 +2467,5 @@ en:
title: 'Internal Server Error'
code: CHIP_500
status: 500


11 changes: 11 additions & 0 deletions lib/common/exceptions/authorization_not_performed.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require 'common/exceptions/service_error'

module Common
module Exceptions
# For when a controller has not performed an authorization using Pundit
class AuthorizationNotPerformedError < ServiceError
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,31 @@ module AccreditedRepresentativePortal
class ApplicationController < SignIn::ApplicationController
include SignIn::AudienceValidator
include Authenticable
include Pundit::Authorization

rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

service_tag 'accredited-representative-portal' # ARP DataDog monitoring: https://bit.ly/arp-datadog-monitoring
validates_access_token_audience Settings.sign_in.arp_client_id

before_action :verify_pilot_enabled_for_user
around_action :handle_exceptions
after_action :verify_pundit_authorization

private

def user_not_authorized
render json: { error: 'You are not authorized to perform this action.' }, status: :unauthorized
end

def verify_pundit_authorization
if action_name == 'index'
verify_policy_scoped
else
verify_authorized
end
end

def handle_exceptions
yield
rescue => e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,39 @@ module AccreditedRepresentativePortal
module V0
class InProgressFormsController < ApplicationController
def update
form = find_form || build_form
form.update!(
@form = find_form || build_form
authorize(@form, :update?, policy_class: AccreditedRepresentativePortal::InProgressFormPolicy)

@form.update!(
form_data: params[:form_data],
metadata: params[:metadata]
)

render json: InProgressFormSerializer.new(form)
render json: InProgressFormSerializer.new(@form)
end

def show
form = find_form
render json: form&.data_and_metadata || {}
@form = find_form
authorize(@form, :show?, policy_class: AccreditedRepresentativePortal::InProgressFormPolicy)
render json: @form&.data_and_metadata || {}
end

def destroy
form = find_form or
@form = find_form or
raise Common::Exceptions::RecordNotFound, params[:id]
form.destroy
@form.destroy

head :no_content
end

private

def find_form
InProgressForm.form_for_user(params[:id], @current_user)
@form = InProgressForm.form_for_user(params[:id], @current_user)
end

def build_form
build_form_for_user(params[:id], @current_user)
@form = build_form_for_user(params[:id], @current_user)
end

# NOTE: The in-progress form module can upstream this convenience that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,26 @@
module AccreditedRepresentativePortal
module V0
class PowerOfAttorneyRequestsController < ApplicationController
POA_REQUEST_ITEM_MOCK_DATA = {
status: 'Pending',
declinedReason: nil,
powerOfAttorneyCode: '091',
submittedAt: '2024-04-30T11:03:17Z',
acceptedOrDeclinedAt: nil,
isAddressChangingAuthorized: false,
isTreatmentDisclosureAuthorized: true,
veteran: {
firstName: 'Jon',
middleName: nil,
lastName: 'Smith',
participantId: '6666666666666'
},
representative: {
email: '[email protected]',
firstName: 'Jane',
lastName: 'Doe'
},
claimant: {
firstName: 'Sam',
lastName: 'Smith',
participantId: '777777777777777',
relationshipToVeteran: 'Child'
},
claimantAddress: {
city: 'Hartford',
state: 'CT',
zip: '06107',
country: 'GU',
militaryPostOffice: nil,
militaryPostalCode: nil
}
}.freeze

POA_REQUEST_LIST_MOCK_DATA = [
POA_REQUEST_ITEM_MOCK_DATA,
POA_REQUEST_ITEM_MOCK_DATA,
POA_REQUEST_ITEM_MOCK_DATA
].freeze

def index
render json: POA_REQUEST_LIST_MOCK_DATA
data = policy_scope(PowerOfAttorneyRequest)
render json: { data: data, meta: { totalRecords: data.size } }
end

def show
render json: POA_REQUEST_ITEM_MOCK_DATA
if poa_request && authorize(poa_request, :show?,
policy_class: AccreditedRepresentativePortal::PowerOfAttorneyRequestPolicy)
render json: { data: poa_request }
else
head :not_found
end
end

private

def poa_request
::AccreditedRepresentativePortal::POA_REQUEST_LIST_MOCK_DATA.find do |poa|
poa[:id].to_s == params['id']
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ def show
private

def in_progress_forms
InProgressForm.for_user(@current_user).map do |form|
policy_scope(
InProgressForm, policy_scope_class: AccreditedRepresentativePortal::InProgressFormPolicy::Scope
).map do |form|
{
form: form.form_id,
metadata: form.metadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ class PowerOfAttorneyRequest < ApplicationRecord
has_one :resolution,
class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestResolution',
inverse_of: :power_of_attorney_request

def self.policy_class
PowerOfAttorneyRequestPolicy
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class ApplicationPolicy
attr_reader :user, :record

def initialize(user, record)
raise Pundit::NotAuthorizedError, 'must be logged in' unless user

@user = user
@record = record
end
Expand Down Expand Up @@ -53,6 +55,8 @@ def override_warning

class Scope
def initialize(user, scope)
raise Pundit::NotAuthorizedError, 'must be logged in' unless user

@user = user
@scope = scope
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class InProgressFormPolicy < ApplicationPolicy
def update?
authorize
end

def show?
authorize
end

def destroy?
authorize
end

class Scope
attr_reader :user, :scope

def initialize(user, scope)
@user = user
@scope = scope
end

def resolve
InProgressForm.for_user(user)
end
end

private

def authorize
return false unless user

true
end
end
end
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequestsPolicy < ApplicationPolicy
class PowerOfAttorneyRequestPolicy < ApplicationPolicy
def index?
authorize
end
Expand All @@ -10,6 +10,12 @@ def show?
authorize
end

class Scope < ApplicationPolicy::Scope
def resolve
::AccreditedRepresentativePortal::POA_REQUEST_LIST_MOCK_DATA
end
end

private

def pilot_user_email_poa_codes
Expand All @@ -23,7 +29,7 @@ def authorize
return false unless @user

pilot_user_poa_codes = Set.new(pilot_user_email_poa_codes[@user&.email])
poa_requests_poa_codes = Set.new(Array.wrap(@record), &:poa_code)
poa_requests_poa_codes = Set.new(Array.wrap(@record)) { |r| r.dig(:attributes, :powerOfAttorneyCode) }

pilot_user_poa_codes >= poa_requests_poa_codes
end
Expand Down
Loading