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
|
@@ -422,6 +422,42 @@
|
|
|
422
422
|
}
|
|
423
423
|
});
|
|
424
424
|
|
|
425
|
+
/* ------------------------------------------- rule builder preview */
|
|
426
|
+
// The preview is the sentence the saved rule shows, rendered by the server
|
|
427
|
+
// (the preview action, SentencesHelper): as the value, "the current user"
|
|
428
|
+
// or "or empty" change, the panel's content is fetched again. Field and
|
|
429
|
+
// operator changes re-render the whole builder (refresh above).
|
|
430
|
+
// Only the latest request may fill the panel: a newer input aborts the one
|
|
431
|
+
// before, so a late answer never overwrites a newer sentence. An answer that
|
|
432
|
+
// is no fragment — a redirect (a lapsed session's login page), a whole
|
|
433
|
+
// document — leaves the panel as it is: the builder's refresh and save,
|
|
434
|
+
// the no-JS path, still show the sentence.
|
|
435
|
+
let previewRequest = null;
|
|
436
|
+
function previewRule(form) {
|
|
437
|
+
const panel = form && $('[data-pf-rule-preview]', form);
|
|
438
|
+
if (!panel) return;
|
|
439
|
+
const data = new FormData(form); data.delete('authenticity_token'); data.delete('_method');
|
|
440
|
+
const base = panel.getAttribute('data-pf-rule-preview');
|
|
441
|
+
const url = base + (base.indexOf('?') < 0 ? '?' : '&') + new URLSearchParams(data).toString();
|
|
442
|
+
if (previewRequest) previewRequest.abort();
|
|
443
|
+
const request = new AbortController();
|
|
444
|
+
previewRequest = request;
|
|
445
|
+
fetch(url, { headers: { [FRAGMENT_HEADER]: '1' }, credentials: 'same-origin', signal: request.signal })
|
|
446
|
+
.then(function (res) { return res.ok && !res.redirected ? res.text() : Promise.reject(res.status); })
|
|
447
|
+
.then(function (html) {
|
|
448
|
+
if (request !== previewRequest || /<html[\s>]/i.test(html)) return;
|
|
449
|
+
setContent(panel, html);
|
|
450
|
+
})
|
|
451
|
+
.catch(function () {})
|
|
452
|
+
.then(function () { if (previewRequest === request) previewRequest = null; });
|
|
453
|
+
}
|
|
454
|
+
const previewHandler = debounce(previewRule, 250);
|
|
455
|
+
const PREVIEW_INPUTS = '[name="rule[value]"], [name="rule[values_text]"], [name="rule[values][]"], [name="rule[value_from]"], ' +
|
|
456
|
+
'[name="rule[value_to]"], [name="rule[current_user]"], [name="rule[include_nulls]"]';
|
|
457
|
+
['input', 'change'].forEach(function (type) {
|
|
458
|
+
on(type, PREVIEW_INPUTS, function (e, el) { previewHandler(el.form); });
|
|
459
|
+
});
|
|
460
|
+
|
|
425
461
|
/* ------------------------------------------- typed confirmation */
|
|
426
462
|
on('input', 'input[data-pf-confirm-match]', function (e, input) {
|
|
427
463
|
const btn = input.form && input.form.querySelector('[data-pf-confirm-target]');
|
|
@@ -4,10 +4,15 @@ module PermissionForge
|
|
|
4
4
|
include PermissionForge::UiHelper
|
|
5
5
|
include PermissionForge::BadgesHelper
|
|
6
6
|
include PermissionForge::PeopleHelper
|
|
7
|
+
include PermissionForge::LabelsHelper
|
|
7
8
|
include PermissionForge::SentencesHelper
|
|
9
|
+
include PermissionForge::ConditionsHelper
|
|
10
|
+
include PermissionForge::RuleProblemsHelper
|
|
8
11
|
include PermissionForge::AuditSentencesHelper
|
|
9
12
|
include PermissionForge::ChangesHelper
|
|
10
13
|
include PermissionForge::AuditHelper
|
|
14
|
+
include PermissionForge::AuditRecordsHelper
|
|
15
|
+
include PermissionForge::AuditAttributionHelper
|
|
11
16
|
include PermissionForge::SettingsHelper
|
|
12
17
|
|
|
13
18
|
# Sidebar link; the current section gets aria-current="page".
|
|
@@ -54,10 +59,11 @@ module PermissionForge
|
|
|
54
59
|
Rails.application.class.module_parent_name.to_s.titleize.presence
|
|
55
60
|
end
|
|
56
61
|
|
|
62
|
+
# @deprecated Use {UiHelper#pf_relative}. Read Rails' datetime.distance_in_words keys
|
|
63
|
+
# (English without rails-i18n); now the same as pf_relative, removed in 0.16.
|
|
57
64
|
def pf_time_ago(time)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
t('permission_forge.shared.time_ago', time: time_ago_in_words(time))
|
|
65
|
+
PermissionForge::Deprecation.warn('pf_time_ago is deprecated; use pf_relative', key: :pf_time_ago)
|
|
66
|
+
pf_relative(time)
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def pf_available_locales
|
|
@@ -86,82 +92,26 @@ module PermissionForge
|
|
|
86
92
|
"#{request.path}?locale=#{CGI.escape(loc)}"
|
|
87
93
|
end
|
|
88
94
|
|
|
89
|
-
# Display label for a user in the assignment dropdown
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# The id is appended in `#<id>` form to keep labels unique.
|
|
95
|
+
# Display label for a user in the assignment dropdown: the person's
|
|
96
|
+
# label (Admin::PersonLabel — `config.user_label_formatter`, else name
|
|
97
|
+
# then email, else "User"), with the email in parentheses when the
|
|
98
|
+
# label is a name and no formatter is configured. The id is appended in
|
|
99
|
+
# `#<id>` form to keep labels unique.
|
|
95
100
|
def pf_user_option_label(user)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
label = pf_person(user)
|
|
102
|
+
return "#{t('permission_forge.shared.user')} ##{user.id}" if label.fallback?
|
|
103
|
+
|
|
104
|
+
email = label.secondary unless PermissionForge.configuration.user_label_formatter
|
|
105
|
+
base = email ? "#{label.name} (#{email})" : label.name
|
|
99
106
|
"#{base} ##{user.id}"
|
|
100
107
|
end
|
|
101
108
|
|
|
102
109
|
private
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
email = pf_raw_user_attribute(user, :email).to_s.strip.presence
|
|
107
|
-
pf_format_name_and_email(name, email)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def pf_compose_name(user)
|
|
111
|
-
[pf_raw_user_attribute(user, :first_name), pf_raw_user_attribute(user, :last_name)]
|
|
112
|
-
.compact.map { |part| part.to_s.strip }.reject(&:empty?).join(' ').presence
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def pf_format_name_and_email(name, email)
|
|
116
|
-
return "#{name} (#{email})" if name && email
|
|
117
|
-
return name if name
|
|
118
|
-
return email if email
|
|
119
|
-
|
|
120
|
-
t('permission_forge.shared.user')
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def pf_first_present(user, *methods)
|
|
124
|
-
methods.each do |m|
|
|
125
|
-
value = pf_raw_user_attribute(user, m).to_s.strip
|
|
126
|
-
return value unless value.empty?
|
|
127
|
-
end
|
|
128
|
-
nil
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Reads a user attribute for the admin assignment dropdown, bypassing
|
|
132
|
-
# MaskedAttributes. The dropdown is permission-management infrastructure —
|
|
133
|
-
# admins need to see user labels to assign permissions — so it must not be
|
|
134
|
-
# subject to the same field masking as ordinary reads.
|
|
135
|
-
#
|
|
136
|
-
# Real columns are read via `read_attribute_before_type_cast` FIRST: the
|
|
137
|
-
# public getter on a masked model routes every call through
|
|
138
|
-
# AttributeAccessGuard (a per-attribute permission lookup) and can hand
|
|
139
|
-
# back a masked or hidden sentinel — wrong for these labels and, across a
|
|
140
|
-
# large dropdown, the difference between milliseconds and tens of seconds.
|
|
141
|
-
# Virtual attributes (host-app methods like a composed `display_name`)
|
|
142
|
-
# still resolve via the public method, as does a column whose raw value is
|
|
143
|
-
# blank. Consequence: a host method overriding a *populated* column is not
|
|
144
|
-
# consulted here — `config.user_label_formatter` is the escape hatch for
|
|
145
|
-
# custom label logic.
|
|
111
|
+
# A user attribute for the admin's labels, read raw-column-first past
|
|
112
|
+
# MaskedAttributes (Admin::PersonLabel.raw_value: the one definition).
|
|
146
113
|
def pf_raw_user_attribute(user, attr)
|
|
147
|
-
|
|
148
|
-
if pf_readable_column?(user, attr_name)
|
|
149
|
-
stored = user.read_attribute_before_type_cast(attr_name)
|
|
150
|
-
return stored if stored.to_s.strip.present?
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
user.try(attr)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# True when `attr_name` is a real, loaded column on an AR-like user —
|
|
157
|
-
# i.e., safe to read raw. Guards against partial SELECTs from a custom
|
|
158
|
-
# user_list_resolver, where an unloaded column must not be touched.
|
|
159
|
-
def pf_readable_column?(user, attr_name)
|
|
160
|
-
klass = user.class
|
|
161
|
-
klass.respond_to?(:column_names) &&
|
|
162
|
-
klass.column_names.include?(attr_name) &&
|
|
163
|
-
user.respond_to?(:read_attribute_before_type_cast) &&
|
|
164
|
-
(!user.respond_to?(:has_attribute?) || user.has_attribute?(attr_name))
|
|
114
|
+
Admin::PersonLabel.raw_value(user, attr)
|
|
165
115
|
end
|
|
166
116
|
end
|
|
167
117
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Who created and who last edited a permission record, as the audit log
|
|
3
|
+
# names them: the one definition the footers, the Assignments tab,
|
|
4
|
+
# "Assigned by" and the audit tabs (and their CSV) use — the record's
|
|
5
|
+
# creation event (Admin::LastChanges.creations) or latest change event
|
|
6
|
+
# (Admin::LastChanges#last_edit), else the created_by / updated_by columns.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
module AuditAttributionHelper
|
|
10
|
+
# "Who · when" of an entry's creation: the creation event's actor when
|
|
11
|
+
# there is one (as the audit log names them), else created_by.
|
|
12
|
+
def pf_audit_created(entry)
|
|
13
|
+
t('permission_forge.ui.audit.who_when', who: pf_audit_entry_creator(entry), when: pf_date(entry.created_at))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The creator of an Admin::AuditTrail entry (its creation_event is preloaded).
|
|
17
|
+
def pf_audit_entry_creator(entry)
|
|
18
|
+
event = entry.creation_event
|
|
19
|
+
event ? pf_audit_actor_name(event) : pf_person_name(entry.created_by)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Who created a permission record, as the audit log names them — the one
|
|
23
|
+
# definition wherever the admin shows it (footers, assignment rows, the
|
|
24
|
+
# audit tabs): the actor of the record's creation event when the log has
|
|
25
|
+
# one (an HTTP Basic login or an API client stamps no created_by), else
|
|
26
|
+
# created_by, else "System". `creations` is a pf_audit_creations lookup
|
|
27
|
+
# over the page's records; without one that covers the record, the
|
|
28
|
+
# page's preloaded lookup or a lookup of its own is used.
|
|
29
|
+
def pf_audit_creator(record, creations = nil)
|
|
30
|
+
event = pf_audit_creation_event(record, creations)
|
|
31
|
+
event ? pf_audit_actor_name(event) : pf_person_name(record.try(:created_by))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The record's creation event, or nil (see pf_audit_creator for `creations`).
|
|
35
|
+
def pf_audit_creation_event(record, creations = nil)
|
|
36
|
+
lookup = [creations, @pf_creations].find { |candidate| candidate&.covers?(record) }
|
|
37
|
+
(lookup || pf_audit_creations([record]))[record]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The creation events of `records` in one lookup (two queries per type).
|
|
41
|
+
def pf_audit_creations(records)
|
|
42
|
+
PermissionForge::Admin::LastChanges.creations(records)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# "Who · when" of an entry's last edit: the latest change event's actor
|
|
46
|
+
# and time when there is one, else updated_by / updated_at.
|
|
47
|
+
def pf_audit_edited(entry)
|
|
48
|
+
return pf_audit_who_when(entry.updated_by, entry.updated_at, relative: true) unless entry.last_event
|
|
49
|
+
|
|
50
|
+
t('permission_forge.ui.audit.who_when', who: pf_audit_actor_name(entry.last_event), when: pf_relative(entry.updated_at))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The record's last edit as an event (who really made it), or nil when
|
|
54
|
+
# the audit log has none for it or the record was edited after it.
|
|
55
|
+
# Uses the page's preloaded lookup when it covers the record.
|
|
56
|
+
def pf_audit_last_change(record)
|
|
57
|
+
lookup = @pf_last_changes if @pf_last_changes&.covers?(record)
|
|
58
|
+
(lookup || PermissionForge::Admin::LastChanges.new([record])).last_edit(record)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# One lookup of the edits and one of the creations (pf_audit_creator)
|
|
62
|
+
# for every footer the page shows. Nothing while the audit trail is off.
|
|
63
|
+
def pf_audit_preload_last_changes(records)
|
|
64
|
+
return unless PermissionForge.configuration.audit_trail_enabled
|
|
65
|
+
|
|
66
|
+
@pf_last_changes = PermissionForge::Admin::LastChanges.new(records)
|
|
67
|
+
@pf_creations = pf_audit_creations(records)
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# [created by, last edited by] names for the audit-trail CSV.
|
|
72
|
+
def pf_audit_people(entry)
|
|
73
|
+
edited_by = entry.last_event ? pf_audit_actor_name(entry.last_event) : pf_person_name(entry.updated_by)
|
|
74
|
+
[pf_audit_entry_creator(entry), edited_by]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -1,139 +1,65 @@
|
|
|
1
1
|
module PermissionForge
|
|
2
|
-
# Labels
|
|
3
|
-
#
|
|
2
|
+
# Labels for audit events (the audit log, the timelines, an event); the
|
|
3
|
+
# rows of the Audit tabs' record lists are AuditRecordsHelper's.
|
|
4
4
|
#
|
|
5
5
|
# @api private
|
|
6
6
|
module AuditHelper
|
|
7
7
|
# Every category has a glyph and a word (never colour alone).
|
|
8
8
|
CATEGORY_GLYPHS = { 'change' => '✎', 'admin' => '⚑', 'system' => '⚙', 'access' => '◉' }.freeze
|
|
9
9
|
CATEGORY_VARIANTS = { 'change' => :changed, 'admin' => :warn, 'system' => :default, 'access' => :mask }.freeze
|
|
10
|
-
# The message Audit::EventQuery gives a filter that needs a value
|
|
11
|
-
QUERY_BLANK_MESSAGE = '
|
|
12
|
-
# [record label, where] per audited type; evaluated in the view context.
|
|
13
|
-
AUDIT_LABELS = {
|
|
14
|
-
'permission_set' => ->(record) { [record.name, nil] },
|
|
15
|
-
'group' => ->(record) { [record.name, nil] },
|
|
16
|
-
'resource' => ->(record) { [pf_resource_label(record), record.permission_set&.name] },
|
|
17
|
-
'field' => ->(record) { [record.label.presence || record.name, pf_audit_resource_where(record.resource)] },
|
|
18
|
-
'action' => ->(record) { [record.label.presence || record.name, pf_audit_resource_where(record.resource)] },
|
|
19
|
-
'rule' => ->(record) { [record.name, pf_audit_resource_where(record.resource)] },
|
|
20
|
-
'assignment' => ->(record) { [pf_person_name(record.user), record.permission_set&.name] },
|
|
21
|
-
'group_membership' => ->(record) { [pf_person_name(record.user), record.permission_set_group&.name] }
|
|
22
|
-
}.freeze
|
|
23
|
-
|
|
24
|
-
# Link target per audited type; evaluated in the view context.
|
|
25
|
-
AUDIT_PATHS = {
|
|
26
|
-
'permission_set' => ->(record) { permission_forge.permission_set_path(record) },
|
|
27
|
-
'resource' => ->(record) { permission_forge.permission_set_resource_path(record.permission_set_id, record.name) },
|
|
28
|
-
'field' => ->(record) { pf_audit_nested_path(record.resource, :field, record.name) },
|
|
29
|
-
'action' => ->(record) { pf_audit_nested_path(record.resource, :action, record.id) },
|
|
30
|
-
'rule' => ->(record) { pf_audit_nested_path(record.resource, :rule, record.id) },
|
|
31
|
-
'assignment' => ->(record) { permission_forge.person_path(record.user_id) },
|
|
32
|
-
'group' => ->(record) { permission_forge.group_path(record) },
|
|
33
|
-
'group_membership' => ->(record) { permission_forge.group_path(record.permission_set_group_id) }
|
|
34
|
-
}.freeze
|
|
35
|
-
|
|
36
|
-
def pf_audit_type_label(type)
|
|
37
|
-
t("permission_forge.ui.audit.types.#{type}")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# [type label, record label, where] — also used for CSV export.
|
|
41
|
-
def pf_audit_describe(entry)
|
|
42
|
-
record = entry.record
|
|
43
|
-
label, where = pf_audit_label_and_where(entry.type, record)
|
|
44
|
-
[pf_audit_type_label(entry.type), label, where]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def pf_audit_label_and_where(type, record)
|
|
48
|
-
pf_audit_lookup(AUDIT_LABELS, type, record)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def pf_audit_resource_where(resource)
|
|
52
|
-
return nil unless resource
|
|
53
|
-
|
|
54
|
-
"#{resource.permission_set&.name} › #{pf_resource_label(resource)}"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def pf_audit_path(entry)
|
|
58
|
-
pf_audit_lookup(AUDIT_PATHS, entry.type, entry.record)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Runs the registry entry for `type` in the view context; nil for unknown types.
|
|
62
|
-
def pf_audit_lookup(registry, type, record)
|
|
63
|
-
handler = registry[type]
|
|
64
|
-
instance_exec(record, &handler) if handler
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def pf_audit_nested_path(resource, kind, key)
|
|
68
|
-
return nil unless resource
|
|
69
|
-
|
|
70
|
-
case kind
|
|
71
|
-
when :field then permission_forge.permission_set_resource_field_path(resource.permission_set_id, resource.name, key)
|
|
72
|
-
when :action then permission_forge.permission_set_resource_action_path(resource.permission_set_id, resource.name, key)
|
|
73
|
-
when :rule then permission_forge.edit_permission_set_resource_rule_path(resource.permission_set_id, resource.name, key)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
10
|
+
# The message Audit::EventQuery gives a filter that needs a value.
|
|
11
|
+
QUERY_BLANK_MESSAGE = 'permission_forge.validations.blank'.freeze
|
|
77
12
|
def pf_audit_who_when(person, time, relative: false)
|
|
78
13
|
t('permission_forge.ui.audit.who_when', who: pf_person_name(person),
|
|
79
14
|
when: relative ? pf_relative(time) : pf_date(time))
|
|
80
15
|
end
|
|
81
16
|
|
|
82
|
-
#
|
|
83
|
-
# and time when there is one, else updated_by / updated_at.
|
|
84
|
-
def pf_audit_edited(entry)
|
|
85
|
-
return pf_audit_who_when(entry.updated_by, entry.updated_at, relative: true) unless entry.last_event
|
|
86
|
-
|
|
87
|
-
t('permission_forge.ui.audit.who_when', who: pf_audit_actor_name(entry.last_event), when: pf_relative(entry.updated_at))
|
|
88
|
-
end
|
|
17
|
+
# --- audit events ---------------------------------------------------------
|
|
89
18
|
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
(lookup || PermissionForge::Admin::LastChanges.new([record])).last_edit(record)
|
|
19
|
+
# Who acted, as recorded: the name snapshot, with what kind of actor it
|
|
20
|
+
# was when that is not a person ("ops (password prompt)", "API client").
|
|
21
|
+
# Admin::ActorLabel, the one definition.
|
|
22
|
+
def pf_audit_actor_name(event)
|
|
23
|
+
PermissionForge::Admin::ActorLabel.for(event)
|
|
96
24
|
end
|
|
97
25
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
26
|
+
# Why, as recorded on an event (or an assignment): a reason the gem
|
|
27
|
+
# wrote itself is a code (Audit::ReasonCodes: `expired`) rendered in the
|
|
28
|
+
# page's language; any other reason as it was typed. nil when there is
|
|
29
|
+
# none. The CSV export and the API keep the stored value.
|
|
30
|
+
def pf_audit_reason(record)
|
|
31
|
+
reason = record.try(:reason).to_s.strip
|
|
32
|
+
return if reason.empty?
|
|
33
|
+
return reason unless PermissionForge::Audit::ReasonCodes.code?(reason)
|
|
103
34
|
|
|
104
|
-
|
|
105
|
-
nil
|
|
35
|
+
t("permission_forge.audit_log.reason_codes.#{reason}")
|
|
106
36
|
end
|
|
107
37
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
38
|
+
# An event that named no actor (code outside a request with no
|
|
39
|
+
# PermissionForge.with_audit_actor, or an actor resolver that failed):
|
|
40
|
+
# pf_audit_actor_name reads it as the actor type "Unknown".
|
|
41
|
+
def pf_audit_unknown_actor?(event)
|
|
42
|
+
PermissionForge::Admin::ActorLabel.unknown?(event)
|
|
112
43
|
end
|
|
113
44
|
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
type = event.actor_type.to_s
|
|
120
|
-
label = event.actor_label.presence
|
|
121
|
-
return t("permission_forge.audit_log.actor_named.#{type}", name: label, default: label) if label
|
|
122
|
-
return t('permission_forge.audit_log.person_id', id: event.actor_id) if type == 'user' && event.actor_id.present?
|
|
123
|
-
|
|
124
|
-
pf_audit_actor_type_label(type)
|
|
45
|
+
# Where "Unknown" comes from, said the same way wherever it shows (the
|
|
46
|
+
# event page, a record footer). Both already show "Unknown" as the actor
|
|
47
|
+
# right before it, so the explanation does not repeat the word.
|
|
48
|
+
def pf_audit_unknown_actor_hint
|
|
49
|
+
t('permission_forge.audit_events.show.unknown_actor_html', method: tag.code('PermissionForge.with_audit_actor'))
|
|
125
50
|
end
|
|
126
51
|
|
|
52
|
+
# A type, category or source outside the catalogue is shown as stored.
|
|
127
53
|
def pf_audit_actor_type_label(type)
|
|
128
|
-
|
|
54
|
+
PermissionForge::Admin::ActorLabel.type_label(type)
|
|
129
55
|
end
|
|
130
56
|
|
|
131
57
|
def pf_audit_category_label(category)
|
|
132
|
-
t("permission_forge.audit_log.categories.#{category}", default: category.to_s
|
|
58
|
+
t("permission_forge.audit_log.categories.#{category}", default: category.to_s)
|
|
133
59
|
end
|
|
134
60
|
|
|
135
61
|
def pf_audit_source_label(source)
|
|
136
|
-
t("permission_forge.audit_log.sources.#{source}", default: source.to_s
|
|
62
|
+
t("permission_forge.audit_log.sources.#{source}", default: source.to_s)
|
|
137
63
|
end
|
|
138
64
|
|
|
139
65
|
def pf_audit_category_badge(category)
|
|
@@ -173,28 +99,40 @@ module PermissionForge
|
|
|
173
99
|
# what the event says about it.
|
|
174
100
|
def pf_audit_set_name(event, lookups)
|
|
175
101
|
return nil if event.permission_set_id.blank? && event.permission_set_uuid.blank?
|
|
176
|
-
|
|
102
|
+
if event.record_type == 'PermissionForge::PermissionSet' && event.record_label.present?
|
|
103
|
+
return pf_record_name_text(event.record_label, :permission_set)
|
|
104
|
+
end
|
|
177
105
|
|
|
178
106
|
name = lookups.permission_set_name(event)
|
|
179
|
-
return name if name
|
|
107
|
+
return pf_record_name_text(name, :permission_set) if name
|
|
180
108
|
|
|
181
109
|
t('permission_forge.audit_log.deleted_set', id: event.permission_set_id || event.permission_set_uuid)
|
|
182
110
|
end
|
|
183
111
|
|
|
184
112
|
def pf_audit_group_name(event, lookups)
|
|
185
113
|
name = lookups.group_name(event)
|
|
186
|
-
return name if name
|
|
114
|
+
return pf_record_name_text(name, :permission_set_group) if name
|
|
187
115
|
|
|
188
116
|
id = PermissionForge::Admin::AuditLookups.group_id(event)
|
|
189
117
|
id ? t('permission_forge.audit_log.deleted_group', id: id) : t('permission_forge.audit_log.unknown_group')
|
|
190
118
|
end
|
|
191
119
|
|
|
192
|
-
# What the event is about: its label snapshot
|
|
120
|
+
# What the event is about: its label snapshot (a set's, group's or
|
|
121
|
+
# rule's install-default name in the page's language: Admin::RecordName),
|
|
122
|
+
# else the type's label (Admin::AuditTrail::TYPES; a class outside them
|
|
123
|
+
# as stored) and id.
|
|
193
124
|
def pf_audit_record_name(event)
|
|
194
|
-
return event
|
|
125
|
+
return pf_audit_label_text(event) if event.record_label.present?
|
|
195
126
|
return '' if event.record_type.blank?
|
|
196
127
|
|
|
197
|
-
|
|
128
|
+
type = PermissionForge::Admin::AuditTrail::TYPES.key(event.record_type.to_s)
|
|
129
|
+
[type ? pf_audit_type_label(type) : event.record_type.to_s, event.record_id || event.record_uuid].compact.join(' #')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# An event's record label as Admin::RecordName reads a name of its type.
|
|
133
|
+
def pf_audit_label_text(event)
|
|
134
|
+
klass = PermissionForge::ModelLookup.model_for(event.record_type.to_s) if event.record_type.to_s.start_with?('PermissionForge::')
|
|
135
|
+
klass ? pf_record_name_text(event.record_label, klass) : event.record_label
|
|
198
136
|
end
|
|
199
137
|
|
|
200
138
|
# A value of a before/after or snapshot table: text as it is, nothing as
|
|
@@ -202,18 +140,18 @@ module PermissionForge
|
|
|
202
140
|
def pf_audit_value(value)
|
|
203
141
|
case value
|
|
204
142
|
when nil then t('permission_forge.audit_log.no_value')
|
|
205
|
-
when String then value.empty? ? t('permission_forge.audit_log.empty_text') : value
|
|
143
|
+
when String then value.empty? ? t('permission_forge.audit_log.empty_text') : pf_host_data(value)
|
|
206
144
|
when true, false then t("permission_forge.audit_log.boolean.#{value}")
|
|
207
145
|
when Numeric then value.to_s
|
|
208
|
-
else ActiveSupport::JSON.encode(value)
|
|
146
|
+
else pf_host_data(ActiveSupport::JSON.encode(value))
|
|
209
147
|
end
|
|
210
148
|
end
|
|
211
149
|
|
|
212
|
-
# One line per refused filter, in the admin's own words
|
|
213
|
-
#
|
|
214
|
-
#
|
|
150
|
+
# One line per refused filter, in the admin's own words: a value that
|
|
151
|
+
# is needed, or the value that is not valid. A filter outside the
|
|
152
|
+
# catalogue is named as stored.
|
|
215
153
|
def pf_audit_filter_error(name, messages, value)
|
|
216
|
-
field = t("permission_forge.audit_events.index.filters.#{name}", default: name.to_s
|
|
154
|
+
field = t("permission_forge.audit_events.index.filters.#{name}", default: name.to_s)
|
|
217
155
|
scope = 'permission_forge.audit_events.index.invalid'
|
|
218
156
|
return t("#{scope}.blank", field: field) if Array(messages).include?(I18n.t(QUERY_BLANK_MESSAGE))
|
|
219
157
|
return t("#{scope}.invalid", field: field) if value.blank?
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Labels and links for the rows of the Audit tabs' record lists (Person →
|
|
3
|
+
# Audit, Permission Set → Audit, under audit_trail_enabled) and their CSV
|
|
4
|
+
# exports: each record named as the admin names it on a page, as stored in
|
|
5
|
+
# the CSV.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module AuditRecordsHelper
|
|
9
|
+
# [record label, where] per audited type; evaluated in the view
|
|
10
|
+
# context, `csv` true for the CSV export. On a page names and labels
|
|
11
|
+
# read as everywhere else in the admin: a set's, group's or rule's
|
|
12
|
+
# install-default name in the page's language (pf_record_name), a
|
|
13
|
+
# field's or action's generated label as no label (Admin::FieldLabel,
|
|
14
|
+
# Admin::ActionLabel; a verbatim name in <code>). The CSV keeps the
|
|
15
|
+
# values as stored (an export is data, as the log's exports keep a
|
|
16
|
+
# stored reason).
|
|
17
|
+
AUDIT_LABELS = {
|
|
18
|
+
'permission_set' => ->(record, csv) { [pf_audit_name(record, csv), nil] },
|
|
19
|
+
'group' => ->(record, csv) { [pf_audit_name(record, csv), nil] },
|
|
20
|
+
'resource' => ->(record, csv) { [pf_resource_label(record), pf_audit_name(record.permission_set, csv)] },
|
|
21
|
+
'field' => lambda { |record, csv|
|
|
22
|
+
label = csv ? pf_audit_stored_label(record) : pf_field_label(record.resource&.name, record.name, row: record)
|
|
23
|
+
[label, pf_audit_resource_where(record.resource, csv)]
|
|
24
|
+
},
|
|
25
|
+
'action' => lambda { |record, csv|
|
|
26
|
+
[csv ? pf_audit_stored_label(record) : pf_audit_action_label(record), pf_audit_resource_where(record.resource, csv)]
|
|
27
|
+
},
|
|
28
|
+
'rule' => ->(record, csv) { [pf_audit_name(record, csv), pf_audit_resource_where(record.resource, csv)] },
|
|
29
|
+
'assignment' => ->(record, csv) { [pf_person_name(record.user), pf_audit_name(record.permission_set, csv)] },
|
|
30
|
+
'group_membership' => ->(record, csv) { [pf_person_name(record.user), pf_audit_name(record.permission_set_group, csv)] }
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# Link target per audited type; evaluated in the view context.
|
|
34
|
+
AUDIT_PATHS = {
|
|
35
|
+
'permission_set' => ->(record) { permission_forge.permission_set_path(record) },
|
|
36
|
+
'resource' => ->(record) { permission_forge.permission_set_resource_path(record.permission_set_id, record.name) },
|
|
37
|
+
'field' => ->(record) { pf_audit_nested_path(record.resource, :field, record.name) },
|
|
38
|
+
'action' => ->(record) { pf_audit_nested_path(record.resource, :action, record.id) },
|
|
39
|
+
'rule' => ->(record) { pf_audit_nested_path(record.resource, :rule, record.id) },
|
|
40
|
+
'assignment' => ->(record) { permission_forge.person_path(record.user_id) },
|
|
41
|
+
'group' => ->(record) { permission_forge.group_path(record) },
|
|
42
|
+
'group_membership' => ->(record) { permission_forge.group_path(record.permission_set_group_id) }
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
def pf_audit_type_label(type)
|
|
46
|
+
t("permission_forge.ui.audit.types.#{type}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# [type label, record label, where] of a page's row (AUDIT_LABELS).
|
|
50
|
+
def pf_audit_describe(entry, csv: false)
|
|
51
|
+
label, where = pf_audit_lookup(AUDIT_LABELS, entry.type, entry.record, csv)
|
|
52
|
+
[pf_audit_type_label(entry.type), label, where]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# [type label, record label, where] of a CSV row: stored values.
|
|
56
|
+
def pf_audit_csv_describe(entry)
|
|
57
|
+
pf_audit_describe(entry, csv: true)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# "<set> › <model>" for a row under a Resource.
|
|
61
|
+
def pf_audit_resource_where(resource, csv)
|
|
62
|
+
return nil unless resource
|
|
63
|
+
|
|
64
|
+
"#{pf_audit_name(resource.permission_set, csv)} › #{pf_resource_label(resource)}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A set's, group's or rule's name: as the admin names it, or as stored for the CSV.
|
|
68
|
+
def pf_audit_name(record, csv)
|
|
69
|
+
csv ? record&.name : pf_record_name(record).presence
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A field's or action's label as stored, else its name (the CSV).
|
|
73
|
+
def pf_audit_stored_label(row)
|
|
74
|
+
row.label.presence || row.name
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# An action's label (Admin::ActionLabel), a verbatim key in <code>.
|
|
78
|
+
def pf_audit_action_label(action)
|
|
79
|
+
label = PermissionForge::Admin::ActionLabel.for(action)
|
|
80
|
+
label.verbatim? ? tag.code(label.text, class: 'pf-mono-key') : label.text
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def pf_audit_path(entry)
|
|
84
|
+
pf_audit_lookup(AUDIT_PATHS, entry.type, entry.record)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Runs the registry entry for `type` in the view context; nil for unknown types.
|
|
88
|
+
def pf_audit_lookup(registry, type, *)
|
|
89
|
+
handler = registry[type]
|
|
90
|
+
instance_exec(*, &handler) if handler
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def pf_audit_nested_path(resource, kind, key)
|
|
94
|
+
return nil unless resource
|
|
95
|
+
|
|
96
|
+
case kind
|
|
97
|
+
when :field then permission_forge.permission_set_resource_field_path(resource.permission_set_id, resource.name, key)
|
|
98
|
+
when :action then permission_forge.permission_set_resource_action_path(resource.permission_set_id, resource.name, key)
|
|
99
|
+
when :rule then permission_forge.edit_permission_set_resource_rule_path(resource.permission_set_id, resource.name, key)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|