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
|
@@ -8,6 +8,7 @@ module PermissionForge
|
|
|
8
8
|
|
|
9
9
|
include PermissionForge::Concerns::HasUuid
|
|
10
10
|
include PermissionForge::Concerns::ValidatesColumnLengths
|
|
11
|
+
include PermissionForge::Concerns::ValidatesRequiredColumns
|
|
11
12
|
include PermissionForge::Concerns::HasAuditTrail
|
|
12
13
|
include PermissionForge::Concerns::HasTenantScoping
|
|
13
14
|
include PermissionForge::Concerns::InvalidatesInBatch
|
|
@@ -24,7 +25,8 @@ module PermissionForge
|
|
|
24
25
|
# a RecordNotUnique 500 (and prevents silent duplicate rows on schemas that
|
|
25
26
|
# predate the index). BulkPermissionSetAssignment already de-dupes; this
|
|
26
27
|
# covers the single-assignment controller/API paths.
|
|
27
|
-
validates :user_id, uniqueness: { scope: :permission_set_id
|
|
28
|
+
validates :user_id, uniqueness: { scope: :permission_set_id,
|
|
29
|
+
message: ->(*) { I18n.t('permission_forge.validations.permission_set_user.taken') } }
|
|
28
30
|
|
|
29
31
|
# An integer user_id column would store a UUID or string user key as its
|
|
30
32
|
# leading digits, handing this assignment to every user who shares them.
|
|
@@ -39,8 +41,14 @@ module PermissionForge
|
|
|
39
41
|
# (InvalidatesInBatch#note_previous_value).
|
|
40
42
|
after_save { note_previous_value('user_id') }
|
|
41
43
|
|
|
44
|
+
# The window of "ending soon": Home's card, the People page filter and
|
|
45
|
+
# the end-date cell ("ends in N days") all read it.
|
|
46
|
+
ENDING_SOON_DAYS = 7
|
|
47
|
+
|
|
42
48
|
scope :active_assignments, -> { where(expires_at: nil).or(where(expires_at: Time.current..)) }
|
|
43
49
|
scope :expired, -> { where.not(expires_at: nil).where(expires_at: ...Time.current) }
|
|
50
|
+
# Assignments that end within `days` from now (today's included, ended ones not).
|
|
51
|
+
scope :ending_within, ->(days = ENDING_SOON_DAYS) { where(expires_at: Time.current..days.days.from_now.end_of_day) }
|
|
44
52
|
|
|
45
53
|
# Permanent assignments last reviewed before `cutoff` — the access
|
|
46
54
|
# review list and StaleAssignmentAuditor. Reviewed means a "Keep"
|
|
@@ -51,9 +59,18 @@ module PermissionForge
|
|
|
51
59
|
|
|
52
60
|
apply_tenant_scoping! # from the configuration, as the class loads (HasTenantScoping)
|
|
53
61
|
|
|
62
|
+
# Whether an end date has passed: the one comparison behind #expired?,
|
|
63
|
+
# the `expired` scope and the admin's end-date cell.
|
|
64
|
+
#
|
|
65
|
+
# @param expires_at [Time, nil]
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def self.ended?(expires_at)
|
|
68
|
+
expires_at.present? && expires_at < Time.current
|
|
69
|
+
end
|
|
70
|
+
|
|
54
71
|
# The review columns exist (bin/rails generate permission_forge:upgrade).
|
|
55
72
|
def self.review_supported?
|
|
56
|
-
|
|
73
|
+
PermissionForge::SchemaRead.column?(self, 'reviewed_at', ignored: false)
|
|
57
74
|
end
|
|
58
75
|
|
|
59
76
|
# Arel for "when this assignment was last reviewed" (see stale_before).
|
|
@@ -97,7 +114,25 @@ module PermissionForge
|
|
|
97
114
|
end
|
|
98
115
|
|
|
99
116
|
def expired?
|
|
100
|
-
|
|
117
|
+
self.class.ended?(expires_at)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Whether the assignment ends within `days` (see .ending_within).
|
|
121
|
+
#
|
|
122
|
+
# @param days [Integer]
|
|
123
|
+
# @return [Boolean]
|
|
124
|
+
def ending_soon?(days = ENDING_SOON_DAYS)
|
|
125
|
+
expires_at.present? && !expired? && expires_at <= days.days.from_now.end_of_day
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Whether a permanent assignment is due for review: last reviewed more
|
|
129
|
+
# than `days` ago (see .stale_before; Admin::AttentionItems.stale_days
|
|
130
|
+
# is the threshold the admin uses).
|
|
131
|
+
#
|
|
132
|
+
# @param days [Integer]
|
|
133
|
+
# @return [Boolean]
|
|
134
|
+
def stale?(days)
|
|
135
|
+
expires_at.nil? && last_reviewed_at < days.days.ago
|
|
101
136
|
end
|
|
102
137
|
|
|
103
138
|
def active_assignment?
|
|
@@ -9,6 +9,9 @@ module PermissionForge
|
|
|
9
9
|
include PermissionForge::Concerns::HasActiveScope
|
|
10
10
|
include PermissionForge::Concerns::HasUuid
|
|
11
11
|
include PermissionForge::Concerns::ValidatesColumnLengths
|
|
12
|
+
include PermissionForge::Concerns::RequiresName # name present, on create or rename
|
|
13
|
+
include PermissionForge::Concerns::NormalizesLabel # a blank label is stored as nil
|
|
14
|
+
include PermissionForge::Concerns::ValidatesRequiredColumns
|
|
12
15
|
include PermissionForge::Concerns::HasAuditTrail
|
|
13
16
|
include PermissionForge::Concerns::HasTenantScoping
|
|
14
17
|
include PermissionForge::Concerns::HasPermissionSetUsers
|
|
@@ -17,6 +20,10 @@ module PermissionForge
|
|
|
17
20
|
include PermissionForge::Concerns::InvalidatesManagedActions
|
|
18
21
|
include PermissionForge::Concerns::RecordsAuditEvents
|
|
19
22
|
|
|
23
|
+
# Its name is configuration, not personal data: #inspect shows it even
|
|
24
|
+
# when filter_parameters (the GDPR patterns) hide a `name` (InspectionFilters).
|
|
25
|
+
PermissionForge::InspectionFilters.show_configuration_names(self)
|
|
26
|
+
|
|
20
27
|
# A Ruby constant path: Resource#name is resolved to a model class.
|
|
21
28
|
NAME_FORMAT = PermissionForge::RESOURCE_NAME_FORMAT
|
|
22
29
|
|
|
@@ -40,16 +47,13 @@ module PermissionForge
|
|
|
40
47
|
has_many :permission_set_rules, class_name: 'PermissionForge::PermissionSetRule', dependent: :destroy,
|
|
41
48
|
extend: Concerns::AuditedCollection
|
|
42
49
|
|
|
43
|
-
validates :name,
|
|
44
|
-
|
|
50
|
+
validates :name, uniqueness: { scope: :permission_set_id,
|
|
51
|
+
message: ->(*) { I18n.t('permission_forge.validations.resource.name_taken') } }
|
|
45
52
|
# Resource#name is resolved to a model class via safe_constantize (e.g. by
|
|
46
53
|
# ResourceFieldGenerator and MaskedAttributes auto-apply), so constrain it to
|
|
47
54
|
# a Ruby constant path. Prevents naming a resource after arbitrary strings.
|
|
48
|
-
validates :name, format: {
|
|
49
|
-
|
|
50
|
-
message: I18n.t('permission_forge.validations.resource.invalid_name',
|
|
51
|
-
default: 'must be a valid model class name (e.g. Customer or Admin::Report)')
|
|
52
|
-
}, allow_blank: true
|
|
55
|
+
validates :name, format: { with: NAME_FORMAT, message: ->(*) { I18n.t('permission_forge.validations.resource.invalid_name') } },
|
|
56
|
+
allow_blank: true
|
|
53
57
|
|
|
54
58
|
validates_dependency :edit_access, requires: :read_access
|
|
55
59
|
validates_dependency :delete_access, requires: :read_access
|
|
@@ -72,7 +76,9 @@ module PermissionForge
|
|
|
72
76
|
view_all || modify_all
|
|
73
77
|
end
|
|
74
78
|
|
|
75
|
-
# Grants full CRUD access +
|
|
79
|
+
# Grants full CRUD access + read and edit on every field, unmasked (also
|
|
80
|
+
# the columns that look like personal data, which GdprFieldAuditor and the
|
|
81
|
+
# admin's privacy audit still report), for a resource on a permission set.
|
|
76
82
|
# Intended for seeds, migrations, or console use only.
|
|
77
83
|
def self.grant_full_access!(permission_set:, resource_name:)
|
|
78
84
|
PermissionForge::Services::ResourceWithFieldsBuilder.call(
|
|
@@ -82,6 +88,10 @@ module PermissionForge
|
|
|
82
88
|
|
|
83
89
|
private
|
|
84
90
|
|
|
91
|
+
# A blank label is stored as no label (NormalizesLabel): the admin names
|
|
92
|
+
# the model from the host's translation (Admin::ModelLabel).
|
|
93
|
+
def generated_label = nil
|
|
94
|
+
|
|
85
95
|
def note_newly_named_model
|
|
86
96
|
@pf_newly_named = true if previously_new_record? || saved_change_to_name?
|
|
87
97
|
end
|
|
@@ -9,6 +9,9 @@ module PermissionForge
|
|
|
9
9
|
include PermissionForge::Concerns::HasActiveScope
|
|
10
10
|
include PermissionForge::Concerns::HasUuid
|
|
11
11
|
include PermissionForge::Concerns::ValidatesColumnLengths
|
|
12
|
+
include PermissionForge::Concerns::RequiresName # name present, on create or rename
|
|
13
|
+
include PermissionForge::Concerns::NormalizesLabel # a blank label is stored as the name
|
|
14
|
+
include PermissionForge::Concerns::ValidatesRequiredColumns
|
|
12
15
|
include PermissionForge::Concerns::HasAuditTrail
|
|
13
16
|
include PermissionForge::Concerns::HasTenantScoping
|
|
14
17
|
include PermissionForge::Concerns::HasPermissionSetUsers
|
|
@@ -16,6 +19,10 @@ module PermissionForge
|
|
|
16
19
|
include PermissionForge::Concerns::InvalidatesManagedActions
|
|
17
20
|
include PermissionForge::Concerns::RecordsAuditEvents
|
|
18
21
|
|
|
22
|
+
# Its name is configuration, not personal data: #inspect shows it even
|
|
23
|
+
# when filter_parameters (the GDPR patterns) hide a `name` (InspectionFilters).
|
|
24
|
+
PermissionForge::InspectionFilters.show_configuration_names(self)
|
|
25
|
+
|
|
19
26
|
permission_set_user_lookup :via_resource
|
|
20
27
|
|
|
21
28
|
# optional: as declared since 0.13 (the foreign key holds); stated, so the
|
|
@@ -28,10 +35,6 @@ module PermissionForge
|
|
|
28
35
|
# instead of a RecordNotUnique 500. Checked when a row is created, renamed
|
|
29
36
|
# or moved, so duplicates that predate the index stay editable; the
|
|
30
37
|
# security audit lists them (`duplicate_actions`).
|
|
31
|
-
# Checked for a new row and on a rename only: a row whose name was
|
|
32
|
-
# blank before 0.14 stays editable in its other attributes.
|
|
33
|
-
validates :name, presence: { message: ->(*) { I18n.t('permission_forge.validations.name_blank') } },
|
|
34
|
-
if: -> { new_record? || will_save_change_to_name? }
|
|
35
38
|
validates :name, uniqueness: { scope: :resource_id,
|
|
36
39
|
message: ->(*) { I18n.t('permission_forge.errors.admin.action_taken') } },
|
|
37
40
|
if: -> { new_record? || will_save_change_to_name? || will_save_change_to_resource_id? }
|
|
@@ -50,9 +53,15 @@ module PermissionForge
|
|
|
50
53
|
is_active && resource.in_effect?
|
|
51
54
|
end
|
|
52
55
|
|
|
53
|
-
#
|
|
56
|
+
# The action's name as the admin shows it (Admin::ActionLabel): the
|
|
57
|
+
# label an admin wrote; a label the gem generated (blank, the key, the
|
|
58
|
+
# key humanized: GeneratedText.label?) counts as none, and then, on an
|
|
59
|
+
# English page, Rails' English for the key ("Export csv"), else the key
|
|
60
|
+
# as stored ("export_csv").
|
|
61
|
+
#
|
|
62
|
+
# @return [String]
|
|
54
63
|
def display_name
|
|
55
|
-
|
|
64
|
+
PermissionForge::Admin::ActionLabel.for(self).text
|
|
56
65
|
end
|
|
57
66
|
end
|
|
58
67
|
end
|
|
@@ -8,7 +8,11 @@ module PermissionForge
|
|
|
8
8
|
|
|
9
9
|
include PermissionForge::Concerns::HasActiveScope
|
|
10
10
|
include PermissionForge::Concerns::HasUuid
|
|
11
|
+
include PermissionForge::Concerns::KeepsCopiedValues
|
|
11
12
|
include PermissionForge::Concerns::ValidatesColumnLengths
|
|
13
|
+
include PermissionForge::Concerns::RequiresName # name present, on create or rename
|
|
14
|
+
include PermissionForge::Concerns::NormalizesLabel # a blank label is stored as the name
|
|
15
|
+
include PermissionForge::Concerns::ValidatesRequiredColumns
|
|
12
16
|
include PermissionForge::Concerns::HasAuditTrail
|
|
13
17
|
include PermissionForge::Concerns::HasTenantScoping
|
|
14
18
|
include PermissionForge::Concerns::HasPermissionSetUsers
|
|
@@ -16,8 +20,17 @@ module PermissionForge
|
|
|
16
20
|
include PermissionForge::Concerns::PermissionsRegenerator
|
|
17
21
|
include PermissionForge::Concerns::RecordsAuditEvents
|
|
18
22
|
|
|
23
|
+
# Its name is configuration, not personal data: #inspect shows it even
|
|
24
|
+
# when filter_parameters (the GDPR patterns) hide a `name` (InspectionFilters).
|
|
25
|
+
PermissionForge::InspectionFilters.show_configuration_names(self)
|
|
26
|
+
|
|
19
27
|
permission_set_user_lookup :via_resource
|
|
20
28
|
|
|
29
|
+
# The install migration's column default for label: a row created
|
|
30
|
+
# without one (the API, the console) is labelled with its name instead
|
|
31
|
+
# (NormalizesLabel), never with this.
|
|
32
|
+
UNTITLED_LABEL = PermissionForge::GeneratedText::UNTITLED_FIELD
|
|
33
|
+
|
|
21
34
|
# optional: as declared since 0.13 (the foreign key holds); stated, so the
|
|
22
35
|
# host's belongs_to_required_by_default decides nothing here.
|
|
23
36
|
belongs_to :resource, class_name: 'PermissionForge::Resource', optional: true
|
|
@@ -29,6 +42,69 @@ module PermissionForge
|
|
|
29
42
|
.merge(PermissionForge::Resource.active).merge(PermissionForge::PermissionSet.active)
|
|
30
43
|
}
|
|
31
44
|
|
|
45
|
+
# The rows that show a field in the clear — the SQL twin of
|
|
46
|
+
# #exposes_in_clear? (a test pins that they agree). The set's direction
|
|
47
|
+
# is the caller's: `in_effect.merge(PermissionSet.granting)` for the
|
|
48
|
+
# rows that reach someone's hash.
|
|
49
|
+
scope :exposing_in_clear, -> { active.where(read_access: true, mask_access: false) }
|
|
50
|
+
|
|
51
|
+
# The rows in the clear nobody marked "This is intended" in the admin's
|
|
52
|
+
# privacy audit (sensitive_acknowledged_at) — the SQL twin of
|
|
53
|
+
# #exposes_unacknowledged?, what the GDPR audit, the security audit,
|
|
54
|
+
# Home and the boot warning report.
|
|
55
|
+
scope :exposing_unacknowledged, lambda {
|
|
56
|
+
acknowledgement_supported? ? exposing_in_clear.where(sensitive_acknowledged_at: nil) : exposing_in_clear
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Whether the table has the column "This is intended" writes (added by
|
|
60
|
+
# the upgrade generator; an older install has none, and nothing is
|
|
61
|
+
# acknowledged there).
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean]
|
|
64
|
+
def self.acknowledgement_supported?
|
|
65
|
+
PermissionForge::SchemaRead.column?(self, 'sensitive_acknowledged_at', ignored: false)
|
|
66
|
+
rescue StandardError
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The rows in effect that mask with a strategy this app does not know
|
|
71
|
+
# (Services::MaskValue.known?): written past the model, or a custom
|
|
72
|
+
# strategy since removed. Masked fully until fixed. The one reading the
|
|
73
|
+
# security audit's unknown_mask_strategies and Home's card both take.
|
|
74
|
+
#
|
|
75
|
+
# @return [Array<ResourceField>] with their Resource and Permission Set loaded
|
|
76
|
+
def self.with_unknown_mask_strategy
|
|
77
|
+
in_effect.where(mask_access: true).includes(resource: :permission_set).order(:resource_id, :id)
|
|
78
|
+
.reject { |field| PermissionForge::Services::MaskValue.known?(field.mask_strategy) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The one definition of "readable in the clear": an active row that
|
|
82
|
+
# grants read and no mask. On a granting set's row only — a muting row
|
|
83
|
+
# takes access away, so the question does not arise there. The GDPR
|
|
84
|
+
# audit (unmasked_field, cross_set_unmasking), the security audit's
|
|
85
|
+
# personal_data check, Home's privacy card and the boot warning all ask
|
|
86
|
+
# this; a Hidden row (read off) is never in the clear, nor is a row
|
|
87
|
+
# written past the validation with mask on and read off.
|
|
88
|
+
#
|
|
89
|
+
# @return [Boolean]
|
|
90
|
+
def exposes_in_clear?
|
|
91
|
+
is_active && read_access && !mask_access
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# In the clear and not marked "This is intended": the one definition of
|
|
95
|
+
# a personal-data exposure the four surfaces report (the field must
|
|
96
|
+
# also look like personal data: SensitiveColumns).
|
|
97
|
+
#
|
|
98
|
+
# @return [Boolean]
|
|
99
|
+
def exposes_unacknowledged?
|
|
100
|
+
exposes_in_clear? && !acknowledged_as_intended?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [Boolean] an admin marked the field "This is intended"
|
|
104
|
+
def acknowledged_as_intended?
|
|
105
|
+
self.class.acknowledgement_supported? && has_attribute?(:sensitive_acknowledged_at) && sensitive_acknowledged_at.present?
|
|
106
|
+
end
|
|
107
|
+
|
|
32
108
|
# A duplicate (resource_id, name) row would let a second field entry with
|
|
33
109
|
# mask_access: false silently unmask the field via FieldPermissionMerger's
|
|
34
110
|
# AND logic. Enforced by a DB unique index (idx_pf_resource_fields_unique)
|
|
@@ -37,26 +113,30 @@ module PermissionForge
|
|
|
37
113
|
# collation does, and so the way the index does. On MySQL's default
|
|
38
114
|
# (case-insensitive) collation "Email" collides with "email" in the index;
|
|
39
115
|
# a forced case-sensitive check would let it through to a RecordNotUnique.
|
|
40
|
-
|
|
41
|
-
# blank before 0.14 stays editable in its other attributes.
|
|
42
|
-
validates :name, presence: { message: ->(*) { I18n.t('permission_forge.validations.name_blank') } },
|
|
43
|
-
if: -> { new_record? || will_save_change_to_name? }
|
|
44
|
-
validates :name, uniqueness: { scope: :resource_id }
|
|
116
|
+
validates :name, uniqueness: { scope: :resource_id, message: ->(*) { I18n.t('permission_forge.validations.taken') } }
|
|
45
117
|
|
|
46
118
|
validates_dependency :edit_access, requires: :read_access
|
|
47
119
|
validates_dependency :mask_access, requires: :read_access
|
|
48
120
|
|
|
121
|
+
# The strategy is checked for a new row and when it, or mask_access,
|
|
122
|
+
# changes (mask_strategy_check_due?): a row whose strategy this app no
|
|
123
|
+
# longer knows — written past the model, or a custom strategy since
|
|
124
|
+
# removed from config.custom_mask_strategies — stays editable in its
|
|
125
|
+
# other attributes. Its values are masked fully until it is fixed
|
|
126
|
+
# (Services::MaskValue); `rake permission_forge:audit` lists it
|
|
127
|
+
# (unknown_mask_strategies), the field drawer flags it.
|
|
128
|
+
# A copy keeps its original's strategy (KeepsCopiedValues): it masks
|
|
129
|
+
# fully, as the original does.
|
|
130
|
+
keeps_copied :mask_access, :mask_strategy
|
|
49
131
|
validates :mask_strategy, presence: {
|
|
50
|
-
message: I18n.t('permission_forge.validations.resource_field.mask_strategy_required'
|
|
51
|
-
default: 'must be present when mask access is enabled')
|
|
132
|
+
message: ->(*) { I18n.t('permission_forge.validations.resource_field.mask_strategy_required') }
|
|
52
133
|
},
|
|
53
|
-
if: :
|
|
134
|
+
if: :mask_strategy_check_due?
|
|
54
135
|
validates :mask_strategy, inclusion: {
|
|
55
136
|
in: ->(_) { PermissionForge::Services::MaskValue.strategies.keys },
|
|
56
|
-
message: I18n.t('permission_forge.validations.resource_field.invalid_mask_strategy'
|
|
57
|
-
default: 'is not a valid mask strategy')
|
|
137
|
+
message: ->(*) { I18n.t('permission_forge.validations.resource_field.invalid_mask_strategy') }
|
|
58
138
|
},
|
|
59
|
-
if: :
|
|
139
|
+
if: :mask_strategy_check_due?
|
|
60
140
|
|
|
61
141
|
apply_tenant_scoping! # from the configuration, as the class loads (HasTenantScoping)
|
|
62
142
|
|
|
@@ -65,5 +145,11 @@ module PermissionForge
|
|
|
65
145
|
def mask_access?
|
|
66
146
|
mask_access == true
|
|
67
147
|
end
|
|
148
|
+
|
|
149
|
+
def mask_strategy_check_due?
|
|
150
|
+
return false if copied_unchanged?(:mask_access, :mask_strategy)
|
|
151
|
+
|
|
152
|
+
mask_access? && (new_record? || will_save_change_to_mask_strategy? || will_save_change_to_mask_access?)
|
|
153
|
+
end
|
|
68
154
|
end
|
|
69
155
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<% person = pf_person_name(@assignment.user) %>
|
|
2
2
|
<% set = @assignment.permission_set %>
|
|
3
|
-
<% content_for :pf_title, t('.title', set: set
|
|
4
|
-
<%= pf_dialog_frame({ title: t('.title', set: set
|
|
3
|
+
<% content_for :pf_title, t('.title', set: pf_record_name(set), name: person) %>
|
|
4
|
+
<%= pf_dialog_frame({ title: t('.title', set: pf_record_name(set), name: person), danger: true,
|
|
5
5
|
breadcrumb: pf_breadcrumb([t('permission_forge.nav.people'), permission_forge.people_path], [person, permission_forge.person_path(@assignment.user_id)], t('.crumb')) }) do %>
|
|
6
6
|
<p class="pf-text-2">
|
|
7
7
|
<% first = pf_person(@assignment.user).first_name %>
|
|
8
8
|
<% if set.is_muting %>
|
|
9
|
-
<%= t('.body_muting', name: first, set: set
|
|
9
|
+
<%= t('.body_muting', name: first, set: pf_record_name(set)) %>
|
|
10
10
|
<% elsif @lost.any? %>
|
|
11
|
-
<%= t('.body', name: first, resources: @lost
|
|
11
|
+
<%= t('.body', name: first, resources: pf_to_sentence(@lost)) %>
|
|
12
12
|
<% else %>
|
|
13
13
|
<%= t('.body_nothing', name: first) %>
|
|
14
14
|
<% end %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<% content_for :pf_title, t('.title') %>
|
|
2
2
|
<% errors = @errors || {} %>
|
|
3
3
|
<% title = if @person then t('.title_person', name: pf_person_name(@person))
|
|
4
|
-
elsif @preset_set then t('.title_set', set: @preset_set
|
|
4
|
+
elsif @preset_set then t('.title_set', set: pf_record_name(@preset_set))
|
|
5
5
|
else t('.title') end %>
|
|
6
6
|
<% close_path = pf_back_or(@person ? permission_forge.person_path(@person) : permission_forge.people_path) %>
|
|
7
7
|
<%= form_with url: permission_forge.assignments_path, method: :post, id: 'pf-assign-form' do %>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<% if errors.any? %>
|
|
12
12
|
<% errors.each_value do |msg| %><span class="pf-error" role="alert"><%= msg %></span> <% end %>
|
|
13
13
|
<% elsif @person && @chosen_sets.any? %>
|
|
14
|
-
<%= t('.summary', name: pf_person_name(@person), sets: @chosen_sets.map(&:name)
|
|
14
|
+
<%= t('.summary', name: pf_person_name(@person), sets: pf_to_sentence(@chosen_sets.map(&:name)),
|
|
15
15
|
until: (@expiry == 'never' ? t('.forever') : t('.until', date: pf_expiry_preset_date(@expiry) || @custom_date))) %>
|
|
16
16
|
<% else %>
|
|
17
17
|
<%= t('.applies_now') %>
|
|
@@ -63,16 +63,20 @@
|
|
|
63
63
|
<% end %>
|
|
64
64
|
<% @sets.each do |set| %>
|
|
65
65
|
<% has = @existing_ids.include?(set.id) %>
|
|
66
|
+
<%# A locked set that would widen access by taking a holder is shown, but cannot be picked. %>
|
|
67
|
+
<% locked = !has && @locked_ids.include?(set.id) %>
|
|
66
68
|
<label class="pf-option">
|
|
67
|
-
<input type="checkbox" name="set_ids[]" value="<%= set.id %>" <%= 'checked' if @selected_ids.include?(set.id) && !has %> <%= 'disabled' if has %>>
|
|
69
|
+
<input type="checkbox" name="set_ids[]" value="<%= set.id %>" <%= 'checked' if @selected_ids.include?(set.id) && !has && !locked %> <%= 'disabled' if has || locked %>>
|
|
68
70
|
<span class="pf-option__main">
|
|
69
71
|
<span class="pf-row pf-row--tight">
|
|
70
|
-
<span class="pf-fw-600"><%= set
|
|
72
|
+
<span class="pf-fw-600"><%= pf_record_name(set) %></span>
|
|
71
73
|
<% if has %><%= pf_badge(t('.already')) %><% end %>
|
|
74
|
+
<% if locked %><%= pf_lock_badge %><% end %>
|
|
72
75
|
<% if set.is_muting %><%= pf_badge("⊘ #{t('permission_forge.ui.badges.muting')}", variant: :muting) %><% end %>
|
|
73
76
|
<% unless set.is_active %><%= pf_off_badge %><% end %>
|
|
74
77
|
</span>
|
|
75
|
-
<% if set.description.present? %><span class="pf-fs-12 pf-text-3"><%= set.description %></span><% end %>
|
|
78
|
+
<% if set.description.present? %><span class="pf-fs-12 pf-text-3" data-pf-host-data><%= set.description %></span><% end %>
|
|
79
|
+
<% if locked %><span class="pf-fs-12 pf-text-3"><%= t('.locked_hint') %></span><% end %>
|
|
76
80
|
</span>
|
|
77
81
|
<span class="pf-option__aside"><%= pf_people_text(@people_counts.fetch(set.id, 0)) %></span>
|
|
78
82
|
</label>
|
|
@@ -83,7 +87,7 @@
|
|
|
83
87
|
<% if muting.any? %>
|
|
84
88
|
<div class="pf-banner pf-banner--muting">
|
|
85
89
|
<span class="pf-banner__glyph" aria-hidden="true">⊘</span>
|
|
86
|
-
<div class="pf-banner__body"><span><%= t('.muting_notice', sets: muting.map(&:name)
|
|
90
|
+
<div class="pf-banner__body"><span><%= t('.muting_notice', sets: pf_to_sentence(muting.map(&:name)), name: @person ? pf_person(@person).first_name : t('.this_person')) %></span></div>
|
|
87
91
|
</div>
|
|
88
92
|
<% end %>
|
|
89
93
|
</section>
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
<select id="pf-audit-set" class="pf-select pf-select--sm" name="permission_set" <%= invalid.('permission_set') %>>
|
|
93
93
|
<option value=""><%= t('.filters.any_set') %></option>
|
|
94
94
|
<% sets.each do |id, name| %>
|
|
95
|
-
<option value="<%= id %>" <%= 'selected' if value.('permission_set') == id.to_s %>><%= name %></option>
|
|
95
|
+
<option value="<%= id %>" <%= 'selected' if value.('permission_set') == id.to_s %>><%= pf_record_name_text(name, :permission_set) %></option>
|
|
96
96
|
<% end %>
|
|
97
97
|
<% if value.('permission_set').present? && sets.none? { |id, _| id.to_s == value.('permission_set') } %>
|
|
98
98
|
<option value="<%= value.('permission_set') %>" selected><%= t('.filters.deleted_set', id: value.('permission_set')) %></option>
|
|
@@ -21,17 +21,24 @@
|
|
|
21
21
|
<dl class="pf-card__body pf-kv">
|
|
22
22
|
<dt><%= t('.actor') %></dt>
|
|
23
23
|
<dd>
|
|
24
|
+
<% unknown = pf_audit_unknown_actor?(event) %>
|
|
24
25
|
<span class="pf-fw-600"><%= pf_audit_actor_name(event) %></span>
|
|
25
|
-
|
|
26
|
+
<%# An unknown actor's name is its type already: "Unknown · Unknown" said nothing twice. %>
|
|
27
|
+
<% unless unknown %>
|
|
28
|
+
<span class="pf-text-3">· <%= pf_audit_actor_type_label(event.actor_type) %><% if d.actor_id? %> · <%= t('.id', id: event.actor_id) %><% end %></span>
|
|
29
|
+
<% end %>
|
|
30
|
+
<%# The actor line above already reads "Unknown": the explanation does not start with it again. %>
|
|
31
|
+
<% if unknown %><p class="pf-fs-13 pf-text-2"><%= pf_audit_unknown_actor_hint %></p><% end %>
|
|
26
32
|
<div class="pf-row pf-row--tight pf-fs-12">
|
|
27
|
-
|
|
33
|
+
<%# With no actor named, the link lists every event without one, and says so. %>
|
|
34
|
+
<%= link_to (unknown ? t('.all_unknown_actor') : t('.all_by_actor')), pf_audit_log_path(actor_type: event.actor_type, actor_id: event.actor_id) %>
|
|
28
35
|
<% if d.actor_user %>· <%= link_to t('.open_person'), permission_forge.person_path(d.actor_user) %><% end %>
|
|
29
36
|
</div>
|
|
30
37
|
</dd>
|
|
31
38
|
<dt><%= t('.source') %></dt>
|
|
32
39
|
<dd><%= pf_audit_source_label(event.source) %> <span class="pf-mono-key"><%= event.source %></span></dd>
|
|
33
40
|
<dt><%= t('.reason') %></dt>
|
|
34
|
-
<dd><%= event
|
|
41
|
+
<dd><%= pf_audit_reason(event) || t('.no_reason') %></dd>
|
|
35
42
|
<dt><%= t('.request_id') %></dt>
|
|
36
43
|
<dd class="pf-audit-code"><%= event.request_id.presence || t('permission_forge.audit_log.no_value') %></dd>
|
|
37
44
|
<dt><%= t('.ip') %></dt>
|
|
@@ -90,7 +97,7 @@
|
|
|
90
97
|
</div>
|
|
91
98
|
|
|
92
99
|
<% if d.changes? %>
|
|
93
|
-
|
|
100
|
+
<%= pf_scroll_frame(tag: :section, 'aria-labelledby': 'pf-event-changes', class: 'pf-card') do %>
|
|
94
101
|
<div class="pf-card__header">
|
|
95
102
|
<h2 class="pf-h2" id="pf-event-changes"><%= t('.changes') %></h2>
|
|
96
103
|
<span class="pf-card__desc"><%= t('.changes_desc', count: d.changes_count) %></span>
|
|
@@ -107,11 +114,11 @@
|
|
|
107
114
|
<% end %>
|
|
108
115
|
</tbody>
|
|
109
116
|
</table>
|
|
110
|
-
|
|
117
|
+
<% end %>
|
|
111
118
|
<% end %>
|
|
112
119
|
|
|
113
120
|
<% if d.snapshot? %>
|
|
114
|
-
|
|
121
|
+
<%= pf_scroll_frame(tag: :section, 'aria-labelledby': 'pf-event-snapshot', class: 'pf-card') do %>
|
|
115
122
|
<div class="pf-card__header">
|
|
116
123
|
<h2 class="pf-h2" id="pf-event-snapshot"><%= t('.snapshot') %></h2>
|
|
117
124
|
<span class="pf-card__desc"><%= d.snapshot_of_removed? ? t('.snapshot_destroyed') : t('.snapshot_desc') %></span>
|
|
@@ -124,11 +131,11 @@
|
|
|
124
131
|
<% end %>
|
|
125
132
|
</tbody>
|
|
126
133
|
</table>
|
|
127
|
-
|
|
134
|
+
<% end %>
|
|
128
135
|
<% end %>
|
|
129
136
|
|
|
130
137
|
<% if d.other? %>
|
|
131
|
-
|
|
138
|
+
<%= pf_scroll_frame(tag: :section, 'aria-labelledby': 'pf-event-data', class: 'pf-card') do %>
|
|
132
139
|
<div class="pf-card__header"><h2 class="pf-h2" id="pf-event-data"><%= t('.data') %></h2></div>
|
|
133
140
|
<table class="pf-audit-table">
|
|
134
141
|
<thead><tr><th scope="col"><%= t('.key') %></th><th scope="col"><%= t('.value') %></th></tr></thead>
|
|
@@ -138,7 +145,7 @@
|
|
|
138
145
|
<% end %>
|
|
139
146
|
</tbody>
|
|
140
147
|
</table>
|
|
141
|
-
|
|
148
|
+
<% end %>
|
|
142
149
|
<% end %>
|
|
143
150
|
|
|
144
151
|
<% if d.no_data? %>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<% set = @permission_set %>
|
|
2
2
|
<% back = permission_forge.permission_set_path(set, tab: 'assignments') %>
|
|
3
|
-
<% content_for :pf_title, t('.title', count: @people.size, set: set
|
|
4
|
-
<%= pf_dialog_frame({ title: t('.title', count: @people.size, set: set
|
|
5
|
-
breadcrumb: pf_breadcrumb([t('permission_forge.nav.permission_sets'), permission_forge.permission_sets_path], [set
|
|
3
|
+
<% content_for :pf_title, t('.title', count: @people.size, set: pf_record_name(set)) %>
|
|
4
|
+
<%= pf_dialog_frame({ title: t('.title', count: @people.size, set: pf_record_name(set)), danger: true,
|
|
5
|
+
breadcrumb: pf_breadcrumb([t('permission_forge.nav.permission_sets'), permission_forge.permission_sets_path], [pf_record_name(set), back], t('.crumb')) }) do %>
|
|
6
6
|
<% if @people.empty? %>
|
|
7
7
|
<p class="pf-text-2"><%= t('.none') %></p>
|
|
8
8
|
<div class="pf-actions pf-row--right"><%= link_to t('permission_forge.ui.close'), back, class: 'pf-btn', data: { pf_close: true } %></div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<% set = @permission_set %>
|
|
2
|
-
<% content_for :pf_title, t('.title', set: set
|
|
2
|
+
<% content_for :pf_title, t('.title', set: pf_record_name(set)) %>
|
|
3
3
|
<% close_path = permission_forge.permission_set_path(set, tab: 'assignments') %>
|
|
4
4
|
<%= form_with url: permission_forge.permission_set_bulk_assignment_path(set), method: :post do %>
|
|
5
5
|
<% footer = capture do %>
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
<%= @result ? t('.assign_count', count: @result.to_assign.size) : t('.assign') %>
|
|
16
16
|
</button>
|
|
17
17
|
<% end %>
|
|
18
|
-
<%= pf_sheet_frame({ title: t('.title', set: set
|
|
19
|
-
breadcrumb: pf_breadcrumb([t('permission_forge.nav.permission_sets'), permission_forge.permission_sets_path], [set
|
|
18
|
+
<%= pf_sheet_frame({ title: t('.title', set: pf_record_name(set)), close_path: close_path, footer: footer,
|
|
19
|
+
breadcrumb: pf_breadcrumb([t('permission_forge.nav.permission_sets'), permission_forge.permission_sets_path], [pf_record_name(set), close_path], t('.crumb')) }) do %>
|
|
20
20
|
<div class="pf-field">
|
|
21
21
|
<label class="pf-label" for="pf-bulk-text"><%= t('.paste') %></label>
|
|
22
22
|
<textarea id="pf-bulk-text" name="identifiers" rows="5" class="pf-textarea pf-textarea--mono" placeholder="<%= t('.paste_placeholder') %>"><%= @text %></textarea>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<select id="pf-lookup-resource" name="resource" class="pf-select" data-pf-autosubmit>
|
|
8
8
|
<option value=""><%= t('.pick_resource') %></option>
|
|
9
9
|
<% @resource_names.each do |name| %>
|
|
10
|
-
<option value="<%= name %>" <%= 'selected' if name == @resource_name
|
|
10
|
+
<option value="<%= name %>" <%= 'selected' if name == @resource_name %> data-pf-host-data><%= pf_resource_label(name) %> (<%= name %>)</option>
|
|
11
11
|
<% end %>
|
|
12
12
|
</select>
|
|
13
13
|
</div>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<select id="pf-lookup-field" name="field" class="pf-select" data-pf-autosubmit <%= 'disabled' unless @resource_name %>>
|
|
17
17
|
<option value=""><%= t('.pick_field') %></option>
|
|
18
18
|
<% @field_options.each do |name, label| %>
|
|
19
|
-
<option value="<%= name %>" <%= 'selected' if name == @field
|
|
19
|
+
<option value="<%= name %>" <%= 'selected' if name == @field %> data-pf-host-data><%= label %> (<%= name %>)</option>
|
|
20
20
|
<% end %>
|
|
21
21
|
</select>
|
|
22
22
|
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<% items = @attention.broken_rules %>
|
|
2
|
+
<section class="pf-card" aria-labelledby="pf-card-broken-rules">
|
|
3
|
+
<div class="pf-card__header">
|
|
4
|
+
<div class="pf-stack pf-stack--tight pf-grow">
|
|
5
|
+
<div class="pf-row">
|
|
6
|
+
<span class="pf-text-block" aria-hidden="true">⊘</span>
|
|
7
|
+
<h2 class="pf-h2" id="pf-card-broken-rules"><%= t('.title') %></h2>
|
|
8
|
+
<span class="pf-count-pill pf-count-pill--warn"><%= items.size %></span>
|
|
9
|
+
</div>
|
|
10
|
+
<p class="pf-card__desc"><%= t('.description') %></p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<% items.each do |item| %>
|
|
14
|
+
<% set = item.permission_set %>
|
|
15
|
+
<% resource = item.resource %>
|
|
16
|
+
<div class="pf-card__row pf-card__row--top">
|
|
17
|
+
<div class="pf-stack pf-stack--tight pf-grow">
|
|
18
|
+
<div class="pf-row pf-row--tight">
|
|
19
|
+
<strong><%= t('.rule', rule: pf_record_name(item.rule), resource: pf_resource_label(resource), set: pf_record_name(set)) %></strong>
|
|
20
|
+
<%= pf_lock_badge if item.locked? %>
|
|
21
|
+
</div>
|
|
22
|
+
<span class="pf-fs-13 pf-text-block"><%= pf_rule_problem_line(item.problem) %></span>
|
|
23
|
+
<span class="pf-fs-13 pf-text-2"><%= t('.people', count: item.people_count) %></span>
|
|
24
|
+
<%# A locked rule cannot be fixed here (its page offers only a copy): say where it can be. %>
|
|
25
|
+
<% if item.locked? %><span class="pf-fs-13 pf-text-2"><%= t('.locked_fix_html', flag: content_tag(:code, 'is_editable: false')) %></span><% end %>
|
|
26
|
+
</div>
|
|
27
|
+
<% if item.locked? %>
|
|
28
|
+
<%= link_to t('.open'), permission_forge.permission_set_resource_path(set, resource.name, tab: 'rules'), class: 'pf-btn pf-btn--ghost' %>
|
|
29
|
+
<% else %>
|
|
30
|
+
<%= pf_modal_link t('.fix'), permission_forge.edit_permission_set_resource_rule_path(set, resource.name, item.rule),
|
|
31
|
+
kind: 'drawer', class: 'pf-btn pf-btn--sm' %>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
<% end %>
|
|
35
|
+
</section>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<% items = @attention.broken_conditions %>
|
|
2
|
+
<section class="pf-card" aria-labelledby="pf-card-conditions">
|
|
3
|
+
<div class="pf-card__header">
|
|
4
|
+
<div class="pf-stack pf-stack--tight pf-grow">
|
|
5
|
+
<div class="pf-row">
|
|
6
|
+
<span class="pf-text-warn" aria-hidden="true">⚠</span>
|
|
7
|
+
<h2 class="pf-h2" id="pf-card-conditions"><%= t('.title') %></h2>
|
|
8
|
+
<span class="pf-count-pill pf-count-pill--warn"><%= items.size %></span>
|
|
9
|
+
</div>
|
|
10
|
+
<p class="pf-card__desc"><%= t('.description') %></p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<% items.each do |item| %>
|
|
14
|
+
<% set = item.permission_set %>
|
|
15
|
+
<div class="pf-card__row pf-card__row--top">
|
|
16
|
+
<div class="pf-stack pf-stack--tight pf-grow">
|
|
17
|
+
<div class="pf-row pf-row--tight">
|
|
18
|
+
<strong><%= pf_record_name(set) %></strong>
|
|
19
|
+
<%= pf_set_badges(set) %>
|
|
20
|
+
</div>
|
|
21
|
+
<%# The effect as the set's Conditions tab words it (pf_condition_effect). %>
|
|
22
|
+
<span class="pf-fs-13 pf-text-2"><%= pf_condition_effect(set) %></span>
|
|
23
|
+
<span class="pf-fs-13 pf-text-2"><%= t('.people', count: item.people_count) %></span>
|
|
24
|
+
</div>
|
|
25
|
+
<%= link_to t('.open'), permission_forge.permission_set_path(set, tab: 'conditions'), class: 'pf-btn pf-btn--sm' %>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
</section>
|