diff --git a/app/controllers/admin/user_controller.rb b/app/controllers/admin/user_controller.rb index f344909..3b37e55 100644 --- a/app/controllers/admin/user_controller.rb +++ b/app/controllers/admin/user_controller.rb @@ -2,14 +2,16 @@ module Admin class UserController < AdminController def index - @users = User.order(:name) + @users = policy_scope User.order(:name) end def new @user = User.new + authorize @user end def create + authorize User default_passwd = SecureRandom.urlsafe_base64(12) @user = User.create({ password: default_passwd }.merge(user_params.to_h)) @@ -24,14 +26,17 @@ module Admin def view @user = User.find(params[:user_id]) + authorize @user end def edit @user = User.find(params[:user_id]) + authorize @user end def update @user = User.find(params[:user_id]) + authorize @user if @user.update_attributes(user_params) redirect_to admin_user_path(@user.to_i), diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 423782b..0deebc0 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -4,8 +4,12 @@ class AdminController < ApplicationController layout 'admin' before_action :authorize_user + # TODO: after_action :verify_authorized, except: :index + # TODO: after_action :verify_policy_scoped, only: :index + rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized + # TODO: move to DashboardController#index def dashboard authorize :admin, :dashboard? @quizzes = Quiz.includes(:questions).all @@ -25,6 +29,6 @@ class AdminController < ApplicationController def user_not_authorized flash[:error] = "You are not authorized to perform this action." - redirect_to(request.referer || root_path) + redirect_to(request.referer || admin_login_path) end end diff --git a/app/models/user.rb b/app/models/user.rb index f57a07d..ec706e5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,6 +15,12 @@ class User < ApplicationRecord save end + # TODO: move to mixin: UserRoles + # define remaining helpers + def admin? + role == 'admin' + end + private def gen_reset_token diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 0000000..5e0c857 --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true +class ApplicationPolicy + attr_reader :user, :record + + def initialize(user, record) + raise Pundit::NotAuthorizedError, "Must be logged in." unless user + @user = user + @record = record + end + + def index? + false + end + + def show? + scope.where(id: record.id).exists? + end + + def view? + show? + end + + def create? + false + end + + def new? + create? + end + + def update? + false + end + + def edit? + update? + end + + def destroy? + false + end + + def scope + Pundit.policy_scope!(user, record.class) + end + + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + # This is a closed system. + raise Pundit::NotAuthorizedError, "No access to resource." + end + end +end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 0000000..ef5c64a --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +class UserPolicy < ApplicationPolicy + def view? + user.admin? && show? + end + + def create? + user.admin? + end + + def update? + user.admin? + end + + class Scope < Scope + def resolve + return scope if user.admin? + raise Pundit::NotAuthorizedError, "No access to resource." + end + end +end diff --git a/test/policies/application_policy_test.rb b/test/policies/application_policy_test.rb new file mode 100644 index 0000000..66337a9 --- /dev/null +++ b/test/policies/application_policy_test.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +require 'test_helper' + +class ApplicationPolicyTest < PolicyAssertions::Test + # Verify default policies are most restrictive + + test 'should require a user' do + assert_raise Pundit::NotAuthorizedError do + ApplicationPolicy.new(nil, User.new) + end + end + + test 'should not allow collections' do + assert_raise Pundit::NotAuthorizedError do + ApplicationPolicy::Scope.new(users(:admin), User).resolve + end + end + + test 'should not permit by default' do + admin = users(:admin) + refute ApplicationPolicy.new(admin, User.new).view? + refute ApplicationPolicy.new(admin, User.new).show? + refute ApplicationPolicy.new(admin, nil).index? + refute ApplicationPolicy.new(admin, nil).create? + refute ApplicationPolicy.new(admin, nil).new? + refute ApplicationPolicy.new(admin, nil).update? + refute ApplicationPolicy.new(admin, nil).edit? + refute ApplicationPolicy.new(admin, nil).destroy? + end +end diff --git a/test/policies/user_policy_test.rb b/test/policies/user_policy_test.rb new file mode 100644 index 0000000..88d3413 --- /dev/null +++ b/test/policies/user_policy_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +require 'test_helper' + +class UserPolicyTest < PolicyAssertions::Test + test 'should allow admin to scope' do + scope = UserPolicy::Scope.new(users(:admin), User).resolve + assert_equal User.count, scope.count + end + + test 'should not allow non_admin' do + assert_raise Pundit::NotAuthorizedError do + UserPolicy::Scope.new(users(:manager), User).resolve + end + end + + test 'should require current_user' do + assert_raise Pundit::NotAuthorizedError do + UserPolicy.new(nil, User.first).view? + end + end + + def test_view + refute_permit users(:manager), User.first + assert_permit users(:admin), User.first + end + + def test_create_and_update + refute_permit users(:manager), User + assert_permit users(:admin), User + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 01cdb2c..7b7fb5b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,6 +7,7 @@ SimpleCov.start 'rails' do add_group 'Models', %w(app/models app/validators) add_group 'Services & Workers', %w(app/workers app/services) add_group "Jobs", 'app/jobs' + add_group "Policies", 'app/policies' end require File.expand_path('../../config/environment', __FILE__)