class Post < ActiveRecord::Basebelongs_to :authorvalidate :author_is_admindef author_is_admin unless author.role == 'admin' errors.add(:author_id, 'must be an admin') end endend
class UsersController < ApplicationControllerdef update user = User.find(params[:id]) unless user.supervisor == current_user raise "Trying to edit unauthorized user!" end if params[:user][:role] == 'admin' raise "Trying to set unauthorized value!" end user.update_attributes(params[:user]) endend
Scope-based authorization solution for Ruby on Rails.
github.com/makandra/consul
Enums for ActiveRecord attributes and associations.
github.com/makandra/assignable_values
... but you can do it in plain Ruby, cancan, etc.
Post.where(:author_id => user.id)
class Power include Consul::Powerdef initialize(user) @user = user endpower :posts do Post.where(:author_id => user.id) endend
Note how we are not storing permissions with the user.
Power.current = Power.new(user) Power.current.posts # => #<ActiveRecord::Relation> Power.current.posts? # => true or false Power.current.post?(post) # => true or false
class PostsController < ApplicationControllerdef show @object = end_of_association_chain.find(params[:id]) enddef index @collection = end_of_association_chain.all enddef end_of_association_chain Power.current.posts endend
class PostsController < ApplicationControllerdef show @object = end_of_association_chain.find(params[:id]) enddef index @collection = end_of_association_chain.all enddef end_of_association_chain Power.current.posts endend
class PostsController < ApplicationControllerresource_controllerdef end_of_association_chain Power.current.posts endend
class PostsController < ApplicationControllerresource_controllerpower :posts, :as => :end_of_association_chainend
class Power # ...power :posts do Post.where(:author_id => user.id) endpower :updatable_posts do posts.where(:published => false) endend
class PostsController < ApplicationControllerresource_controllerdef end_of_association_chain if action_name == 'edit' || action_name == 'update' Power.current.updatable_posts else Power.current.posts end endend
class PostsController < ApplicationControllerresource_controllerpower :crud => :posts, :as => :end_of_association_chainend
class Powerdef initialize(user) @user = user endpower :posts do case role when :admin then Post when :author then Post.where(:author_id => user.id) when :guest then Post.where(:open => true) end endprivatedef role @user.role.to_sym endend
class PostsController < ApplicationControllerresource_controllerpower :crud => :posts, :as => :end_of_association_chainend
Power
repository.class Power # ...power :assignable_post_fields do case role when :admin then %w[subject body state] when :author then %w[subject body] end endend
class PostsController < ApplicationController # ...def update @object = end_of_association_chain.find(params[:id]) @object.update_attributes!(object_params) redirect_to @object enddef object_params params[:post].slice(*Power.current.assignable_post_fields) endend
class PostsController < ApplicationController # ...def update @object = end_of_association_chain.find(params[:id]) @object.update_attributes!(object_params) redirect_to @object enddef object_params params[:post].slice(*Power.current.assignable_post_fields) endend
class Post < ActiveRecord::Basevalidates_inclusion_of :state,↲ :in => %w[draft delivered published]end
class Post < ActiveRecord::Baseassignable_values_for :state do %w[draft delivered published] endend
Power
repositoryclass Post < ActiveRecord::Baseassignable_values_for :state, ↲ :through => lambda { Power.current }end
class Post < ActiveRecord::Baseauthorize_values_for :stateend
class Power # ...power :assignable_post_states do case role when :admin then %w[draft delivered published] when :author then %w[draft delivered] end endend
The authorization rule manifests as a validation in the Post
model:
post = Post.new(:state => 'published')Power.current = Power.new(admin_user) post.assignable_states # => ['draft', 'delivered', 'published'] post.valid? # => truePower.current = Power.new(author_user) post.assignable_states # => ['draft', 'delivered'] post.valid? # => false
post = Post.new(:state => 'published')Power.current = Power.new(author_user) post.assignable_states # => ['draft', 'delivered'] post.valid? # => falsePower.current = nil post.valid? # => true
class PostsController < ApplicationControllerresource_controllerpower :crud => :postsend
Again the controller didn't need to change.
class Post < ActiveRecord::Basebelongs_to :author authorize_values_for :authorendclass Power # ...power :assignable_post_authors do User.where(:locked => false) endend
What happens to a user's posts when I lock her account?
class Power # ...power :assignable_post_authors do User.where(:locked => false) endend
class Post < ActiveRecord::Baseauthorize_values_for :stateendclass Power # ...power :assignable_post_states do |post| if user.supervisor_of?(post.author) %w[draft pending published] else %w[draft pending] end endend
Get to know makandra:
http://www.makandra.de
http://makandra.com
Talk to me afterwards or send me a message:
henning.koch@makandra.de
@triskweline
Replay this talk:
makandra.com/talks
Solving bizarre authorization requirementsHenning Koch, makandra GmbH |