Skip to content

Instantly share code, notes, and snippets.

@RStankov
Created June 22, 2018 14:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RStankov/ed5cc5e80ba13aed8823a3f765469ea0 to your computer and use it in GitHub Desktop.
Save RStankov/ed5cc5e80ba13aed8823a3f765469ea0 to your computer and use it in GitHub Desktop.
module ApplicationPolicy
READ = :read
MANAGE = :manage
MANAGE_FIELD = { parent_role: MANAGE } # NOTE(rstankov): Used for GraphQL fields
UPDATE = :update
MODERATE = :moderate
extend Ship::Policy
extend Posts::Policy
# ... extend with policies
end
module SpecSupport
module AuthorizationHelper
class ToBeAbleTo
def initialize(policy, ability, subject)
@policy = policy
@ability = ability
@subject = subject
end
def matches?(user)
@policy.can?(user, @ability, @subject)
end
def failure_message
"Expected user to be able to #{ @ability.inspect } #{ @subject.inspect unless @subject == :empty }, but isn't"
end
def failure_message_when_negated
"Expected user not to be able to #{ @ability.inspect } #{ @subject.inspect unless @subject == :empty }, but is"
end
end
def be_able_to(ability, subject = :empty)
ToBeAbleTo.new(described_class, ability, subject)
end
end
end
# NOTE(rstankov): Documentation: http://graphql-ruby.org/pro/authorization#custom-authorization-strategy
class Graph::Utils::AuthorizationStrategy
def initialize(ctx)
@current_user = ctx[:current_user]
end
def allowed?(gate, value)
ApplicationPolicy.can?(@current_user, gate.role, value)
end
end
class Graph::Mutations::CommentUpdate < API::V2::Resolvers::Mutation
input :body, types.String
node :comment, type: Comment
authorize ApplicationPolicy::UPDATE
returns Graph::Types::CommentType
def perform
form = ::Comments::Update.new(comment: node, user: current_user, request_info: request_info)
form.update inputs
form
end
end
module KittyPolicy
def can?(user, ability, subject = :empty)
if subject == :empty
public_send Utils.rule_name(ability), user
else
public_send Utils.rule_name(ability, subject), user, subject
end
end
def authorize!(*args)
raise AccessDenied.new(*args) unless can?(*args)
end
private
def can(abilities, subject = nil, allow_guest: false, &block)
Array(abilities).each do |ability|
define_method Utils.rule_name(ability, subject) do |*args|
(args[0] || allow_guest) && !!block.call(*args)
end
end
end
class AccessDenied < StandardError
attr_reader :user, :ability, :subject
def initialize(user = nil, ability = nil, subject = nil)
@user = user
@ability = ability
@subject = subject
super 'Not authorized'
end
end
module Utils
extend self
def rule_name(ability, subject = nil)
name = "can_#{ ability }"
name << "_#{ subject_to_string(subject).underscore.tr('/', '_') }" if subject
name << '?'
end
private
def subject_to_string(subject)
case subject
when Class, Symbol then subject.to_s
when String then subject.gsub(/[^\w]/, '')
else subject.class.to_s
end
end
end
end
module Ship::Policy
extend KittyPolicy
can :manage, Ship::Account do |user, account|
account.user_id == user.id || account.memers.where(user_id: user.id).exists?
end
can :manage, Ship::Contact do |user, contact|
can? user, :manage, contact.account
end
can :read, Ship::Survey, allow_guest: true do |user, survey|
survey.published? || can?(user, :manage, survey.account)
end
end
require 'spec_helper'
describe Ships::Policy do
include SpecSupport::AuthorizationHelper
let(:account) { build :ship_account }
let(:owner) { account.user }
let(:member) { build(:ship_member, account: account).user }
let(:user) { build :user }
let(:guest) { nil }
describe "can? :manage, Ship::Survey" do
let(:survey) { build :ship_survey, account: account }
it 'returns true for account owner' do
expect(owner).to be_able_to ability, survey
end
it 'returns true for account members' do
expect(member).to be_able_to ability, survey
end
it 'returns false for other users' do
expect(user).not_to be_able_to ability, survey
end
it 'returns false for guest users' do
expect(guest).not_to be_able_to ability, survey
end
end
# ...
end
Graph::Types::ShipSurvey = GraphQL::ObjectType.define do
name 'ShipSurvey'
authorize ApplicationPolicy::READ
field :id, !types.ID
field :title, !types.String
field :can_manage, function: Graph::Resolvers::CanResolver.new(ApplicationPolicy::MANAGE_FIELD)
field :answers, authorize: ApplicationPolicy::MANAGE_FIELD, fallback: [], function: Graph::Resolvers::Ship::Surveys::Answers
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment