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
  1. You always need to have an Admin Author even in dev
  2. You cannot determinate what Admin is capable of.
  3. 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
  1. Hard to test properly and maintain
  2. You cannot determinate what Admin is capable of.
  3. Logic of Post Authorization is scattered over app. 

User should have certain rights

  1. User could belong to different departments - admins, team leaders, reviewers, qa.
  2. Each department could have their own preset of rights. And user receive all of them but could  Receive/Lose any of them
  3. Each Right is based on CRUD actions.
  4. Each Right should have ability to restrict access to certain amount of associated resource.
  5. 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
  1. Keep association polymorphic to normalise tables.
  2. 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
  1. Belongs to set of Rights. Which could be YAML based or stored in DB.
  2. 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