Module: Verifica

Defined in:
lib/verifica.rb,
lib/verifica/ace.rb,
lib/verifica/acl.rb,
lib/verifica/sid.rb,
lib/verifica/errors.rb,
lib/verifica/version.rb,
lib/verifica/authorizer.rb,
lib/verifica/acl_builder.rb,
lib/verifica/configuration.rb,
lib/verifica/authorization_result.rb,
lib/verifica/resource_configuration.rb

Overview

Verifica is Ruby’s most scalable authorization solution ready to handle sophisticated authorization rules.

  • Framework and database agnostic

  • Scalable. Start from 10, grow to 10M records in the database while having the same authorization architecture

  • Supports any actor in your application. Traditional current_user, external service, API client, you name it

  • No global state. Only local, immutable objects

  • Plain old Ruby, zero dependencies, no magic

Verifica is designed around Access Control List. ACL clearly separates authorization rules definition (who can do what for any given resource) and execution (can current_user delete this post?).

Examples:

require "verifica"

User = Struct.new(:id, :role, keyword_init: true) do
  # Verifica expects each security subject to respond to #subject_id, #subject_type, and #subject_sids
  alias_method :subject_id, :id
  def subject_type = :user

  def subject_sids(**)
    role == "root" ? ["root"] : ["authenticated", "user:#{id}"]
  end
end

Video = Struct.new(:id, :author_id, :public, keyword_init: true) do
  # Verifica expects each secured resource to respond to #resource_id, and #resource_type
  alias_method :resource_id, :id
  def resource_type = :video
end

video_acl_provider = lambda do |video, **|
  Verifica::Acl.build do |acl|
    acl.allow "root", [:read, :write, :delete, :comment]
    acl.allow "user:#{video.author_id}", [:read, :write, :delete, :comment]

    if video.public
      acl.allow "authenticated", [:read, :comment]
    end
  end
end

authorizer = Verifica.authorizer do |config|
  config.register_resource :video, [:read, :write, :delete, :comment], video_acl_provider
end

public_video = Video.new(id: 1, author_id: 1000, public: true)
private_video = Video.new(id: 2, author_id: 1000, public: false)

superuser = User.new(id: 777, role: "root")
video_author = User.new(id: 1000, role: "user")
other_user = User.new(id: 2000, role: "user")

authorizer.authorized?(superuser, private_video, :delete) # => true
authorizer.authorized?(video_author, private_video, :delete) # => true
authorizer.authorized?(other_user, private_video, :read) # => false
authorizer.authorized?(other_user, public_video, :comment) # => true

begin
  # raises Verifica::AuthorizationError: Authorization FAILURE. Subject 'user' id='2000'. Resource 'video' id='1'. Action 'write'
  authorizer.authorize(other_user, public_video, :write)
rescue Verifica::AuthorizationError => e
  e.explain # => Long-form explanation of why action is not authorized, your debugging friend
end

# #authorization_result returns a special object with a bunch of useful info
auth_result = authorizer.authorization_result(superuser, private_video, :delete)
auth_result.success? # => true
auth_result.subject_id # => 777
auth_result.resource_type # => :video
auth_result.action # => :delete
auth_result.allowed_actions # => [:read, :write, :delete, :comment]
auth_result.explain # => Long-form explanation of why action is authorized

Defined Under Namespace

Modules: Sid Classes: Ace, Acl, AclBuilder, AuthorizationError, AuthorizationResult, Authorizer, Configuration, Error, ResourceConfiguration

Constant Summary collapse

EMPTY_ACL =

Empty, frozen Access Control List. Semantically means that no actions are allowed

Verifica::Acl.new(EMPTY_ARRAY).freeze
VERSION =
"1.0.2"

Class Method Summary collapse

Class Method Details

.authorizer {|config| ... } ⇒ Authorizer

Creates a new Configuration and yields it to the given block

Examples:

post_acl_provider = lambda do |post, **|
  Verifica::Acl.build do |acl|
    acl.allow "root", [:read, :write, :delete, :comment]
    acl.allow "user:#{post.author_id}", [:read, :write, :delete, :comment]

    if post.public
      acl.allow "authenticated", [:read, :comment]
    end
  end
end

user_acl_provider = lambda do |user, **|
  Verifica::Acl.build do |acl|
    acl.allow "root", [:read, :write, :delete]
    acl.allow "user:#{user.id}", [:read, :write]
  end
end

authorizer = Verifica.authorizer do |config|
  config.register_resource :post, [:read, :write, :delete, :comment], post_acl_provider
  config.register_resource :user, [:read, :write, :delete], user_acl_provider
end

Yields:

  • (config)

Returns:

  • (Authorizer)

    a new Authorizer configured by the given block



123
124
125
126
127
# File 'lib/verifica.rb', line 123

def self.authorizer
  config = Configuration.new
  yield config
  Authorizer.new(config.resources)
end

.subject_sids(subject, **context) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/verifica/authorizer.rb', line 5

def self.subject_sids(subject, **context)
  if subject.nil?
    raise Error, "Subject should not be nil"
  end

  sids = subject.subject_sids(**context)
  unless sids.is_a?(Array) || sids.is_a?(Set)
    raise Error, "Expected subject to respond to #subject_sids with Array or Set of SIDs but got '#{sids.class}'"
  end

  sids
end