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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'securerandom'
|
|
4
|
+
require_relative 'database_reach'
|
|
4
5
|
|
|
5
6
|
module PermissionForge
|
|
6
7
|
# Carries auto-applied masking (MaskedModels) to every process. The
|
|
@@ -33,7 +34,17 @@ module PermissionForge
|
|
|
33
34
|
# Either way a process that has not synced (its boot could not reach the
|
|
34
35
|
# database, a model could not be masked) applies at its next executor
|
|
35
36
|
# run, and a development code reload applies again (`to_prepare`).
|
|
36
|
-
#
|
|
37
|
+
#
|
|
38
|
+
# The token needs a shared store. One that keeps nothing (:null, a
|
|
39
|
+
# :null_store) is never asked for it: the token would read as lost on
|
|
40
|
+
# every check. One that lives in each process (:memory, a :memory_store)
|
|
41
|
+
# reaches no other process. In development, where Rails' default store is
|
|
42
|
+
# one of these, each executor run re-reads the Resource names from the
|
|
43
|
+
# database instead, at most every DATABASE_RECHECK seconds (database
|
|
44
|
+
# recheck): a model named from a console, `bin/rails runner` or a rake task
|
|
45
|
+
# is masked in the server at its next request. Other environments get no
|
|
46
|
+
# such query (CacheWarnings warns at boot): a process then masks a model
|
|
47
|
+
# first named in another one only after a restart.
|
|
37
48
|
#
|
|
38
49
|
# @api private
|
|
39
50
|
module MaskingSync
|
|
@@ -45,6 +56,21 @@ module PermissionForge
|
|
|
45
56
|
# Seconds the store is left alone after it did not answer.
|
|
46
57
|
RETRY_PAUSE = 30
|
|
47
58
|
|
|
59
|
+
# Development with a store that cannot carry the token: seconds between
|
|
60
|
+
# two reads of the Resource names (one query each).
|
|
61
|
+
DATABASE_RECHECK = 2
|
|
62
|
+
|
|
63
|
+
# The cause named by the token warning when the store raised nothing: a
|
|
64
|
+
# read of the token, and a read right after writing one, came back empty.
|
|
65
|
+
SILENT_STORE = ' (it did not answer, or a read right after a write came back empty: the store is down, full, ' \
|
|
66
|
+
'or evicts the key at once)'
|
|
67
|
+
|
|
68
|
+
# check!'s warning when a sync raised: a SECURITY one, the process fails open until the next sync.
|
|
69
|
+
SYNC_FAILED = 'Auto-applied masking could not be synced: a model named in another process may stay unmasked here until the next sync'
|
|
70
|
+
# The boot step's warning when a model could not be masked (boot!).
|
|
71
|
+
INCOMPLETE_AT_BOOT = 'Auto-applied masking is not complete at boot (see the warnings above, or the Resource ' \
|
|
72
|
+
'table cannot be read): it is tried again at each request or job.'
|
|
73
|
+
|
|
48
74
|
# One sync (or apply) at a time; one probe of a failing store at a time.
|
|
49
75
|
SYNC_LOCK = Mutex.new
|
|
50
76
|
PROBE_LOCK = Mutex.new
|
|
@@ -57,20 +83,34 @@ module PermissionForge
|
|
|
57
83
|
@announcement_owed = false
|
|
58
84
|
@failing_until = nil
|
|
59
85
|
@failing_store = nil
|
|
86
|
+
@recheck_after = nil
|
|
60
87
|
|
|
61
88
|
class << self
|
|
62
89
|
# Executor `to_run`. Row-driven: one store read; applies when another
|
|
63
90
|
# process named a model since this one last synced. The store is not
|
|
64
|
-
# read while it is failing (RETRY_PAUSE), nor at all for lists
|
|
65
|
-
# token is then unknown (nil), and only
|
|
66
|
-
# applies (from the database).
|
|
91
|
+
# read while it is failing (RETRY_PAUSE), nor at all for lists or a
|
|
92
|
+
# store that keeps nothing: the token is then unknown (nil), and only
|
|
93
|
+
# a process that has not synced applies (from the database). In
|
|
94
|
+
# development with a store that cannot carry the token, the Resource
|
|
95
|
+
# names instead (database_recheck?). Never raises.
|
|
96
|
+
#
|
|
97
|
+
# A process that has not synced (its boot could not use the database)
|
|
98
|
+
# asks the database first (database_unavailable?: one table check per
|
|
99
|
+
# run until it syncs, none after): while it cannot
|
|
100
|
+
# read the Resource table, nothing can be masked from it, and the store
|
|
101
|
+
# is not read — its own database may be the one that is not there
|
|
102
|
+
# (Solid Cache in a `rails new` app whose production database is not
|
|
103
|
+
# configured raised the same ArgumentError, logged as a SECURITY store
|
|
104
|
+
# failure on every runner).
|
|
67
105
|
def check!
|
|
68
106
|
return unless MaskedModels.enabled?
|
|
107
|
+
return recheck_database if database_recheck?
|
|
108
|
+
return if database_unavailable?
|
|
69
109
|
|
|
70
|
-
token =
|
|
110
|
+
token = token_in_store? ? answered_token : nil
|
|
71
111
|
sync(token) if changed?(token)
|
|
72
112
|
rescue StandardError => e
|
|
73
|
-
|
|
113
|
+
Warnings.emit(:masking_sync_failed, "#{SYNC_FAILED} (#{e.class}: #{e.message}).", severity: :security)
|
|
74
114
|
end
|
|
75
115
|
|
|
76
116
|
# After a Resource row naming `name` has been created or renamed and
|
|
@@ -85,9 +125,9 @@ module PermissionForge
|
|
|
85
125
|
# (asked only when it decides something)
|
|
86
126
|
def named!(name)
|
|
87
127
|
return unless MaskedModels.enabled?
|
|
88
|
-
return apply!(name) unless
|
|
128
|
+
return apply!(name) unless token_in_store?
|
|
89
129
|
|
|
90
|
-
|
|
130
|
+
MaskedModels.masked?(name) && yield ? apply!(name) : announce!(name)
|
|
91
131
|
end
|
|
92
132
|
|
|
93
133
|
# The boot step: applies to every selected model and syncs with the
|
|
@@ -98,11 +138,8 @@ module PermissionForge
|
|
|
98
138
|
#
|
|
99
139
|
# @return [Boolean] whether every selected model is masked
|
|
100
140
|
def boot!
|
|
101
|
-
done = sync(
|
|
102
|
-
unless done
|
|
103
|
-
Rails.logger&.warn('[PermissionForge] Auto-applied masking is not complete at boot (see the warnings ' \
|
|
104
|
-
'above, or the Resource table cannot be read): it is tried again at each request or job.')
|
|
105
|
-
end
|
|
141
|
+
done = sync(token_in_store? ? current_token : nil, always: true)
|
|
142
|
+
Warnings.emit(:masking_sync_incomplete_at_boot, INCOMPLETE_AT_BOOT, severity: :security) unless done
|
|
106
143
|
done
|
|
107
144
|
end
|
|
108
145
|
|
|
@@ -123,6 +160,7 @@ module PermissionForge
|
|
|
123
160
|
SYNC_LOCK.synchronize { @token = UNSYNCED }
|
|
124
161
|
@announcement_owed = false
|
|
125
162
|
@failing_until = nil
|
|
163
|
+
@recheck_after = nil
|
|
126
164
|
MaskedModels.reset_warnings!
|
|
127
165
|
end
|
|
128
166
|
|
|
@@ -138,8 +176,43 @@ module PermissionForge
|
|
|
138
176
|
!until_time.nil? && now < until_time && PermissionForge.cache.equal?(@failing_store)
|
|
139
177
|
end
|
|
140
178
|
|
|
179
|
+
# Whether this process learns of a model named in another one from the
|
|
180
|
+
# Resource rows (development, a row-driven setting, a store that cannot
|
|
181
|
+
# carry the token: it keeps nothing or lives in each process). Asks
|
|
182
|
+
# nothing outside development.
|
|
183
|
+
#
|
|
184
|
+
# @return [Boolean]
|
|
185
|
+
def database_recheck?
|
|
186
|
+
return false unless Rails.env.development? && MaskedModels.row_driven?
|
|
187
|
+
|
|
188
|
+
cache = PermissionForge.cache
|
|
189
|
+
cache.process_local? || !cache.keeps_entries?
|
|
190
|
+
end
|
|
191
|
+
|
|
141
192
|
private
|
|
142
193
|
|
|
194
|
+
# A process that has not synced, whose Resource table cannot be read
|
|
195
|
+
# (DatabaseReach: unreachable, not configured, not migrated).
|
|
196
|
+
def database_unavailable? = !synced? && !DatabaseReach.table_exists?(PermissionForge::Resource)
|
|
197
|
+
|
|
198
|
+
# Whether the token is read and written: a row-driven setting and a
|
|
199
|
+
# store that keeps entries, outside a database recheck.
|
|
200
|
+
#
|
|
201
|
+
# @return [Boolean]
|
|
202
|
+
def token_in_store?
|
|
203
|
+
MaskedModels.row_driven? && PermissionForge.cache.keeps_entries? && !database_recheck?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# The database recheck: every model the Resource rows name, at most
|
|
207
|
+
# every DATABASE_RECHECK seconds — and at each run until it succeeded
|
|
208
|
+
# once (a process that has not synced).
|
|
209
|
+
def recheck_database
|
|
210
|
+
return if synced? && @recheck_after && now < @recheck_after
|
|
211
|
+
|
|
212
|
+
@recheck_after = now + DATABASE_RECHECK
|
|
213
|
+
sync(nil, always: true)
|
|
214
|
+
end
|
|
215
|
+
|
|
143
216
|
# Applies the concern to one model in this process.
|
|
144
217
|
def apply!(name)
|
|
145
218
|
SYNC_LOCK.synchronize { MaskedModels.apply(only: name) }
|
|
@@ -157,8 +230,9 @@ module PermissionForge
|
|
|
157
230
|
apply!(name)
|
|
158
231
|
return if publish_token
|
|
159
232
|
|
|
160
|
-
|
|
161
|
-
|
|
233
|
+
Warnings.emit("masking_sync.token_not_taken.#{name}",
|
|
234
|
+
"The cache store did not take the masking token: other processes mask #{name} once the " \
|
|
235
|
+
'store answers again and this process has written it.', severity: :security)
|
|
162
236
|
end
|
|
163
237
|
|
|
164
238
|
# The token to check against — nil while the store is failing. A
|
|
@@ -228,16 +302,23 @@ module PermissionForge
|
|
|
228
302
|
@failing_until = nil
|
|
229
303
|
end
|
|
230
304
|
|
|
231
|
-
#
|
|
305
|
+
# Warned once per outage: the first failure after the store answered.
|
|
306
|
+
# The warning names the cause (the error, or what a silent store did)
|
|
307
|
+
# and the remedy.
|
|
232
308
|
def store_failed!(error = nil)
|
|
233
309
|
first = @failing_until.nil?
|
|
234
310
|
@failing_until = now + RETRY_PAUSE
|
|
235
311
|
@failing_store = PermissionForge.cache
|
|
236
312
|
return unless first
|
|
237
313
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
314
|
+
Warnings.emit(:masking_sync_store_failed,
|
|
315
|
+
'The cache store did not give or keep the masking token' \
|
|
316
|
+
"#{error ? " (#{error.class}: #{error.message})" : SILENT_STORE}: auto-applied " \
|
|
317
|
+
"masking is not synced with other processes for the next #{RETRY_PAUSE} seconds — a model " \
|
|
318
|
+
'a Resource row names in another process is masked here once the store answers. A store ' \
|
|
319
|
+
'that is up but loses the token at once cannot sync masking: use a shared store that keeps ' \
|
|
320
|
+
'entries, or include PermissionForge::Concerns::MaskedAttributes in the models ' \
|
|
321
|
+
'(docs/masked-attributes.md).', severity: :security)
|
|
241
322
|
end
|
|
242
323
|
|
|
243
324
|
def now
|
|
@@ -251,12 +332,6 @@ module PermissionForge
|
|
|
251
332
|
token.nil? ? !synced? : token != @token
|
|
252
333
|
end
|
|
253
334
|
|
|
254
|
-
# Whether this process has the concern on the model (false for a name
|
|
255
|
-
# that resolves to no model here: not known to be masked).
|
|
256
|
-
def masked_here?(name)
|
|
257
|
-
MaskedModels.masked?(name)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
335
|
def sync(token, always: false)
|
|
261
336
|
SYNC_LOCK.synchronize do
|
|
262
337
|
return true if !always && synced? && token == @token
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'warnings'
|
|
4
|
+
|
|
3
5
|
module PermissionForge
|
|
4
6
|
# What a model took from the configuration as it loaded — the user class of
|
|
5
7
|
# an assignment (PermissionSetUser#user, PermissionSetGroupUser#user), the
|
|
@@ -8,16 +10,21 @@ module PermissionForge
|
|
|
8
10
|
# changed after the class loaded (a PermissionForge.configure or a setter
|
|
9
11
|
# in after_initialize, a console) changes nothing in the class: a
|
|
10
12
|
# development reload picks it up, an eager-loaded production process never
|
|
11
|
-
# does. The engine
|
|
12
|
-
# blocks have run, and PermissionForge.configure
|
|
13
|
-
# loads a model. (table_name_prefix is not
|
|
14
|
-
# method that reads it on every call.)
|
|
13
|
+
# does. The engine warns about each difference (Warnings.emit) once the
|
|
14
|
+
# host's after_initialize blocks have run, and PermissionForge.configure
|
|
15
|
+
# does too. Nothing here loads a model. (table_name_prefix is not
|
|
16
|
+
# compared: table_name is a method that reads it on every call.)
|
|
15
17
|
#
|
|
16
18
|
# @api private
|
|
17
19
|
module ModelBindings
|
|
18
20
|
# The settings a model declares as it loads, each with the association
|
|
19
21
|
# that carries it (the tenant is read from HasTenantScoping).
|
|
20
22
|
ASSOCIATIONS = { user_class: :user, audit_user_class: :created_by }.freeze
|
|
23
|
+
# The severity of a mismatch, by setting (Warnings): a model loaded with
|
|
24
|
+
# another tenant model (or none) is not isolated as configured, access
|
|
25
|
+
# across tenants; another user class only points an association
|
|
26
|
+
# elsewhere.
|
|
27
|
+
SEVERITY = { tenant_model: :security }.freeze
|
|
21
28
|
|
|
22
29
|
class << self
|
|
23
30
|
# One sentence per setting that loaded models declared differently.
|
|
@@ -25,22 +32,18 @@ module PermissionForge
|
|
|
25
32
|
# @param config [Configuration]
|
|
26
33
|
# @return [Array<String>]
|
|
27
34
|
def mismatches(config = PermissionForge.configuration)
|
|
28
|
-
|
|
29
|
-
loaded_models.each do |model|
|
|
30
|
-
declared_otherwise(model, config).each do |setting, declared|
|
|
31
|
-
found[[setting, config.public_send(setting), declared]] << model.name
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
found.map { |difference, models| sentence(difference, models) }
|
|
35
|
+
differences(config).map { |difference, models| sentence(difference, models) }
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
#
|
|
38
|
+
# Warns about every mismatch, one key per setting.
|
|
38
39
|
#
|
|
39
40
|
# @param config [Configuration]
|
|
40
|
-
# @param logger [Logger, nil]
|
|
41
41
|
# @return [void]
|
|
42
|
-
def warn!(config = PermissionForge.configuration
|
|
43
|
-
|
|
42
|
+
def warn!(config = PermissionForge.configuration)
|
|
43
|
+
differences(config).each do |difference, models|
|
|
44
|
+
setting = difference.first
|
|
45
|
+
Warnings.emit("model_bindings.#{setting}", sentence(difference, models), severity: SEVERITY.fetch(setting, :config))
|
|
46
|
+
end
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
# The model classes already loaded, without loading one.
|
|
@@ -53,6 +56,18 @@ module PermissionForge
|
|
|
53
56
|
|
|
54
57
|
private
|
|
55
58
|
|
|
59
|
+
# { [setting, what the configuration says, what the models declared] =>
|
|
60
|
+
# the model names } for every difference.
|
|
61
|
+
def differences(config)
|
|
62
|
+
found = Hash.new { |hash, key| hash[key] = [] }
|
|
63
|
+
loaded_models.each do |model|
|
|
64
|
+
declared_otherwise(model, config).each do |setting, declared|
|
|
65
|
+
found[[setting, config.public_send(setting), declared]] << model.name
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
found
|
|
69
|
+
end
|
|
70
|
+
|
|
56
71
|
# [setting, what the model declared] for each setting the model
|
|
57
72
|
# declared and the configuration now says differently. A model without
|
|
58
73
|
# the association (a PermissionSet has no :user) declares nothing.
|
|
@@ -81,10 +96,6 @@ module PermissionForge
|
|
|
81
96
|
'development; never in an eager-loaded production process). Configure PermissionForge in ' \
|
|
82
97
|
'config/initializers.'
|
|
83
98
|
end
|
|
84
|
-
|
|
85
|
-
def default_logger
|
|
86
|
-
Rails.logger if defined?(Rails) && Rails.respond_to?(:logger)
|
|
87
|
-
end
|
|
88
99
|
end
|
|
89
100
|
end
|
|
90
101
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
# "This name is a model with a table" has one definition: the class a
|
|
5
|
+
# Resource name or a rule's Resource stands for, when it is an ActiveRecord
|
|
6
|
+
# model that is not abstract and whose table exists. Anything else — a
|
|
7
|
+
# module (`Kernel`), a class that is no model (`String`), an abstract
|
|
8
|
+
# model, a model whose table is not there yet, a name that is no constant,
|
|
9
|
+
# a constant whose file raises while it loads — is no model: the callers
|
|
10
|
+
# describe or skip, they never fail on it (a Resource named `Kernel` made
|
|
11
|
+
# the field generator raise NoMethodError and the API answer 500). A table
|
|
12
|
+
# found missing is asked of the database again once (SchemaRead.table?).
|
|
13
|
+
# The one place a Resource name is constantized (a G4 needle).
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# ModelLookup.model_for('Customer') # => Customer
|
|
17
|
+
# ModelLookup.model_for('Kernel') # => nil
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
module ModelLookup
|
|
21
|
+
# What is rescued while a name is resolved: anything raised as the
|
|
22
|
+
# constant loads, a model file's SyntaxError or LoadError included.
|
|
23
|
+
UNTELLABLE = [StandardError, ScriptError].freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# @param name [String, Symbol, nil] a constant path
|
|
28
|
+
# @return [Class, nil] the model, or nil when the name is no model with a table
|
|
29
|
+
def model_for(name)
|
|
30
|
+
klass = name.to_s.safe_constantize
|
|
31
|
+
klass if klass.is_a?(Class) && klass < ActiveRecord::Base && !klass.abstract_class? && SchemaRead.table?(klass, klass.table_name)
|
|
32
|
+
rescue *UNTELLABLE
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param name [String, Symbol, nil]
|
|
37
|
+
# @return [Boolean] whether the name is a model with a table
|
|
38
|
+
def model?(name)
|
|
39
|
+
!model_for(name).nil?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
module Operators
|
|
5
|
+
# The model whose encrypted column a rule names through a name its
|
|
6
|
+
# query gives a table that is on no model along the rule's path: an
|
|
7
|
+
# alias (`twins_notes.body` on a self-join), a `has_many :through`'s
|
|
8
|
+
# join table, a table a default scope or a host's join brings in
|
|
9
|
+
# (RuleSources). RuleColumn leaves such a name to the database
|
|
10
|
+
# (UNKNOWN) — except a column that holds ciphertext, which no built-in
|
|
11
|
+
# operator can compare (RuleColumn#encrypted?): failing to see one would
|
|
12
|
+
# compare the ciphertext, and `not_equal` would show the rows it should
|
|
13
|
+
# hide.
|
|
14
|
+
#
|
|
15
|
+
# - The name is resolved to the table it stands for, and every loaded
|
|
16
|
+
# model on that table (PermissionForge::EncryptedAttributes.models_on,
|
|
17
|
+
# single-table inheritance included) says whether it encrypts the
|
|
18
|
+
# column: one that does makes it encrypted.
|
|
19
|
+
# - A name whose table cannot be told, or that no loaded model is on (a
|
|
20
|
+
# subquery, a table without a model), is judged by every model along
|
|
21
|
+
# the path, the rule's own included: a column of that name that any of
|
|
22
|
+
# them encrypts counts as encrypted (fail closed).
|
|
23
|
+
#
|
|
24
|
+
# Names are compared as the database compares them (RuleColumn.same_name?).
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
class EncryptedAlias
|
|
28
|
+
# @param models [Array<Class>] the rule's model, then the model of each
|
|
29
|
+
# association on its path
|
|
30
|
+
# @param joined [Hash{String => String, nil}, Array<String>, nil] the
|
|
31
|
+
# names the rule's query gives its tables (RuleSources.tables; a list
|
|
32
|
+
# of names: their tables not known)
|
|
33
|
+
def initialize(models, joined)
|
|
34
|
+
@models = models
|
|
35
|
+
@joined = joined || {}
|
|
36
|
+
@exact = RuleColumn.exact_names?(models.last)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The model whose encrypted column `name` is, when `table` is a name
|
|
40
|
+
# the query gives a table: one on that table, or one along the path;
|
|
41
|
+
# nil otherwise.
|
|
42
|
+
#
|
|
43
|
+
# @param table [String] the field name's qualifier
|
|
44
|
+
# @param name [String] the column as written
|
|
45
|
+
# @return [Class, nil]
|
|
46
|
+
def model(table, name)
|
|
47
|
+
source = @joined.find { |alias_name, _table| RuleColumn.same_name?(alias_name, table, @exact) } or return
|
|
48
|
+
|
|
49
|
+
(on_table(source).presence || @models).find { |model| encrypts?(model, name) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# The loaded models on the table the name stands for.
|
|
55
|
+
def on_table(source)
|
|
56
|
+
_alias_name, table = source
|
|
57
|
+
return [] unless table
|
|
58
|
+
|
|
59
|
+
PermissionForge::EncryptedAttributes.models_on(table, exact: @exact)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def encrypts?(model, name)
|
|
63
|
+
PermissionForge::EncryptedAttributes.names(model).any? { |attribute| RuleColumn.same_name?(attribute.to_s, name, @exact) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -18,6 +18,12 @@ module PermissionForge
|
|
|
18
18
|
#
|
|
19
19
|
# @api public
|
|
20
20
|
module Registry
|
|
21
|
+
# The operators whose strategy adds the NULL rows under a rule's
|
|
22
|
+
# `include_nulls` ("…, or empty"); the others ignore the flag. The one
|
|
23
|
+
# list: the RLS policy (Services::RlsOperatorSql), the rule builder and
|
|
24
|
+
# the rule's sentence read it (a test compares it with the strategies).
|
|
25
|
+
NULL_INCLUDING = [Constants::EQUAL, Constants::NOT_EQUAL, Constants::IN, Constants::NOT_IN].freeze
|
|
26
|
+
|
|
21
27
|
module_function
|
|
22
28
|
|
|
23
29
|
# @param name [String] operator constant from PermissionSetRule (e.g., 'equal')
|