Praetorian is a minimalist Crystal authorization system inspired by Pundit. It aims to be both lightweight and dependency-less.
dependencies:
praetorian:
github: ilanusse/praetorianPraetorian, inspired by Pundit, works with policy classes. This shard is not designed to be extra compatible with any framework but rather with flexibility in mind. This is a simple example that allows updating a post if the user is an admin, or if the post is unpublished:
class Post
def policy_class
PostPolicy
end
end
class PostPolicy
include Praetorian::Policy
property user, post
def initialize(user, post)
@user = user
@post = post
end
def update?
user.admin? || !post.published?
end
end
# Somewhere in your code
def update
@post = Post.find(params[:id])
Praetorian.authorize(current_user, @post, :update?) # You can also use .authorise if you're a Brit
# Rest of code flow
endThere are two things to notice here:
-
The Post is a class that should obey a certain Policy. We can either write a
policy_classmethod to return the policy class name, or Praetorian will assume the policy classname to be#{variable_name}Policy. -
The Policy class includes
Praetorian::Policy. This adds default query methods to our policy as defaults that should be overwritten as necessary.
The default query methods defined in Praetorian::Policy are: index?, show?, create?, new?, update?, edit?, destroy?.
A Praetorian::NotAuthorizedException will be raised if the user is not authorized to perform said query on the record.
Ok. So far, pretty simple.
You can set up a simple base class to inherit from:
class ApplicationPolicy
include Praetorian::Policy
property user, object
def initialize(user, object)
@user = user
@object = object
end
endYou can include the shard as a module in your controller base class to avoid the prefix:
class ApplicationController
include Praetorian
end
class PostController < ApplicationController
@post = Post.find(params[:id])
authorize(current_user, @post, :update?) # yay no prefix
endYou can pass an argument to override the policy class if necessary. For example:
def create
@publication = find_publication # assume this method returns any model that behaves like a publication
# @publication.class => Post
Praetorian.authorize(current_user, @publication, :create?, PublicationPolicy)
# Rest of code flow
endLicensed under the MIT license, see the separate LICENSE.txt file.