module Permissify
Permissify — activity-based authorization.
Permissify.define(:article) do fact(:draft) { |ctx| ctx.resource.status == "draft" } fact(:owned) { |ctx| ctx.resource.user_id == ctx.actor.id } permission(:editable) { |ctx| ctx.all?(:draft, :owned) } action :edit, maps_to: :editable end decision = Permissify.decide(actor: user, action: :edit, resource: article, resource_key: :article) decision.allowed? # => true / false
Safety contract: unknown resource / action / permission / fact and any evaluation error all DENY. There is no superuser bypass and no generated “:#{action}able” fallback — everything is explicit and observable.
Constants
- VERSION
The gem version (semantic versioning).
Public Class Methods
Convenience boolean projection of a decision.
# File lib/permissify.rb, line 89 def allow?(**kwargs) decide(**kwargs).allowed? end
Evaluate one authorization question and return an immutable Decision.
actor-
the subject performing the action
action-
the verb, resolved to a permission via an action alias
resource-
the object under test
resource_key-
selects the registered definition
environment-
optional request/tenant context facts may read
fact_source-
optional external
FactSourcefor facts not registered inline
Unknown resource/action/permission/fact and any evaluation error all DENY, each with a distinct Decision#reason. Never returns a bare boolean or nil.
# File lib/permissify.rb, line 55 def decide(actor:, action:, resource:, resource_key:, environment: {}, fact_source: FactSource::Null.new) resource_def = registry.resource(resource_key) return deny(:unknown_resource, nil, resource_key: resource_key) unless resource_def action_key = action.to_sym permission_name = resource_def.actions[action_key] return deny(:unknown_action, nil, resource_key: resource_key, action: action_key) unless permission_name permission_block = resource_def.permissions[permission_name] return deny(:unknown_permission, permission_name, resource_key: resource_key) unless permission_block context = DecisionContext.new( actor: actor, resource: resource, action: action_key, resource_def: resource_def, environment: environment, fact_source: fact_source ) begin allowed = permission_block.call(context) ? true : false rescue MissingFact => e return deny(:missing_fact, permission_name, checks: context.checks, missing: e.name) rescue StandardError => e return deny(:fact_error, permission_name, checks: context.checks, error: e.class.name) end Decision.new( allowed: allowed, permission: permission_name, reason: allowed ? :allowed : :denied, checks: context.checks, metadata: { resource_key: resource_key, action: action_key } ) end
Register a resource’s facts, permissions, and action aliases. The block is evaluated by the DSL; see Permissify::DSL for the available declarations.
# File lib/permissify.rb, line 40 def define(resource_key, &block) registry.define(resource_key, &block) end
# File lib/permissify.rb, line 29 def registry @registry ||= Registry.new end
Reset the global registry — for tests and dev reloads.
# File lib/permissify.rb, line 34 def reset! @registry = Registry.new end