Releases: davidteren/current_scope
Release list
v0.3.1
Post-0.3.0 patch: silent-security footguns and management-console lockout
guards from the solid-solution worklist (PR #100). No intended host API break.
Fixed
config.sod_actionsnormalizes symbols and freezes the list (#91).
Writing[:approve]used to leave SoD off silently because the resolver
matches action-segment strings. The writer now maps symbols → strings,
freezes the list, and raises on full keys / non-name elements — same honesty
ascollection_read_actions=.collection_read_actions=rejects Hash / nested-array junk and warns on
destroy_all/update_all. A Hash coerced to a never-matching member
silently restored pre-#65 semantics; bulk write names the docs cite as
escalation examples now warn likecreate/update/destroy.- Last full-access console lockout guards. Demoting or deleting a
full-access role, or clearing/destroying an org-wide assignment, is refused
when it would leave zero full-access org holders. Checks use holder
semantics (not “any empty spare full_access role row”), run under
transactional locks, and allow deleting unassigned full-access roles.
Demotion only treats an explicitfull_accessparam as demoting. - Role destroy cascade audit no longer 500s on orphaned polymorphic
subjects/resources — event targets resolve defensively, like the members
page already did. full_accesstoggle is recorded on role update audit events
(full_access_from/full_access_to).ambient_collection_modelis private on the Permissions mixin (internal
gate/view binding, not host API).
Changed
- Management UI: role delete confirm names non-zero cascade holder counts and
uses the danger button; scoped-picker labels associate with controls;
subjects Set controls get subject-scopedaria-labels; lockout flashes
include a recovery step.
v0.3.0
Highlights
The record-less (collection) gate grew a type: this release closes the #19 scoped-index trap and the #50 cross-type escalation together.
- Type-bound collection gates (#50). A controller opts scoped grants into collection actions by declaring both hooks:
def current_scope_record = nilplusdef current_scope_model = TheType. A scoped grant now only opens record-less gates of its own type (STI-normalized viabase_class); with no declared type the gate fails closed with a diagnosablemodel_undeclared(ormodel_invalid) reason. - Listed reads derive from the scoped list (#65). For actions in
config.collection_read_actions(default["index"]), the gate asksscope_for(...).exists?— the same id-narrowed query the list renders from — so "Owner of Report #7" finally opens the reports index and sees exactly their record. Gate and list agree by construction;full_accessscoped roles are honored here and only here. - Report mode (#37):
config.enforcement = :reportobserves would-deny requests (access.would_denyledger rows +X-Current-Scope-Reason: would_deny) instead of 403ing — the retrofit story for legacy apps. Note: only:no_grantis downgraded;model_undeclared/model_invalidstill 403. - Strict
permission_keys=(#20): unknown keys now raise instead of silently dropping (assign_permission_keys(..., scrub: true)is the deliberate opt-in to dropping). - Ungated-surface detection (#62): grid badges for provably ungated controllers, a rake task, and a production tripwire posture.
- Console 403 says why (#23):
not_full_accessreason + a short explanatory page instead of a barehead :forbidden. - Dev diagnostics (#41), error-isolated
subject_label(#22), and a stack of hardening/review fixes — including the 0.3.0 release-gate fixes (#93).
Upgrade-breaking route rename (#85, errata): org-wide role assignment is now resources :role_assignments (plural). Hosts POSTing /current_scope/role_assignment directly get a 404 — use /current_scope/role_assignments; helpers role_assignment_path (create) → role_assignments_path, remove_role_assignment_path → role_assignment_path(id).
Full details, including the who-this-changes upgrade notes: CHANGELOG.
Verified by a three-part release gate (deep multi-lens review, test audit with hand-mutation probes, security review — records in docs/reviews/) and a six-host post-release shakedown (current_scope_test_scenarios + the showcase, all green against the published gem).
v0.2.0
Added
- Impersonation / act-as:
Currentcarries the realactoralongside the
effectiveuser;config.actor_method,config.sod_identity, and a
read-only-while-impersonatingMutationGuard. - Append-only audit ledger (
current_scope_events) with a read-only index. scope_for(Model)— the list-side complement toallowed_to?, from the same
grants (STI-aware: normalizes tobase_class).- Scoped-role picker (Role → Subject → Type → Record) +
CurrentScope::Scopeable,
with an opt-incurrent_scope_searchable_scopehook for indexed search. - Host test helpers
grant_role!/grant_scoped_role!(survive the request cycle). CurrentScope::GatingTripwire— opt-in mixin that catches ungated controllers.CurrentScope.grant!+current_scope:grantrake task to bootstrap the first admin.- Pagination for the subjects page and events index.
Changed
- Separation of duties is opt-in:
config.sod_actionsnow defaults to[]. - Declared Rails floor is
>= 8.1(the management UI relies onparams.expect
array semantics introduced in 8.1); the CI test job exercises it. config.auditis tri-state:false | true | :strict.
Security
- Production guardrail:
config.allow_mutations_while_impersonating = trueraises
at boot in production unlessCURRENT_SCOPE_ALLOW_PROD_IMPERSONATION_MUTATIONS
is set. - The impersonation-boundary API raises when
config.actor_methodis unset,
instead of silently running with inert act-as security.
Fixed
with_current_userrestores the ambient context correctly across the whole
>= 8.1floor (was version-fragile).