permission_forge 0.14.1
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 +7 -0
- data/CHANGELOG.md +1138 -0
- data/LICENSE.txt +21 -0
- data/README.md +356 -0
- data/app/controllers/concerns/permission_forge/acting_admin.rb +17 -0
- data/app/controllers/concerns/permission_forge/api/crud_actions.rb +84 -0
- data/app/controllers/concerns/permission_forge/api/nested_resource_lookup.rb +18 -0
- data/app/controllers/concerns/permission_forge/api/pagination.rb +50 -0
- data/app/controllers/concerns/permission_forge/api/response_renderer.rb +65 -0
- data/app/controllers/concerns/permission_forge/api/serializer_resolver.rb +30 -0
- data/app/controllers/concerns/permission_forge/api_authentication.rb +40 -0
- data/app/controllers/concerns/permission_forge/assignment_authorization.rb +97 -0
- data/app/controllers/concerns/permission_forge/audit_log_gate.rb +55 -0
- data/app/controllers/concerns/permission_forge/basic_authentication.rb +77 -0
- data/app/controllers/concerns/permission_forge/cache_confirmation.rb +32 -0
- data/app/controllers/concerns/permission_forge/forgery_protection.rb +29 -0
- data/app/controllers/concerns/permission_forge/html_format.rb +40 -0
- data/app/controllers/concerns/permission_forge/param_shapes.rb +87 -0
- data/app/controllers/concerns/permission_forge/tenant_required.rb +42 -0
- data/app/controllers/permission_forge/api/audit_events_controller.rb +33 -0
- data/app/controllers/permission_forge/api/base_controller.rb +220 -0
- data/app/controllers/permission_forge/api/dashboard_controller.rb +30 -0
- data/app/controllers/permission_forge/api/permission_set_group_users_controller.rb +51 -0
- data/app/controllers/permission_forge/api/permission_set_groups_controller.rb +71 -0
- data/app/controllers/permission_forge/api/permission_set_rules_controller.rb +37 -0
- data/app/controllers/permission_forge/api/permission_set_users_controller.rb +105 -0
- data/app/controllers/permission_forge/api/permission_sets_controller.rb +87 -0
- data/app/controllers/permission_forge/api/resource_actions_controller.rb +58 -0
- data/app/controllers/permission_forge/api/resource_fields_controller.rb +30 -0
- data/app/controllers/permission_forge/api/resources_controller.rb +56 -0
- data/app/controllers/permission_forge/application_controller.rb +198 -0
- data/app/controllers/permission_forge/assets_controller.rb +97 -0
- data/app/controllers/permission_forge/assignments_controller.rb +266 -0
- data/app/controllers/permission_forge/audit_events_controller.rb +94 -0
- data/app/controllers/permission_forge/bulk_assignments_controller.rb +108 -0
- data/app/controllers/permission_forge/field_lookups_controller.rb +38 -0
- data/app/controllers/permission_forge/home_controller.rb +24 -0
- data/app/controllers/permission_forge/maintenance_controller.rb +13 -0
- data/app/controllers/permission_forge/people_controller.rb +190 -0
- data/app/controllers/permission_forge/permission_set_editor.rb +71 -0
- data/app/controllers/permission_forge/permission_set_group_users_controller.rb +57 -0
- data/app/controllers/permission_forge/permission_set_groups_controller.rb +111 -0
- data/app/controllers/permission_forge/permission_set_rules_controller.rb +97 -0
- data/app/controllers/permission_forge/permission_sets_controller.rb +227 -0
- data/app/controllers/permission_forge/preferences_controller.rb +26 -0
- data/app/controllers/permission_forge/resource_actions_controller.rb +106 -0
- data/app/controllers/permission_forge/resource_fields_controller.rb +129 -0
- data/app/controllers/permission_forge/resources_controller.rb +189 -0
- data/app/controllers/permission_forge/settings_controller.rb +33 -0
- data/app/frontend/permission_forge/brand/apple-touch-icon.png +0 -0
- data/app/frontend/permission_forge/brand/chevron.svg +1 -0
- data/app/frontend/permission_forge/brand/favicon.ico +0 -0
- data/app/frontend/permission_forge/brand/favicon.svg +1 -0
- data/app/frontend/permission_forge/brand/logo-horizontal-dark.svg +1 -0
- data/app/frontend/permission_forge/brand/logo-horizontal-light.svg +1 -0
- data/app/frontend/permission_forge/brand/mark-small.svg +1 -0
- data/app/frontend/permission_forge/brand/mark.svg +1 -0
- data/app/frontend/permission_forge/pf_admin.css +766 -0
- data/app/frontend/permission_forge/pf_admin.js +482 -0
- data/app/helpers/permission_forge/application_helper.rb +167 -0
- data/app/helpers/permission_forge/audit_helper.rb +230 -0
- data/app/helpers/permission_forge/audit_sentences_helper.rb +84 -0
- data/app/helpers/permission_forge/badges_helper.rb +134 -0
- data/app/helpers/permission_forge/changes_helper.rb +117 -0
- data/app/helpers/permission_forge/people_helper.rb +118 -0
- data/app/helpers/permission_forge/sentences_helper.rb +175 -0
- data/app/helpers/permission_forge/settings_helper.rb +152 -0
- data/app/helpers/permission_forge/ui_helper.rb +184 -0
- data/app/models/permission_forge/audit_event.rb +159 -0
- data/app/models/permission_forge/permission_set.rb +100 -0
- data/app/models/permission_forge/permission_set_group.rb +83 -0
- data/app/models/permission_forge/permission_set_group_user.rb +43 -0
- data/app/models/permission_forge/permission_set_rule.rb +99 -0
- data/app/models/permission_forge/permission_set_user.rb +158 -0
- data/app/models/permission_forge/resource.rb +100 -0
- data/app/models/permission_forge/resource_action.rb +58 -0
- data/app/models/permission_forge/resource_field.rb +69 -0
- data/app/views/layouts/permission_forge/application.html.erb +30 -0
- data/app/views/layouts/permission_forge/fragment.html.erb +2 -0
- data/app/views/permission_forge/assignments/_person_options.html.erb +26 -0
- data/app/views/permission_forge/assignments/confirm_destroy.html.erb +24 -0
- data/app/views/permission_forge/assignments/new.html.erb +99 -0
- data/app/views/permission_forge/audit_events/index.html.erb +161 -0
- data/app/views/permission_forge/audit_events/show.html.erb +146 -0
- data/app/views/permission_forge/bulk_assignments/confirm_revoke.html.erb +21 -0
- data/app/views/permission_forge/bulk_assignments/new.html.erb +69 -0
- data/app/views/permission_forge/field_lookups/show.html.erb +32 -0
- data/app/views/permission_forge/home/_audit_card.html.erb +19 -0
- data/app/views/permission_forge/home/_ending_card.html.erb +32 -0
- data/app/views/permission_forge/home/_expired_card.html.erb +34 -0
- data/app/views/permission_forge/home/_privacy_card.html.erb +67 -0
- data/app/views/permission_forge/home/_rules_card.html.erb +23 -0
- data/app/views/permission_forge/home/_stale_card.html.erb +39 -0
- data/app/views/permission_forge/home/_tidy_card.html.erb +18 -0
- data/app/views/permission_forge/home/index.html.erb +69 -0
- data/app/views/permission_forge/people/_access_tab.html.erb +58 -0
- data/app/views/permission_forge/people/_audit_tab.html.erb +93 -0
- data/app/views/permission_forge/people/_effective_field_source.html.erb +11 -0
- data/app/views/permission_forge/people/_effective_tab.html.erb +118 -0
- data/app/views/permission_forge/people/_effective_why.html.erb +48 -0
- data/app/views/permission_forge/people/_why_item.html.erb +19 -0
- data/app/views/permission_forge/people/confirm_remove_all.html.erb +27 -0
- data/app/views/permission_forge/people/consent_preview.html.erb +39 -0
- data/app/views/permission_forge/people/erasure_preview.html.erb +28 -0
- data/app/views/permission_forge/people/index.html.erb +78 -0
- data/app/views/permission_forge/people/show.html.erb +54 -0
- data/app/views/permission_forge/permission_set_groups/confirm_destroy.html.erb +12 -0
- data/app/views/permission_forge/permission_set_groups/index.html.erb +55 -0
- data/app/views/permission_forge/permission_set_groups/show.html.erb +100 -0
- data/app/views/permission_forge/permission_set_rules/_builder.html.erb +145 -0
- data/app/views/permission_forge/permission_set_rules/_value_input.html.erb +42 -0
- data/app/views/permission_forge/permission_set_rules/confirm_destroy.html.erb +13 -0
- data/app/views/permission_forge/permission_set_rules/edit.html.erb +3 -0
- data/app/views/permission_forge/permission_set_rules/new.html.erb +3 -0
- data/app/views/permission_forge/permission_sets/_assignments_tab.html.erb +74 -0
- data/app/views/permission_forge/permission_sets/_audit_tab.html.erb +80 -0
- data/app/views/permission_forge/permission_sets/_conditions_tab.html.erb +83 -0
- data/app/views/permission_forge/permission_sets/_header.html.erb +36 -0
- data/app/views/permission_forge/permission_sets/_records_summary.html.erb +16 -0
- data/app/views/permission_forge/permission_sets/_resource_summary.html.erb +11 -0
- data/app/views/permission_forge/permission_sets/_resources_tab.html.erb +49 -0
- data/app/views/permission_forge/permission_sets/confirm_destroy.html.erb +23 -0
- data/app/views/permission_forge/permission_sets/confirm_switch_mode.html.erb +15 -0
- data/app/views/permission_forge/permission_sets/index.html.erb +83 -0
- data/app/views/permission_forge/permission_sets/new.html.erb +63 -0
- data/app/views/permission_forge/permission_sets/review.html.erb +26 -0
- data/app/views/permission_forge/permission_sets/show.html.erb +50 -0
- data/app/views/permission_forge/resource_actions/_across_sets.html.erb +24 -0
- data/app/views/permission_forge/resource_actions/_add_select.html.erb +12 -0
- data/app/views/permission_forge/resource_actions/_effect.html.erb +25 -0
- data/app/views/permission_forge/resource_actions/_endpoints.html.erb +16 -0
- data/app/views/permission_forge/resource_actions/_form.html.erb +44 -0
- data/app/views/permission_forge/resource_actions/_managed_note.html.erb +11 -0
- data/app/views/permission_forge/resource_actions/confirm_destroy.html.erb +13 -0
- data/app/views/permission_forge/resource_actions/new.html.erb +31 -0
- data/app/views/permission_forge/resource_actions/show.html.erb +39 -0
- data/app/views/permission_forge/resource_fields/_explain.html.erb +24 -0
- data/app/views/permission_forge/resource_fields/show.html.erb +186 -0
- data/app/views/permission_forge/resources/_actions_tab.html.erb +88 -0
- data/app/views/permission_forge/resources/_fields_tab.html.erb +89 -0
- data/app/views/permission_forge/resources/_rules_tab.html.erb +55 -0
- data/app/views/permission_forge/resources/_sync.html.erb +57 -0
- data/app/views/permission_forge/resources/confirm_destroy.html.erb +17 -0
- data/app/views/permission_forge/resources/new.html.erb +57 -0
- data/app/views/permission_forge/resources/show.html.erb +117 -0
- data/app/views/permission_forge/settings/_ladder.html.erb +15 -0
- data/app/views/permission_forge/settings/_row.html.erb +38 -0
- data/app/views/permission_forge/settings/show.html.erb +67 -0
- data/app/views/permission_forge/shared/_across_sets.html.erb +31 -0
- data/app/views/permission_forge/shared/_audit_events.html.erb +36 -0
- data/app/views/permission_forge/shared/_audit_footer.html.erb +16 -0
- data/app/views/permission_forge/shared/_audit_off_note.html.erb +15 -0
- data/app/views/permission_forge/shared/_audit_pagination.html.erb +19 -0
- data/app/views/permission_forge/shared/_audit_refused_note.html.erb +7 -0
- data/app/views/permission_forge/shared/_audit_timeline.html.erb +29 -0
- data/app/views/permission_forge/shared/_crud_cell.html.erb +18 -0
- data/app/views/permission_forge/shared/_dialog_frame.html.erb +11 -0
- data/app/views/permission_forge/shared/_expiry_picker.html.erb +27 -0
- data/app/views/permission_forge/shared/_head_extra.html.erb +3 -0
- data/app/views/permission_forge/shared/_help_banner.html.erb +10 -0
- data/app/views/permission_forge/shared/_level_control.html.erb +18 -0
- data/app/views/permission_forge/shared/_mode_banners.html.erb +22 -0
- data/app/views/permission_forge/shared/_nav.html.erb +12 -0
- data/app/views/permission_forge/shared/_preferences.html.erb +24 -0
- data/app/views/permission_forge/shared/_reason_field.html.erb +20 -0
- data/app/views/permission_forge/shared/_savebar.html.erb +13 -0
- data/app/views/permission_forge/shared/_sheet_frame.html.erb +22 -0
- data/app/views/permission_forge/shared/_sidebar.html.erb +18 -0
- data/app/views/permission_forge/shared/_tech_details.html.erb +20 -0
- data/app/views/permission_forge/shared/_toasts.html.erb +27 -0
- data/app/views/permission_forge/shared/_topbar.html.erb +15 -0
- data/app/views/permission_forge/shared/_user_select.html.erb +16 -0
- data/app/views/permission_forge/shared/audit_refused.html.erb +7 -0
- data/config/locales/permission_forge.en.yml +2115 -0
- data/config/locales/permission_forge.mk.yml +2166 -0
- data/config/locales/permission_forge.sq.yml +2164 -0
- data/config/routes.rb +143 -0
- data/lib/generators/permission_forge/install/install_generator.rb +105 -0
- data/lib/generators/permission_forge/install/templates/initializer.rb.tt +148 -0
- data/lib/generators/permission_forge/install/templates/migration.rb.tt +198 -0
- data/lib/generators/permission_forge/rls/rls_generator.rb +57 -0
- data/lib/generators/permission_forge/rls/templates/enable_permission_forge_rls.rb.tt +15 -0
- data/lib/generators/permission_forge/upgrade/templates/add_admin_ui_columns.rb.tt +8 -0
- data/lib/generators/permission_forge/upgrade/templates/add_audit_events.rb.tt +98 -0
- data/lib/generators/permission_forge/upgrade/templates/add_resource_fields_unique_index.rb.tt +69 -0
- data/lib/generators/permission_forge/upgrade/upgrade_generator.rb +66 -0
- data/lib/permission_forge/admin/action_across_sets.rb +76 -0
- data/lib/permission_forge/admin/action_rows.rb +156 -0
- data/lib/permission_forge/admin/attention_items.rb +207 -0
- data/lib/permission_forge/admin/audit_event_detail.rb +75 -0
- data/lib/permission_forge/admin/audit_log.rb +193 -0
- data/lib/permission_forge/admin/audit_log_days.rb +41 -0
- data/lib/permission_forge/admin/audit_lookups.rb +105 -0
- data/lib/permission_forge/admin/audit_name_history.rb +85 -0
- data/lib/permission_forge/admin/audit_trail.rb +169 -0
- data/lib/permission_forge/admin/bulk_people_matcher.rb +109 -0
- data/lib/permission_forge/admin/change_set.rb +123 -0
- data/lib/permission_forge/admin/change_set_parts/actions.rb +27 -0
- data/lib/permission_forge/admin/change_set_parts/base.rb +58 -0
- data/lib/permission_forge/admin/change_set_parts/context.rb +50 -0
- data/lib/permission_forge/admin/change_set_parts/field_edit.rb +67 -0
- data/lib/permission_forge/admin/change_set_parts/field_levels.rb +71 -0
- data/lib/permission_forge/admin/change_set_parts/new_actions.rb +91 -0
- data/lib/permission_forge/admin/change_set_parts/new_fields.rb +63 -0
- data/lib/permission_forge/admin/change_set_parts/resource_edit.rb +55 -0
- data/lib/permission_forge/admin/change_set_parts/resource_flags.rb +19 -0
- data/lib/permission_forge/admin/change_set_parts/set_attrs.rb +45 -0
- data/lib/permission_forge/admin/change_set_parts/values.rb +20 -0
- data/lib/permission_forge/admin/column_fields.rb +48 -0
- data/lib/permission_forge/admin/conditions_form.rb +78 -0
- data/lib/permission_forge/admin/controller_catalog.rb +166 -0
- data/lib/permission_forge/admin/csv.rb +41 -0
- data/lib/permission_forge/admin/editable_lock.rb +157 -0
- data/lib/permission_forge/admin/effective_access.rb +158 -0
- data/lib/permission_forge/admin/effective_fields.rb +176 -0
- data/lib/permission_forge/admin/expiry_presets.rb +51 -0
- data/lib/permission_forge/admin/exports/document.rb +89 -0
- data/lib/permission_forge/admin/exports/events.rb +51 -0
- data/lib/permission_forge/admin/exports/holdings.rb +118 -0
- data/lib/permission_forge/admin/exports.rb +37 -0
- data/lib/permission_forge/admin/field_across_sets.rb +51 -0
- data/lib/permission_forge/admin/field_detail.rb +112 -0
- data/lib/permission_forge/admin/field_level.rb +74 -0
- data/lib/permission_forge/admin/field_rows.rb +137 -0
- data/lib/permission_forge/admin/last_changes.rb +94 -0
- data/lib/permission_forge/admin/model_catalog.rb +216 -0
- data/lib/permission_forge/admin/people_candidates.rb +108 -0
- data/lib/permission_forge/admin/people_directory.rb +158 -0
- data/lib/permission_forge/admin/permission_set_factory.rb +48 -0
- data/lib/permission_forge/admin/person_label.rb +95 -0
- data/lib/permission_forge/admin/record_id.rb +19 -0
- data/lib/permission_forge/admin/rule_builder.rb +58 -0
- data/lib/permission_forge/admin/rule_form.rb +175 -0
- data/lib/permission_forge/admin/rule_form_input.rb +74 -0
- data/lib/permission_forge/admin/sensitive_columns.rb +68 -0
- data/lib/permission_forge/admin/settings_catalog.rb +261 -0
- data/lib/permission_forge/admin/settings_values.rb +130 -0
- data/lib/permission_forge/api/serializers/audit_event_serializer.rb +27 -0
- data/lib/permission_forge/api/serializers/base_serializer.rb +36 -0
- data/lib/permission_forge/api/serializers/permission_set_group_serializer.rb +12 -0
- data/lib/permission_forge/api/serializers/permission_set_group_user_serializer.rb +11 -0
- data/lib/permission_forge/api/serializers/permission_set_rule_serializer.rb +25 -0
- data/lib/permission_forge/api/serializers/permission_set_serializer.rb +13 -0
- data/lib/permission_forge/api/serializers/permission_set_user_serializer.rb +11 -0
- data/lib/permission_forge/api/serializers/resource_action_serializer.rb +13 -0
- data/lib/permission_forge/api/serializers/resource_field_serializer.rb +14 -0
- data/lib/permission_forge/api/serializers/resource_serializer.rb +15 -0
- data/lib/permission_forge/audit/actor.rb +144 -0
- data/lib/permission_forge/audit/actor_resolver.rb +103 -0
- data/lib/permission_forge/audit/audited_views.rb +208 -0
- data/lib/permission_forge/audit/boot_warnings.rb +58 -0
- data/lib/permission_forge/audit/column_values.rb +56 -0
- data/lib/permission_forge/audit/context.rb +98 -0
- data/lib/permission_forge/audit/detached.rb +62 -0
- data/lib/permission_forge/audit/event_catalog.rb +110 -0
- data/lib/permission_forge/audit/event_query.rb +192 -0
- data/lib/permission_forge/audit/failure.rb +47 -0
- data/lib/permission_forge/audit/field_access.rb +186 -0
- data/lib/permission_forge/audit/gate.rb +167 -0
- data/lib/permission_forge/audit/job_context.rb +32 -0
- data/lib/permission_forge/audit/log_access.rb +36 -0
- data/lib/permission_forge/audit/recorder.rb +168 -0
- data/lib/permission_forge/audit/refusals.rb +61 -0
- data/lib/permission_forge/audit/request_context.rb +152 -0
- data/lib/permission_forge/audit/runtime.rb +178 -0
- data/lib/permission_forge/audit/runtime_emission.rb +135 -0
- data/lib/permission_forge/audit/settings_observer.rb +70 -0
- data/lib/permission_forge/audit/source.rb +54 -0
- data/lib/permission_forge/audit/subscribers/base.rb +69 -0
- data/lib/permission_forge/audit/subscribers/legacy_hooks.rb +97 -0
- data/lib/permission_forge/audit/subscribers/log.rb +45 -0
- data/lib/permission_forge/audit/subscribers.rb +58 -0
- data/lib/permission_forge/audit.rb +302 -0
- data/lib/permission_forge/boot_checks.rb +184 -0
- data/lib/permission_forge/cache/backlog.rb +142 -0
- data/lib/permission_forge/cache/base.rb +200 -0
- data/lib/permission_forge/cache/bulk_invalidation.rb +131 -0
- data/lib/permission_forge/cache/failure.rb +171 -0
- data/lib/permission_forge/cache/handled_errors.rb +82 -0
- data/lib/permission_forge/cache/holder_invalidation.rb +85 -0
- data/lib/permission_forge/cache/invalidation.rb +180 -0
- data/lib/permission_forge/cache/memory_cache.rb +77 -0
- data/lib/permission_forge/cache/null_cache.rb +40 -0
- data/lib/permission_forge/cache/rails_cache.rb +231 -0
- data/lib/permission_forge/cache/rebuild.rb +149 -0
- data/lib/permission_forge/cache/settlement.rb +63 -0
- data/lib/permission_forge/cache/user_keys.rb +206 -0
- data/lib/permission_forge/cache/variant_removal.rb +55 -0
- data/lib/permission_forge/cache/withheld_notice.rb +60 -0
- data/lib/permission_forge/concerns/audited_collection.rb +34 -0
- data/lib/permission_forge/concerns/auto_permission_scoping.rb +158 -0
- data/lib/permission_forge/concerns/controller_action_guard.rb +106 -0
- data/lib/permission_forge/concerns/field_mask_checker.rb +45 -0
- data/lib/permission_forge/concerns/has_active_scope.rb +12 -0
- data/lib/permission_forge/concerns/has_audit_trail.rb +59 -0
- data/lib/permission_forge/concerns/has_permission_set_users.rb +115 -0
- data/lib/permission_forge/concerns/has_tenant_scoping.rb +86 -0
- data/lib/permission_forge/concerns/has_uuid.rb +24 -0
- data/lib/permission_forge/concerns/invalidates_in_batch.rb +207 -0
- data/lib/permission_forge/concerns/invalidates_managed_actions.rb +80 -0
- data/lib/permission_forge/concerns/masked_attributes/alias_access.rb +280 -0
- data/lib/permission_forge/concerns/masked_attributes/store_access.rb +100 -0
- data/lib/permission_forge/concerns/masked_attributes/validation_reads.rb +153 -0
- data/lib/permission_forge/concerns/masked_attributes/virtual_readers.rb +242 -0
- data/lib/permission_forge/concerns/masked_attributes.rb +576 -0
- data/lib/permission_forge/concerns/permission_cache_accessor.rb +416 -0
- data/lib/permission_forge/concerns/permission_scoped_findable.rb +337 -0
- data/lib/permission_forge/concerns/permissioned_user.rb +78 -0
- data/lib/permission_forge/concerns/permissions_memo.rb +160 -0
- data/lib/permission_forge/concerns/permissions_per_context.rb +288 -0
- data/lib/permission_forge/concerns/permissions_regenerator.rb +43 -0
- data/lib/permission_forge/concerns/records_audit_events.rb +286 -0
- data/lib/permission_forge/concerns/resource_permission_checker.rb +116 -0
- data/lib/permission_forge/concerns/validates_column_lengths.rb +69 -0
- data/lib/permission_forge/concerns/validates_dependency.rb +54 -0
- data/lib/permission_forge/conditional_sets.rb +361 -0
- data/lib/permission_forge/configuration/admin_ui_settings.rb +108 -0
- data/lib/permission_forge/configuration/audit_settings.rb +184 -0
- data/lib/permission_forge/configuration/cache_settings.rb +76 -0
- data/lib/permission_forge/configuration/controller_action_settings.rb +112 -0
- data/lib/permission_forge/configuration/gdpr_settings.rb +70 -0
- data/lib/permission_forge/configuration/masked_attributes_settings.rb +83 -0
- data/lib/permission_forge/configuration/permits_attributes_settings.rb +26 -0
- data/lib/permission_forge/configuration/presets.rb +139 -0
- data/lib/permission_forge/configuration/query_scoping_settings.rb +107 -0
- data/lib/permission_forge/configuration/strategy_values.rb +57 -0
- data/lib/permission_forge/configuration/virtual_attributes_settings.rb +65 -0
- data/lib/permission_forge/configuration.rb +624 -0
- data/lib/permission_forge/controller_actions/authorizer.rb +171 -0
- data/lib/permission_forge/controller_actions/managed_index.rb +213 -0
- data/lib/permission_forge/controller_actions/mapping.rb +167 -0
- data/lib/permission_forge/controller_actions.rb +122 -0
- data/lib/permission_forge/deprecation.rb +86 -0
- data/lib/permission_forge/engine.rb +113 -0
- data/lib/permission_forge/execution_state.rb +318 -0
- data/lib/permission_forge/invalidation_batch.rb +206 -0
- data/lib/permission_forge/masked_models.rb +226 -0
- data/lib/permission_forge/masking_sync.rb +271 -0
- data/lib/permission_forge/model_bindings.rb +90 -0
- data/lib/permission_forge/open_transaction.rb +303 -0
- data/lib/permission_forge/operators/constants.rb +41 -0
- data/lib/permission_forge/operators/registry.rb +300 -0
- data/lib/permission_forge/operators/rule_column.rb +120 -0
- data/lib/permission_forge/operators/validations.rb +111 -0
- data/lib/permission_forge/operators/value_casters/number.rb +68 -0
- data/lib/permission_forge/operators/value_casters/temporal.rb +69 -0
- data/lib/permission_forge/operators/value_casters.rb +80 -0
- data/lib/permission_forge/operators/value_typing.rb +115 -0
- data/lib/permission_forge/query_scoping_warnings.rb +68 -0
- data/lib/permission_forge/rake_audit.rb +65 -0
- data/lib/permission_forge/resource_chain.rb +190 -0
- data/lib/permission_forge/rls.rb +209 -0
- data/lib/permission_forge/services/attribute_access_guard.rb +138 -0
- data/lib/permission_forge/services/audit_event_purger.rb +150 -0
- data/lib/permission_forge/services/bulk_permission_set_assignment.rb +108 -0
- data/lib/permission_forge/services/concerns/across_tenants.rb +69 -0
- data/lib/permission_forge/services/concerns/audited_operation.rb +43 -0
- data/lib/permission_forge/services/concerns/clonable.rb +29 -0
- data/lib/permission_forge/services/consent_withdrawal_handler.rb +135 -0
- data/lib/permission_forge/services/dashboard_stats.rb +43 -0
- data/lib/permission_forge/services/data_subject_erasure.rb +306 -0
- data/lib/permission_forge/services/expired_assignment_cleaner.rb +152 -0
- data/lib/permission_forge/services/extra_field_defaults.rb +71 -0
- data/lib/permission_forge/services/field_permission_merger.rb +83 -0
- data/lib/permission_forge/services/filters_response_attributes.rb +144 -0
- data/lib/permission_forge/services/gdpr_field_auditor/text_report.rb +63 -0
- data/lib/permission_forge/services/gdpr_field_auditor.rb +223 -0
- data/lib/permission_forge/services/host_key_guard.rb +110 -0
- data/lib/permission_forge/services/mask_value.rb +93 -0
- data/lib/permission_forge/services/permission_cache_clearer.rb +174 -0
- data/lib/permission_forge/services/permission_hash_builder.rb +305 -0
- data/lib/permission_forge/services/permission_rule_applier.rb +192 -0
- data/lib/permission_forge/services/permission_set_cloner.rb +89 -0
- data/lib/permission_forge/services/permission_set_condition_evaluator.rb +102 -0
- data/lib/permission_forge/services/permission_set_group_cloner.rb +52 -0
- data/lib/permission_forge/services/permits_attributes/nested_records.rb +106 -0
- data/lib/permission_forge/services/permits_attributes.rb +270 -0
- data/lib/permission_forge/services/resource_field_generator.rb +93 -0
- data/lib/permission_forge/services/resource_with_fields_builder.rb +47 -0
- data/lib/permission_forge/services/rls_operator_sql.rb +131 -0
- data/lib/permission_forge/services/rls_policy_generator.rb +365 -0
- data/lib/permission_forge/services/security_audit/action_check.rb +157 -0
- data/lib/permission_forge/services/security_audit/assignment_check.rb +120 -0
- data/lib/permission_forge/services/security_audit/audit_trail_check.rb +166 -0
- data/lib/permission_forge/services/security_audit/base_check.rb +57 -0
- data/lib/permission_forge/services/security_audit/config_check.rb +212 -0
- data/lib/permission_forge/services/security_audit/integrity_check.rb +105 -0
- data/lib/permission_forge/services/security_audit/personal_data_check.rb +79 -0
- data/lib/permission_forge/services/security_audit/privilege_check.rb +67 -0
- data/lib/permission_forge/services/security_audit/rule_check.rb +141 -0
- data/lib/permission_forge/services/security_audit/text_report.rb +46 -0
- data/lib/permission_forge/services/security_audit/user_model_check.rb +67 -0
- data/lib/permission_forge/services/security_audit.rb +75 -0
- data/lib/permission_forge/services/stale_assignment_auditor.rb +68 -0
- data/lib/permission_forge/services/unscoped_query_detector.rb +132 -0
- data/lib/permission_forge/version.rb +3 -0
- data/lib/permission_forge/virtual_fields/discovery.rb +65 -0
- data/lib/permission_forge/virtual_fields.rb +102 -0
- data/lib/permission_forge.rb +464 -0
- data/lib/tasks/permission_forge.rake +31 -0
- data/lib/tasks/permission_forge_audit_events.rake +28 -0
- data/lib/tasks/permission_forge_cache.rake +26 -0
- data/lib/tasks/permission_forge_expiration.rake +30 -0
- data/lib/tasks/permission_forge_gdpr.rake +69 -0
- data/lib/tasks/permission_forge_rls.rake +38 -0
- metadata +515 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Raised for an invalid or missing setting, or an integration the host did not wire.
|
|
3
|
+
#
|
|
4
|
+
# @api public
|
|
5
|
+
class ConfigurationError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Every setting of the gem, set in a `PermissionForge.configure` block; the groups of options live in
|
|
8
|
+
# `Configuration::*Settings` modules. Defaults are the library defaults; `config.preset` closes or restores them in one line.
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
class Configuration # rubocop:disable Metrics/ClassLength
|
|
12
|
+
# User model class name (string, lazily constantized)
|
|
13
|
+
attr_accessor :user_class
|
|
14
|
+
|
|
15
|
+
# Lambda that returns the current user or nil
|
|
16
|
+
attr_accessor :current_user_resolver
|
|
17
|
+
|
|
18
|
+
# Lambda that checks if a user is active (receives user, returns boolean).
|
|
19
|
+
# Read by the security audit (assignments held by inactive users); it does
|
|
20
|
+
# not remove access by itself.
|
|
21
|
+
attr_accessor :user_active_check
|
|
22
|
+
|
|
23
|
+
# Lambda that returns an enumerable/AR relation of users eligible for
|
|
24
|
+
# admin UI assignment dropdowns.
|
|
25
|
+
#
|
|
26
|
+
# Default: `user_model.order(:id)` — chosen because `:name` is often a
|
|
27
|
+
# virtual attribute or a Ruby method (e.g., `"#{first_name} #{last_name}"`)
|
|
28
|
+
# rather than a real column, so a SQL ORDER BY would fail.
|
|
29
|
+
#
|
|
30
|
+
# The User class must respond to `:name` and `:email` (as method or column)
|
|
31
|
+
# for display labels. Override to scope or sort however you like:
|
|
32
|
+
# config.user_list_resolver = -> { User.active.order(:last_name, :first_name) }
|
|
33
|
+
attr_accessor :user_list_resolver
|
|
34
|
+
|
|
35
|
+
# Hard cap on how many users the admin UI assignment dropdowns render.
|
|
36
|
+
# Applied on top of user_list_resolver (after excluding already-assigned
|
|
37
|
+
# users), so an unscoped host users table can never turn a show page into
|
|
38
|
+
# a six-figure <option> list. When the cap kicks in, the dropdown shows a
|
|
39
|
+
# localized "first N users" hint prompting a narrower user_list_resolver.
|
|
40
|
+
# A resolver relation carrying its own smaller LIMIT is preserved.
|
|
41
|
+
# Default: 500. Set to nil to disable (not recommended for large tables).
|
|
42
|
+
attr_accessor :user_list_limit
|
|
43
|
+
|
|
44
|
+
# Lambda that formats a user for the assignment dropdown label.
|
|
45
|
+
# Receives the user, returns a String (the human-readable part).
|
|
46
|
+
# The combobox automatically appends ` #<id>` so the JS can extract the id.
|
|
47
|
+
#
|
|
48
|
+
# Default: tries name, full_name, display_name (in that order), then email,
|
|
49
|
+
# then a localized fallback. Override for full control:
|
|
50
|
+
# config.user_label_formatter = ->(u) { "#{u.last_name}, #{u.first_name} <#{u.email}>" }
|
|
51
|
+
attr_accessor :user_label_formatter
|
|
52
|
+
|
|
53
|
+
# Cache backend:
|
|
54
|
+
# :rails_cache — uses Rails.cache (works with any Rails cache store:
|
|
55
|
+
# redis_cache_store, solid_cache_store, mem_cache_store,
|
|
56
|
+
# file_store, memory_store, etc.)
|
|
57
|
+
# :memory — simple in-memory hash (good for dev/test, no dependencies)
|
|
58
|
+
# :null — no caching (permissions recomputed every time)
|
|
59
|
+
# ActiveSupport::Cache::Store instance — use a specific cache store directly
|
|
60
|
+
attr_accessor :cache_backend
|
|
61
|
+
|
|
62
|
+
# Key prefix for cache entries (avoids collisions with other cached data)
|
|
63
|
+
attr_accessor :cache_key_prefix
|
|
64
|
+
|
|
65
|
+
# Cache TTL in seconds for permission entries. Cached permissions expire
|
|
66
|
+
# after this duration as a staleness backstop — the cache is rebuilt
|
|
67
|
+
# transparently on the next read (read-through), so a TTL never leaves a
|
|
68
|
+
# user without permissions.
|
|
69
|
+
# Default: 3600 (1 hour). Set to nil for no expiration (not recommended).
|
|
70
|
+
attr_accessor :cache_ttl
|
|
71
|
+
|
|
72
|
+
# Cache TTL in seconds applied when any of the user's permission sets has
|
|
73
|
+
# activation conditions (session flag, IP allowlist, login hours). The
|
|
74
|
+
# conditions are evaluated on every read, against the request that reads
|
|
75
|
+
# (ConditionalSets); this clamps the base entry and its per-context
|
|
76
|
+
# variants as a backstop. The effective TTL is
|
|
77
|
+
# min(cache_ttl, conditional_set_cache_ttl).
|
|
78
|
+
# Default: 300 (5 minutes). Set to nil to disable the clamp.
|
|
79
|
+
attr_accessor :conditional_set_cache_ttl
|
|
80
|
+
|
|
81
|
+
# Table name prefix for all permission_forge tables
|
|
82
|
+
attr_accessor :table_name_prefix
|
|
83
|
+
|
|
84
|
+
# Lambda that generates UUIDs
|
|
85
|
+
attr_accessor :uuid_generator
|
|
86
|
+
|
|
87
|
+
# Enable created_by_id/updated_by_id tracking
|
|
88
|
+
attr_accessor :audit_trail_enabled
|
|
89
|
+
|
|
90
|
+
# Class name for audit trail belongs_to associations
|
|
91
|
+
attr_accessor :audit_user_class
|
|
92
|
+
|
|
93
|
+
# Fields to ignore when auto-generating resource fields
|
|
94
|
+
attr_accessor :ignored_fields
|
|
95
|
+
|
|
96
|
+
# Hash of regex => lambda for field name variants
|
|
97
|
+
# e.g. /_id\z/ => ->(name) { name.sub(/_id\z/, '_uuid') }
|
|
98
|
+
attr_accessor :field_variant_mappings
|
|
99
|
+
|
|
100
|
+
# Method name that models can define for extra permission fields
|
|
101
|
+
attr_accessor :extra_fields_method
|
|
102
|
+
|
|
103
|
+
# Lambda that builds the profile hash cached alongside permissions
|
|
104
|
+
# Receives user, returns hash
|
|
105
|
+
attr_accessor :profile_builder
|
|
106
|
+
|
|
107
|
+
# Lambda that returns the current request context for activation conditions.
|
|
108
|
+
# Returns a hash with optional keys: :ip, :session_flags, :time
|
|
109
|
+
# Default: -> { {} } (no conditions — backward compatible)
|
|
110
|
+
# Example: -> { { ip: Current.ip, session_flags: Current.session_flags, time: Time.current } }
|
|
111
|
+
# Called on every permission read of a user who holds a conditional set:
|
|
112
|
+
# keep it cheap, return {} (or anything with #to_h) where there is no
|
|
113
|
+
# request, and never read permissions inside it (that raises
|
|
114
|
+
# ConfigurationError).
|
|
115
|
+
attr_accessor :request_context_resolver
|
|
116
|
+
|
|
117
|
+
# How activation-condition sets (session flag / allowed_ips / login hours) are
|
|
118
|
+
# treated when request_context_resolver returns a blank context.
|
|
119
|
+
# false (default) — treat conditional sets as active (backward compatible; a
|
|
120
|
+
# boot warning fires so the fail-open is visible)
|
|
121
|
+
# true — fail closed: with no request context (nothing, or neither
|
|
122
|
+
# an IP nor session flags — a job, the console) conditional
|
|
123
|
+
# GRANTING sets are dropped and conditional MUTING sets
|
|
124
|
+
# still apply; with a context, a MUTING set whose
|
|
125
|
+
# condition cannot be judged (no IP for allowed_ips, no
|
|
126
|
+
# session_flags key for a flag) applies
|
|
127
|
+
# Recommended: set to true.
|
|
128
|
+
attr_accessor :conditional_sets_strict_mode
|
|
129
|
+
|
|
130
|
+
# Multi-tenancy via acts_as_tenant (optional, nil = disabled).
|
|
131
|
+
# Set to the tenant model class name (e.g., 'Account') to enable.
|
|
132
|
+
# Requires the acts_as_tenant gem and a tenant column on all PF tables.
|
|
133
|
+
# Boot raises ConfigurationError when this is set but the gem is not
|
|
134
|
+
# loaded, so a misconfigured install can never run without isolation.
|
|
135
|
+
# Rows whose tenant column is NULL are only visible while no tenant is
|
|
136
|
+
# set (console, jobs); acts_as_tenant hides them under every tenant.
|
|
137
|
+
attr_accessor :tenant_model
|
|
138
|
+
|
|
139
|
+
# Foreign key column name for tenant association.
|
|
140
|
+
# Default: inferred from tenant_model (e.g., 'Account' -> :account_id,
|
|
141
|
+
# 'Billing::Account' -> :account_id)
|
|
142
|
+
attr_accessor :tenant_column
|
|
143
|
+
|
|
144
|
+
# Admin UI — HTTP Basic Auth (enabled by default, like Mission Control)
|
|
145
|
+
# If enabled but credentials not set, returns 401 Unauthorized.
|
|
146
|
+
# Set credentials via config or Rails credentials.
|
|
147
|
+
attr_accessor :http_basic_auth_enabled
|
|
148
|
+
# The admin UI's HTTP Basic username and password (see http_basic_auth_enabled).
|
|
149
|
+
# @return [String, nil]
|
|
150
|
+
attr_accessor :http_basic_auth_user, :http_basic_auth_password
|
|
151
|
+
|
|
152
|
+
# Base controller class for admin UI (default: "::ActionController::Base")
|
|
153
|
+
# Override to inherit from your own admin controller (e.g. "::AdminController")
|
|
154
|
+
# which handles authentication via Devise, Warden, etc.
|
|
155
|
+
attr_accessor :base_controller_class
|
|
156
|
+
|
|
157
|
+
# JSON Admin API (disabled by default — opt-in)
|
|
158
|
+
# When enabled, mounts RESTful JSON endpoints under /api/ within the engine.
|
|
159
|
+
attr_accessor :api_enabled
|
|
160
|
+
|
|
161
|
+
# Lambda that receives the controller instance and returns true/false for auth.
|
|
162
|
+
# When nil and API enabled, all requests return 401 (safe default).
|
|
163
|
+
# Example: ->(controller) { controller.current_user&.has_role?(:admin) }
|
|
164
|
+
attr_accessor :api_auth
|
|
165
|
+
|
|
166
|
+
# Base controller class for API controllers (default: "::ActionController::Base").
|
|
167
|
+
# Override to inherit from your own base (e.g. "::Api::V1::Admin::BaseController").
|
|
168
|
+
attr_accessor :api_base_controller_class
|
|
169
|
+
|
|
170
|
+
# Lambda that paginates a scope: receives (scope, page, per_page) -> paginated scope.
|
|
171
|
+
# When nil, a built-in LIMIT/OFFSET paginator is used.
|
|
172
|
+
# Must respond to .total_count for meta. Example: ->(s, p, pp) { s.page(p).per(pp) }
|
|
173
|
+
attr_accessor :api_pagination_handler
|
|
174
|
+
|
|
175
|
+
# Maximum records per page for API collection endpoints.
|
|
176
|
+
# Hard cap applied regardless of client-requested per_page.
|
|
177
|
+
attr_accessor :api_max_per_page
|
|
178
|
+
|
|
179
|
+
# Default records per page when per_page param is not provided.
|
|
180
|
+
attr_accessor :api_default_per_page
|
|
181
|
+
|
|
182
|
+
# When true, the /api/stats endpoint requires admin access.
|
|
183
|
+
# The api_auth lambda must set @admin = true on the controller.
|
|
184
|
+
attr_accessor :api_stats_require_admin
|
|
185
|
+
|
|
186
|
+
# Record identifier field for API lookups (default: :uuid).
|
|
187
|
+
# Change to :id if your app uses integer IDs in URLs.
|
|
188
|
+
attr_accessor :api_record_identifier
|
|
189
|
+
|
|
190
|
+
# Hash of serializer overrides for API responses.
|
|
191
|
+
# Keys are resource symbols, values are serializer classes.
|
|
192
|
+
# Built-in serializers: respond to .new(record).as_json and .collection(records).
|
|
193
|
+
# AMS serializers: also work — the base controller detects and adapts automatically.
|
|
194
|
+
# Example: { permission_set: MyApp::PermissionSetSerializer }
|
|
195
|
+
attr_accessor :api_serializer_overrides
|
|
196
|
+
|
|
197
|
+
# When true, PermissionSetRuleSerializer replaces field_value with "[REDACTED]"
|
|
198
|
+
# in API responses. Rule field_values can contain sensitive data (user IDs,
|
|
199
|
+
# account numbers, business logic values). Enable for GDPR/HIPAA environments.
|
|
200
|
+
# Default: false (backward-compatible)
|
|
201
|
+
attr_accessor :api_redact_rule_field_values
|
|
202
|
+
|
|
203
|
+
# Strategy when PermissionForge.current_user is nil in PermissionScopedFindable:
|
|
204
|
+
# :bypass — silent bypass, returns unscoped results (backward-compatible default)
|
|
205
|
+
# :raise — raises NotAuthorizedError
|
|
206
|
+
# :deny — returns nil (record not found)
|
|
207
|
+
attr_accessor :nil_user_strategy
|
|
208
|
+
|
|
209
|
+
# Lambda for action-level authorization on API mutation endpoints (clone, etc.).
|
|
210
|
+
# Receives (controller, action_name, record) and should return true/false.
|
|
211
|
+
# When nil, no extra authorization is performed (backward-compatible).
|
|
212
|
+
# Example: ->(controller, action, record) { controller.current_user&.admin? }
|
|
213
|
+
attr_accessor :api_action_authorization
|
|
214
|
+
|
|
215
|
+
# Lambda for authorization checks on user-to-permission-set assignments
|
|
216
|
+
# and group memberships. Receives (controller, target, user_ids): the
|
|
217
|
+
# Permission Set (or Permission Set Group), and an Array of the people
|
|
218
|
+
# concerned cast to the user model's primary-key type (Integers for
|
|
219
|
+
# integer keys) on every endpoint. Returns true/false. When nil, the
|
|
220
|
+
# request is refused while api_require_authorization is on (the default).
|
|
221
|
+
attr_accessor :api_assignment_authorization
|
|
222
|
+
|
|
223
|
+
# When true, API mutation and assignment endpoints REQUIRE an authorization
|
|
224
|
+
# lambda to be configured: if api_action_authorization / api_assignment_authorization
|
|
225
|
+
# is nil, the request is denied (403) instead of silently allowed. This closes
|
|
226
|
+
# the "api_auth proves identity but not admin-ness" escalation, where any
|
|
227
|
+
# authenticated caller could otherwise self-grant a permission set.
|
|
228
|
+
# true (default) — nil authorizer = deny
|
|
229
|
+
# false — nil authorizer = allow: every caller api_auth lets in
|
|
230
|
+
# may change permissions (opt-out; boot warning fires)
|
|
231
|
+
attr_accessor :api_require_authorization
|
|
232
|
+
|
|
233
|
+
# Optional callback when view_all/modify_all bypasses row-level rules.
|
|
234
|
+
# Receives (user:, resource:, reason:). Errors are swallowed to prevent
|
|
235
|
+
# callback failures from breaking queries.
|
|
236
|
+
attr_reader :on_rule_bypass
|
|
237
|
+
|
|
238
|
+
# I18n — available locales for the admin UI.
|
|
239
|
+
# When set, only matching locale files are loaded (performance optimization).
|
|
240
|
+
# When nil (default), falls back to PermissionForge::BUNDLED_LOCALES — the
|
|
241
|
+
# locales the gem ships translations for ([:en, :sq, :mk]). Avoids the
|
|
242
|
+
# admin UI rendering host-app locales (e.g. Faker) the gem can't translate.
|
|
243
|
+
# Example: [:en, :sq, :mk]
|
|
244
|
+
attr_accessor :available_locales
|
|
245
|
+
|
|
246
|
+
# Optional callback for field-level audit logging.
|
|
247
|
+
# Called for each field processed by FiltersResponseAttributes.
|
|
248
|
+
# Receives: lambda(resource_name:, field_name:, action:, user:)
|
|
249
|
+
# action is one of: :hidden, :masked, :passed
|
|
250
|
+
attr_reader :on_field_filtered
|
|
251
|
+
|
|
252
|
+
# Both hooks are subscribers of the runtime audit events
|
|
253
|
+
# (Audit::Subscribers::LegacyHooks): setting one tells Audit::Gate.
|
|
254
|
+
def on_rule_bypass=(hook)
|
|
255
|
+
@on_rule_bypass = hook
|
|
256
|
+
PermissionForge::Audit::Gate.changed! if defined?(PermissionForge::Audit::Gate)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def on_field_filtered=(hook)
|
|
260
|
+
@on_field_filtered = hook
|
|
261
|
+
PermissionForge::Audit::Gate.changed! if defined?(PermissionForge::Audit::Gate)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Strategy for fields not mapped in the permission hash:
|
|
265
|
+
# :block — exclude unmapped fields from response (secure default)
|
|
266
|
+
# :pass_through — field passes through unmasked (use only if all fields are explicitly mapped)
|
|
267
|
+
# :warn — pass through but log a warning
|
|
268
|
+
# :mask_all — apply full masking to unconfigured fields
|
|
269
|
+
# Unknown values fall back to :block (fail closed).
|
|
270
|
+
attr_accessor :unconfigured_field_strategy
|
|
271
|
+
|
|
272
|
+
# How FiltersResponseAttributes behaves when there is no current user
|
|
273
|
+
# (system/console/background context):
|
|
274
|
+
# :bypass — return attributes unfiltered (default, backward-compatible)
|
|
275
|
+
# :block — return {} (strict fail-closed)
|
|
276
|
+
# When a user IS present but has no configured access to the resource,
|
|
277
|
+
# every field is routed through unconfigured_field_strategy regardless of
|
|
278
|
+
# this setting (fail closed).
|
|
279
|
+
attr_accessor :response_filter_nil_user_strategy
|
|
280
|
+
|
|
281
|
+
# Custom mask strategies: Hash of strategy_name => callable(value)
|
|
282
|
+
# Merged with built-in strategies in MaskValue.strategies
|
|
283
|
+
attr_accessor :custom_mask_strategies
|
|
284
|
+
|
|
285
|
+
# GDPR defaults: when true, ResourceFieldGenerator auto-masks fields
|
|
286
|
+
# matching known GDPR-sensitive patterns (email, phone, name, address, etc.)
|
|
287
|
+
attr_reader :gdpr_defaults
|
|
288
|
+
|
|
289
|
+
# When true, raises ConfigurationError at boot if gdpr_defaults was never explicitly set.
|
|
290
|
+
attr_accessor :gdpr_require_explicit_choice
|
|
291
|
+
|
|
292
|
+
# When true, automatically appends GDPR field patterns to
|
|
293
|
+
# Rails.application.config.filter_parameters during engine initialization.
|
|
294
|
+
# This ensures sensitive fields (email, phone, SSN, etc.) are redacted in
|
|
295
|
+
# Rails logs, error reporters, and parameter inspect output.
|
|
296
|
+
# Default: true (enabled by default for security)
|
|
297
|
+
attr_accessor :filter_parameters_integration
|
|
298
|
+
|
|
299
|
+
# Hash of regex => strategy string for GDPR field pattern matching
|
|
300
|
+
attr_reader :gdpr_field_patterns
|
|
301
|
+
|
|
302
|
+
DEFAULT_IGNORED_FIELDS = %w[
|
|
303
|
+
id created_at updated_at token_digest token_last_digits
|
|
304
|
+
created_by updated_by token password encrypted_password
|
|
305
|
+
deleted_at uuid deactivated
|
|
306
|
+
].freeze
|
|
307
|
+
|
|
308
|
+
DEFAULT_FIELD_VARIANT_MAPPINGS = {
|
|
309
|
+
/_id\z/ => ->(name) { "#{name.sub(/_id\z/, '')}_uuid" },
|
|
310
|
+
/_cents\z/ => ->(name) { name.sub(/_cents\z/, '') }
|
|
311
|
+
}.freeze
|
|
312
|
+
|
|
313
|
+
# Column names that are personal data, with the mask for each. One
|
|
314
|
+
# definition for masking detection (ResourceFieldGenerator,
|
|
315
|
+
# GdprFieldAuditor, Admin::SensitiveColumns, the security audit) and for
|
|
316
|
+
# Rails' filter_parameters (PermissionForge.gdpr_filter_parameters), which
|
|
317
|
+
# Rails also applies to ActiveRecord::Base.filter_attributes — so a match
|
|
318
|
+
# prints as [FILTERED] in #inspect. A word matches anywhere in the name,
|
|
319
|
+
# as Rails' own filter_parameter_logging does (`phone` in `cellphone`); a
|
|
320
|
+
# stem short enough to sit inside an unrelated word (tel, city, zip, dob,
|
|
321
|
+
# fax, mobile) matches a whole name segment (between `_`, the ends, a
|
|
322
|
+
# digit or a camelCase capital) or the start of one: `telephone`,
|
|
323
|
+
# `home_tel`, `tel2`, `telNumber`, `cityName`, `zipcode` yes — `hotel_id`,
|
|
324
|
+
# `capacity`, `telemetry_enabled`, `automobile_id`, `gzip_enabled` no.
|
|
325
|
+
# Every alternative stands alone (no group with an inner `|`):
|
|
326
|
+
# Admin::SensitiveColumns names the one that matched.
|
|
327
|
+
DEFAULT_GDPR_FIELD_PATTERNS = {
|
|
328
|
+
/\Aemail\z|_email\z/ => 'email',
|
|
329
|
+
/phone|telefon|\Amobile|_mobile|\Afax|_fax|\Atel\z|\Atel_|\Atel[A-Z0-9]|_tel\z|_tel_|_tel[A-Z0-9]/ => 'last4',
|
|
330
|
+
/\Assn\z|social_security|national_id/ => 'last4',
|
|
331
|
+
/iban|bank_account|credit_card|card_number/ => 'last4',
|
|
332
|
+
/\Aname\z|first_name|last_name|full_name|contact_name/ => 'first1_last4',
|
|
333
|
+
/address|street|\Acity\z|\Acity_|\Acity[A-Z0-9]|_city\z|_city_|_city[A-Z0-9]|\Azip|_zip|postal/ => 'full',
|
|
334
|
+
/date_of_birth|\Adob\z|\Adob_|\Adob[A-Z0-9]|_dob\z|_dob_|_dob[A-Z0-9]|birthday/ => 'full',
|
|
335
|
+
/ip_address|\Aip\z/ => 'full',
|
|
336
|
+
# Matched by accident before the short stems became whole segments
|
|
337
|
+
# (`ethnicity` through `city`, `telegram_*` through `tel`); named here.
|
|
338
|
+
/ethnic|telegram/ => 'full'
|
|
339
|
+
}.freeze
|
|
340
|
+
|
|
341
|
+
include GdprSettings
|
|
342
|
+
include MaskedAttributesSettings
|
|
343
|
+
include PermitsAttributesSettings
|
|
344
|
+
include QueryScopingSettings
|
|
345
|
+
include AdminUiSettings
|
|
346
|
+
include VirtualAttributesSettings
|
|
347
|
+
include ControllerActionSettings
|
|
348
|
+
include CacheSettings
|
|
349
|
+
include AuditSettings
|
|
350
|
+
prepend StrategyValues
|
|
351
|
+
# In front of StrategyValues: an option counts as set only once its value passed.
|
|
352
|
+
prepend Presets
|
|
353
|
+
|
|
354
|
+
def initialize
|
|
355
|
+
@gdpr_defaults_explicitly_set = false
|
|
356
|
+
set_preset_defaults
|
|
357
|
+
set_core_defaults
|
|
358
|
+
set_admin_defaults
|
|
359
|
+
set_masked_attributes_defaults
|
|
360
|
+
set_permits_attributes_defaults
|
|
361
|
+
set_query_scoping_defaults
|
|
362
|
+
set_admin_ui_defaults
|
|
363
|
+
set_virtual_attributes_defaults
|
|
364
|
+
set_controller_action_defaults
|
|
365
|
+
set_cache_failure_defaults
|
|
366
|
+
set_audit_defaults
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# @return [Class] the class of `config.user_class`
|
|
370
|
+
def user_model
|
|
371
|
+
@user_class.constantize
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Whether klass is the user model (user_class) or one of its subclasses.
|
|
375
|
+
# False when user_class names no class. The answer is kept per user_class
|
|
376
|
+
# value and per class (by identity): a class reloaded in development is a
|
|
377
|
+
# new object and is asked again; so is every class after user_class
|
|
378
|
+
# changes.
|
|
379
|
+
#
|
|
380
|
+
# @param klass [Class, nil]
|
|
381
|
+
# @return [Boolean]
|
|
382
|
+
def user_model_class?(klass)
|
|
383
|
+
return false unless klass.is_a?(Class)
|
|
384
|
+
|
|
385
|
+
answers = (@user_model_answers ||= {})[@user_class] ||= {}.compare_by_identity
|
|
386
|
+
answers.fetch(klass) do
|
|
387
|
+
model = @user_class.to_s.safe_constantize
|
|
388
|
+
answers[klass] = model.is_a?(Class) && klass <= model
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Field-name variants produced by the configured mappings (e.g. `user_id`
|
|
393
|
+
# -> `user_uuid`, `price_cents` -> `price`). Shared by the granting and
|
|
394
|
+
# muting phases of PermissionHashBuilder so they always cover the same
|
|
395
|
+
# names.
|
|
396
|
+
#
|
|
397
|
+
# @param field_name [String, Symbol]
|
|
398
|
+
# @return [Array<String>]
|
|
399
|
+
def field_variants_for(field_name)
|
|
400
|
+
name = field_name.to_s
|
|
401
|
+
field_variant_mappings.filter_map do |pattern, transform|
|
|
402
|
+
transform.call(name) if name.match?(pattern)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Returns an enumerable/AR relation of users for admin UI dropdowns.
|
|
407
|
+
# Uses `user_list_resolver` when configured, otherwise falls back to
|
|
408
|
+
# `user_model.order(:id)` — schema-agnostic because `:name` is frequently
|
|
409
|
+
# a method or virtual attribute, not a real column.
|
|
410
|
+
def user_list
|
|
411
|
+
if @user_list_resolver
|
|
412
|
+
@user_list_resolver.call
|
|
413
|
+
else
|
|
414
|
+
user_model.order(:id)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Convenience: returns user_list with the given ids excluded, capped at
|
|
419
|
+
# user_list_limit. Works for both AR relations (where.not/limit) and plain
|
|
420
|
+
# arrays (reject/first). Exclusion happens before the cap so assigned
|
|
421
|
+
# users never consume dropdown slots.
|
|
422
|
+
def assignable_users(exclude_ids: [])
|
|
423
|
+
scope = user_list
|
|
424
|
+
ids = Array(exclude_ids).compact
|
|
425
|
+
if ids.any?
|
|
426
|
+
scope = if scope.respond_to?(:where)
|
|
427
|
+
scope.where.not(id: ids)
|
|
428
|
+
else
|
|
429
|
+
scope.reject { |u| ids.include?(u.id) }
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
apply_user_list_limit(scope)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# @return [Class] the class of `config.audit_user_class` (who created or edited a row)
|
|
436
|
+
def audit_user_model
|
|
437
|
+
@audit_user_class.constantize
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# @return [Cache::Base] the permission cache, built from the cache settings on first use
|
|
441
|
+
def cache_store
|
|
442
|
+
@cache_store ||= build_cache_store
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Drops the built cache store (the next read builds a new one) and retires every `permissions_hash` memo.
|
|
446
|
+
# @return [void]
|
|
447
|
+
def reset_cache_store!
|
|
448
|
+
@cache_store = nil
|
|
449
|
+
# A new store invalidates every instance-level permissions_hash memo:
|
|
450
|
+
# memoized values were read from the store being discarded.
|
|
451
|
+
PermissionForge.bump_permissions_cache_version!
|
|
452
|
+
# ... and no invalidation is owed to it.
|
|
453
|
+
PermissionForge::Cache::Invalidation.reset!
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
private
|
|
457
|
+
|
|
458
|
+
# Caps the dropdown user list at user_list_limit.
|
|
459
|
+
def apply_user_list_limit(scope)
|
|
460
|
+
limit = user_list_limit
|
|
461
|
+
return scope unless limit&.positive?
|
|
462
|
+
return cap_relation_limit(scope, limit) if scope.respond_to?(:limit)
|
|
463
|
+
return scope.first(limit) if scope.respond_to?(:first)
|
|
464
|
+
|
|
465
|
+
scope
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# An AR relation already carrying a smaller LIMIT (from user_list_resolver)
|
|
469
|
+
# is left untouched — relation.limit(n) would silently WIDEN it.
|
|
470
|
+
def cap_relation_limit(scope, limit)
|
|
471
|
+
existing = scope.respond_to?(:limit_value) ? scope.limit_value : nil
|
|
472
|
+
return scope if existing && existing <= limit
|
|
473
|
+
|
|
474
|
+
scope.limit(limit)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def set_core_defaults
|
|
478
|
+
set_user_defaults
|
|
479
|
+
set_cache_defaults
|
|
480
|
+
set_field_defaults
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def set_user_defaults
|
|
484
|
+
@user_class = 'User'
|
|
485
|
+
@current_user_resolver = default_current_user_resolver
|
|
486
|
+
@user_active_check = default_user_active_check
|
|
487
|
+
@user_list_resolver = nil
|
|
488
|
+
@user_list_limit = 500
|
|
489
|
+
@user_label_formatter = nil
|
|
490
|
+
@audit_trail_enabled = true
|
|
491
|
+
@audit_user_class = 'User'
|
|
492
|
+
@profile_builder = default_profile_builder
|
|
493
|
+
@request_context_resolver = -> { {} }
|
|
494
|
+
@conditional_sets_strict_mode = false
|
|
495
|
+
@tenant_model = nil
|
|
496
|
+
@tenant_column = nil
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
public
|
|
500
|
+
|
|
501
|
+
def tenant_enabled?
|
|
502
|
+
@tenant_model.present?
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Association acts_as_tenant declares on every permission model (e.g.
|
|
506
|
+
# :account). Namespaces are dropped: `belongs_to :"billing/account"` is not
|
|
507
|
+
# a valid association, so 'Billing::Account' also yields :account.
|
|
508
|
+
def tenant_association_name
|
|
509
|
+
return nil unless tenant_enabled?
|
|
510
|
+
|
|
511
|
+
tenant_base_name.to_sym
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Foreign-key column acts_as_tenant filters on. Honours tenant_column,
|
|
515
|
+
# otherwise "<association name>_id".
|
|
516
|
+
def resolved_tenant_column
|
|
517
|
+
return nil unless tenant_enabled?
|
|
518
|
+
|
|
519
|
+
(@tenant_column || "#{tenant_base_name}_id").to_sym
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
private
|
|
523
|
+
|
|
524
|
+
def tenant_base_name
|
|
525
|
+
@tenant_model.to_s.demodulize.underscore
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def set_cache_defaults
|
|
529
|
+
@cache_backend = :memory
|
|
530
|
+
@cache_key_prefix = 'pf'
|
|
531
|
+
@cache_ttl = 3600
|
|
532
|
+
@conditional_set_cache_ttl = 300
|
|
533
|
+
@table_name_prefix = 'permission_forge_'
|
|
534
|
+
@uuid_generator = -> { SecureRandom.uuid }
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def set_field_defaults
|
|
538
|
+
@ignored_fields = DEFAULT_IGNORED_FIELDS
|
|
539
|
+
@field_variant_mappings = DEFAULT_FIELD_VARIANT_MAPPINGS.dup
|
|
540
|
+
@extra_fields_method = :extra_fields_for_permissions
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def set_admin_defaults
|
|
544
|
+
set_ui_defaults
|
|
545
|
+
set_gdpr_defaults
|
|
546
|
+
set_api_defaults
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def set_ui_defaults
|
|
550
|
+
@http_basic_auth_enabled = true
|
|
551
|
+
@http_basic_auth_user = nil
|
|
552
|
+
@http_basic_auth_password = nil
|
|
553
|
+
@base_controller_class = '::ActionController::Base'
|
|
554
|
+
@on_field_filtered = nil
|
|
555
|
+
@unconfigured_field_strategy = :block
|
|
556
|
+
@response_filter_nil_user_strategy = :bypass
|
|
557
|
+
@custom_mask_strategies = {}
|
|
558
|
+
@available_locales = nil
|
|
559
|
+
@nil_user_strategy = :bypass
|
|
560
|
+
@on_rule_bypass = nil
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def set_gdpr_defaults
|
|
564
|
+
@gdpr_defaults = true
|
|
565
|
+
@gdpr_require_explicit_choice = false
|
|
566
|
+
@gdpr_field_patterns = DEFAULT_GDPR_FIELD_PATTERNS.dup
|
|
567
|
+
@filter_parameters_integration = true
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def set_api_defaults
|
|
571
|
+
@api_enabled = false
|
|
572
|
+
@api_auth = nil
|
|
573
|
+
@api_base_controller_class = '::ActionController::Base'
|
|
574
|
+
@api_stats_require_admin = true
|
|
575
|
+
@api_pagination_handler = nil
|
|
576
|
+
@api_max_per_page = 100
|
|
577
|
+
@api_default_per_page = 15
|
|
578
|
+
@api_record_identifier = :uuid
|
|
579
|
+
@api_serializer_overrides = {}
|
|
580
|
+
@api_action_authorization = nil
|
|
581
|
+
@api_assignment_authorization = nil
|
|
582
|
+
@api_require_authorization = true
|
|
583
|
+
@api_redact_rule_field_values = false
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def default_current_user_resolver
|
|
587
|
+
-> { defined?(Current) ? Current.user : nil }
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def default_user_active_check
|
|
591
|
+
->(user) { user.respond_to?(:active_for_authentication?) ? user.active_for_authentication? : true }
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def default_profile_builder
|
|
595
|
+
lambda { |user|
|
|
596
|
+
{
|
|
597
|
+
id: user.id,
|
|
598
|
+
uuid: user.try(:uuid),
|
|
599
|
+
name: user.try(:name),
|
|
600
|
+
email: user.try(:email)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def build_cache_store
|
|
606
|
+
case @cache_backend
|
|
607
|
+
when :rails_cache
|
|
608
|
+
PermissionForge::Cache::RailsCache.new(prefix: @cache_key_prefix)
|
|
609
|
+
when :memory
|
|
610
|
+
PermissionForge::Cache::MemoryCache.new(prefix: @cache_key_prefix)
|
|
611
|
+
when :null
|
|
612
|
+
PermissionForge::Cache::NullCache.new
|
|
613
|
+
else
|
|
614
|
+
if @cache_backend.is_a?(ActiveSupport::Cache::Store)
|
|
615
|
+
PermissionForge::Cache::RailsCache.new(prefix: @cache_key_prefix, store: @cache_backend)
|
|
616
|
+
else
|
|
617
|
+
raise ArgumentError,
|
|
618
|
+
"Unknown cache backend: #{@cache_backend.inspect}. " \
|
|
619
|
+
'Use :rails_cache, :memory, :null, or pass an ActiveSupport::Cache::Store instance.'
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
end
|