Class: Parse::Role
- Inherits:
-
Object
- Object
- Object
- Parse::Role
- Defined in:
- lib/parse/model/classes/role.rb,
lib/parse/stack/generators/templates/model_role.rb
Overview
This class represents the data and columns contained in the standard Parse _Role collection.
Roles allow the an application to group a set of User records with the same set of
permissions, so that specific records in the database can have ACLs related to a role
than trying to add all the users in a group.
The default schema for Role is as follows:
class Parse::Role < Parse::Object
# See Parse::Object for inherited properties...
property :name
# A role may have child roles.
has_many :roles, through: :relation
# The set of users who belong to this role.
has_many :users, through: :relation
end
Instance Attribute Summary collapse
-
#name ⇒ String
The name of this role.
Class Method Summary collapse
-
.all_for_user(user, max_depth: 10, master: false, as: nil) ⇒ Set<String>
Return the transitive upward closure of role names a user inherits permissions from.
-
.all_names ⇒ Array<String>
Get all role names in the system.
-
.exists?(role_name) ⇒ Boolean
Check if a role with the given name exists.
-
.expand_inheritance_upward(starting_roles, max_depth: 10) ⇒ Set<String>
Walk upward from a starting frontier of Role objects through the
_Role.rolesinverse relation, collecting every role name reachable. -
.find_by_name(role_name) ⇒ Parse::Role?
Find a role by its name.
-
.find_or_create(role_name, acl: nil) ⇒ Parse::Role
Find or create a role by name.
Instance Method Summary collapse
-
#add_child_role(role) ⇒ self
Add a child role to this role's hierarchy.
-
#add_child_roles(*role_list) ⇒ self
Add multiple child roles to this role's hierarchy.
-
#add_user(user) ⇒ self
Add a single user to this role.
-
#add_users(*user_list) ⇒ self
Add multiple users to this role.
-
#all_child_roles(max_depth: 10, visited: Set.new) ⇒ Array<Parse::Role>
Get all child roles recursively.
-
#all_parent_role_names(max_depth: 10) ⇒ Set<String>
Get the set of role names whose presence in a
_rpermarray grants access to this role's members. -
#all_users(max_depth: 10, visited: Set.new, master: false, as: nil) ⇒ Array<Parse::User>
Get all users belonging to this role, including users from child roles recursively.
-
#child_roles_count ⇒ Integer
Get the count of direct child roles.
-
#grant_capabilities_to(grantee) ⇒ self
Grant this role's capabilities to the given role's users.
-
#grant_capabilities_to!(grantee) ⇒ self
Auto-saving variant of #grant_capabilities_to.
-
#has_child_role?(role) ⇒ Boolean
Check if a role is a direct child of this role.
-
#has_user?(user) ⇒ Boolean
Check if a user belongs to this role (direct membership only).
-
#inherits_capabilities_from(source) ⇒ Parse::Role
Inverse spelling of #grant_capabilities_to: "this role's users inherit +source+'s capabilities".
-
#inherits_capabilities_from!(source) ⇒ self
Auto-saving variant of #inherits_capabilities_from.
-
#remove_child_role(role) ⇒ self
Remove a child role from this role's hierarchy.
-
#remove_child_roles(*role_list) ⇒ self
Remove multiple child roles from this role's hierarchy.
-
#remove_user(user) ⇒ self
Remove a single user from this role.
-
#remove_users(*user_list) ⇒ self
Remove multiple users from this role.
-
#roles ⇒ RelationCollectionProxy<Role>
This attribute is mapped as a
has_manyParse relation association with the Role class, as roles can be associated with multiple child roles to support role inheritance. -
#total_users_count ⇒ Integer
Get the total count of users including child roles.
-
#users ⇒ RelationCollectionProxy<User>
This attribute is mapped as a
has_manyParse relation association with the User class. -
#users_count ⇒ Integer
Get the count of direct users in this role.
Instance Attribute Details
#name ⇒ String
Returns the name of this role.
54 |
# File 'lib/parse/model/classes/role.rb', line 54 property :name |
Class Method Details
.all_for_user(user, max_depth: 10, master: false, as: nil) ⇒ Set<String>
When neither master: nor as: is supplied, the
mongo-direct fast path is skipped; the method falls
through to the Parse-Server walk
(Parse::Role.all(users: user_pointer)) which goes through
the default Parse::Client. This preserves backward
compatibility for the many SDK-internal call sites that
compose ACL scopes (acl_scope, atlas_search session,
query/constraints) — none of those have a caller scope to
forward. The fast path is opt-in for performance-conscious
callers that can supply explicit authorization.
Return the transitive upward closure of role names a user inherits permissions from.
Parse Server _Role inheritance: when role X holds role Y
in its roles relation, users of Y inherit +X+'s
permissions. So given a user U, the permission set is built
by:
1. Querying for every role +D+ where +U+ is a direct member
(+_Role.users+ contains +U+).
2. For each direct role +D+, walking upward to every role
+P+ that lists +D+ in its +roles+ relation. Repeat until
no new parents are found.
This is the correct primitive for building _rperm predicates
(e.g., ACLReadableByConstraint, ACLWritableByConstraint,
and the Atlas Search ACL $match injection). The legacy walk
via #all_child_roles on the user's direct roles traverses
the wrong direction and over-grants — it returns roles whose
users include the input user through inheritance, not the
roles the input user inherits permissions from.
Cycle-safe: a visited-id set guards against pathological
_Role.roles cycles (e.g. A→B→A).
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/parse/model/classes/role.rb', line 169 def all_for_user(user, max_depth: 10, master: false, as: nil) names = Set.new return names if user.nil? || max_depth <= 0 user_pointer = role_lookup_pointer_for(user) return names if user_pointer.nil? # The fast path is opt-in. When neither `master:` nor `as:` is # supplied, skip it entirely — the underlying mongo helper # would raise ArgumentError, and we don't want to surprise # the many backward-compat call sites (acl_scope.resolve_for_user, # atlas_search Session.role_names_for, query/constraints' ACL # constraint building, agent default-scope composition) that # have no scope to forward. if master == true || !as.nil? fast_path_result = all_for_user_mongo_fast_path( user_pointer.id, max_depth, master: master, as: as, ) if fast_path_result.is_a?(Set) ActiveSupport::Notifications.instrument( "parse.role.expand", direction: :forward, target_id: user_pointer.id, depth: max_depth, source: :mongo_direct, result_count: fast_path_result.size, ) return fast_path_result end end begin direct_roles = Parse::Role.all(users: user_pointer) rescue return names end result = (direct_roles, max_depth: max_depth) ActiveSupport::Notifications.instrument( "parse.role.expand", direction: :forward, target_id: user_pointer.id, depth: max_depth, source: :parse_server, result_count: result.size, ) result end |
.all_names ⇒ Array<String>
Get all role names in the system.
103 104 105 |
# File 'lib/parse/model/classes/role.rb', line 103 def all_names query.results.map(&:name) end |
.exists?(role_name) ⇒ Boolean
Check if a role with the given name exists.
110 111 112 |
# File 'lib/parse/model/classes/role.rb', line 110 def exists?(role_name) query(name: role_name).count > 0 end |
.expand_inheritance_upward(starting_roles, max_depth: 10) ⇒ Set<String>
Walk upward from a starting frontier of Parse::Role objects
through the _Role.roles inverse relation, collecting every
role name reachable. Used by all_for_user (frontier = the
user's direct roles) and #all_parent_role_names
(frontier = the role itself).
The starting frontier is INCLUDED in the returned set, because
the semantics is "every role name whose presence in _rperm
grants access" — direct membership counts.
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/parse/model/classes/role.rb', line 265 def (starting_roles, max_depth: 10) names = Set.new visited_ids = Set.new frontier = [] Array(starting_roles).each do |role| next if role.nil? || role.id.nil? next if visited_ids.include?(role.id) visited_ids << role.id names << role.name if role.respond_to?(:name) && role.name.present? frontier << role end depth = 0 while frontier.any? && depth < max_depth next_frontier = [] frontier.each do |role| next if role.nil? || role.id.nil? begin parents = Parse::Role.all(roles: role) rescue next end parents.each do |parent| next if parent.nil? || parent.id.nil? next if visited_ids.include?(parent.id) visited_ids << parent.id names << parent.name if parent.respond_to?(:name) && parent.name.present? next_frontier << parent end end frontier = next_frontier depth += 1 end names end |
.find_by_name(role_name) ⇒ Parse::Role?
Find a role by its name.
81 82 83 |
# File 'lib/parse/model/classes/role.rb', line 81 def find_by_name(role_name) query(name: role_name).first end |
.find_or_create(role_name, acl: nil) ⇒ Parse::Role
Find or create a role by name.
91 92 93 94 95 96 97 98 99 |
# File 'lib/parse/model/classes/role.rb', line 91 def find_or_create(role_name, acl: nil) role = find_by_name(role_name) return role if role role = new(name: role_name) role.acl = acl if acl role.save role end |
Instance Method Details
#add_child_role(role) ⇒ self
Add a child role to this role's hierarchy.
The method name is misleading — prefer #grant_capabilities_to!
or #inherits_capabilities_from!. add_child_role mutates the
receiver's roles relation; per Parse Server semantics, putting
role Y in role X's roles relation grants X's capabilities to
users-of-Y. The "child" terminology has the inheritance direction
exactly inverted from intuitive org-chart reading. Retained for
backward compatibility and as the low-level structural primitive;
new callers should use the direction-explicit semantic methods.
IMPORTANT — Parse Server _Role inheritance semantics: when role X
holds role Y in its roles relation, users of Y inherit X's
permissions (not the other way around). So calling
admin.add_child_role(moderator) does NOT grant Moderator's
capabilities to Admin; it grants Admin's capabilities to every
Moderator user — privilege escalation.
If you want Admins to have everything Moderators can do, you need to add ADMIN to MODERATOR's roles relation:
moderator.add_child_role(admin) # Admins now have Moderator capabilities
Direction-explicit replacements:
admin.inherits_capabilities_from!(moderator) # admin perspective
moderator.grant_capabilities_to!(admin) # moderator perspective
Both bang variants auto-save and return self.
397 398 399 400 401 |
# File 'lib/parse/model/classes/role.rb', line 397 def add_child_role(role) assert_not_self_reference!(role, :add_child_role) roles.add(role) self end |
#add_child_roles(*role_list) ⇒ self
Add multiple child roles to this role's hierarchy. See #add_child_role for the inheritance-direction caveat.
408 409 410 411 412 413 |
# File 'lib/parse/model/classes/role.rb', line 408 def add_child_roles(*role_list) flat = role_list.flatten flat.each { |r| assert_not_self_reference!(r, :add_child_roles) } roles.add(flat) self end |
#add_user(user) ⇒ self
Add a single user to this role.
333 334 335 336 |
# File 'lib/parse/model/classes/role.rb', line 333 def add_user(user) users.add(user) self end |
#add_users(*user_list) ⇒ self
Add multiple users to this role.
343 344 345 346 |
# File 'lib/parse/model/classes/role.rb', line 343 def add_users(*user_list) users.add(user_list.flatten) self end |
#all_child_roles(max_depth: 10, visited: Set.new) ⇒ Array<Parse::Role>
Get all child roles recursively. Cycle-safe; see #all_users.
699 700 701 702 703 704 705 706 707 708 709 710 |
# File 'lib/parse/model/classes/role.rb', line 699 def all_child_roles(max_depth: 10, visited: Set.new) return [] if max_depth <= 0 return [] if id.nil? || visited.include?(id) visited << id direct_children = roles.all nested_children = direct_children.flat_map do |child| child.all_child_roles(max_depth: max_depth - 1, visited: visited) end (direct_children + nested_children).uniq { |r| r.id } end |
#all_parent_role_names(max_depth: 10) ⇒ Set<String>
Get the set of role names whose presence in a _rperm array
grants access to this role's members. That's the role itself
plus every role P that lists this role in its roles relation,
transitively upward — because users of this role inherit +P+'s
permissions under Parse Server's role-inheritance semantics
(see #add_child_role).
The instance-side analogue to all_for_user; the
two share an internal BFS via
expand_inheritance_upward. Use this method when
compiling an ACL predicate around a role argument, e.g.
:ACL.readable_by => admin_role: the role itself contributes
"role:Admin", and any role whose .roles relation contains
admin_role also grants Admins access through inheritance.
The legacy #all_child_roles walk is NOT a substitute. Child
roles inherit FROM this role (their members get this role's
capabilities), so child-role names in _rperm would not grant
this role's members anything — the walk traverses the wrong
direction for ACL composition.
691 692 693 |
# File 'lib/parse/model/classes/role.rb', line 691 def all_parent_role_names(max_depth: 10) Parse::Role.([self], max_depth: max_depth) end |
#all_users(max_depth: 10, visited: Set.new, master: false, as: nil) ⇒ Array<Parse::User>
When neither master: nor as: is supplied, the
mongo-direct fast path is skipped; the method falls through
to the Parse-Server walk through the per-relation query
interface, which goes through the default Parse::Client.
Get all users belonging to this role, including users from child roles recursively.
Cycle-safe: a visited set guards against pathological
_Role.roles cycles (e.g. A→B→A) that would otherwise cause
exponential per-node query fan-out.
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/parse/model/classes/role.rb', line 557 def all_users(max_depth: 10, visited: Set.new, master: false, as: nil) return [] if max_depth <= 0 return [] if id.nil? || visited.include?(id) # The fast path is opt-in (same rationale as {.all_for_user}). if master == true || !as.nil? fast_path = all_users_mongo_fast_path(max_depth, master: master, as: as) if fast_path.is_a?(Array) ActiveSupport::Notifications.instrument( "parse.role.expand", direction: :reverse, target_id: id, depth: max_depth, source: :mongo_direct, result_count: fast_path.size, ) return fast_path end end visited << id direct_users = users.all child_roles = roles.all child_users = child_roles.flat_map do |child_role| child_role.all_users(max_depth: max_depth - 1, visited: visited) end result = (direct_users + child_users).uniq { |u| u.id } ActiveSupport::Notifications.instrument( "parse.role.expand", direction: :reverse, target_id: id, depth: max_depth, source: :parse_server, result_count: result.size, ) result end |
#child_roles_count ⇒ Integer
Get the count of direct child roles.
720 721 722 |
# File 'lib/parse/model/classes/role.rb', line 720 def child_roles_count roles.query.count end |
#grant_capabilities_to(grantee) ⇒ self
Grant this role's capabilities to the given role's users. Reads as:
"users with grantee now have +self+'s capabilities."
Equivalent to self.add_child_role(grantee) but unambiguous about
the direction of inheritance.
Non-saving — the caller must call self.save to persist. See
#grant_capabilities_to! for the auto-saving variant.
444 445 446 447 448 |
# File 'lib/parse/model/classes/role.rb', line 444 def grant_capabilities_to(grantee) assert_not_self_reference!(grantee, :grant_capabilities_to) roles.add(grantee) self end |
#grant_capabilities_to!(grantee) ⇒ self
Auto-saving variant of #grant_capabilities_to. Performs the
relation mutation AND persists self in one call. Returns self
consistently so the caller can chain or store the result without
tracking which object was mutated. Prefer this in tests and
one-shot scripts where batching multiple mutations isn't needed.
462 463 464 465 466 |
# File 'lib/parse/model/classes/role.rb', line 462 def grant_capabilities_to!(grantee) grant_capabilities_to(grantee) save! self end |
#has_child_role?(role) ⇒ Boolean
Check if a role is a direct child of this role.
524 525 526 527 |
# File 'lib/parse/model/classes/role.rb', line 524 def has_child_role?(role) return false unless role.is_a?(Parse::Role) && role.id.present? roles.query.where(objectId: role.id).count > 0 end |
#has_user?(user) ⇒ Boolean
Check if a user belongs to this role (direct membership only).
516 517 518 519 |
# File 'lib/parse/model/classes/role.rb', line 516 def has_user?(user) return false unless user.is_a?(Parse::User) && user.id.present? users.query.where(objectId: user.id).count > 0 end |
#inherits_capabilities_from(source) ⇒ Parse::Role
Inverse spelling of #grant_capabilities_to: "this role's users
inherit +source+'s capabilities". Performs the relation mutation
on source, not on self.
Save target. The mutation lives on source.roles. To persist,
the caller must save source, NOT self. This asymmetry exists
because Parse Server stores the relation on the role that holds
the roles list, and that role is source. The non-bang form is
retained for callers that need to batch multiple mutations on
source before a single save; prefer #inherits_capabilities_from!
for the one-shot case where the auto-save matches intent.
489 490 491 492 493 |
# File 'lib/parse/model/classes/role.rb', line 489 def inherits_capabilities_from(source) assert_not_self_reference!(source, :inherits_capabilities_from) source.roles.add(self) source end |
#inherits_capabilities_from!(source) ⇒ self
Auto-saving variant of #inherits_capabilities_from. Performs the
mutation on source.roles AND saves source for you, then
returns self so the caller can keep working with the role they
called the method on. Resolves the most common stumbling block
with #inherits_capabilities_from: the "save target" asymmetry.
507 508 509 510 511 |
# File 'lib/parse/model/classes/role.rb', line 507 def inherits_capabilities_from!(source) inherits_capabilities_from(source) source.save! self end |
#remove_child_role(role) ⇒ self
Remove a child role from this role's hierarchy.
418 419 420 421 |
# File 'lib/parse/model/classes/role.rb', line 418 def remove_child_role(role) roles.remove(role) self end |
#remove_child_roles(*role_list) ⇒ self
Remove multiple child roles from this role's hierarchy.
426 427 428 429 |
# File 'lib/parse/model/classes/role.rb', line 426 def remove_child_roles(*role_list) roles.remove(role_list.flatten) self end |
#remove_user(user) ⇒ self
Remove a single user from this role.
351 352 353 354 |
# File 'lib/parse/model/classes/role.rb', line 351 def remove_user(user) users.remove(user) self end |
#remove_users(*user_list) ⇒ self
Remove multiple users from this role.
359 360 361 362 |
# File 'lib/parse/model/classes/role.rb', line 359 def remove_users(*user_list) users.remove(user_list.flatten) self end |
#roles ⇒ RelationCollectionProxy<Role>
This attribute is mapped as a has_many Parse relation association with the Parse::Role class,
as roles can be associated with multiple child roles to support role inheritance.
The roles Parse relation provides a mechanism to create a hierarchical inheritable types of permissions
by assigning child roles.
60 |
# File 'lib/parse/model/classes/role.rb', line 60 has_many :roles, through: :relation |
#total_users_count ⇒ Integer
Get the total count of users including child roles.
726 727 728 |
# File 'lib/parse/model/classes/role.rb', line 726 def total_users_count all_users.count end |
#users ⇒ RelationCollectionProxy<User>
This attribute is mapped as a has_many Parse relation association with the User class.
63 |
# File 'lib/parse/model/classes/role.rb', line 63 has_many :users, through: :relation |
#users_count ⇒ Integer
Get the count of direct users in this role.
714 715 716 |
# File 'lib/parse/model/classes/role.rb', line 714 def users_count users.query.count end |