module Strongbolt::UserAbilities::InstanceMethods
Public Instance Methods
add_tenant(tenant)
click to toggle source
Adds a managed tenant to the user
# File lib/strongbolt/user_abilities.rb, line 23 def add_tenant(tenant) sing_tenant_name = tenant.class.name.demodulize.underscore send("users_#{sing_tenant_name.pluralize}").create! sing_tenant_name => tenant # users_tenants.create! tenant: tenant end
can?(action, instance, attrs = :any, all_instance = false)
click to toggle source
Main method for user, used to check whether the user is authorized to perform a certain action on an instance/class
# File lib/strongbolt/user_abilities.rb, line 33 def can?(action, instance, attrs = :any, all_instance = false) without_grant do # Get the actual instance if we were given AR instance = instance.try(:first) if instance.is_a?(ActiveRecord::Relation) return false if instance.nil? # We require this to be an *existing* user, that the action and attribute be symbols # and that the instance is a class or a String raise ArgumentError, 'Action must be a symbol and instance must be Class, String, Symbol or AR' unless self.id.present? && action.is_a?(Symbol) && (instance.is_a?(ActiveRecord::Base) || instance.is_a?(Class) || instance.is_a?(String)) && attrs.is_a?(Symbol) # Pre-populate all the capabilities into a results cache for quick lookup. Permissions for all "non-owned" objects are # immediately available; additional lookups are required for owned objects (e.g. User, CheckoutBag, etc.). # The results cache key is formatted as "action model attribute" (attribute can be any, all or an actual attribute) # -any, all, an ID, or "owned" (if the ID will be verified later) is appended to the key based on which instances # a user has access to populate_capabilities_cache unless @results_cache.present? # Determine the model name and the actual model (if we need to traverse the hierarchy) if instance.is_a?(ActiveRecord::Base) model = instance.class model_name = model.send(:name_for_authorization) elsif instance.is_a?(Class) model = instance model_name = model.send(:name_for_authorization) else model = nil # We could do model_name.constantize, but there's a big cost to doing this # if we don't need it, so just defer until we determine there's an actual need model_name = instance end # Look up the various possible valid entries in the cache that would allow us to see this return capability_in_cache?(action, instance, model_name, attrs, all_instance) end # end w/o grant end
cannot?(*args)
click to toggle source
Convenient method
# File lib/strongbolt/user_abilities.rb, line 71 def cannot?(*args) !can?(*args) end
capabilities()
click to toggle source
# File lib/strongbolt/user_abilities.rb, line 12 def capabilities @capabilities_cache ||= Strongbolt::Capability.unscoped.joins(:roles) .joins('INNER JOIN strongbolt_roles as children_roles ON strongbolt_roles.lft <= children_roles.lft AND children_roles.rgt <= strongbolt_roles.rgt') .joins('INNER JOIN strongbolt_roles_user_groups rug ON rug.role_id = children_roles.id') .joins('INNER JOIN strongbolt_user_groups_users ugu ON ugu.user_group_id = rug.user_group_id') .where('ugu.user_id = ?', self.id).distinct.to_a.concat(Strongbolt.default_capabilities) end
owns?(instance)
click to toggle source
Checks if the user owns the instance given
# File lib/strongbolt/user_abilities.rb, line 78 def owns?(instance) raise ArgumentError unless instance.is_a?(Object) && !instance.is_a?(Class) # If the user id is set, does this (a) user id match the user_id field of the instance # or (b) if this is a User instance, does the user id match the instance id? key = instance.is_a?(User) ? :id : :user_id !id.nil? && instance.try(key) == id end
Private Instance Methods
capability_in_cache?(action, instance, model_name, attrs = :any, all_instance = false)
click to toggle source
# File lib/strongbolt/user_abilities.rb, line 146 def capability_in_cache?(action, instance, model_name, attrs = :any, all_instance = false) action_model = "#{action}#{model_name}" Strongbolt.logger.warn 'User has no results cache' if @results_cache.empty? Strongbolt.logger.debug { "Authorizing user to perform #{action} on #{instance.inspect}" } # we don't know or care about tenants or if this is a new record if instance.is_a?(ActiveRecord::Base) && !instance.new_record? # First, check if we have a hash/cache hit for User being able to do this action to every instance of the model/class return true if @results_cache["#{action_model}all-all"] # Access to all attributes on ENTIRE class? return true if @results_cache["#{action_model}#{attrs}-all"] # Access to this specific attribute on ENTIRE class? # If we're checking on a specific instance of the class, not the general model, # append the id to the key id = instance.try(:id) return true if @results_cache["#{action_model}all-#{id}"] # Access to all this instance's attributes? return true if @results_cache["#{action_model}#{attrs}-#{id}"] # Access to this instance's attribute? # Checking ownership and tenant access # Block access for non tenanted instance valid_tenants = has_access_to_tenants?(instance) # Then if the model is owned but isn't preloaded yet if instance.class.owned? # Tests if the owner id of the instance is the same than the user if (own_instance = instance.strongbolt_owner_id == self.id) @results_cache["#{action_model}all-#{id}"] = own_instance && valid_tenants && @results_cache["#{action_model}all-owned"] @results_cache["#{action_model}#{attrs}-#{id}"] = own_instance && valid_tenants && @results_cache["#{action_model}#{attrs}-owned"] return true if @results_cache["#{action_model}all-#{id}"] || @results_cache["#{action_model}#{attrs}-#{id}"] else @results_cache["#{action_model}all-#{id}"] = false @results_cache["#{action_model}#{attrs}-#{id}"] = false end end # Finally we check for tenanted instances @results_cache["#{action_model}all-#{id}"] = @results_cache["#{action_model}all-tenanted"] && valid_tenants # Access to all attributes on tenanted class? @results_cache["#{action_model}#{attrs}-#{id}"] = @results_cache["#{action_model}#{attrs}-tenanted"] && valid_tenants # Access to this specific attribute on tenanted class? return true if @results_cache["#{action_model}all-#{id}"] || @results_cache["#{action_model}#{attrs}-#{id}"] elsif instance.is_a?(ActiveRecord::Base) && instance.new_record? return true if @results_cache["#{action_model}all-all"] # Access to all attributes on ENTIRE class? return true if @results_cache["#{action_model}#{attrs}-all"] # Access to this specific attribute on ENTIRE class? # Checking if the instance is from valid tenants (if necessary) valid_tenants = has_access_to_tenants?(instance) return true if @results_cache["#{action_model}all-tenanted"] && valid_tenants # Access to all attributes on tenanted class? return true if @results_cache["#{action_model}#{attrs}-tenanted"] && valid_tenants # Access to this specific attribute on tenanted class? # Finally, in the case where it's a non tenanted model (it still need to have valid_tenants == true) return true if @results_cache["#{action_model}all-any"] && valid_tenants return true if @results_cache["#{action_model}#{attrs}-any"] && valid_tenants else # First, check if we have a hash/cache hit for User being able to do this action to every instance of the model/class return true if @results_cache["#{action_model}all-all"] # Access to all attributes on ENTIRE class? return true if @results_cache["#{action_model}#{attrs}-all"] # Access to this specific attribute on ENTIRE class? return true if @results_cache["#{action_model}all-any"] && !all_instance # Access to all attributes on at least once instance? return true if @results_cache["#{action_model}#{attrs}-any"] && !all_instance # Access to this specific attribute on at least once instance? end # logger.info "Cache miss for checking access to #{key}" false end
has_access_to_tenants?(instance, tenants = nil)
click to toggle source
Checks if the instance given fulfills tenant management rules
returns true even if instance has no relationship to any tenant
# File lib/strongbolt/user_abilities.rb, line 213 def has_access_to_tenants?(instance, tenants = nil) # If no tenants list given, we take all tenants ||= Strongbolt.tenants # Populate the cache if needed populate_tenants_cache # Go over each tenants and check if we access to at least one of the tenant # models linked to it found_any_tenant_relationship = false has_access_to_any_tenant = tenants.inject(false) do |result, tenant| begin if instance.class == tenant tenant_ids = [instance.id] elsif instance.respond_to?(tenant.send(:singular_association_name)) tenant_ids = if instance.send(tenant.send(:singular_association_name)).present? [instance.send(tenant.send(:singular_association_name)).id] else [] end elsif instance.respond_to?(tenant.send(:plural_association_name)) tenant_ids = instance.send("#{tenant.send(:singular_association_name)}_ids") else next result end # When we perform a :select on a model, we may omit # the attribute(s) that link(s) to the tenant. # In that case, we have to suppose the user has access rescue ActiveModel::MissingAttributeError tenant_ids = [] end found_any_tenant_relationship = true unless tenant_ids.empty? has_access_to_current_tenant = (!tenant_ids.empty? && (@tenants_cache[tenant.name] & tenant_ids).present?) result || has_access_to_current_tenant end has_access_to_any_tenant || !found_any_tenant_relationship end
populate_capabilities_cache()
click to toggle source
Populate the capabilities cache
# File lib/strongbolt/user_abilities.rb, line 91 def populate_capabilities_cache beginning = Time.now @results_cache ||= {} @model_ancestor_cache ||= {} # User can find itself by default @results_cache['findUserany-any'] = true @results_cache["findUserany-#{id}"] = true # # Store every capability fetched # capabilities.each do |capability| k = "#{capability.action}#{capability.model}" attr_k = capability.attr || 'all' @results_cache["#{k}#{attr_k}-any"] = true @results_cache["#{k}any-any"] = true if capability.require_ownership user_id = self.try(:id) # We can use the ID of the User object for the key here because # there's only one of them if capability.model == Strongbolt::Configuration.user_class @results_cache["#{k}#{attr_k}-#{user_id}"] = true @results_cache["#{k}any-#{user_id}"] = true else # On the other hand, it doesn't make sense to pre-populate the valid # IDs for the models with a lot of instances when we probably are never # going to need to know this. Instead, adding 'owned' is a hint to actually look # up later if we own a particular geography. @results_cache["#{k}#{attr_k}-owned"] = true @results_cache["#{k}any-owned"] = true end elsif capability.require_tenant_access # If tenant access required @results_cache["#{k}#{attr_k}-tenanted"] = true @results_cache["#{k}any-tenanted"] = true else @results_cache["#{k}#{attr_k}-all"] = true @results_cache["#{k}any-all"] = true end end # End each capability Strongbolt.logger.info "Populated capabilities in #{(Time.now - beginning) * 1000}ms" @results_cache end
populate_tenants_cache()
click to toggle source
Populate a hash of tenants as keys and ids array as values
# File lib/strongbolt/user_abilities.rb, line 253 def populate_tenants_cache return if @tenants_cache.present? Strongbolt.logger.debug "Populating tenants cache for user #{self.id}" @tenants_cache = {} # Go over each tenants Strongbolt.tenants.each do |tenant| @tenants_cache[tenant.name] = send("accessible_#{tenant.send(:plural_association_name)}").pluck(:id) Strongbolt.logger.debug "#{@tenants_cache[tenant.name].size} #{tenant.name}" end end