Skip to content

Commit 9ac2b94

Browse files
committed
feat(utilities/group_permissions): improve performance
1 parent 5d2a0dd commit 9ac2b94

2 files changed

Lines changed: 32 additions & 11 deletions

File tree

shard.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ shards:
163163

164164
office365:
165165
git: https://github.com/placeos/office365.git
166-
version: 1.27.1
166+
version: 1.27.2
167167

168168
openai:
169169
git: https://github.com/spider-gazelle/crystal-openai.git
@@ -215,15 +215,15 @@ shards:
215215

216216
placeos-frontend-loader:
217217
git: https://github.com/placeos/frontend-loader.git
218-
version: 2.7.1+git.commit.693b47914d40284458518503be7441dfc7ff98aa
218+
version: 2.7.1+git.commit.4d12eb11b0b381f4d7927b3d7086cbc5b42f21c9
219219

220220
placeos-log-backend:
221221
git: https://github.com/place-labs/log-backend.git
222222
version: 0.13.0
223223

224224
placeos-models:
225225
git: https://github.com/placeos/models.git
226-
version: 9.98.3
226+
version: 9.98.4
227227

228228
placeos-resource:
229229
git: https://github.com/place-labs/resource.git
@@ -275,7 +275,7 @@ shards:
275275

276276
search-ingest:
277277
git: https://github.com/placeos/search-ingest.git
278-
version: 2.11.3+git.commit.f3e14c701667f3fb185a82b352d4e053c7f082d2
278+
version: 2.11.3+git.commit.57d54cc34bece9abf2c2704f76358b3cd4a3199a
279279

280280
secrets-env: # Overridden
281281
git: https://github.com/spider-gazelle/secrets-env.git

src/placeos-rest-api/utilities/group_permissions.cr

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,21 @@ module PlaceOS::Api
1515
# GroupUser entry wins). A Manage grant on a group implicitly covers
1616
# that group's descendants.
1717
module Utils::GroupPermissions
18+
# Per-request memo caches. Controllers are per-request `ActionController::Base`
19+
# instances, so these are request-scoped and cannot serve stale authz (gates
20+
# run in before_actions, before any mutation). Never cache across requests.
21+
@group_memberships_cache : Hash(String, Hash(UUID, ::PlaceOS::Model::Permissions))?
22+
@subsystem_perms_cache : Hash(String, Hash(String, ::PlaceOS::Model::Permissions))?
23+
1824
# Effective per-group Permissions for the user, keyed by group id.
1925
# Groups where the user has no transitive membership are absent.
26+
# Memoised per request (called repeatedly across gates/reads).
2027
def group_memberships(user : ::PlaceOS::Model::User) : Hash(UUID, ::PlaceOS::Model::Permissions)
28+
cache = (@group_memberships_cache ||= {} of String => Hash(UUID, ::PlaceOS::Model::Permissions))
29+
cache[user.id.as(String)] ||= compute_group_memberships(user)
30+
end
31+
32+
private def compute_group_memberships(user : ::PlaceOS::Model::User) : Hash(UUID, ::PlaceOS::Model::Permissions)
2133
user_id = user.id.as(String)
2234

2335
# Direct GroupUser rows, scoped to the user's authority.
@@ -186,16 +198,25 @@ module PlaceOS::Api
186198
# `subsystems`) include `required`, or Manage (a superset). The model
187199
# resolver already ANDs the user's group perms with the GroupZone
188200
# grants, so a non-zero result means both sides agree.
201+
# Memoised `{zone_id => Permissions}` map for the current user within
202+
# `subsystem`, resolved once per request (then reused by every gate /
203+
# scoping query for that subsystem).
204+
def subsystem_zone_permissions(subsystem : String) : Hash(String, ::PlaceOS::Model::Permissions)
205+
cache = (@subsystem_perms_cache ||= {} of String => Hash(String, ::PlaceOS::Model::Permissions))
206+
cache[subsystem] ||= ::PlaceOS::Model::Group.resolve_subsystem_permissions(
207+
current_user.authority_id.as(String), subsystem, current_user.id.as(String),
208+
)
209+
end
210+
189211
def subsystem_grants_on_zones?(
190212
subsystems : Array(String),
191213
zones : Array(String),
192214
required : ::PlaceOS::Model::Permissions,
193215
) : Bool
194216
return false if zones.empty?
195-
authority_id = current_user.authority_id.as(String)
196-
user_id = current_user.id.as(String)
197217
subsystems.any? do |subsystem|
198-
perms = ::PlaceOS::Model::Group.effective_permissions(authority_id, subsystem, user_id, zones)
218+
perms_map = subsystem_zone_permissions(subsystem)
219+
perms = zones.reduce(::PlaceOS::Model::Permissions::None) { |acc, z| acc | (perms_map[z]? || ::PlaceOS::Model::Permissions::None) }
199220
perms.manage? || (perms & required) != ::PlaceOS::Model::Permissions::None
200221
end
201222
end
@@ -268,11 +289,11 @@ module PlaceOS::Api
268289
end
269290

270291
# Zone ids reachable by the current user via the "support" subsystem.
271-
# Useful for scoping index/list queries.
292+
# Useful for scoping index/list queries. Served from the per-request memo.
272293
def support_accessible_zone_ids : Array(String)
273-
authority_id = current_user.authority_id.as(String)
274-
user_id = current_user.id.as(String)
275-
::PlaceOS::Model::Group.accessible_zone_ids(authority_id, SUPPORT_SUBSYSTEM, user_id)
294+
subsystem_zone_permissions(SUPPORT_SUBSYSTEM).compact_map do |zid, perms|
295+
zid if perms != ::PlaceOS::Model::Permissions::None
296+
end
276297
end
277298
end
278299
end

0 commit comments

Comments
 (0)