permission_forge 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2372 -0
- data/README.md +76 -25
- data/app/controllers/concerns/permission_forge/api/crud_actions.rb +3 -2
- data/app/controllers/concerns/permission_forge/api/input_shapes.rb +200 -0
- data/app/controllers/concerns/permission_forge/api/pagination.rb +12 -5
- data/app/controllers/concerns/permission_forge/api/response_renderer.rb +37 -1
- data/app/controllers/concerns/permission_forge/api_authentication.rb +13 -2
- data/app/controllers/concerns/permission_forge/cache_confirmation.rb +42 -11
- data/app/controllers/concerns/permission_forge/change_review.rb +42 -0
- data/app/controllers/concerns/permission_forge/copy_refusal.rb +39 -0
- data/app/controllers/concerns/permission_forge/param_shapes.rb +207 -7
- data/app/controllers/concerns/permission_forge/save_notice.rb +69 -0
- data/app/controllers/permission_forge/api/audit_events_controller.rb +1 -1
- data/app/controllers/permission_forge/api/base_controller.rb +163 -12
- data/app/controllers/permission_forge/api/dashboard_controller.rb +29 -4
- data/app/controllers/permission_forge/api/permission_set_group_users_controller.rb +5 -2
- data/app/controllers/permission_forge/api/permission_set_groups_controller.rb +5 -4
- data/app/controllers/permission_forge/api/permission_set_rules_controller.rb +7 -5
- data/app/controllers/permission_forge/api/permission_set_users_controller.rb +80 -19
- data/app/controllers/permission_forge/api/permission_sets_controller.rb +12 -6
- data/app/controllers/permission_forge/api/resource_actions_controller.rb +1 -1
- data/app/controllers/permission_forge/api/resource_fields_controller.rb +3 -4
- data/app/controllers/permission_forge/api/resources_controller.rb +26 -12
- data/app/controllers/permission_forge/api/unmatched_routes_controller.rb +20 -0
- data/app/controllers/permission_forge/application_controller.rb +39 -5
- data/app/controllers/permission_forge/assignments_controller.rb +36 -34
- data/app/controllers/permission_forge/bulk_assignments_controller.rb +8 -4
- data/app/controllers/permission_forge/field_lookups_controller.rb +1 -1
- data/app/controllers/permission_forge/maintenance_controller.rb +6 -2
- data/app/controllers/permission_forge/people_controller.rb +14 -14
- data/app/controllers/permission_forge/permission_set_editor.rb +20 -6
- data/app/controllers/permission_forge/permission_set_group_users_controller.rb +3 -1
- data/app/controllers/permission_forge/permission_set_groups_controller.rb +23 -9
- data/app/controllers/permission_forge/permission_set_rules_controller.rb +20 -10
- data/app/controllers/permission_forge/permission_sets_controller.rb +23 -24
- data/app/controllers/permission_forge/resource_actions_controller.rb +11 -6
- data/app/controllers/permission_forge/resource_fields_controller.rb +37 -19
- data/app/controllers/permission_forge/resources_controller.rb +12 -11
- data/app/frontend/permission_forge/pf_admin.css +99 -17
- data/app/frontend/permission_forge/pf_admin.js +36 -0
- data/app/helpers/permission_forge/application_helper.rb +22 -72
- data/app/helpers/permission_forge/audit_attribution_helper.rb +77 -0
- data/app/helpers/permission_forge/audit_helper.rb +57 -119
- data/app/helpers/permission_forge/audit_records_helper.rb +103 -0
- data/app/helpers/permission_forge/audit_sentences_helper.rb +56 -2
- data/app/helpers/permission_forge/badges_helper.rb +74 -17
- data/app/helpers/permission_forge/changes_helper.rb +24 -6
- data/app/helpers/permission_forge/conditions_helper.rb +119 -0
- data/app/helpers/permission_forge/labels_helper.rb +134 -0
- data/app/helpers/permission_forge/people_helper.rb +11 -19
- data/app/helpers/permission_forge/rule_problems_helper.rb +73 -0
- data/app/helpers/permission_forge/sentences_helper.rb +119 -98
- data/app/helpers/permission_forge/settings_helper.rb +2 -2
- data/app/helpers/permission_forge/ui_helper.rb +65 -4
- data/app/models/permission_forge/audit_event.rb +5 -1
- data/app/models/permission_forge/permission_set.rb +208 -18
- data/app/models/permission_forge/permission_set_group.rb +10 -4
- data/app/models/permission_forge/permission_set_group_user.rb +3 -1
- data/app/models/permission_forge/permission_set_rule.rb +254 -17
- data/app/models/permission_forge/permission_set_user.rb +38 -3
- data/app/models/permission_forge/resource.rb +18 -8
- data/app/models/permission_forge/resource_action.rb +15 -6
- data/app/models/permission_forge/resource_field.rb +97 -11
- data/app/views/permission_forge/assignments/confirm_destroy.html.erb +4 -4
- data/app/views/permission_forge/assignments/new.html.erb +10 -6
- data/app/views/permission_forge/audit_events/index.html.erb +1 -1
- data/app/views/permission_forge/audit_events/show.html.erb +16 -9
- data/app/views/permission_forge/bulk_assignments/confirm_revoke.html.erb +3 -3
- data/app/views/permission_forge/bulk_assignments/new.html.erb +3 -3
- data/app/views/permission_forge/field_lookups/show.html.erb +2 -2
- data/app/views/permission_forge/home/_broken_rules_card.html.erb +35 -0
- data/app/views/permission_forge/home/_conditions_card.html.erb +28 -0
- data/app/views/permission_forge/home/_ending_card.html.erb +11 -5
- data/app/views/permission_forge/home/_expired_card.html.erb +1 -1
- data/app/views/permission_forge/home/_mask_strategies_card.html.erb +27 -0
- data/app/views/permission_forge/home/_privacy_card.html.erb +21 -7
- data/app/views/permission_forge/home/_rules_card.html.erb +1 -1
- data/app/views/permission_forge/home/_secret_rules_card.html.erb +33 -0
- data/app/views/permission_forge/home/_stale_card.html.erb +7 -5
- data/app/views/permission_forge/home/_tidy_card.html.erb +1 -1
- data/app/views/permission_forge/home/index.html.erb +5 -6
- data/app/views/permission_forge/people/_access_tab.html.erb +26 -19
- data/app/views/permission_forge/people/_audit_tab.html.erb +8 -8
- data/app/views/permission_forge/people/_effective_field_source.html.erb +13 -7
- data/app/views/permission_forge/people/_effective_tab.html.erb +16 -15
- data/app/views/permission_forge/people/_effective_why.html.erb +20 -16
- data/app/views/permission_forge/people/_why_item.html.erb +1 -1
- data/app/views/permission_forge/people/confirm_remove_all.html.erb +3 -3
- data/app/views/permission_forge/people/erasure_preview.html.erb +1 -1
- data/app/views/permission_forge/permission_set_groups/confirm_destroy.html.erb +3 -3
- data/app/views/permission_forge/permission_set_groups/index.html.erb +3 -3
- data/app/views/permission_forge/permission_set_groups/show.html.erb +28 -19
- data/app/views/permission_forge/permission_set_rules/_builder.html.erb +35 -15
- data/app/views/permission_forge/permission_set_rules/_preview.html.erb +17 -0
- data/app/views/permission_forge/permission_set_rules/confirm_destroy.html.erb +3 -3
- data/app/views/permission_forge/permission_set_rules/preview.html.erb +12 -0
- data/app/views/permission_forge/permission_sets/_assignments_tab.html.erb +37 -22
- data/app/views/permission_forge/permission_sets/_audit_tab.html.erb +4 -4
- data/app/views/permission_forge/permission_sets/_condition_problems.html.erb +15 -0
- data/app/views/permission_forge/permission_sets/_conditions_tab.html.erb +14 -8
- data/app/views/permission_forge/permission_sets/_header.html.erb +6 -5
- data/app/views/permission_forge/permission_sets/_records_summary.html.erb +2 -2
- data/app/views/permission_forge/permission_sets/_resources_tab.html.erb +4 -4
- data/app/views/permission_forge/permission_sets/confirm_destroy.html.erb +4 -4
- data/app/views/permission_forge/permission_sets/confirm_switch_mode.html.erb +3 -3
- data/app/views/permission_forge/permission_sets/index.html.erb +6 -6
- data/app/views/permission_forge/permission_sets/new.html.erb +2 -2
- data/app/views/permission_forge/permission_sets/review.html.erb +13 -3
- data/app/views/permission_forge/permission_sets/show.html.erb +7 -5
- data/app/views/permission_forge/resource_actions/_across_sets.html.erb +7 -10
- data/app/views/permission_forge/resource_actions/_effect.html.erb +5 -5
- data/app/views/permission_forge/resource_actions/_form.html.erb +1 -1
- data/app/views/permission_forge/resource_actions/_managed_note.html.erb +5 -3
- data/app/views/permission_forge/resource_actions/confirm_destroy.html.erb +1 -1
- data/app/views/permission_forge/resource_actions/new.html.erb +1 -1
- data/app/views/permission_forge/resource_actions/show.html.erb +2 -2
- data/app/views/permission_forge/resource_fields/_explain.html.erb +19 -15
- data/app/views/permission_forge/resource_fields/show.html.erb +21 -12
- data/app/views/permission_forge/resources/_actions_tab.html.erb +4 -4
- data/app/views/permission_forge/resources/_fields_tab.html.erb +17 -9
- data/app/views/permission_forge/resources/_rules_tab.html.erb +9 -6
- data/app/views/permission_forge/resources/_sync.html.erb +8 -5
- data/app/views/permission_forge/resources/confirm_destroy.html.erb +3 -3
- data/app/views/permission_forge/resources/new.html.erb +3 -2
- data/app/views/permission_forge/resources/show.html.erb +6 -6
- data/app/views/permission_forge/settings/_row.html.erb +2 -2
- data/app/views/permission_forge/settings/show.html.erb +10 -1
- data/app/views/permission_forge/shared/_across_sets.html.erb +8 -9
- data/app/views/permission_forge/shared/_audit_events.html.erb +2 -2
- data/app/views/permission_forge/shared/_audit_footer.html.erb +13 -2
- data/app/views/permission_forge/shared/_crud_cell.html.erb +6 -6
- data/app/views/permission_forge/shared/_mode_banners.html.erb +1 -1
- data/app/views/permission_forge/shared/_savebar.html.erb +4 -2
- data/config/locales/permission_forge.en.yml +480 -69
- data/config/locales/permission_forge.mk.yml +499 -79
- data/config/locales/permission_forge.sq.yml +509 -80
- data/config/routes.rb +38 -23
- data/lib/generators/permission_forge/install/install_generator.rb +21 -0
- data/lib/generators/permission_forge/install/templates/initializer.rb.tt +35 -34
- data/lib/generators/permission_forge/install/templates/migration.rb.tt +5 -5
- data/lib/permission_forge/activation_conditions.rb +200 -0
- data/lib/permission_forge/admin/action_label.rb +33 -0
- data/lib/permission_forge/admin/action_rows.rb +3 -1
- data/lib/permission_forge/admin/actor_label.rb +52 -0
- data/lib/permission_forge/admin/attention_items.rb +152 -31
- data/lib/permission_forge/admin/audit_log.rb +4 -7
- data/lib/permission_forge/admin/audit_trail.rb +33 -17
- data/lib/permission_forge/admin/bulk_people_matcher.rb +32 -14
- data/lib/permission_forge/admin/chain_entries.rb +42 -0
- data/lib/permission_forge/admin/change_set.rb +12 -1
- data/lib/permission_forge/admin/change_set_parts/base.rb +5 -4
- data/lib/permission_forge/admin/change_set_parts/context.rb +3 -1
- data/lib/permission_forge/admin/change_set_parts/field_edit.rb +4 -1
- data/lib/permission_forge/admin/change_set_parts/field_levels.rb +1 -1
- data/lib/permission_forge/admin/change_set_parts/new_actions.rb +1 -1
- data/lib/permission_forge/admin/change_set_parts/new_fields.rb +38 -11
- data/lib/permission_forge/admin/change_set_parts/resource_edit.rb +17 -1
- data/lib/permission_forge/admin/change_set_parts/resource_flags.rb +1 -1
- data/lib/permission_forge/admin/change_set_parts/set_attrs.rb +4 -0
- data/lib/permission_forge/admin/change_set_parts/values.rb +10 -4
- data/lib/permission_forge/admin/column_fields.rb +22 -21
- data/lib/permission_forge/admin/conditions_form.rb +47 -15
- data/lib/permission_forge/admin/controller_catalog.rb +42 -14
- data/lib/permission_forge/admin/editable_lock.rb +83 -3
- data/lib/permission_forge/admin/effective_access.rb +147 -48
- data/lib/permission_forge/admin/effective_fields.rb +75 -67
- data/lib/permission_forge/admin/error_sentence.rb +47 -0
- data/lib/permission_forge/admin/expiry_presets.rb +17 -0
- data/lib/permission_forge/admin/exports/holdings.rb +6 -3
- data/lib/permission_forge/admin/field_across_sets.rb +7 -4
- data/lib/permission_forge/admin/field_chain.rb +109 -0
- data/lib/permission_forge/admin/field_detail.rb +24 -11
- data/lib/permission_forge/admin/field_label.rb +109 -0
- data/lib/permission_forge/admin/field_level.rb +4 -68
- data/lib/permission_forge/admin/field_provenance.rb +132 -0
- data/lib/permission_forge/admin/field_rows.rb +26 -11
- data/lib/permission_forge/admin/last_changes.rb +14 -0
- data/lib/permission_forge/admin/list_sentence.rb +32 -0
- data/lib/permission_forge/admin/mask_strategy_choices.rb +57 -0
- data/lib/permission_forge/admin/model_catalog.rb +83 -39
- data/lib/permission_forge/admin/model_label.rb +66 -0
- data/lib/permission_forge/admin/people_candidates.rb +21 -9
- data/lib/permission_forge/admin/people_directory.rb +23 -5
- data/lib/permission_forge/admin/permission_set_factory.rb +6 -15
- data/lib/permission_forge/admin/person_label.rb +105 -16
- data/lib/permission_forge/admin/prefill.rb +92 -0
- data/lib/permission_forge/admin/record_name.rb +45 -0
- data/lib/permission_forge/admin/rule_builder.rb +77 -3
- data/lib/permission_forge/admin/rule_form.rb +44 -10
- data/lib/permission_forge/admin/rule_form_input.rb +27 -11
- data/lib/permission_forge/admin/rule_notes.rb +43 -0
- data/lib/permission_forge/admin/rule_preview.rb +96 -0
- data/lib/permission_forge/admin/sensitive_columns.rb +10 -62
- data/lib/permission_forge/admin/settings_catalog.rb +64 -12
- data/lib/permission_forge/admin/state_label.rb +97 -0
- data/lib/permission_forge/admin_method_override.rb +44 -0
- data/lib/permission_forge/api/schema.rb +322 -0
- data/lib/permission_forge/api/serializers/base_serializer.rb +87 -1
- data/lib/permission_forge/api/serializers/permission_set_group_user_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/permission_set_rule_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/permission_set_serializer.rb +5 -2
- data/lib/permission_forge/api/serializers/permission_set_user_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/resource_action_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/resource_field_serializer.rb +1 -1
- data/lib/permission_forge/api/serializers/resource_serializer.rb +1 -1
- data/lib/permission_forge/api_authorizations.rb +59 -0
- data/lib/permission_forge/api_only_admin.rb +81 -0
- data/lib/permission_forge/audit/actor_codes.rb +32 -0
- data/lib/permission_forge/audit/actor_resolver.rb +1 -2
- data/lib/permission_forge/audit/boot_warnings.rb +29 -9
- data/lib/permission_forge/audit/event_catalog.rb +16 -6
- data/lib/permission_forge/audit/event_query.rb +3 -3
- data/lib/permission_forge/audit/reason_codes.rb +25 -0
- data/lib/permission_forge/audit/runtime.rb +9 -2
- data/lib/permission_forge/audit/settings_observer.rb +2 -2
- data/lib/permission_forge/audit/unmasked_without_user.rb +169 -0
- data/lib/permission_forge/audit.rb +24 -10
- data/lib/permission_forge/boot_checks.rb +180 -63
- data/lib/permission_forge/cache/base.rb +10 -0
- data/lib/permission_forge/cache/null_cache.rb +5 -0
- data/lib/permission_forge/cache/outcome.rb +119 -0
- data/lib/permission_forge/cache/rails_cache.rb +6 -0
- data/lib/permission_forge/cache/report.rb +150 -0
- data/lib/permission_forge/cache/user_keys.rb +8 -4
- data/lib/permission_forge/cache_warnings.rb +140 -0
- data/lib/permission_forge/concerns/auto_permission_scoping.rb +19 -2
- data/lib/permission_forge/concerns/has_tenant_scoping.rb +1 -1
- data/lib/permission_forge/concerns/has_uuid.rb +1 -1
- data/lib/permission_forge/concerns/keeps_copied_values.rb +48 -0
- data/lib/permission_forge/concerns/masked_attributes/alias_access.rb +12 -2
- data/lib/permission_forge/concerns/masked_attributes/virtual_readers.rb +6 -4
- data/lib/permission_forge/concerns/masked_attributes.rb +150 -34
- data/lib/permission_forge/concerns/normalizes_label.rb +78 -0
- data/lib/permission_forge/concerns/permission_cache_accessor.rb +3 -3
- data/lib/permission_forge/concerns/permission_scoped_findable.rb +121 -29
- data/lib/permission_forge/concerns/permissioned_user.rb +1 -1
- data/lib/permission_forge/concerns/records_audit_events.rb +13 -6
- data/lib/permission_forge/concerns/requires_name.rb +37 -0
- data/lib/permission_forge/concerns/validates_column_lengths.rb +24 -0
- data/lib/permission_forge/concerns/validates_dependency.rb +5 -2
- data/lib/permission_forge/concerns/validates_required_columns.rb +74 -0
- data/lib/permission_forge/conditional_sets.rb +13 -6
- data/lib/permission_forge/configuration/admin_ui_settings.rb +18 -4
- data/lib/permission_forge/configuration/audit_settings.rb +2 -1
- data/lib/permission_forge/configuration/controller_action_settings.rb +5 -4
- data/lib/permission_forge/configuration/masked_attributes_settings.rb +73 -19
- data/lib/permission_forge/configuration/presets.rb +20 -11
- data/lib/permission_forge/configuration/query_scoping_settings.rb +55 -0
- data/lib/permission_forge/configuration/strategy_values.rb +2 -1
- data/lib/permission_forge/configuration.rb +41 -18
- data/lib/permission_forge/controller_actions/managed_index.rb +8 -7
- data/lib/permission_forge/database_reach.rb +93 -0
- data/lib/permission_forge/deprecation.rb +1 -1
- data/lib/permission_forge/docs/api_samples.rb +250 -0
- data/lib/permission_forge/docs/configuration_reference.rb +172 -0
- data/lib/permission_forge/encrypted_attributes.rb +150 -0
- data/lib/permission_forge/engine.rb +46 -14
- data/lib/permission_forge/execution_state/restore.rb +74 -0
- data/lib/permission_forge/execution_state.rb +8 -1
- data/lib/permission_forge/field_level.rb +123 -0
- data/lib/permission_forge/field_names.rb +145 -0
- data/lib/permission_forge/field_variants.rb +96 -0
- data/lib/permission_forge/generated_text.rb +95 -0
- data/lib/permission_forge/inspection_filters.rb +83 -0
- data/lib/permission_forge/managed_fields.rb +66 -0
- data/lib/permission_forge/masked_models.rb +25 -19
- data/lib/permission_forge/masking_sync.rb +100 -25
- data/lib/permission_forge/model_bindings.rb +30 -19
- data/lib/permission_forge/model_lookup.rb +42 -0
- data/lib/permission_forge/operators/encrypted_alias.rb +67 -0
- data/lib/permission_forge/operators/registry.rb +6 -0
- data/lib/permission_forge/operators/rule_column.rb +242 -27
- data/lib/permission_forge/operators/rule_path.rb +179 -0
- data/lib/permission_forge/operators/rule_problem.rb +309 -0
- data/lib/permission_forge/operators/rule_sources.rb +96 -0
- data/lib/permission_forge/operators/value_casters/postgresql_types.rb +237 -0
- data/lib/permission_forge/operators/value_casters/temporal.rb +9 -20
- data/lib/permission_forge/operators/value_casters.rb +61 -13
- data/lib/permission_forge/operators/value_typing.rb +83 -7
- data/lib/permission_forge/query_scoping_warnings.rb +22 -17
- data/lib/permission_forge/rake_audit.rb +5 -4
- data/lib/permission_forge/rake_dry_run.rb +32 -0
- data/lib/permission_forge/rake_output.rb +189 -0
- data/lib/permission_forge/record_read_access.rb +51 -0
- data/lib/permission_forge/resource_chain.rb +14 -2
- data/lib/permission_forge/schema_read/ordered_types.rb +72 -0
- data/lib/permission_forge/schema_read.rb +335 -0
- data/lib/permission_forge/secret_columns.rb +49 -0
- data/lib/permission_forge/sensitive_columns.rb +114 -0
- data/lib/permission_forge/services/attribute_access_guard.rb +22 -14
- data/lib/permission_forge/services/bulk_permission_set_assignment.rb +16 -8
- data/lib/permission_forge/services/concerns/clonable.rb +9 -0
- data/lib/permission_forge/services/concerns/settles_cache.rb +58 -0
- data/lib/permission_forge/services/consent_withdrawal_handler.rb +37 -19
- data/lib/permission_forge/services/data_subject_erasure.rb +83 -45
- data/lib/permission_forge/services/expired_assignment_cleaner.rb +62 -25
- data/lib/permission_forge/services/extra_field_defaults.rb +1 -1
- data/lib/permission_forge/services/field_permission_merger.rb +44 -17
- data/lib/permission_forge/services/filters_response_attributes.rb +20 -5
- data/lib/permission_forge/services/gdpr_field_auditor/text_report.rb +33 -5
- data/lib/permission_forge/services/gdpr_field_auditor.rb +68 -48
- data/lib/permission_forge/services/host_key_guard.rb +8 -5
- data/lib/permission_forge/services/mask_value.rb +72 -11
- data/lib/permission_forge/services/permission_cache_clearer.rb +36 -15
- data/lib/permission_forge/services/permission_hash_builder.rb +48 -19
- data/lib/permission_forge/services/permission_rule_applier.rb +131 -68
- data/lib/permission_forge/services/permission_set_cloner.rb +32 -8
- data/lib/permission_forge/services/permission_set_condition_evaluator.rb +38 -36
- data/lib/permission_forge/services/permission_set_group_cloner.rb +2 -1
- data/lib/permission_forge/services/permits_attributes/result.rb +31 -0
- data/lib/permission_forge/services/permits_attributes.rb +48 -27
- data/lib/permission_forge/services/resource_field_generator/row_builder.rb +96 -0
- data/lib/permission_forge/services/resource_field_generator.rb +71 -42
- data/lib/permission_forge/services/resource_with_fields_builder.rb +2 -1
- data/lib/permission_forge/services/rls_operator_sql.rb +83 -24
- data/lib/permission_forge/services/rls_policy_generator.rb +83 -39
- data/lib/permission_forge/services/rls_read_access_sql.rb +131 -0
- data/lib/permission_forge/services/security_audit/action_check.rb +49 -5
- data/lib/permission_forge/services/security_audit/assignment_check.rb +2 -1
- data/lib/permission_forge/services/security_audit/audit_trail_check.rb +1 -3
- data/lib/permission_forge/services/security_audit/conditions_check.rb +92 -0
- data/lib/permission_forge/services/security_audit/config_check.rb +41 -18
- data/lib/permission_forge/services/security_audit/integrity_check.rb +71 -4
- data/lib/permission_forge/services/security_audit/masking_without_user.rb +66 -0
- data/lib/permission_forge/services/security_audit/personal_data_check.rb +7 -11
- data/lib/permission_forge/services/security_audit/rule_check.rb +180 -50
- data/lib/permission_forge/services/security_audit/text_report.rb +14 -2
- data/lib/permission_forge/services/security_audit/unmanaged_actions.rb +145 -0
- data/lib/permission_forge/services/security_audit/user_model_check.rb +4 -3
- data/lib/permission_forge/services/security_audit.rb +16 -4
- data/lib/permission_forge/services/stale_assignment_auditor.rb +8 -5
- data/lib/permission_forge/timestamp_fields.rb +60 -0
- data/lib/permission_forge/unique_conflict.rb +117 -0
- data/lib/permission_forge/version.rb +1 -1
- data/lib/permission_forge/virtual_fields/discovery.rb +2 -1
- data/lib/permission_forge/virtual_fields.rb +15 -5
- data/lib/permission_forge/warnings.rb +263 -0
- data/lib/permission_forge/web_request.rb +248 -0
- data/lib/permission_forge.rb +54 -0
- data/lib/tasks/permission_forge.rake +26 -6
- data/lib/tasks/permission_forge_audit_events.rake +8 -4
- data/lib/tasks/permission_forge_cache.rake +12 -12
- data/lib/tasks/permission_forge_docs.rake +48 -0
- data/lib/tasks/permission_forge_expiration.rake +11 -5
- data/lib/tasks/permission_forge_gdpr.rake +36 -20
- metadata +84 -1
|
@@ -84,14 +84,27 @@ module PermissionForge
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Without a user there is no entry to decide by:
|
|
87
|
-
# config.masked_attributes_nil_user_strategy says whether
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
87
|
+
# config.masked_attributes_nil_user_strategy says whether the attribute
|
|
88
|
+
# passes (:bypass, the default), is hidden (:hide), or is hidden inside
|
|
89
|
+
# a web request only (:hide_in_requests) — never on the user model,
|
|
90
|
+
# which sign-in reads before anyone is signed in
|
|
91
|
+
# (Configuration#masked_attributes_without_user_for). A read returned
|
|
92
|
+
# as stored inside a web request under :bypass is recorded
|
|
93
|
+
# (Audit::UnmaskedWithoutUser). The model is the record's class, or
|
|
94
|
+
# else the one the first name of the chain names (an anonymous
|
|
95
|
+
# subclass answers as its base).
|
|
92
96
|
def without_user
|
|
93
|
-
model = @model || @resource_name
|
|
94
|
-
PermissionForge.configuration.
|
|
97
|
+
model = @model || PermissionForge::ModelLookup.model_for(@resource_name)
|
|
98
|
+
case PermissionForge.configuration.masked_attributes_without_user_for(model)
|
|
99
|
+
when :hide then hidden
|
|
100
|
+
when :record then unmasked_in_request(model)
|
|
101
|
+
else bypass
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def unmasked_in_request(model)
|
|
106
|
+
PermissionForge::Audit::UnmaskedWithoutUser.note(model || @resource_name, @attribute)
|
|
107
|
+
bypass
|
|
95
108
|
end
|
|
96
109
|
|
|
97
110
|
def bypass
|
|
@@ -116,13 +129,8 @@ module PermissionForge
|
|
|
116
129
|
end
|
|
117
130
|
|
|
118
131
|
def unconfigured
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
strategy = FiltersResponseAttributes::UNCONFIGURED_FIELD_STRATEGIES.fetch(
|
|
122
|
-
strategy_key,
|
|
123
|
-
FiltersResponseAttributes::UNCONFIGURED_FIELD_STRATEGIES[:block]
|
|
124
|
-
)
|
|
125
|
-
include_field, value = strategy.call(@resource_name.to_s, @attribute, @raw_value)
|
|
132
|
+
# FiltersResponseAttributes.unconfigured_strategy: the one lookup (an unknown key blocks).
|
|
133
|
+
include_field, value = FiltersResponseAttributes.unconfigured_strategy.call(@resource_name.to_s, @attribute, @raw_value)
|
|
126
134
|
notify(:unconfigured)
|
|
127
135
|
[include_field, value]
|
|
128
136
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative '../cache/report'
|
|
2
|
+
|
|
1
3
|
module PermissionForge
|
|
2
4
|
module Services
|
|
3
5
|
# Assigns or revokes a permission set for multiple users in a single
|
|
@@ -10,7 +12,7 @@ module PermissionForge
|
|
|
10
12
|
# result = BulkPermissionSetAssignment.call(
|
|
11
13
|
# permission_set: ps, user_ids: [1, 2, 3], action: :assign
|
|
12
14
|
# )
|
|
13
|
-
# # => { assigned: 3, revoked: 0, invalidated: 3 }
|
|
15
|
+
# # => { assigned: 3, revoked: 0, invalidated: 3, cache: { confirmed: true, process_local: false, unconfirmed: 0 } }
|
|
14
16
|
#
|
|
15
17
|
# `invalidated` counts the users assigned or revoked whose invalidation
|
|
16
18
|
# the cache store confirmed. Fewer means the store failed for the rest
|
|
@@ -18,7 +20,11 @@ module PermissionForge
|
|
|
18
20
|
# may live until cache_ttl. A user who already held the set (assign) or
|
|
19
21
|
# never did (revoke) changed nothing and is not invalidated. Inside a
|
|
20
22
|
# transaction of the caller the invalidations happen at its commit, after
|
|
21
|
-
# this returns: `invalidated` is then nil (not known).
|
|
23
|
+
# this returns: `invalidated` is then nil (not known). `cache` is the one
|
|
24
|
+
# Cache::Outcome every cache-touching operation answers (Cache::Report):
|
|
25
|
+
# `confirmed` (nil inside the caller's transaction), `process_local`
|
|
26
|
+
# (the store lives in this process only, so other processes kept their
|
|
27
|
+
# entries), `unconfirmed`.
|
|
22
28
|
#
|
|
23
29
|
# Every row is created or destroyed through its callbacks, so each person
|
|
24
30
|
# gets an assignment.granted / assignment.revoked audit event, all with
|
|
@@ -35,7 +41,7 @@ module PermissionForge
|
|
|
35
41
|
VALID_ACTIONS = ACTION_STRATEGIES.keys.freeze
|
|
36
42
|
|
|
37
43
|
# Assigns or revokes a Permission Set for many users in one transaction.
|
|
38
|
-
# @return [Hash] `{ assigned:, revoked:, invalidated: }`
|
|
44
|
+
# @return [Hash] `{ assigned:, revoked:, invalidated:, cache: }`
|
|
39
45
|
def self.call(...) = new(...).call
|
|
40
46
|
|
|
41
47
|
# @raise [ArgumentError] unless `action` is one of VALID_ACTIONS
|
|
@@ -59,17 +65,19 @@ module PermissionForge
|
|
|
59
65
|
@changed_user_ids = []
|
|
60
66
|
end
|
|
61
67
|
|
|
62
|
-
# @return [Hash] `{ assigned:, revoked:, invalidated: }`
|
|
68
|
+
# @return [Hash] `{ assigned:, revoked:, invalidated:, cache: }`
|
|
63
69
|
def call
|
|
64
70
|
validate_action!
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
# what the store confirmed (Cache::Report) — not what a host's override of invalidate_permissions returns;
|
|
73
|
+
# pending inside a caller's transaction (Report#deferred?)
|
|
74
|
+
report = PermissionForge::Cache::Report.measure do
|
|
68
75
|
ActiveRecord::Base.transaction { send(ACTION_STRATEGIES.fetch(@action)) }
|
|
69
76
|
end
|
|
77
|
+
outcome = report.outcome(user_ids: @changed_user_ids)
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
{ assigned: @assigned_count, revoked: @revoked_count,
|
|
80
|
+
invalidated: report.deferred? ? nil : @changed_user_ids.size - outcome.unconfirmed, cache: outcome.to_h }
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
private
|
|
@@ -24,6 +24,15 @@ module PermissionForge
|
|
|
24
24
|
def reset_on_copy(record)
|
|
25
25
|
RESET_ON_COPY.select { |column| record.has_attribute?(column) }.index_with(nil)
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
# A row whose blank name the validations would refuse on a new record
|
|
29
|
+
# (ResourceField, ResourceAction: `validates :name, presence`), so a
|
|
30
|
+
# copy of it can never be saved.
|
|
31
|
+
def refused_for_blank_name?(record)
|
|
32
|
+
return false unless record.respond_to?(:name) && record.name.to_s.strip.empty?
|
|
33
|
+
|
|
34
|
+
record.class.validators_on(:name).any? { |validator| validator.kind == :presence }
|
|
35
|
+
end
|
|
27
36
|
end
|
|
28
37
|
end
|
|
29
38
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
module Services
|
|
5
|
+
module Concerns
|
|
6
|
+
# For an operation whose summary event is written in its transaction,
|
|
7
|
+
# before its cache work (erasure.performed, consent.withdrawn record
|
|
8
|
+
# `cache_confirmed: nil`): one `cache.settled` event, on the
|
|
9
|
+
# operation's correlation id, says what the cache store answered once
|
|
10
|
+
# the work ran.
|
|
11
|
+
#
|
|
12
|
+
# Outside a transaction that is the outcome the operation just
|
|
13
|
+
# measured. Inside a caller's one (Cache::Report#deferred?) the work is
|
|
14
|
+
# only done for everybody at its commit: it runs again there, measured,
|
|
15
|
+
# and the event is written then, in the context of the operation
|
|
16
|
+
# (OpenTransaction#after_commit: never after a rollback). The work
|
|
17
|
+
# always runs, also with auditing off; a failure there is reported,
|
|
18
|
+
# never raised out of the commit, and a store failure is what the event
|
|
19
|
+
# records (`cache_confirmed: false`).
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
module SettlesCache
|
|
23
|
+
EVENT = 'cache.settled'
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# @param operation [String] the operation's name in the event's data ('erasure')
|
|
28
|
+
# @param subject [Object] the person whose cached permissions it cleared
|
|
29
|
+
# @param report [Cache::Report] the operation's measured report
|
|
30
|
+
# @yield the cache work, run again after a caller's commit
|
|
31
|
+
def settle_cache(operation, subject, report, &work)
|
|
32
|
+
transaction = PermissionForge::OpenTransaction.current if report.deferred?
|
|
33
|
+
return record_settled(operation, subject, report.outcome) unless transaction
|
|
34
|
+
|
|
35
|
+
context = PermissionForge::Audit::Context.current
|
|
36
|
+
transaction.after_commit do
|
|
37
|
+
PermissionForge::Audit::Context.with(**context) do
|
|
38
|
+
record_settled(operation, subject, PermissionForge::Cache::Report.of(&work))
|
|
39
|
+
end
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
PermissionForge::Cache::Failure.report(operation: :invalidate, user_id: subject.id, error: e)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Written after the operation's transaction: like Audit::Detached, a
|
|
46
|
+
# failure to store it is reported (or logged), never raised — the
|
|
47
|
+
# operation has happened.
|
|
48
|
+
def record_settled(operation, subject, outcome)
|
|
49
|
+
PermissionForge::Audit.record(EVENT, subject_user_id: subject.id,
|
|
50
|
+
data: { 'operation' => operation, **outcome.event_data })
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
strategy = PermissionForge.configuration.audit_hook_failure_strategy
|
|
53
|
+
PermissionForge::Audit::Failure.handle(e, strategy: strategy == :raise ? :report : strategy, operation: :store, event: EVENT)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative '../cache/report'
|
|
2
|
+
|
|
1
3
|
module PermissionForge
|
|
2
4
|
module Services
|
|
3
5
|
# Handles consent withdrawal by removing a user from all permission sets
|
|
@@ -9,11 +11,20 @@ module PermissionForge
|
|
|
9
11
|
# user: user,
|
|
10
12
|
# consent_flag: 'marketing_consent'
|
|
11
13
|
# )
|
|
12
|
-
# # => { revoked_sets: 2, invalidated: true
|
|
14
|
+
# # => { revoked_sets: 2, invalidated: true, affected_sets: ["Marketing Access", "Newsletter"],
|
|
15
|
+
# # affected_assignment_count: 2, dry_run: false,
|
|
16
|
+
# # cache: { confirmed: true, process_local: false, unconfirmed: 0 } }
|
|
13
17
|
#
|
|
14
18
|
# `invalidated` is false when nothing was revoked, and when the cache
|
|
15
19
|
# store did not confirm the invalidation (reported through
|
|
16
|
-
# `config.on_cache_failure`).
|
|
20
|
+
# `config.on_cache_failure`). `cache` is the one Cache::Outcome every
|
|
21
|
+
# cache-touching operation answers (Cache::Report): `confirmed` (true
|
|
22
|
+
# also when nothing had to be invalidated), `process_local` (the store
|
|
23
|
+
# lives in this process only: other processes kept their entries),
|
|
24
|
+
# `unconfirmed`. A dry run and a real run answer the same keys:
|
|
25
|
+
# `affected_sets` (the names of the sets gated on the flag),
|
|
26
|
+
# `affected_assignment_count` (this person's assignments to them),
|
|
27
|
+
# `dry_run`; a dry run's `revoked_sets` is 0 and its `invalidated` false.
|
|
17
28
|
#
|
|
18
29
|
# @example Dry run (inspect without revoking)
|
|
19
30
|
# result = ConsentWithdrawalHandler.call(
|
|
@@ -21,7 +32,8 @@ module PermissionForge
|
|
|
21
32
|
# consent_flag: 'marketing_consent',
|
|
22
33
|
# dry_run: true
|
|
23
34
|
# )
|
|
24
|
-
# # => { revoked_sets: 0, affected_sets: ["Marketing Access"],
|
|
35
|
+
# # => { revoked_sets: 0, invalidated: false, affected_sets: ["Marketing Access"],
|
|
36
|
+
# # affected_assignment_count: 1, dry_run: true, cache: { confirmed: nil, work: false, ... } }
|
|
25
37
|
#
|
|
26
38
|
# The audit log: assignments are destroyed through their callbacks (one
|
|
27
39
|
# `assignment.revoked` each), then ONE `consent.withdrawn` event names the
|
|
@@ -32,8 +44,10 @@ module PermissionForge
|
|
|
32
44
|
# The revocations and `consent.withdrawn` happen in ONE transaction, like
|
|
33
45
|
# DataSubjectErasure: if the summary cannot be written under
|
|
34
46
|
# audit_change_failure_strategy :raise, no assignment is removed. The
|
|
35
|
-
# cache invalidation runs after the commit
|
|
36
|
-
#
|
|
47
|
+
# cache invalidation runs after the commit, so the summary records the
|
|
48
|
+
# outcome as pending (`cache_confirmed: nil`, the store's scope in
|
|
49
|
+
# `process_local`); the result's `invalidated` and `cache` say what the
|
|
50
|
+
# store confirmed.
|
|
37
51
|
#
|
|
38
52
|
# Tenants: with a tenant ambient, the person's assignments in that
|
|
39
53
|
# tenant. With none (a platform task) in every tenant — also under
|
|
@@ -46,6 +60,7 @@ module PermissionForge
|
|
|
46
60
|
class ConsentWithdrawalHandler
|
|
47
61
|
include Concerns::AuditedOperation
|
|
48
62
|
include Concerns::AcrossTenants
|
|
63
|
+
include Concerns::SettlesCache
|
|
49
64
|
|
|
50
65
|
# Removes a user from every Permission Set gated on the consent flag.
|
|
51
66
|
# @return [Hash] see {#call}
|
|
@@ -59,27 +74,31 @@ module PermissionForge
|
|
|
59
74
|
@reason = reason
|
|
60
75
|
end
|
|
61
76
|
|
|
62
|
-
# @return [Hash] `{ revoked_sets:, invalidated
|
|
77
|
+
# @return [Hash] `{ revoked_sets:, invalidated:, affected_sets:, affected_assignment_count:, dry_run:, cache: }`
|
|
63
78
|
def call
|
|
64
79
|
validate!
|
|
65
80
|
|
|
66
81
|
across_tenants do
|
|
67
82
|
affected_sets = consent_gated_sets
|
|
68
83
|
affected_assignments = user_assignments_for(affected_sets)
|
|
69
|
-
|
|
84
|
+
found = found(affected_sets, affected_assignments)
|
|
85
|
+
next found.merge(revoked_sets: 0, invalidated: false, cache: PermissionForge::Cache::Report.none.to_h) if @dry_run
|
|
70
86
|
|
|
71
|
-
audited(actor: @actor, reason: @reason) { withdraw(affected_assignments) }
|
|
87
|
+
audited(actor: @actor, reason: @reason) { withdraw(affected_assignments).merge(found) }
|
|
72
88
|
end
|
|
73
89
|
end
|
|
74
90
|
|
|
75
91
|
private
|
|
76
92
|
|
|
77
93
|
def withdraw(affected_assignments)
|
|
94
|
+
@report = PermissionForge::Cache::Report.new
|
|
78
95
|
revoked_count = ActiveRecord::Base.transaction { revoke_and_record(affected_assignments) }
|
|
79
|
-
# what the store confirmed — not what a host's override of the method returns
|
|
80
|
-
|
|
96
|
+
# what the store confirmed — not what a host's override of the method returns;
|
|
97
|
+
# pending inside a caller's transaction (Report#deferred?)
|
|
98
|
+
outcome = @report.measure { @user.invalidate_permissions if revoked_count.positive? }.outcome
|
|
99
|
+
settle_cache('consent_withdrawal', @user, @report) { @user.invalidate_permissions } if revoked_count.positive?
|
|
81
100
|
|
|
82
|
-
{ revoked_sets: revoked_count, invalidated:
|
|
101
|
+
{ revoked_sets: revoked_count, invalidated: revoked_count.positive? && outcome.confirmed, cache: outcome.to_h }
|
|
83
102
|
end
|
|
84
103
|
|
|
85
104
|
def revoke_and_record(affected_assignments)
|
|
@@ -97,6 +116,7 @@ module PermissionForge
|
|
|
97
116
|
end
|
|
98
117
|
end
|
|
99
118
|
|
|
119
|
+
# The cache outcome is pending here: the invalidation follows the commit.
|
|
100
120
|
def summary_data(sets, revoked_count)
|
|
101
121
|
{
|
|
102
122
|
'operation' => 'consent_withdrawal',
|
|
@@ -105,7 +125,8 @@ module PermissionForge
|
|
|
105
125
|
'set_uuids' => sets.map(&:uuid).uniq,
|
|
106
126
|
'set_names' => sets.map(&:name).uniq,
|
|
107
127
|
'revoked' => revoked_count,
|
|
108
|
-
'dry_run' => false
|
|
128
|
+
'dry_run' => false,
|
|
129
|
+
**@report.pending.event_data
|
|
109
130
|
}
|
|
110
131
|
end
|
|
111
132
|
|
|
@@ -122,13 +143,10 @@ module PermissionForge
|
|
|
122
143
|
PermissionForge::PermissionSetUser.where(user: @user, permission_set: sets)
|
|
123
144
|
end
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
affected_assignment_count: assignments.count,
|
|
130
|
-
dry_run: true
|
|
131
|
-
}
|
|
146
|
+
# What the run found before it removed anything: the same keys for a
|
|
147
|
+
# dry run and a real one.
|
|
148
|
+
def found(sets, assignments)
|
|
149
|
+
{ affected_sets: sets.pluck(:name), affected_assignment_count: assignments.count, dry_run: @dry_run ? true : false }
|
|
132
150
|
end
|
|
133
151
|
end
|
|
134
152
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative '../cache/report'
|
|
2
|
+
|
|
1
3
|
module PermissionForge
|
|
2
4
|
module Services
|
|
3
5
|
# Supports a GDPR Art. 17 (right to erasure) request for a host user.
|
|
@@ -26,7 +28,9 @@ module PermissionForge
|
|
|
26
28
|
#
|
|
27
29
|
# @example Full erasure
|
|
28
30
|
# result = DataSubjectErasure.call(user: user)
|
|
29
|
-
# # => { assignments_removed: 3, group_assignments_removed: 1,
|
|
31
|
+
# # => { assignments_removed: 3, group_assignments_removed: 1, permission_sets: ["Sales"], groups: [],
|
|
32
|
+
# # actor_labels_cleared: 2, record_labels_cleared: 6, events_scrubbed: 8, cache_cleared: true,
|
|
33
|
+
# # callback_invoked: true, cache: { confirmed: true, process_local: false, unconfirmed: 0 }, ... }
|
|
30
34
|
#
|
|
31
35
|
# `cache_cleared` is false when the cache store did not confirm the
|
|
32
36
|
# removal (it is unreachable, say): the assignments are gone, but the
|
|
@@ -37,9 +41,22 @@ module PermissionForge
|
|
|
37
41
|
# transaction has committed, and a failure then is reported through
|
|
38
42
|
# `config.on_cache_failure` alone.
|
|
39
43
|
#
|
|
44
|
+
# `cache` is the one Cache::Outcome every cache-touching operation
|
|
45
|
+
# answers (Cache::Report): `confirmed` (as `cache_cleared`),
|
|
46
|
+
# `process_local`, `unconfirmed`. `cache_cleared` speaks for the store
|
|
47
|
+
# this process reaches. When that store lives in this process only (an
|
|
48
|
+
# in-memory store, Cache::Base#process_local? — `cache_backend = :memory`,
|
|
49
|
+
# or `Rails.cache` a `:memory_store`), a run from a rake task or a console
|
|
50
|
+
# cleared nothing in the web and job processes: they keep the person's
|
|
51
|
+
# cached permissions until they restart or the entries expire
|
|
52
|
+
# (cache_ttl). `erasure.performed` then says `process_local: true`; its
|
|
53
|
+
# `cache_confirmed` is nil, because the event is written in the
|
|
54
|
+
# erasure's transaction and the cache work follows it.
|
|
55
|
+
#
|
|
40
56
|
# @example Dry run (inspect without removing)
|
|
41
57
|
# result = DataSubjectErasure.call(user: user, dry_run: true)
|
|
42
|
-
# # => { assignments_count: 3, group_assignments_count: 1, permission_sets: [...],
|
|
58
|
+
# # => { assignments_count: 3, group_assignments_count: 1, permission_sets: [...],
|
|
59
|
+
# # actor_labels_to_clear: 2, record_labels_to_clear: 4, dry_run: true }
|
|
43
60
|
#
|
|
44
61
|
# The audit log (docs/audit-log.md): the rows are destroyed through their
|
|
45
62
|
# callbacks, so each assignment writes its own `assignment.revoked`
|
|
@@ -77,6 +94,10 @@ module PermissionForge
|
|
|
77
94
|
class DataSubjectErasure
|
|
78
95
|
include Concerns::AuditedOperation
|
|
79
96
|
include Concerns::AcrossTenants
|
|
97
|
+
include Concerns::SettlesCache
|
|
98
|
+
|
|
99
|
+
# The name labels erasure clears, by the count they are reported under.
|
|
100
|
+
LABELS = { actor_labels: :actor_label, record_labels: :record_label }.freeze
|
|
80
101
|
|
|
81
102
|
# Erases a user's permission data (GDPR Art. 17).
|
|
82
103
|
# @return [Hash] see {#call}
|
|
@@ -104,22 +125,31 @@ module PermissionForge
|
|
|
104
125
|
private
|
|
105
126
|
|
|
106
127
|
def erase
|
|
128
|
+
@report = PermissionForge::Cache::Report.new
|
|
107
129
|
removed = ActiveRecord::Base.transaction { remove_and_record }
|
|
108
|
-
|
|
130
|
+
outcome = clear_cache
|
|
131
|
+
settle_cache('erasure', @user, @report) { remove_user_cache }
|
|
109
132
|
|
|
110
|
-
removed.merge(cache_cleared:
|
|
133
|
+
removed.merge(cache_cleared: outcome.confirmed, callback_invoked: run_callback, cache: outcome.to_h)
|
|
111
134
|
end
|
|
112
135
|
|
|
113
136
|
def remove_and_record
|
|
114
137
|
sets = permission_set_assignments.includes(:permission_set).filter_map(&:permission_set)
|
|
115
138
|
groups = group_assignments.includes(:permission_set_group).filter_map(&:permission_set_group)
|
|
116
139
|
|
|
117
|
-
counts = { assignments_removed:
|
|
118
|
-
group_assignments_removed:
|
|
140
|
+
counts = { assignments_removed: remove_all(permission_set_assignments),
|
|
141
|
+
group_assignments_removed: remove_all(group_assignments) }
|
|
119
142
|
counts.merge!(remaining_elsewhere)
|
|
120
143
|
scrubbed = scrub_labels
|
|
121
144
|
record_summary(sets, groups, counts, scrubbed)
|
|
122
|
-
counts
|
|
145
|
+
counts.merge(result_details(sets, groups, scrubbed))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The names removed and the labels cleared, as the result reports them.
|
|
149
|
+
def result_details(sets, groups, scrubbed)
|
|
150
|
+
{ permission_sets: sets.map(&:name).uniq, groups: groups.map(&:name).uniq,
|
|
151
|
+
actor_labels_cleared: scrubbed[:actor_labels], record_labels_cleared: scrubbed[:record_labels],
|
|
152
|
+
events_scrubbed: scrubbed.values.sum }
|
|
123
153
|
end
|
|
124
154
|
|
|
125
155
|
# What the person still has in other tenants (nothing is removed there).
|
|
@@ -149,14 +179,24 @@ module PermissionForge
|
|
|
149
179
|
# basic auth login that shares the id is somebody else), and recorded
|
|
150
180
|
# in `erasure.performed`.
|
|
151
181
|
def scrub_labels
|
|
152
|
-
events = scrubbed_events
|
|
153
|
-
id = @user.id.to_s
|
|
154
182
|
{
|
|
155
|
-
actor_labels:
|
|
156
|
-
record_labels:
|
|
183
|
+
actor_labels: labelled(:actor_labels).update_all(actor_label: nil),
|
|
184
|
+
record_labels: labelled(:record_labels).update_all(record_label: nil)
|
|
157
185
|
}
|
|
158
186
|
end
|
|
159
187
|
|
|
188
|
+
# The events whose label names the person: as the actor (an actor of
|
|
189
|
+
# type 'user', in actor_label) or as the subject (in record_label).
|
|
190
|
+
def labelled(kind)
|
|
191
|
+
id = @user.id.to_s
|
|
192
|
+
events = if kind == :actor_labels
|
|
193
|
+
scrubbed_events.where(actor_type: 'user', actor_id: id)
|
|
194
|
+
else
|
|
195
|
+
scrubbed_events.where(subject_user_id: id)
|
|
196
|
+
end
|
|
197
|
+
events.where.not(LABELS.fetch(kind) => nil)
|
|
198
|
+
end
|
|
199
|
+
|
|
160
200
|
# With a tenant current (a tenant admin), that tenant's events only: a
|
|
161
201
|
# tenant cannot reach another tenant's log. With none (the rake task, a
|
|
162
202
|
# platform operator), every tenant's, and the events without a tenant.
|
|
@@ -172,12 +212,15 @@ module PermissionForge
|
|
|
172
212
|
data: summary_data(sets, groups, counts, scrubbed))
|
|
173
213
|
end
|
|
174
214
|
|
|
215
|
+
# The cache outcome is pending here: the invalidation follows this
|
|
216
|
+
# transaction (@report.pending: the store's scope, nothing confirmed).
|
|
175
217
|
def summary_data(sets, groups, counts, scrubbed)
|
|
176
218
|
cleared = { 'events_scrubbed' => scrubbed.values.sum, 'actor_labels_cleared' => scrubbed[:actor_labels],
|
|
177
219
|
'record_labels_cleared' => scrubbed[:record_labels] }
|
|
178
220
|
names(sets, 'set').merge(names(groups, 'group'), counts.transform_keys(&:to_s), cleared,
|
|
179
221
|
'operation' => 'erasure', 'subject_user_id' => @user.id.to_s,
|
|
180
|
-
'callback_invoked' => !@on_erasure.nil?, 'dry_run' => false
|
|
222
|
+
'callback_invoked' => !@on_erasure.nil?, 'dry_run' => false,
|
|
223
|
+
**@report.pending.event_data)
|
|
181
224
|
end
|
|
182
225
|
|
|
183
226
|
def names(records, kind)
|
|
@@ -196,42 +239,41 @@ module PermissionForge
|
|
|
196
239
|
PermissionForge::PermissionSetGroupUser.where(user: @user)
|
|
197
240
|
end
|
|
198
241
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
count
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def remove_group_assignments
|
|
207
|
-
records = group_assignments
|
|
242
|
+
# Destroys the rows through their callbacks (one audit event each).
|
|
243
|
+
#
|
|
244
|
+
# @return [Integer] how many there were
|
|
245
|
+
def remove_all(records)
|
|
208
246
|
count = records.count
|
|
209
247
|
records.destroy_all
|
|
210
248
|
count
|
|
211
249
|
end
|
|
212
250
|
|
|
213
|
-
#
|
|
214
|
-
# invalidate_permissions: a host app overrides that method.
|
|
251
|
+
# Measured by Cache::Report (the gem's own failure bookkeeping), not
|
|
252
|
+
# the value of invalidate_permissions: a host app overrides that method.
|
|
215
253
|
#
|
|
216
254
|
# An invalidation leaves the per-context variants of a user with
|
|
217
255
|
# conditional sets to their TTL (unreachable). They hold the profile,
|
|
218
256
|
# so they are removed as well (Cache::VariantRemoval) — inside the
|
|
219
|
-
# caller's transaction once it has committed
|
|
220
|
-
#
|
|
221
|
-
#
|
|
257
|
+
# caller's transaction once it has committed (SettlesCache runs the
|
|
258
|
+
# work again there, measured, and records cache.settled), after the
|
|
259
|
+
# assignments' own invalidations: until then another request may still
|
|
260
|
+
# cache one from the rows as they were.
|
|
222
261
|
#
|
|
223
|
-
# @return [
|
|
224
|
-
#
|
|
262
|
+
# @return [PermissionForge::Cache::Outcome] confirmed nil inside an
|
|
263
|
+
# open transaction, where that is only known at its commit
|
|
225
264
|
def clear_cache
|
|
226
|
-
|
|
227
|
-
cleared = PermissionForge::Cache::Failure.none? do
|
|
265
|
+
@report.measure do
|
|
228
266
|
invalidate_user
|
|
229
|
-
PermissionForge::Cache::VariantRemoval.call(**variant_removal) unless
|
|
267
|
+
PermissionForge::Cache::VariantRemoval.call(**variant_removal) unless @report.deferred?
|
|
230
268
|
end
|
|
231
|
-
|
|
269
|
+
@report.outcome
|
|
270
|
+
end
|
|
232
271
|
|
|
233
|
-
|
|
234
|
-
|
|
272
|
+
# The person's cache work after a caller's commit: the invalidation and
|
|
273
|
+
# every per-context variant.
|
|
274
|
+
def remove_user_cache
|
|
275
|
+
invalidate_user
|
|
276
|
+
PermissionForge::Cache::VariantRemoval.call(**variant_removal)
|
|
235
277
|
end
|
|
236
278
|
|
|
237
279
|
# The arguments of the person's Cache::VariantRemoval: the keys the
|
|
@@ -243,17 +285,6 @@ module PermissionForge
|
|
|
243
285
|
{ user_ids: [@user.id], indexed_only: false, **named }
|
|
244
286
|
end
|
|
245
287
|
|
|
246
|
-
# Nothing raises out of the commit: a failure is reported, like every
|
|
247
|
-
# after-commit invalidation (Cache::Failure).
|
|
248
|
-
def remove_variants_after_commit(transaction)
|
|
249
|
-
removal = variant_removal
|
|
250
|
-
transaction.after_commit do
|
|
251
|
-
PermissionForge::Cache::VariantRemoval.call(**removal)
|
|
252
|
-
rescue StandardError => e
|
|
253
|
-
PermissionForge::Cache::Failure.report(operation: :invalidate, user_id: removal[:user_ids].first, error: e)
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
288
|
def invalidate_user
|
|
258
289
|
# invalidate_permissions (not a raw cache delete) so the ambient and
|
|
259
290
|
# tenant-agnostic keys are both removed AND the process-wide memo
|
|
@@ -293,11 +324,18 @@ module PermissionForge
|
|
|
293
324
|
Rails.error.report(e, handled: true, source: 'permission_forge.data_subject_erasure')
|
|
294
325
|
end
|
|
295
326
|
|
|
327
|
+
# The label counts (actor_labels_to_clear, record_labels_to_clear) are of
|
|
328
|
+
# the events there now — none on an app whose audit table is not there
|
|
329
|
+
# yet; the events the erasure itself writes (assignment.revoked, ...) are
|
|
330
|
+
# cleared as well.
|
|
296
331
|
def dry_run_result
|
|
332
|
+
table = PermissionForge::DatabaseReach.table_exists?(PermissionForge::AuditEvent)
|
|
297
333
|
{
|
|
298
334
|
assignments_count: permission_set_assignments.count,
|
|
299
335
|
group_assignments_count: group_assignments.count,
|
|
300
336
|
permission_sets: permission_set_assignments.includes(:permission_set).filter_map { |psu| psu.permission_set&.name },
|
|
337
|
+
**LABELS.keys.to_h { |kind| [:"#{kind}_to_clear", table ? labelled(kind).count : 0] },
|
|
338
|
+
cache: PermissionForge::Cache::Report.none.to_h,
|
|
301
339
|
dry_run: true
|
|
302
340
|
}
|
|
303
341
|
end
|