Authorization.
Why and How?
Topalov Alex 2015
Authorization Example:
Author could create post
Authorization Example:
Author could create posts
... of blogs
Authorization Example:
Authors could create posts
... of their blogs
... if the blog is active
Authorization Example:
Authors could create posts
... of their blogs
... if the blog is active
... but only on workdays
Authorization Example:
Authors could create posts
... of their blogs
... if the blog is active
... but only on workdays
... and when the author has a confirmed contract
Authorization Example:
Authors could create posts
... of their blogs
... if the blog is active
... but only on workdays
... and when the author has a confirmed contract
... or when they're acting as a proxy for a sick colleague
Authorization Example:
Set of rules
Subtitle
1. Restrict access to a resource. A user may only see her own posts 2.One resource, different restrictions A user may edit her posts if they aren't published yet 3. User roles/rights. What posts a user may see depends on his role/right 4. Sensitive attributes Authors may edit a post, but only admins may change its state 5. Restricting attributes/values Authors may change the state of a post, but only admins may set it to 'published' 6. Restricting what may be associated Users with locked accounts may not be assigned as post authors 7. Authorized values that depend on other fields A user may change the state of all posts, but only publish posts by their subordinates.
Anti-patterns
Subtitle
class Post < ActiveRecord::Base
belongs_to :author
validate :author_is_admin
private
def author_is_admin
unless author.admin?
errors.add(:author_id, 'must be an admin')
end
end
end
- You always need to have an Admin Author even in dev
- You cannot determinate what Admin is capable of.
- Logic of Post Authorization is scattered over app.
Anti-patterns
Subtitle
class PostsController < ApplicationController
def update
post = Post.find(params[:id])
if post.author != current_user
raise "Trying to edit unauthorized post!"
end
if params[:post][:edited]
raise "Trying to set unauthorized value!"
end
post.update_attributes(params[:post])
end
end
- Hard to test properly and maintain
- You cannot determinate what Admin is capable of.
- Logic of Post Authorization is scattered over app.
User should have certain rights
- User could belong to different departments - admins, team leaders, reviewers, qa.
- Each department could have their own preset of rights. And user receive all of them but could Receive/Lose any of them
- Each Right is based on CRUD actions.
- Each Right should have ability to restrict access to certain amount of associated resource.
- Each Right could have additional parameter to restrict attributes, values.
Defining User concept.
class User
has_many :departments
has_many :autorizations, as: :autorizable
has_many :rights, through: :rights
def assign_new_department(department)
autorizations << *department.autorizations
end
end
- Keep association polymorphic to normalise tables.
- Copy all department rights with associated specifics.
Defining Autorization concept.
class Autorization
belongs_to :autorizable, polymorphic: true
has_many :rights
# Fields of content.
# autorizable_id - either User ID or Department ID
# autorizable_type - either user or department
# right_id - ID of applicable right
# meta - JSON object of options:
# scope: Scope of an object (published, all, posts etc)
# allowed_attributes: Attributes that allowed for right entity
end
- Belongs to set of Rights. Which could be YAML based or stored in DB.
- Contain specific for each autorization. Like what set of Posts user could have access to, what attributes and values are allowed.
Defining policy concept.
Each object that should have autorization have their own Policy class that contain all specific logic about autorization
class PostController
def index
@posts = PostPolicy.new(current_user, :read).posts
end
def show
@post = PostPolicy.new(current_user, :read).posts.where(id: params[:id]).first
end
def new
@post = PostPolicy.new(current_user, :create, {author_id: params[:author_id]}).build_post
end
def create
@post = PostPolicy.new(current_user, :create, params[:post]).build_post
if @post.save
return redirect_to post_path(@post)
else
render :new
end
end
end
policy Pseudo-class concept.
class PostPolicy
def initialize(user, action, options = {})
@user = user
@action = action
@right = user.rights.where(action: action, entity: 'Product')
@autorization = user.autorizations.where(right_id: @right.id)
end
def build_product
return unless @right
Product.new(sanitized_params)
end
def products
return unless @right
begginging_of_association_tree
end
def sanitazed_params
options[:params].slice(@autorization.meta(:permitted_params))
end
private
def begginging_of_association_tree
Product.public_send(@autorization.meta(:scope))
end
end
Few links for inspiration
1. https://github.com/elabs/pundit
2. https://github.com/makandra/consul
3. https://github.com/makandra/assignable_values
Autorization.
By Alex Topalov
Autorization.
- 1,733