module Volt::Model::Permissions

The permissions module provides helpers for working with Volt permissions.

Public Class Methods

included(base) click to toggle source
# File lib/volt/models/permissions.rb, line 62
def self.included(base)
  base.send(:extend, ClassMethods)
  base.class_attribute :__permissions__
end

Public Instance Methods

action_allowed?(action_name) click to toggle source

Checks if any denies are in place for an action (read or delete)

# File lib/volt/models/permissions.rb, line 118
def action_allowed?(action_name)
  # TODO: this does some unnecessary work
  compute_allow_and_deny(action_name).then do

    deny = @__deny_fields == true || (@__deny_fields && @__deny_fields.size > 0)

    clear_allow_and_deny

    !deny
  end
end
allow(*fields) click to toggle source
# File lib/volt/models/permissions.rb, line 67
def allow(*fields)
  if @__allow_fields
    if @__allow_fields != true
      if fields.size == 0
        # No field's were passed, this means we deny all
        @__allow_fields = true
      else
        # Fields were specified, add them to the list
        @__allow_fields += fields.map(&:to_sym)
      end
    end
  else
    fail 'allow should be called inside of a permissions block'
  end
end
allow_and_deny_fields(action_name) click to toggle source

Return the list of allowed fields

# File lib/volt/models/permissions.rb, line 131
def allow_and_deny_fields(action_name)
  compute_allow_and_deny(action_name).then do

    result = [@__allow_fields, @__deny_fields]

    clear_allow_and_deny

    result
  end
end
deny(*fields) click to toggle source
# File lib/volt/models/permissions.rb, line 83
def deny(*fields)
  if @__deny_fields
    if @__deny_fields != true
      if fields.size == 0
        # No field's were passed, this means we deny all
        @__deny_fields = true
      else
        # Fields were specified, add them to the list
        @__deny_fields += fields.map(&:to_sym)
      end
    end
  else
    fail 'deny should be called inside of a permissions block'
  end
end
filtered_attributes() click to toggle source

Filter fields returns the attributes with any denied or not allowed fields removed based on the current user.

Run with Volt.as_user(...) to change the user

# File lib/volt/models/permissions.rb, line 146
def filtered_attributes
  # Run the read permission check
  allow_and_deny_fields(:read).then do |allow, deny|

    result = nil

    if allow && allow != true && allow.size > 0
      # always keep id
      allow << :id

      # Only keep fields in the allow list
      result = @attributes.select { |key| allow.include?(key) }
    elsif deny == true
      # Only keep id
      # TODO: Should this be a full reject?
      result = @attributes.reject { |key| key != :id }
    elsif deny && deny.size > 0
      # Reject any in the deny list
      result = @attributes.reject { |key| deny.include?(key) }
    else
      result = @attributes
    end

    # Deeply filter any nested models
    result.then do |res|
      keys = []
      values = []
      res.each do |key, value|
        if value.is_a?(Model)
          value = value.filtered_attributes
        end
        keys << key
        values << value
      end

      Promise.when(*values).then do |values|
        keys.zip(values).to_h
      end
    end
  end
end
owner?(key = :user_id) click to toggle source

owner? can be called on a model to check if the currently logged in user (“`Volt.current_user“`) is the owner of this instance.

@param key [Symbol] the name of the attribute where the user_id is stored

# File lib/volt/models/permissions.rb, line 103
def owner?(key = :user_id)
  # Lookup the original user_id
  owner_id = was(key) || send(:"_#{key}")
  !owner_id.nil? && owner_id == Volt.current_user_id
end

Private Instance Methods

add_error_if_changed(errors, field_name) click to toggle source
# File lib/volt/models/permissions.rb, line 254
def add_error_if_changed(errors, field_name)
  if changed?(field_name)
    (errors[field_name] ||= []) << 'can not be changed'
  end
end
clear_allow_and_deny() click to toggle source
# File lib/volt/models/permissions.rb, line 224
def clear_allow_and_deny
  @__deny_fields = nil
  @__allow_fields = nil
end
compute_allow_and_deny(action_name) click to toggle source

Run through the permission blocks for the action name, acumulate all allow/deny fields.

# File lib/volt/models/permissions.rb, line 231
def compute_allow_and_deny(action_name)
  @__deny_fields = []
  @__allow_fields = []

  # Skip permissions can be run on the server to ignore the permissions
  return if Volt.in_mode?(:skip_permissions)

  # Run the permission blocks
  action_name ||= new? ? :create : :update

  # Run each of the permission blocks for this action
  permissions = self.class.__permissions__
  if permissions && (blocks = permissions[action_name])
    results = blocks.map do |block|
      # Call the block, pass the action name
      instance_exec(action_name, &block)
    end

    # Wait for any promises returned
    Promise.when(*results)
  end
end
run_permissions(action_name = nil) click to toggle source
# File lib/volt/models/permissions.rb, line 190
def run_permissions(action_name = nil)
  compute_allow_and_deny(action_name).then do

    errors = {}

    if @__allow_fields == true
      # Allow all fields
    elsif @__allow_fields && @__allow_fields.size > 0
      # Deny all not specified in the allow list
      changed_attributes.keys.each do |field_name|
        unless @__allow_fields.include?(field_name)
          add_error_if_changed(errors, field_name)
        end
      end
    end

    if @__deny_fields == true
      # Don't allow any field changes
      changed_attributes.keys.each do |field_name|
        add_error_if_changed(errors, field_name)
      end
    elsif @__deny_fields
      # Allow all except the denied
      @__deny_fields.each do |field_name|
        add_error_if_changed(errors, field_name) if changed?(field_name)
      end
    end

    clear_allow_and_deny

    errors
  end
end