Class: Parse::CLP
- Inherits:
-
Object
- Object
- Parse::CLP
- Defined in:
- lib/parse/model/clp.rb
Overview
Class-Level Permissions (CLP) for Parse Server classes.
CLPs control access to a class at the schema level, determining who can perform operations on the class and which fields are visible to different users/roles.
## Protected Fields Behavior
When a user matches multiple patterns (e.g., public “*”, “authenticated”, and a role), the protected fields are the intersection of all matching patterns. This means a field is only hidden if it’s protected by ALL patterns that apply to the user.
For example:
-
‘*` protects [“owner”, “test”]
-
‘role:Admin` protects [“owner”]
-
A user with Admin role matches both patterns
-
Result: only “owner” is hidden (intersection), “test” is visible
An empty array ‘[]` for a pattern means “no fields protected” (user sees everything). If any matching pattern has an empty array, the intersection will also be empty.
Constant Summary collapse
- OPERATIONS =
Valid CLP operation keys for permission-based access
%i[find get count create update delete addField].freeze
- POINTER_PERMISSIONS =
Pointer-permission keys (users in these fields get read/write access)
%i[readUserFields writeUserFields].freeze
- ALL_KEYS =
All valid CLP keys
(OPERATIONS + POINTER_PERMISSIONS + [:protectedFields]).freeze
- DEFAULT_PUBLIC_PERMISSION =
Default public permission used as fallback when include_defaults is true but no explicit default_permission has been set.
{ "*" => true }.freeze
Instance Attribute Summary collapse
-
#default_permission ⇒ Hash?
The default permission to use for operations not explicitly set.
-
#permissions ⇒ Hash
readonly
The raw CLP hash.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Equality check.
-
#allowed?(operation, pattern) ⇒ Boolean
Check if a specific pattern has access to an operation.
-
#as_json(include_defaults: nil) ⇒ Hash
(also: #to_h)
Convert to Parse Server CLP format.
-
#dup ⇒ CLP
Create a deep copy of this CLP.
-
#empty? ⇒ Boolean
Check if this CLP is empty.
-
#filter_fields(data, user: nil, roles: [], authenticated: nil) ⇒ Hash
Filter fields from a hash based on protected fields for a user/role.
-
#initialize(data = nil) ⇒ CLP
constructor
Create a new CLP instance.
- #inspect ⇒ Object
-
#merge(other) ⇒ CLP
Merge another CLP into this one (non-destructive).
-
#merge!(other) ⇒ self
Merge another CLP into this one (destructive).
-
#parse_data(data) ⇒ Object
Parse CLP data from Parse Server format.
-
#present? ⇒ Boolean
Check if there are any CLP settings.
-
#protected_fields ⇒ Hash
Get all protected fields configuration.
-
#protected_fields_for(pattern) ⇒ Array<String>
Get protected fields for a specific pattern.
-
#public_access?(operation) ⇒ Boolean
Check if public access is allowed for an operation.
-
#read_user_fields ⇒ Array<String>
Get the read user fields.
-
#requires_authentication?(operation) ⇒ Boolean
Check if authentication is required for an operation.
-
#role_allowed?(operation, role_name) ⇒ Boolean
Check if a role has access to an operation.
-
#set_default_permission(public_access: nil, requires_authentication: false, roles: []) ⇒ self
Set the default permission for operations not explicitly configured.
-
#set_permission(operation, public_access: nil, roles: [], users: [], pointer_fields: [], requires_authentication: false) ⇒ self
Set permissions for a specific operation.
-
#set_protected_fields(pattern, fields) ⇒ self
Set protected fields for a specific user/role pattern.
-
#set_read_user_fields(*fields) ⇒ self
Set pointer-permission fields for read access.
-
#set_write_user_fields(*fields) ⇒ self
Set pointer-permission fields for write access.
-
#write_user_fields ⇒ Array<String>
Get the write user fields.
Constructor Details
Instance Attribute Details
#default_permission ⇒ Hash?
The default permission to use for operations not explicitly set. When set, ‘as_json` will include this for all undefined operations.
304 305 306 |
# File 'lib/parse/model/clp.rb', line 304 def @default_permission end |
#permissions ⇒ Hash (readonly)
Returns the raw CLP hash.
73 74 75 |
# File 'lib/parse/model/clp.rb', line 73 def @permissions end |
Instance Method Details
#==(other) ⇒ Boolean
Equality check.
418 419 420 421 |
# File 'lib/parse/model/clp.rb', line 418 def ==(other) return false unless other.is_a?(CLP) || other.is_a?(Hash) as_json == (other.is_a?(CLP) ? other.as_json : other) end |
#allowed?(operation, pattern) ⇒ Boolean
Check if a specific pattern has access to an operation.
212 213 214 215 216 217 218 219 220 221 |
# File 'lib/parse/model/clp.rb', line 212 def allowed?(operation, pattern) perm = @permissions[operation.to_sym] return false unless perm # Check direct access return true if perm[pattern.to_s] == true return true if perm["*"] == true false end |
#as_json(include_defaults: nil) ⇒ Hash Also known as: to_h
Convert to Parse Server CLP format.
IMPORTANT: Parse Server interprets missing operations as {} (no access). If you have protectedFields but no operations defined, the class becomes effectively master-key-only. Use ‘set_default_permission` or `include_defaults` to ensure all operations are included.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/parse/model/clp.rb', line 321 def as_json(include_defaults: nil) result = {} # Determine if we should include defaults # Auto-enable if any CLP settings exist and no explicit choice made should_include_defaults = if include_defaults.nil? present? && @default_permission else include_defaults end # Determine the default permission to use # Use explicit default_permission if set, otherwise fall back to public effective_default = @default_permission || DEFAULT_PUBLIC_PERMISSION # Add operation permissions OPERATIONS.each do |op| if @permissions[op] result[op.to_s] = @permissions[op] elsif should_include_defaults result[op.to_s] = effective_default.dup end end # Add pointer permissions (readUserFields, writeUserFields) POINTER_PERMISSIONS.each do |perm| result[perm.to_s] = @permissions[perm] if @permissions[perm]&.any? end # Add protected fields result["protectedFields"] = @protected_fields unless @protected_fields.empty? result end |
#dup ⇒ CLP
Create a deep copy of this CLP.
411 412 413 |
# File 'lib/parse/model/clp.rb', line 411 def dup CLP.new(as_json) end |
#empty? ⇒ Boolean
Check if this CLP is empty.
386 387 388 |
# File 'lib/parse/model/clp.rb', line 386 def empty? !present? end |
#filter_fields(data, user: nil, roles: [], authenticated: nil) ⇒ Hash
Filter fields from a hash based on protected fields for a user/role. This is the core method for filtering webhook responses.
Uses intersection logic: when a user matches multiple patterns, only fields that are protected by ALL matching patterns are hidden. This matches Parse Server’s behavior.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/parse/model/clp.rb', line 283 def filter_fields(data, user: nil, roles: [], authenticated: nil) return data if data.nil? return data.map { |item| filter_fields(item, user: user, roles: roles, authenticated: authenticated) } if data.is_a?(Array) return data unless data.is_a?(Hash) # Auto-detect authentication if not specified authenticated = user.present? if authenticated.nil? # Build list of patterns that apply to this user/context applicable_patterns = build_applicable_patterns(user, roles, authenticated, data) # Determine which fields to hide using intersection logic fields_to_hide = determine_fields_to_hide(applicable_patterns) # Return filtered data data.reject { |key, _| fields_to_hide.include?(key.to_s) } end |
#inspect ⇒ Object
423 424 425 |
# File 'lib/parse/model/clp.rb', line 423 def inspect "#<Parse::CLP #{as_json.inspect}>" end |
#merge(other) ⇒ CLP
Merge another CLP into this one (non-destructive).
393 394 395 396 397 398 |
# File 'lib/parse/model/clp.rb', line 393 def merge(other) other_data = other.is_a?(CLP) ? other.as_json : other new_clp = CLP.new(as_json) new_clp.parse_data(other_data) new_clp end |
#merge!(other) ⇒ self
Merge another CLP into this one (destructive).
403 404 405 406 407 |
# File 'lib/parse/model/clp.rb', line 403 def merge!(other) other_data = other.is_a?(CLP) ? other.as_json : other parse_data(other_data) self end |
#parse_data(data) ⇒ Object
Parse CLP data from Parse Server format.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/parse/model/clp.rb', line 85 def parse_data(data) data.each do |key, value| key_sym = key.to_sym if key_sym == :protectedFields @protected_fields = value.transform_keys(&:to_s) elsif OPERATIONS.include?(key_sym) @permissions[key_sym] = value.transform_keys(&:to_s) elsif POINTER_PERMISSIONS.include?(key_sym) # readUserFields and writeUserFields are arrays of field names @permissions[key_sym] = Array(value) else # Store any other keys @permissions[key_sym] = value end end end |
#present? ⇒ Boolean
Check if there are any CLP settings.
380 381 382 |
# File 'lib/parse/model/clp.rb', line 380 def present? @permissions.any? || @protected_fields.any? end |
#protected_fields ⇒ Hash
Get all protected fields configuration.
204 205 206 |
# File 'lib/parse/model/clp.rb', line 204 def protected_fields @protected_fields.transform_values(&:dup) end |
#protected_fields_for(pattern) ⇒ Array<String>
Get protected fields for a specific pattern.
198 199 200 |
# File 'lib/parse/model/clp.rb', line 198 def protected_fields_for(pattern) @protected_fields[pattern.to_s] || [] end |
#public_access?(operation) ⇒ Boolean
Check if public access is allowed for an operation.
226 227 228 |
# File 'lib/parse/model/clp.rb', line 226 def public_access?(operation) allowed?(operation, "*") end |
#read_user_fields ⇒ Array<String>
Get the read user fields.
126 127 128 |
# File 'lib/parse/model/clp.rb', line 126 def read_user_fields @permissions[:readUserFields] || [] end |
#requires_authentication?(operation) ⇒ Boolean
Check if authentication is required for an operation.
249 250 251 252 253 |
# File 'lib/parse/model/clp.rb', line 249 def requires_authentication?(operation) perm = @permissions[operation.to_sym] return false unless perm perm["requiresAuthentication"] == true end |
#role_allowed?(operation, role_name) ⇒ Boolean
Check if a role has access to an operation.
234 235 236 237 |
# File 'lib/parse/model/clp.rb', line 234 def role_allowed?(operation, role_name) role_key = role_name.start_with?("role:") ? role_name : "role:#{role_name}" allowed?(operation, role_key) end |
#set_default_permission(public_access: nil, requires_authentication: false, roles: []) ⇒ self
Set the default permission for operations not explicitly configured. This ensures that when CLPs are pushed to Parse Server, all operations have explicit permissions (avoiding the implicit {} = no access behavior).
367 368 369 370 371 372 373 374 |
# File 'lib/parse/model/clp.rb', line 367 def (public_access: nil, requires_authentication: false, roles: []) perm = {} perm["*"] = true if public_access == true perm["requiresAuthentication"] = true if requires_authentication Array(roles).each { |role| perm["role:#{role}"] = true } @default_permission = perm.empty? ? nil : perm self end |
#set_permission(operation, public_access: nil, roles: [], users: [], pointer_fields: [], requires_authentication: false) ⇒ self
Set permissions for a specific operation.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/parse/model/clp.rb', line 144 def (operation, public_access: nil, roles: [], users: [], pointer_fields: [], requires_authentication: false) operation = operation.to_sym raise ArgumentError, "Invalid operation: #{operation}" unless OPERATIONS.include?(operation) perm = {} # Handle public access # Note: Parse Server only accepts 'true' values for CLP permissions. # Setting public: false means "don't grant public access" which is # achieved by simply not including the "*" key (absence = no access). perm["*"] = true if public_access == true # Handle requiresAuthentication perm["requiresAuthentication"] = true if requires_authentication # Handle roles Array(roles).each do |role| role_key = role.start_with?("role:") ? role : "role:#{role}" perm[role_key] = true end # Handle users Array(users).each do |user_id| perm[user_id] = true end # Handle pointer fields (userField:fieldName pattern) Array(pointer_fields).each do |field| field_key = field.start_with?("pointerFields") ? field : "pointerFields" perm[field_key] ||= [] perm[field_key] << field unless field.start_with?("pointerFields") end @permissions[operation] = perm self end |
#set_protected_fields(pattern, fields) ⇒ self
Set protected fields for a specific user/role pattern.
189 190 191 192 193 |
# File 'lib/parse/model/clp.rb', line 189 def set_protected_fields(pattern, fields) pattern = "*" if pattern.to_sym == :public rescue pattern @protected_fields[pattern.to_s] = Array(fields).map(&:to_s) self end |
#set_read_user_fields(*fields) ⇒ self
Set pointer-permission fields for read access. Users pointed to by these fields can read the object.
108 109 110 111 |
# File 'lib/parse/model/clp.rb', line 108 def set_read_user_fields(*fields) @permissions[:readUserFields] = fields.flatten.map(&:to_s) self end |
#set_write_user_fields(*fields) ⇒ self
Set pointer-permission fields for write access. Users pointed to by these fields can write to the object.
119 120 121 122 |
# File 'lib/parse/model/clp.rb', line 119 def set_write_user_fields(*fields) @permissions[:writeUserFields] = fields.flatten.map(&:to_s) self end |