Fixed handlebars_cache_key to be app_type-aware, preventing cross-app_type cache poisoning - fixes #1279 - #1283
Conversation
Code Review — remaining cross-user/role cache poisoningSummaryThis PR correctly fixes the cross-app_type poisoning (the root cause of the failing specs). However, a review of the full compilation pipeline found that the same class of bug exists one level down: shared compiled files between users in the same app_type. Root cause of the remaining issue
return relative_path if File.exist?(compiled_file)The compiled filename is derived from But the content of
So two users in the same app_type (one privileged, one restricted) get the same compiled filename → whichever request compiles first poisons the file for the other. The per-user multi-file bundle ( Non-issues confirmed
Plan to addressIn # Before (this PR):
Digest::SHA256.hexdigest("#{ver}-#{items}-#{app_type_id}-#{userrole}-#{uac}")[0..12]
# After:
Digest::SHA256.hexdigest("#{ver}-#{items}-#{app_type_id}-#{user_id}-#{userrole}-#{uac}")[0..12]This propagates automatically to Refactor Do not add Disk growth: Tests to add
Suggested follow-upImplement on branch |
…s-context cache poisoning - fixes consected#1279
e31f6c3 to
b798ea3
Compare
Update — per-user scoping added, branch squashedThe branch has been squashed to a single commit ( New changes since the app_type-only fix
Digest is now: Non-issue confirmed
Tests added (all green — 59 examples, 0 failures)
Notes
|
Problem
Fixes #1279.
analysis_plans_process_spec.rbandgrant_aims_process_spec.rbboth failed reproducibly atfind('.common-templates--result-item h4', text: '<App> ...')— the master record'sdetails-tabsnav-pills were missing theactivity_log__project_assignments("Analysis Plan"/"Grant Aims") tab entirely, even though the underlying record, page layout config, and access control were all correct.Root cause
app/helpers/handlebars_precompiler_helper.rb#handlebars_cache_keywas documented as "a cache key common to all users" and did not vary bycurrent_user/app_type_id/access-control. But themaster_tabsHandlebars partial it caches to disk (public/handlebars-<env>/partials/<id>-<hash>.js) genuinely does vary per app type (page_layout_panelsfiltersAdmin::PageLayoutbycurrent_user.app_type_id, and tab visibility depends on access control). Whichever request compiled the partial first permanently "poisoned" the shared on-disk file for every other app type/user, until an unrelated table'supdated_athappened to change.Verified via interactive debugging: deleting the stale compiled file and forcing a fresh compile for the same user correctly included the missing tab, proving the ERB/access-control logic was correct and the defect was purely in the cache-key scoping.
Fix
handlebars_cache_keynow embedsapp_type_iddirectly in its digest (not just indirectly via role/access-control timestamps), so two different app_type contexts can never collide, even when neither has anyAdmin::UserRole/Admin::UserAccessControlrows yet.app_type_access_control_timestamps(shared byhandlebars_cache_keyandaccess_control_version) now scopes those queries toapp_type_id: [app_type_id, nil], matching the established pattern used elsewhere (UserAndRoles#where_user_and_role,PageLayoutsHelper#page_layout_panels) — so changes to global/shared (app_type_id: nil) roles or access controls also correctly invalidate the cache key for every app type.current_user_or_admin_app_type_idandapp_type_access_control_timestampsas shared private helpers, eliminating duplicated query logic betweenhandlebars_cache_keyandaccess_control_version.Testing
spec/helpers/handlebars_precompiler_helper_spec.rbcovering: cross-app_type cache-key/filename divergence, cross-context write poisoning, app_type_id collision safety when role/UAC timestamps are identical, and global (app_type_id: nil) role change invalidation.handlebars_precompiler_helper_spec.rb(52 examples),handlebars_precompiler_spec.rb,application_helper_spec.rb,memcached_clear_template_recovery_spec.rb,template_resilient_versions_spec.rb,global_template_historical_versions_spec.rb,versioned_template_config_spec.rb: all passing, no regressions.analysis_plans_process_spec.rb(1 example) andgrant_aims_process_spec.rb(2 examples).Files changed
app/helpers/handlebars_precompiler_helper.rbspec/helpers/handlebars_precompiler_helper_spec.rb