Module: SuperAuth::ActiveRecord::ByCurrentUser
- Defined in:
- lib/super_auth/active_record/by_current_user.rb
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
-
.held_by(user) ⇒ Object
The compiled rows
userholds, matched the way compile! wrote them: a SuperAuth user by user_id, an application user by its id and class name. -
.included(base) ⇒ Object
Records are filtered to those the current user holds an authorization for, keyed by the querying class's name.
-
.type_family(column) ⇒ Object
ActiveRecord already folds the widths of one storage class into one abstract type (integer and bigint, varchar and char); text joins varchar here because the database compares those two natively.
Class Method Details
.held_by(user) ⇒ Object
The compiled rows user holds, matched the way compile! wrote them: a
SuperAuth user by user_id, an application user by its id and class name.
78 79 80 81 82 83 84 |
# File 'lib/super_auth/active_record/by_current_user.rb', line 78 def self.held_by(user) if SuperAuth.internal_user?(user) SuperAuth::ActiveRecord::Authorization.where(user_id: user.id) else SuperAuth::ActiveRecord::Authorization.where(user_external_id: user.id, user_external_type: user.class.name) end end |
.included(base) ⇒ Object
Records are filtered to those the current user holds an authorization for, keyed by the querying class's name. Because a subclass is its own resource type, privileged methods can be placed on a subclass whose access must be approved explicitly — a grant on the base class does not flow down:
class Resource < ApplicationRecord
super_auth
class ResourceRestartPermission < Resource
def restart!
# dangerous restart operation
end
end
end
Resource::ResourceRestartPermission shares the base class's table and rows, but loading it requires an authorization whose resource_external_type is "Resource::ResourceRestartPermission" (edges to a SuperAuth::Resource registered with that external_type). If you can't load the object, you can't call the method.
A parent step admits a row through a column holding another record's id, the row's tenancy read off the row itself, so a grant on the organization reaches every claim whose organization_id it is without a node per claim:
class Claim < ApplicationRecord
super_auth parent: { column: :organization_id,
resource_type: %w[Organization::Member Organization::Admin] }
end
The steps are OR'd, never collapsed into the parent step alone: a per-record grant admits a row whose parent column is NULL, and a parent grant admits rows that have no node. A subclass inherits the declared parents and is still keyed on its own name; re-declaring on the subclass replaces its parents alone, on the one inherited default scope, since two default scopes AND together and would deny every row the parent step admits.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/super_auth/active_record/by_current_user.rb', line 39 def self.included(base) # The attributes mark a hierarchy that already carries the scope. A # second include, a subclass re-declaring or a host including the module # twice, would add a second default scope. return if base.respond_to?(:super_auth_reach) base.class_attribute :super_auth_reach, instance_writer: false base.class_attribute :super_auth_wildcard, instance_writer: false, default: true base.extend ClassMethods base.send(:default_scope, all_queries: true) do if SuperAuth.current_user.blank? raise SuperAuth::Error, "SuperAuth.current_user not set" if SuperAuth.missing_user_behavior == :raise next none end next self if SuperAuth.current_user.respond_to?(:system?) && SuperAuth.current_user.system? model.super_auth_preflight! held = SuperAuth::ActiveRecord::ByCurrentUser.held_by(SuperAuth.current_user) # Type-level authorization (resource_external_id IS NULL) acts as wildcard: # user has access to ALL records of this type (e.g., admin with ADMIN_ACCESS). if model.super_auth_wildcard && held.where(resource_external_type: model.name, resource_external_id: nil).exists? next self end # One IN-subquery per step of the reach, OR'd: the row's own id against # the class's own type, then each parent column against its types. No # type handling here: the external id columns are created with the # app's pk type (SuperAuth.external_id_type at install time), so the # comparison is natively typed. all_queries, so an instance's update, # destroy and reload carry the same OR. model.super_auth_effective_reach.map do |column, types| where(column => held.where(resource_external_type: types).where.not(resource_external_id: nil).select(:resource_external_id)) end.reduce(:or) end end |
.type_family(column) ⇒ Object
ActiveRecord already folds the widths of one storage class into one abstract type (integer and bigint, varchar and char); text joins varchar here because the database compares those two natively.
89 90 91 |
# File 'lib/super_auth/active_record/by_current_user.rb', line 89 def self.type_family(column) column.type == :text ? :string : column.type end |