Class: Verifica::Authorizer

Inherits:
Object
  • Object
show all
Defined in:
lib/verifica/authorizer.rb

Overview

Authorizer is the heart of Verifica. It's an isolated container with no global state which has a list of resource types registered with their companion AclProviders.

Authorizer pairs great with Dependency Injection or can be configured and passed in a way that is compatible with your framework.

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: true)

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

authorizer.authorize(other_user, public_video, :write)
# raises Verifica::AuthorizationError: Authorization FAILURE. Subject 'user' id='2000'. Resource 'video' id='1'. Action 'write'

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(resource_configs) ⇒ Authorizer

Note:

Use Verifica.authorizer instead of this constructor directly

Returns a new instance of Authorizer.


34
35
36
37
# File 'lib/verifica/authorizer.rb', line 34

def initialize(resource_configs)
  @resources = index_resources(resource_configs).freeze
  freeze
end

Instance Method Details

#allowed_actions(subject, resource, **context) ⇒ Array<Symbol>

Returns array of actions allowed for subject or empty array if none.

Parameters:

  • subject (Object)

    subject of the authorization (e.g. current user, external service)

  • resource (Object)

    resource to get allowed actions for, should respond to #resource_type

Returns:

  • (Array<Symbol>)

    array of actions allowed for subject or empty array if none

Raises:

  • (Error)

    if resource.resource_type isn't registered in self

See Also:


95
96
97
98
99
# File 'lib/verifica/authorizer.rb', line 95

def allowed_actions(subject, resource, **context)
  acl = resource_acl(resource, **context)
  sids = Verifica.subject_sids(subject)
  acl.allowed_actions(sids)
end

#authorize(subject, resource, action, **context) ⇒ AuthorizationResult

Checks the authorization of a subject to perform an action on a resource

  • The subject is asked for its Security Identifiers (SIDs) by subject.subject_sids

  • The resource is asked for its type by resource.resource_type

  • ACL provider registered for this resource type is asked for Verifica::Acl by #call(resource, **context)

  • ACL is checked whether the action is allowed for the subject SIDs

Examples:

def show
  post = Post.find(params[:id])
  authorizer.authorize(current_user, post, :read)

  render json: post
end

Parameters:

  • subject (Object)

    subject of the authorization (e.g. current user, external service)

  • resource (Object)

    resource to authorize for, should respond to #resource_type

  • action (Symbol, String)

    action that subject attempts to perform on the resource

  • context (Hash)

    arbitrary keyword arguments to forward to subject.subject_sids and acl_provider.call

Returns:

  • (AuthorizationResult)

    authorization result with all details if authorization is successful

Raises:

  • (AuthorizationError)

    if subject isn't authorized to perform action on the given resource

  • (Error)

    if resource.resource_type isn't registered in self

See Also:


67
68
69
70
71
72
# File 'lib/verifica/authorizer.rb', line 67

def authorize(subject, resource, action, **context)
  result = authorization_result(subject, resource, action, **context)
  raise AuthorizationError, result if result.failure?

  result
end

#authorized?(subject, resource, action, **context) ⇒ Boolean

The same as #authorize but returns true/false instead of rising an exception

Returns:

  • (Boolean)

    true if action on resource is authorized for subject

Raises:

  • (Error)

    if resource.resource_type isn't registered in self


80
81
82
# File 'lib/verifica/authorizer.rb', line 80

def authorized?(subject, resource, action, **context)
  authorization_result(subject, resource, action, **context).success?
end

#resource_acl(resource, **context) ⇒ Acl

Returns Access Control List for resource.

Parameters:

  • resource (Object)

    resource to get ACL for, should respond to #resource_type

  • context (Hash)

    arbitrary keyword arguments to forward to acl_provider.call

Returns:

  • (Acl)

    Access Control List for resource

Raises:

  • (Error)

    if resource_type isn't registered in self

  • (Error)

    if ACL provider for this resource type doesn't respond to #call(resource, **) with Verifica::Acl object

See Also:


141
142
143
144
145
146
147
148
149
150
# File 'lib/verifica/authorizer.rb', line 141

def resource_acl(resource, **context)
  config = config_by_resource(resource)
  acl = config.acl_provider.call(resource, **context)
  unless acl.is_a?(Verifica::Acl)
    type = resource.resource_type
    raise Error, "'#{type}' resource acl_provider should respond to #call with Acl object but got '#{acl.class}'"
  end

  acl
end

#resource_config(resource_type) ⇒ ResourceConfiguration

Returns configuration for resource_type.

Parameters:

  • resource_type (Symbol, String)

    type of the resource

Returns:

Raises:

  • (Error)

    if resource_type isn't registered in self

See Also:


110
111
112
113
114
115
116
117
118
# File 'lib/verifica/authorizer.rb', line 110

def resource_config(resource_type)
  resource_type = resource_type.to_sym
  config = @resources[resource_type]
  if config.nil?
    raise Error, "Unknown resource '#{resource_type}'. Did you forget to register this resource type?"
  end

  config
end

#resource_type?(resource_type) ⇒ Boolean

Returns true if resource_type is registered in self.

Parameters:

  • resource_type (Symbol, String)

    type of the resource

Returns:

  • (Boolean)

    true if resource_type is registered in self

See Also:


127
128
129
# File 'lib/verifica/authorizer.rb', line 127

def resource_type?(resource_type)
  @resources.key?(resource_type.to_sym)
end