Class: LcpRuby::Authorization::PermissionEvaluator
- Inherits:
-
Object
- Object
- LcpRuby::Authorization::PermissionEvaluator
- Defined in:
- lib/lcp_ruby/authorization/permission_evaluator.rb
Constant Summary collapse
- ACTION_ALIASES =
{ "edit" => "update", "new" => "create" }.freeze
Instance Attribute Summary collapse
-
#effective_config ⇒ Object
readonly
Returns the value of attribute effective_config.
-
#model_name ⇒ Object
readonly
Returns the value of attribute model_name.
-
#permission_definition ⇒ Object
readonly
Returns the value of attribute permission_definition.
-
#roles ⇒ Object
readonly
Returns the value of attribute roles.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Instance Method Summary collapse
- #apply_scope(base_relation) ⇒ Object
- #can?(action) ⇒ Boolean
- #can_access_presenter?(presenter_name) ⇒ Boolean
- #can_execute_action?(action_name) ⇒ Boolean
- #can_for_record?(action, record) ⇒ Boolean
- #field_masked?(field_name) ⇒ Boolean
- #field_readable?(field_name) ⇒ Boolean
- #field_writable?(field_name, record = nil) ⇒ Boolean
-
#initialize(permission_definition, user, model_name) ⇒ PermissionEvaluator
constructor
A new instance of PermissionEvaluator.
- #readable_fields ⇒ Object
- #workflow_field_readonly?(field_name, record) ⇒ Boolean
- #writable_fields(record = nil) ⇒ Object
Constructor Details
#initialize(permission_definition, user, model_name) ⇒ PermissionEvaluator
Returns a new instance of PermissionEvaluator.
6 7 8 9 10 11 12 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 6 def initialize(, user, model_name) @permission_definition = @user = user @model_name = model_name @roles = resolve_roles(user) @effective_config = .merged_role_config_for(@roles) end |
Instance Attribute Details
#effective_config ⇒ Object (readonly)
Returns the value of attribute effective_config.
4 5 6 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 4 def effective_config @effective_config end |
#model_name ⇒ Object (readonly)
Returns the value of attribute model_name.
4 5 6 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 4 def model_name @model_name end |
#permission_definition ⇒ Object (readonly)
Returns the value of attribute permission_definition.
4 5 6 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 4 def @permission_definition end |
#roles ⇒ Object (readonly)
Returns the value of attribute roles.
4 5 6 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 4 def roles @roles end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
4 5 6 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 4 def user @user end |
Instance Method Details
#apply_scope(base_relation) ⇒ Object
139 140 141 142 143 144 145 146 147 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 139 def apply_scope(base_relation) scope_config = .merged_effective_scope_for(roles) return base_relation if scope_config.nil? || scope_config == "all" filtered = ScopeBuilder.new(scope_config, user).apply(base_relation) model_class = base_relation.respond_to?(:klass) ? base_relation.klass : base_relation hint = IncludesHint.build(scope_config, model_class, roles) hint ? filtered.includes(*Array.wrap(hint)) : filtered end |
#can?(action) ⇒ Boolean
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 19 def can?(action) crud_list = effective_config["crud"] return false unless crud_list resolved = ACTION_ALIASES[action.to_s] || action.to_s granted = crud_list.include?(resolved) ActiveSupport::Notifications.instrument("permission.lcp_ruby", { model: model_name.to_s, result: granted ? "granted" : "denied" }) granted end |
#can_access_presenter?(presenter_name) ⇒ Boolean
132 133 134 135 136 137 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 132 def can_access_presenter?(presenter_name) presenters = effective_config["presenters"] return true if presenters.nil? || presenters == "all" Array(presenters).map(&:to_s).include?(presenter_name.to_s) end |
#can_execute_action?(action_name) ⇒ Boolean
118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 118 def can_execute_action?(action_name) actions_config = effective_config["actions"] return true if actions_config == "all" return false unless actions_config.is_a?(Hash) denied = Array(actions_config["denied"]).map(&:to_s) return false if denied.include?(action_name.to_s) allowed = actions_config["allowed"] return true if allowed == "all" Array(allowed).map(&:to_s).include?(action_name.to_s) end |
#can_for_record?(action, record) ⇒ Boolean
34 35 36 37 38 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 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 34 def can_for_record?(action, record) return false unless can?(action) resolved = ACTION_ALIASES[action.to_s] || action.to_s # Inherited record check: walk inherits_from chain BEFORE evaluating # local record_rules. Parent is always asked :show? — visibility is # the inheritance semantic; CRUD on the child is governed locally. # Skipped for :create because the parent record does not yet exist. return false unless inherited_record_check(record, resolved) # Check local record-level rules .record_rules.each do |rule| rule = rule.transform_keys(&:to_s) if rule.is_a?(Hash) condition = rule["condition"] unless condition.is_a?(Hash) raise ConditionError, "record rule '#{rule['name']}' has invalid condition: expected Hash, got #{condition.class}" end next unless ConditionEvaluator.evaluate_any(record, condition, context: { current_user: @user }) denied = (rule.dig("effect", "deny_crud") || []).map(&:to_s) except_roles = (rule.dig("effect", "except_roles") || []).map(&:to_s) if denied.include?(resolved) && (roles & except_roles).empty? return false end end true end |
#field_masked?(field_name) ⇒ Boolean
109 110 111 112 113 114 115 116 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 109 def field_masked?(field_name) override = .field_overrides[field_name.to_s] return false unless override && override["masked_for"] # Masked only if ALL roles are in masked_for masked_roles = override["masked_for"].map(&:to_s) roles.all? { |r| masked_roles.include?(r) } end |
#field_readable?(field_name) ⇒ Boolean
79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 79 def field_readable?(field_name) override = .field_overrides[field_name.to_s] if override && override["readable_by"] return (roles & override["readable_by"].map(&:to_s)).any? end return true if readable_fields.include?(field_name.to_s) # Fallback: custom_data grants access to all custom fields custom_field_name?(field_name) && readable_fields.include?("custom_data") end |
#field_writable?(field_name, record = nil) ⇒ Boolean
91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 91 def field_writable?(field_name, record = nil) override = .field_overrides[field_name.to_s] if override && override["writable_by"] base = (roles & override["writable_by"].map(&:to_s)).any? return false unless base return record ? !workflow_readonly_for_record(record).include?(field_name.to_s) : true end return true if writable_fields(record).include?(field_name.to_s) # Fallback: custom_data grants access to all custom fields custom_field_name?(field_name) && writable_fields(record).include?("custom_data") end |
#readable_fields ⇒ Object
66 67 68 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 66 def readable_fields @readable_fields ||= compute_readable_fields end |
#workflow_field_readonly?(field_name, record) ⇒ Boolean
105 106 107 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 105 def workflow_field_readonly?(field_name, record) workflow_readonly_for_record(record).include?(field_name.to_s) end |
#writable_fields(record = nil) ⇒ Object
70 71 72 73 74 75 76 77 |
# File 'lib/lcp_ruby/authorization/permission_evaluator.rb', line 70 def writable_fields(record = nil) # Unsaved records have id=nil and would all collide on the same cache slot # while their workflow-driven readonly set may differ — bypass the cache. return compute_writable_fields(record) if record && !record.persisted? @writable_fields_cache ||= {} @writable_fields_cache[record&.id] ||= compute_writable_fields(record) end |