Class: LcpRuby::Metadata::PermissionDefinition
- Inherits:
-
Object
- Object
- LcpRuby::Metadata::PermissionDefinition
- Defined in:
- lib/lcp_ruby/metadata/permission_definition.rb
Constant Summary collapse
- DENY_ALL_ROLE =
Sentinel role name used by ‘.deny_all` factory. Never a real role —`roles` is always absent, so every CRUD/action/scope query falls through to `role_config_for`’s empty ‘{}` fallback and returns false. Visible in error logs and PolicyFactory traces, so the fail-closed state is self-documenting (vs. relying on `default_role: nil` accidentally fail-closing via the constructor’s nil-coercion to “viewer”).
"__deny_all__"
Instance Attribute Summary collapse
-
#default_role ⇒ Object
readonly
Returns the value of attribute default_role.
-
#field_overrides ⇒ Object
readonly
Returns the value of attribute field_overrides.
-
#inherits_from ⇒ Object
readonly
Returns the value of attribute inherits_from.
-
#model ⇒ Object
readonly
Returns the value of attribute model.
-
#raw_hash ⇒ Object
readonly
Returns the value of attribute raw_hash.
-
#record_rules ⇒ Object
readonly
Returns the value of attribute record_rules.
-
#roles ⇒ Object
readonly
Returns the value of attribute roles.
-
#source_map ⇒ Object
readonly
Returns the value of attribute source_map.
-
#source_path ⇒ Object
Returns the value of attribute source_path.
-
#source_type ⇒ Object
Returns the value of attribute source_type.
Class Method Summary collapse
-
.deny_all(model_name) ⇒ Object
Builds a fail-closed PermissionDefinition: empty roles hash plus the ‘DENY_ALL_ROLE` sentinel default_role (see constant docstring above for why a sentinel rather than `nil`).
- .from_hash(hash) ⇒ Object
-
.rule_name(rule) ⇒ Object
Read a record_rule’s ‘name:` regardless of whether the hash uses string or symbol keys.
Instance Method Summary collapse
- #default? ⇒ Boolean
-
#effective_scope_for(role_name) ⇒ Object
Returns the effective scope hash for a single role, applying inheritance fallback when the role does not define its own scope.
-
#extends ⇒ Object
‘extends:` is read from raw_hash on demand.
-
#initialize(attrs = {}) ⇒ PermissionDefinition
constructor
A new instance of PermissionDefinition.
-
#merged_effective_scope_for(role_names) ⇒ Object
Computes the effective scope for a set of roles, applying both inheritance fallback and OR-merge semantics for multi-role users.
-
#merged_role_config_for(role_names) ⇒ Object
Merge configs from multiple roles using union/most-permissive semantics.
- #role_config_for(role_name) ⇒ Object
Constructor Details
#initialize(attrs = {}) ⇒ PermissionDefinition
Returns a new instance of PermissionDefinition.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 17 def initialize(attrs = {}) @model = attrs[:model].to_s @roles = attrs[:roles] || {} @default_role = attrs[:default_role] || "viewer" @field_overrides = attrs[:field_overrides] || {} @record_rules = attrs[:record_rules] || [] @inherits_from = normalize_inherits(attrs[:inherits_from]) @raw_hash = attrs[:raw_hash] @source_path = attrs[:source_path] @source_type = attrs[:source_type] # Per-entry provenance (`:per_model` / `:default`) populated only on # merged definitions by Metadata::PermissionMerger. nil otherwise. @source_map = attrs[:source_map] end |
Instance Attribute Details
#default_role ⇒ Object (readonly)
Returns the value of attribute default_role.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def default_role @default_role end |
#field_overrides ⇒ Object (readonly)
Returns the value of attribute field_overrides.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def field_overrides @field_overrides end |
#inherits_from ⇒ Object (readonly)
Returns the value of attribute inherits_from.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def inherits_from @inherits_from end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def model @model end |
#raw_hash ⇒ Object (readonly)
Returns the value of attribute raw_hash.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def raw_hash @raw_hash end |
#record_rules ⇒ Object (readonly)
Returns the value of attribute record_rules.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def record_rules @record_rules end |
#roles ⇒ Object (readonly)
Returns the value of attribute roles.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def roles @roles end |
#source_map ⇒ Object (readonly)
Returns the value of attribute source_map.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def source_map @source_map end |
#source_path ⇒ Object
Returns the value of attribute source_path.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def source_path @source_path end |
#source_type ⇒ Object
Returns the value of attribute source_type.
13 14 15 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 13 def source_type @source_type end |
Class Method Details
.deny_all(model_name) ⇒ Object
Builds a fail-closed PermissionDefinition: empty roles hash plus the ‘DENY_ALL_ROLE` sentinel default_role (see constant docstring above for why a sentinel rather than `nil`).
See docs/design/authorization_hardening.md § “PermissionDefinition.deny_all”.
59 60 61 62 63 64 65 66 67 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 59 def self.deny_all(model_name) new( model: model_name, roles: {}, default_role: DENY_ALL_ROLE, field_overrides: {}, record_rules: [] ) end |
.from_hash(hash) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 41 def self.from_hash(hash) new( model: hash["model"], roles: hash["roles"] || {}, default_role: hash["default_role"], field_overrides: hash["field_overrides"] || {}, record_rules: hash["record_rules"] || [], inherits_from: hash["inherits_from"], raw_hash: hash ) end |
.rule_name(rule) ⇒ Object
Read a record_rule’s ‘name:` regardless of whether the hash uses string or symbol keys. Returns nil for non-Hash entries (defensive for malformed YAML that the schema validator already rejects).
72 73 74 75 76 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 72 def self.rule_name(rule) return nil unless rule.is_a?(Hash) rule["name"] || rule[:name] end |
Instance Method Details
#default? ⇒ Boolean
139 140 141 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 139 def default? model == "_default" end |
#effective_scope_for(role_name) ⇒ Object
Returns the effective scope hash for a single role, applying inheritance fallback when the role does not define its own scope.
Override semantics:
role.key?("scope") == false → inherit (synthesize "inherits" spec)
role["scope"] == "all" → opt out, no inheritance, sees all
role["scope"] == Hash → role-level override, no inheritance
role["scope"] == nil → REJECTED at validation time
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 107 def effective_scope_for(role_name) role = role_config_for(role_name) return role["scope"] if role.key?("scope") return nil unless inherits_from # Synthesize an internal scope spec that ScopeBuilder turns into a # parent-sub-query. This Hash never appears in user-supplied YAML; # the schema validator rejects type=inherits in user input. { "type" => "inherits", "parent" => inherits_from[:parents], "via" => inherits_from[:via], "mode" => inherits_from[:mode] } end |
#extends ⇒ Object
‘extends:` is read from raw_hash on demand. raw_hash is the authoritative source of truth (key presence distinguishes “author wrote nothing” from “author wrote `_default`”); see Metadata::PermissionMerger#merge_default_role for the same rule applied to default_role.
37 38 39 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 37 def extends @raw_hash&.[]("extends") end |
#merged_effective_scope_for(role_names) ⇒ Object
Computes the effective scope for a set of roles, applying both inheritance fallback and OR-merge semantics for multi-role users. Returns one of:
nil — no role defines a scope and inherits_from is absent
"all" — at least one role grants unrestricted access
Hash — a single scope spec (path/field_match/.../inherits)
{type:"union", scopes:[...]} — multiple distinct scopes to OR
130 131 132 133 134 135 136 137 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 130 def merged_effective_scope_for(role_names) scopes = role_names.map { |r| effective_scope_for(r) }.compact return "all" if scopes.any? { |s| s == "all" } return nil if scopes.empty? return scopes.first if scopes.size == 1 { "type" => "union", "scopes" => scopes } end |
#merged_role_config_for(role_names) ⇒ Object
Merge configs from multiple roles using union/most-permissive semantics. Returns a single virtual config hash.
NOTE: the returned hash no longer carries a “scope” key. Scope resolution now flows exclusively through ‘merged_effective_scope_for`, which knows how to incorporate `inherits_from` fallback. Callers that previously read `effective_config` must migrate to `merged_effective_scope_for(roles)`.
90 91 92 93 94 95 96 97 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 90 def merged_role_config_for(role_names) configs = role_names.map { |r| role_config_for(r) } configs = configs.reject(&:empty?) return roles[default_role.to_s] || {} if configs.empty? return configs.first if configs.size == 1 merge_configs(configs) end |
#role_config_for(role_name) ⇒ Object
78 79 80 |
# File 'lib/lcp_ruby/metadata/permission_definition.rb', line 78 def role_config_for(role_name) roles[role_name.to_s] || roles[default_role.to_s] || {} end |