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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
module Services
|
|
5
|
+
module SecurityAuditChecks
|
|
6
|
+
# Activation conditions and field rows whose read-time meaning is not
|
|
7
|
+
# what the row looks like: conditions the validation would refuse
|
|
8
|
+
# today (ActivationConditions: nobody can judge them, so a muting set
|
|
9
|
+
# applies to every request and a granting set never passes) and
|
|
10
|
+
# resources holding both a row and an explicit row for one of its
|
|
11
|
+
# variants (the explicit row decides since 0.15).
|
|
12
|
+
class ConditionsCheck < BaseCheck
|
|
13
|
+
LEVEL_LABELS = { 'hidden' => 'Hidden', 'masked' => 'Masked', 'read' => 'Read-only', 'edit' => 'Editable' }.freeze
|
|
14
|
+
# ActivationConditions#condition_problems kind => its sentence (the value, the set).
|
|
15
|
+
PROBLEMS = {
|
|
16
|
+
ip_entry: ->(value, _set) { "IP entry “#{value}” is not an IP address or CIDR range" },
|
|
17
|
+
ip_empty: ->(_value, _set) { 'IP list has no entry' },
|
|
18
|
+
flag_unusable: ->(value, _set) { "session flag #{value.inspect} holds a control character no request carries" },
|
|
19
|
+
hours_equal: ->(_value, set) { "start hour #{set.allowed_hours_start} equals end hour #{set.allowed_hours_end}" },
|
|
20
|
+
hours_incomplete: ->(_value, set) { set.allowed_hours_start.nil? ? 'start hour missing' : 'end hour missing' },
|
|
21
|
+
hours_range: ->(_value, _set) { 'hour outside 0–23' }
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
[incomplete_conditions, variant_rows_conflict].compact
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def incomplete_conditions
|
|
31
|
+
sets = PermissionForge::PermissionSet.with_broken_conditions # Home's card reads the same
|
|
32
|
+
return if sets.empty?
|
|
33
|
+
|
|
34
|
+
finding(:medium, :incomplete_conditions, 'Activation conditions nobody can judge',
|
|
35
|
+
'These rows hold a condition the validation refuses today: an IP entry that is not an IP address or ' \
|
|
36
|
+
'CIDR range, a list with no entry, a session flag with a control character, the same start and end ' \
|
|
37
|
+
'hour, one hour of the two, or an hour outside ' \
|
|
38
|
+
'0–23. Such a condition cannot be judged, so a muting set applies to every request and a granting set ' \
|
|
39
|
+
'never passes, with or without a request context: it grants nothing. Fix the condition on the ' \
|
|
40
|
+
'set\'s Conditions tab (the admin and the API normalise the list; newline or comma lists and arrays ' \
|
|
41
|
+
'are accepted).',
|
|
42
|
+
items: sets.map { |set| condition_item(set) })
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def condition_item(set)
|
|
46
|
+
what = set.condition_problems.map { |kind, value| describe_problem(kind, value, set) }.join('; ')
|
|
47
|
+
effect = set.is_muting ? 'applies to every request' : 'never passes, grants nothing'
|
|
48
|
+
"#{set.name}#{' (muting)' if set.is_muting}: #{what} — #{effect} · #{people(set.id)}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def describe_problem(kind, value, set)
|
|
52
|
+
PROBLEMS.fetch(kind).call(value, set)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Granting resources in effect: a muting resource's rows and variants
|
|
56
|
+
# all mute (deny direction), so two rows there never disagree.
|
|
57
|
+
def variant_rows_conflict
|
|
58
|
+
resources = PermissionForge::Resource.in_effect.merge(PermissionForge::PermissionSet.granting)
|
|
59
|
+
.includes(:permission_set, :resource_fields).order(:id)
|
|
60
|
+
items = resources.flat_map { |resource| conflicts_of(resource) }
|
|
61
|
+
return if items.empty?
|
|
62
|
+
|
|
63
|
+
finding(:info, :variant_rows_conflict, 'Field rows next to an explicit row for their variant',
|
|
64
|
+
'Since 0.15 an explicit row for a name decides that name: the variant derived from the other row ' \
|
|
65
|
+
'(owner_id → owner_uuid) no longer widens it. Check that each explicit row says what you mean.',
|
|
66
|
+
items: items)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def conflicts_of(resource)
|
|
70
|
+
rows = resource.resource_fields.select(&:is_active)
|
|
71
|
+
by_name = rows.index_by(&:name)
|
|
72
|
+
variants = PermissionForge::FieldVariants.new(resource.name, configuration)
|
|
73
|
+
rows.flat_map do |row|
|
|
74
|
+
variants.call(row.name).filter_map do |variant|
|
|
75
|
+
explicit = by_name[variant]
|
|
76
|
+
conflict_item(resource, row, explicit) if explicit
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def conflict_item(resource, row, explicit)
|
|
82
|
+
"#{resource.permission_set.name} › #{resource.name}: #{row.name} (#{level(row)}) → " \
|
|
83
|
+
"#{explicit.name} (#{level(explicit)}): the #{explicit.name} row decides"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def level(row)
|
|
87
|
+
LEVEL_LABELS.fetch(PermissionForge::Admin::FieldLevel.level_of(row, muting: false))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -6,11 +6,15 @@ module PermissionForge
|
|
|
6
6
|
# Configuration hardening: who can reach the admin, whether the cache is
|
|
7
7
|
# shared, fail-closed flags, GDPR and audit settings, and the JSON API.
|
|
8
8
|
class ConfigCheck < BaseCheck
|
|
9
|
+
include MaskingWithoutUser
|
|
10
|
+
|
|
9
11
|
RULES = %i[
|
|
10
12
|
admin_ui_exposed process_local_cache unconfigured_fields_pass write_protection_off nested_parent_fallback
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
unconfigured_actions_allowed
|
|
14
|
+
nil_user_bypass masked_nil_user_bypass middleware_above_web_request gdpr_defaults_off log_filtering_off audit_trail_off
|
|
15
|
+
conditional_sets_lenient
|
|
16
|
+
detection_raises_in_production api_without_auth api_mutations_unguarded api_authorization_unset
|
|
17
|
+
api_sequential_ids api_rule_values_exposed tenantless_admin strict_overrides
|
|
14
18
|
].freeze
|
|
15
19
|
|
|
16
20
|
def call
|
|
@@ -29,12 +33,18 @@ module PermissionForge
|
|
|
29
33
|
'a controller that authenticates and authorizes admins, or turn HTTP Basic back on.')
|
|
30
34
|
end
|
|
31
35
|
|
|
36
|
+
# The one definition of "process-local" is the store's own
|
|
37
|
+
# (Cache::Base#process_local?: `cache_backend = :memory`, or
|
|
38
|
+
# `Rails.cache` a `:memory_store`), asked of the store the reviewed
|
|
39
|
+
# configuration builds.
|
|
32
40
|
def process_local_cache
|
|
33
|
-
return unless production? && configuration.
|
|
41
|
+
return unless production? && configuration.cache_store.process_local?
|
|
34
42
|
|
|
35
43
|
finding(:high, :process_local_cache, 'Permission cache is local to each process',
|
|
36
|
-
'
|
|
37
|
-
'
|
|
44
|
+
'The permission cache store lives in each process in production (cache_backend :memory, or ' \
|
|
45
|
+
'Rails.cache a :memory_store): a revoked permission stays cached in the other Puma workers until ' \
|
|
46
|
+
'cache_ttl, and an invalidation from a console, runner or rake task never reaches the servers. ' \
|
|
47
|
+
'Use :rails_cache with a shared store (Redis, Memcached, Solid Cache).')
|
|
38
48
|
end
|
|
39
49
|
|
|
40
50
|
def unconfigured_fields_pass
|
|
@@ -64,6 +74,18 @@ module PermissionForge
|
|
|
64
74
|
'it back to false (the default).')
|
|
65
75
|
end
|
|
66
76
|
|
|
77
|
+
# Closed by default since 0.15: :allow is the host's explicit opt-out.
|
|
78
|
+
def unconfigured_actions_allowed
|
|
79
|
+
return unless configuration.allow_unconfigured_actions?
|
|
80
|
+
|
|
81
|
+
finding(:medium, :unconfigured_actions_allowed, 'Controller actions no Permission Set manages are allowed',
|
|
82
|
+
'unconfigured_action_strategy is :allow (the opt-out from the :block default): a guarded ' \
|
|
83
|
+
'controller action that maps to a Resource runs for every signed-in user until a Permission Set ' \
|
|
84
|
+
'manages it, so a new action is open before anyone decides who may run it. ' \
|
|
85
|
+
'unmanaged_actions_allowed lists the actions open now. Manage them, then remove the setting ' \
|
|
86
|
+
'(:block refuses them with 403).')
|
|
87
|
+
end
|
|
88
|
+
|
|
67
89
|
def nil_user_bypass
|
|
68
90
|
strategies = { nil_user_strategy: configuration.nil_user_strategy,
|
|
69
91
|
response_filter_nil_user_strategy: configuration.response_filter_nil_user_strategy }
|
|
@@ -75,18 +97,6 @@ module PermissionForge
|
|
|
75
97
|
'that touches protected models is authenticated first, or use :deny / :raise.')
|
|
76
98
|
end
|
|
77
99
|
|
|
78
|
-
# The one nil-user setting that is about the getters themselves.
|
|
79
|
-
def masked_nil_user_bypass
|
|
80
|
-
return if configuration.hide_masked_attributes_without_user?
|
|
81
|
-
|
|
82
|
-
finding(:medium, :masked_nil_user_bypass, 'Masked attributes are readable with no current user',
|
|
83
|
-
'masked_attributes_nil_user_strategy is :bypass: when nobody is signed in (a job, a mailer, the ' \
|
|
84
|
-
'console, a resolver that returns nil) the getters, attributes and as_json of a masked model return ' \
|
|
85
|
-
'the stored values, whatever nil_user_strategy says. Set it to :hide (the user model stays readable, as ' \
|
|
86
|
-
'sign-in needs), and set the current user in ' \
|
|
87
|
-
'the jobs and mailers that read masked data.')
|
|
88
|
-
end
|
|
89
|
-
|
|
90
100
|
def gdpr_defaults_off
|
|
91
101
|
return if configuration.gdpr_defaults
|
|
92
102
|
|
|
@@ -149,6 +159,19 @@ module PermissionForge
|
|
|
149
159
|
'to refuse changes until they are set.')
|
|
150
160
|
end
|
|
151
161
|
|
|
162
|
+
# Fails closed (each refusal is a 403 naming the option): a note, so
|
|
163
|
+
# that the missing lambda is found before a caller is refused.
|
|
164
|
+
def api_authorization_unset
|
|
165
|
+
lines = PermissionForge::ApiAuthorizations.lines(configuration)
|
|
166
|
+
return if lines.empty?
|
|
167
|
+
|
|
168
|
+
finding(:info, :api_authorization_unset, 'The JSON API refuses what no authorization lambda guards',
|
|
169
|
+
'The API is on and api_auth admits callers, but an authorization lambda it needs is not set: it ' \
|
|
170
|
+
'answers 403 (naming the option) to everything that lambda guards. Set each one listed, or turn ' \
|
|
171
|
+
'the API off if nothing calls it.',
|
|
172
|
+
items: lines)
|
|
173
|
+
end
|
|
174
|
+
|
|
152
175
|
# Tenancy on, and an admin base controller that sets no tenant.
|
|
153
176
|
def tenantless_admin
|
|
154
177
|
defaults = configuration.default_admin_controllers_under_tenancy
|
|
@@ -4,7 +4,8 @@ module PermissionForge
|
|
|
4
4
|
module Services
|
|
5
5
|
module SecurityAuditChecks
|
|
6
6
|
# Rows the models' validations would refuse but the database holds
|
|
7
|
-
# anyway (written by SQL, an older version, or another app)
|
|
7
|
+
# anyway (written by SQL, an older version, or another app), and field
|
|
8
|
+
# rows that name nothing on their model.
|
|
8
9
|
class IntegrityCheck < BaseCheck
|
|
9
10
|
RESOURCE_RULES = {
|
|
10
11
|
'edit without read' => ->(r) { r.edit_access && !r.read_access },
|
|
@@ -18,7 +19,8 @@ module PermissionForge
|
|
|
18
19
|
}.freeze
|
|
19
20
|
|
|
20
21
|
def call
|
|
21
|
-
[duplicate_fields, duplicate_actions, dependency_violations, unknown_models
|
|
22
|
+
[duplicate_fields, duplicate_actions, dependency_violations, unknown_models, unknown_fields,
|
|
23
|
+
unknown_mask_strategies].compact
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
private
|
|
@@ -95,9 +97,74 @@ module PermissionForge
|
|
|
95
97
|
'shares these tables it may still use them — check there before deleting.', items: names)
|
|
96
98
|
end
|
|
97
99
|
|
|
100
|
+
# Field rows in effect whose name is nothing their model has
|
|
101
|
+
# (PermissionForge::FieldNames): a typo such as `emial`. Saving does
|
|
102
|
+
# not refuse them — a key only a serializer computes is filtered by
|
|
103
|
+
# a row of its name — so they are reported here. Low when a muting
|
|
104
|
+
# set holds one (it mutes nothing it was meant to), otherwise info (a
|
|
105
|
+
# granting row grants nothing on the model).
|
|
106
|
+
def unknown_fields
|
|
107
|
+
fields = unknown_field_rows
|
|
108
|
+
return if fields.empty?
|
|
109
|
+
|
|
110
|
+
muting = fields.any? { |field| field.resource.permission_set.is_muting }
|
|
111
|
+
finding(muting ? :low : :info, :unknown_fields, 'Fields naming nothing on their model',
|
|
112
|
+
'No column, attribute, alias, public method, declared or discovered virtual field, or configured ' \
|
|
113
|
+
'variant of the model has these names, so the rows configure nothing on it: a muting row mutes ' \
|
|
114
|
+
'nothing (a typo such as emial leaves email as it was), a granting row grants nothing. Correct ' \
|
|
115
|
+
'or remove the row; for a key only a serializer computes (filtered by FiltersResponseAttributes) ' \
|
|
116
|
+
'declare it in the model\'s extra_fields_for_permissions (see "Extra Virtual Fields" in ' \
|
|
117
|
+
'docs/field-filtering.md).',
|
|
118
|
+
items: fields.map { |field| unknown_field_item(field) })
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def unknown_field_rows
|
|
122
|
+
fields = PermissionForge::ResourceField.in_effect.includes(resource: :permission_set).order(:resource_id, :id)
|
|
123
|
+
fields.group_by(&:resource).flat_map do |resource, rows|
|
|
124
|
+
names = PermissionForge::FieldNames.for(resource.name, siblings: rows.map(&:name), configuration: configuration)
|
|
125
|
+
names ? rows.reject { |field| names.include?(field.name) } : []
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def unknown_field_item(field)
|
|
130
|
+
resource = field.resource
|
|
131
|
+
set = resource.permission_set
|
|
132
|
+
"#{set.name}#{' (muting)' if set.is_muting} › #{resource.name}.#{field.name} · #{people(set.id)}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Field rows in effect that mask with a strategy this app does not
|
|
136
|
+
# know (Services::MaskValue.known?): written past the model, or a
|
|
137
|
+
# custom strategy since removed from config.custom_mask_strategies.
|
|
138
|
+
# Their values are masked fully until the row or the configuration is
|
|
139
|
+
# fixed — medium, high when people hold the set (they read ********
|
|
140
|
+
# where a lighter mask was meant, and nobody is told why).
|
|
141
|
+
def unknown_mask_strategies
|
|
142
|
+
fields = unknown_strategy_rows
|
|
143
|
+
return if fields.empty?
|
|
144
|
+
|
|
145
|
+
held = fields.any? { |field| people_counts.fetch(field.resource.permission_set_id, 0).positive? }
|
|
146
|
+
finding(held ? :high : :medium, :unknown_mask_strategies, 'Fields masked with a strategy this app does not know',
|
|
147
|
+
'No built-in mask strategy and no entry of config.custom_mask_strategies has these names — a row ' \
|
|
148
|
+
'written past the model, or a custom strategy since removed. Such a field is masked fully ' \
|
|
149
|
+
'(********) until the row or the configuration is fixed: pick a strategy in the field drawer, or ' \
|
|
150
|
+
'add the custom strategy back.',
|
|
151
|
+
items: fields.map { |field| unknown_strategy_item(field) })
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The reading Home's card takes too (ResourceField.with_unknown_mask_strategy).
|
|
155
|
+
def unknown_strategy_rows
|
|
156
|
+
PermissionForge::ResourceField.with_unknown_mask_strategy
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def unknown_strategy_item(field)
|
|
160
|
+
resource = field.resource
|
|
161
|
+
"#{resource.permission_set.name} › #{resource.name}.#{field.name}: #{field.mask_strategy.inspect} · " \
|
|
162
|
+
"#{people(resource.permission_set_id)}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The one definition of "a model with a table" (PermissionForge::ModelLookup).
|
|
98
166
|
def model?(name)
|
|
99
|
-
|
|
100
|
-
klass.is_a?(Class) && klass < ActiveRecord::Base
|
|
167
|
+
PermissionForge::ModelLookup.model?(name)
|
|
101
168
|
end
|
|
102
169
|
end
|
|
103
170
|
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
module Services
|
|
5
|
+
module SecurityAuditChecks
|
|
6
|
+
# ConfigCheck's findings about masking with nobody signed in
|
|
7
|
+
# (masked_attributes_nil_user_strategy) and about the request marker
|
|
8
|
+
# they depend on (WebRequest): included by ConfigCheck, which lists
|
|
9
|
+
# them in its RULES.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
module MaskingWithoutUser
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# The one nil-user setting that is about the getters themselves.
|
|
16
|
+
# :bypass is reported with :hide_in_requests as the remedy; under
|
|
17
|
+
# :hide_in_requests what still reads as stored (jobs, the console) is
|
|
18
|
+
# an info; :hide reports nothing here (UserModelCheck notes the user
|
|
19
|
+
# model's exemption).
|
|
20
|
+
def masked_nil_user_bypass
|
|
21
|
+
case configuration.masked_attributes_nil_user_strategy
|
|
22
|
+
when :bypass then masked_nil_user_bypassed
|
|
23
|
+
when :hide_in_requests then masked_nil_user_outside_requests
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def masked_nil_user_bypassed
|
|
28
|
+
finding(:medium, :masked_nil_user_bypass, 'Masked attributes are readable with no current user',
|
|
29
|
+
'masked_attributes_nil_user_strategy is :bypass: when nobody is signed in the getters, attributes ' \
|
|
30
|
+
'and as_json of a masked model return the stored values, whatever nil_user_strategy says — in a job, ' \
|
|
31
|
+
'a mailer or the console, and also in a web request where nobody is signed in (a public page, a ' \
|
|
32
|
+
'webhook, a controller that forgot authentication, a resolver that returns nil). Each such read in ' \
|
|
33
|
+
'a web request is logged (once per model and process) and published as field.unmasked_without_user ' \
|
|
34
|
+
'(stored when audit_runtime_events includes :bypass). Set it to :hide_in_requests: hidden in web ' \
|
|
35
|
+
'requests with nobody signed in, the stored values kept for jobs, rake and the console. Trade-off: ' \
|
|
36
|
+
'a page that reads a masked model before anyone signs in (a tenant lookup, a public page, a mail ' \
|
|
37
|
+
'sent with deliver_now) then reads hidden values: sign the request in first, or read raw columns ' \
|
|
38
|
+
'there. :hide hides everywhere (set the current user in jobs and mailers). The user model stays ' \
|
|
39
|
+
'readable under both, as sign-in needs.')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def masked_nil_user_outside_requests
|
|
43
|
+
finding(:info, :masked_nil_user_outside_requests, 'Masked attributes are readable with no current user outside web requests',
|
|
44
|
+
'masked_attributes_nil_user_strategy is :hide_in_requests: in a web request with nobody signed in ' \
|
|
45
|
+
'every guarded attribute reads as hidden, but jobs, rake tasks, the runner and the console (and ' \
|
|
46
|
+
'Action Cable channel actions) read the stored values. That code runs on your servers, not on a ' \
|
|
47
|
+
'visitor\'s behalf; :hide hides there too, and then needs the current user set in those jobs and mailers.')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Middlewares inserted above WebRequest::Middleware read outside the
|
|
51
|
+
# request: :hide_in_requests does not hide there, :bypass does not
|
|
52
|
+
# record (WebRequest::StackCheck, also a boot warning).
|
|
53
|
+
def middleware_above_web_request
|
|
54
|
+
return if configuration.masked_attributes_nil_user_strategy == :hide
|
|
55
|
+
|
|
56
|
+
above = PermissionForge::WebRequest::StackCheck.middlewares_above
|
|
57
|
+
return if above.blank?
|
|
58
|
+
|
|
59
|
+
finding(:info, :middleware_above_web_request, 'Middlewares run before requests are marked',
|
|
60
|
+
"#{PermissionForge::WebRequest::StackCheck.message(above, 'are not hidden or recorded')} " \
|
|
61
|
+
'Middlewares that read no masked model (CORS, profiling, static files) need nothing.', items: above)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -4,7 +4,8 @@ module PermissionForge
|
|
|
4
4
|
module Services
|
|
5
5
|
module SecurityAuditChecks
|
|
6
6
|
# Personal data shown unmasked, from GdprFieldAuditor. Fields an admin
|
|
7
|
-
# marked "This is intended" in the privacy audit are left out
|
|
7
|
+
# marked "This is intended" in the privacy audit are left out by the
|
|
8
|
+
# auditor itself (ResourceField.exposing_unacknowledged).
|
|
8
9
|
class PersonalDataCheck < BaseCheck
|
|
9
10
|
def call
|
|
10
11
|
report = GdprFieldAuditor.call(configuration: configuration)
|
|
@@ -14,7 +15,7 @@ module PermissionForge
|
|
|
14
15
|
private
|
|
15
16
|
|
|
16
17
|
def unmasked(report)
|
|
17
|
-
rows = report.select { |row| row[:type] == :unmasked_field
|
|
18
|
+
rows = report.select { |row| row[:type] == :unmasked_field }
|
|
18
19
|
return if rows.empty?
|
|
19
20
|
|
|
20
21
|
finding(:medium, :unmasked_personal_data, 'Personal data readable unmasked',
|
|
@@ -47,7 +48,10 @@ module PermissionForge
|
|
|
47
48
|
|
|
48
49
|
finding(:medium, :cross_set_unmasking, 'One set unmasks what another masks',
|
|
49
50
|
'Masks combine with AND: anyone holding both sets sees the full value.',
|
|
50
|
-
items: rows.map
|
|
51
|
+
items: rows.map do |row|
|
|
52
|
+
"#{row[:permission_set]} › #{row[:resource_name]}.#{row[:field_name]} " \
|
|
53
|
+
"(masked in #{Array(row[:masked_in]).join(', ').presence || 'another set'})"
|
|
54
|
+
end)
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def unmapped(report)
|
|
@@ -65,14 +69,6 @@ module PermissionForge
|
|
|
65
69
|
finding(passes ? :high : :low, :unmapped_personal_data, 'Personal-data columns with no active field row', detail,
|
|
66
70
|
items: rows.map { |row| "#{row[:resource]}.#{row[:column]}" })
|
|
67
71
|
end
|
|
68
|
-
|
|
69
|
-
def acknowledged_ids
|
|
70
|
-
@acknowledged_ids ||= if PermissionForge::Admin::AttentionItems.acknowledgement_supported?
|
|
71
|
-
PermissionForge::ResourceField.where.not(sensitive_acknowledged_at: nil).pluck(:id).to_set
|
|
72
|
-
else
|
|
73
|
-
Set.new
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
72
|
end
|
|
77
73
|
end
|
|
78
74
|
end
|