ApplicationController def destroy item = Item.find(params[:id]) if current_user.admin? || current_user.moderator? || item.owner_id == current_user.id item.destroy! head :ok end else head :forbidden end end
item = Item.find(params[:id]) if current_user.admin? || current_user.moderator? || item.owner_id == current_user.id item.destroy! head :ok end else head :forbidden end end authorization BY EXAMPLE 10
item = Item.find(params[:id]) if current_user.admin? || current_user.moderator? || item.owner_id == current_user.id item.destroy! head :ok end else head :forbidden end end Is user allowed to do that? authorization BY EXAMPLE 11
item = Item.find(params[:id]) if current_user.can?(:destroy, item) item.destroy! head :ok end else head :forbidden end end authorization layer in action BY EXAMPLE 15
item = Item.find(params[:id]) - if current_user.admin? || - current_user.moderator? || - item.owner_id == current_user.id + if current_user.can?(:destroy, item) item.destroy! head :ok end else head :forbidden end end BY EXAMPLE 16
::Ability def user_abilities can :create, [Question, Answer] can :update, [Question, Answer], user_id: user.id can :destroy, [Question, Answer], user_id: user.id can :destroy, Attachment, attachable: { user_id: user.id } can [:vote_up, :vote_down], [Question, Answer] do |resource| resource.user_id != user.id end end end
# non-raising predicate method if allowed_to?(:create?) @tags = current_account.tags end # scoping data @products = authorized_scope(current_account.products) end end ACTION POLICY 34
|rel| next rel if user.manager? rel.where(owner_id: user.id) end def create? user.manager? end def show? record.account_id == user.account_id end end ACTION POLICY 35
::Behaviour authorize :user attr_reader :user def initialize(user) @user = user end def call(post, params) authorize! post, to: :update? post.update!(params) end end
::Behaviour authorize :user attr_reader :user def initialize(user) @user = user end def call(post, params) authorize! post, to: :update? post.update!(params) end end connect authorization layer
::Behaviour authorize :user attr_reader :user def initialize(user) @user = user end def call(post, params) authorize! post, to: :update? post.update!(params) end end configure authorization context (what to pass to a policy)
::Behaviour authorize :user attr_reader :user def initialize(user) @user = user end def call(post, params) authorize! post, to: :update? post.update!(params) end end apply authorization (resolve policy, resolve context, invoke rule)
ActionPolicy ::Policy ::Core include ActionPolicy ::Policy ::Authorization authorize :user end requires behaviours to provide the “user” context, and adds the attr reader
def update? user.admin? || (user.id == record.user_id) end alias edit? update? alias destroy? update? alias publish? update? alias unpublish? update? end
def index @products = authorized_scope(current_account.products) end def create @product = Product.new( authorized_scope(params.require(:product)) ) # ... end end
def index @products = authorized_scope(current_account.products) end def create @product = Product.new( authorized_scope(params.require(:product) ) # … end end Use scopes to “filter” collections according to the user’s permissions
def index @products = authorized_scope(current_account.products) end def create @product = Product.new( authorized_scope(params.require(:product) ) # ... end end Use scopes to permit parameters
scope_for :relation do |scope| if user.manager? scope.where(account: account) else scope.where(account: account).assigned(user) end end scope_for :relation, :own do |scope| next target.none if user.listener? scope.where(account: account, user: user) end end
scope_for :relation do |scope| if user.manager? scope.where(account: account) else scope.where(account: account).assigned(user) end end scope_for :relation, :own do |scope| next target.none if user.listener? scope.where(account: account, user: user) end end
scope_for :relation do |scope| if user.manager? scope.where(account: account) else scope.where(account: account).assigned(user) end end scope_for :relation, :own do |scope| next target.none if user.listener? scope.where(account: account, user: user) end end
< ApplicationPolicy def show? full_access? || user.stage_permissions.where( stage_id: record.id ).exists? end def full_access? # slow SQL query we have no time to refactor end end
cache :show? def show? full_access? || user.stage_permissions.where( stage_id: record.id ).exists? end def full_access? # complex SQL end end use the cache store for this rule
#index” do subject { get :index, params: {account_slug: account.slug} } include_examples "account access" include_examples "permission access", "view_applicants" end 45% of controllers test – authorization tests
do subject { get :index, params: {account_slug: account.slug} } it "is authorized" do expect { subject } .to be_authorized_to(:index?).with(ApplicantPolicy) end end
do subject { get :index, params: {account_slug: account.slug} } it "is authorized" do expect { subject } .to be_authorized_to(:index?).with(ApplicantPolicy) end end Action Policy tracks all calls to `authorize!` in test env, then checks for matching authorization (no mocks/stubs)
::TestHelper def test_authorization_performed assert_authorized_to( :index?, Applicant, with: ApplicantPolicy) do get :index, params: {account_slug: account.slug} end assert_have_authorized_scope( type: :active_record_relation, with: PostPolicy) do get :index end.with_target do |target| assert_equal Post.all, target end end
2 let(:record) { build_stubbed(:post, :active, global: true) } 3 succeed "when active and global" 4 failed "when non-active" do 5 before { record.active = false } 6 end 7 failed "when not for my city" do 8 before { record.city = build_stubbed :city } 9 succeed "when user is a manager" do 10 let(:user) { build_stubbed(:manager) } 11 end 12 end 13 end 13 LOC vs 19 LOC
print_method(object, method_name) ast = object.method(method_name).source.then(&Unparser.method(:parse)) # outer node is a method definition itself body = ast.children[2] Visitor.new(object).collect(body) end
print_method(object, method_name) ast = object.method(method_name).source.then(&Unparser.method(:parse)) # outer node is a method definition itself body = ast.children[2] Visitor.new(object).collect(body) end gem “method_source” (required by “pry” or “railties”)
print_method(object, method_name) ast = object.method(method_name).source.then(&Unparser.method(:parse)) # outer node is a method definition itself body = ast.children[2] Visitor.new(object).collect(body) end gem “unparser” (wrapper over “parser”, provides AST to code functionality)
print_method(object, method_name) ast = object.method(method_name).source.then(&Unparser.method(:parse)) # outer node is a method definition itself body = ast.children[2] Visitor.new(object).collect(body) end
def show? user.has_permission?(:view_applicants) && allowed_to?(:show?, stage) end end User doesn’t have an access to the stage. The allowed_to? method tracks the failed checks.
ActionPolicy ::Unauthorized do |ex| # either p exception.reasons.details # => { stage: [:show?] } # or nothing (if the first check failed) p exception.reasons.details # => {} – that’s not good end
ActionPolicy ::Unauthorized do |ex| # either p exception.reasons.details # => { stage: [:show?] } # or p exception.reasons.details # => { applicant: [:view?] } end
Event < Base field :can_rsvp, Types ::AuthorizationResult, null: false def can_rsvp policy = policy_for(record: object) policy.apply(rule) policy.result end end end
rsvp_events. where(pack_id: record.pack_id). all?(&:grace_period_started?) || begin details[:name] = record.pack.name false end end Additional details metadata