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,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
class VirtualFields
|
|
5
|
+
# Finds virtual (non-column) fields a model exposes without declaring them
|
|
6
|
+
# through extra_fields_method: attribute-API attributes and store accessors.
|
|
7
|
+
#
|
|
8
|
+
# It only reports names. Whether they are listed is VirtualFields'
|
|
9
|
+
# decision (config.virtual_field_discovery), and a discovered name never
|
|
10
|
+
# gets implicit access — ExtraFieldDefaults reads declared names only.
|
|
11
|
+
#
|
|
12
|
+
# Nested-attribute names (`<association>_attributes`) are left out on
|
|
13
|
+
# purpose: PermitsAttributes filters those payloads by the nested model's
|
|
14
|
+
# own Resource, and a field row on the parent would change that (an
|
|
15
|
+
# Editable row passes the payload unfiltered, any other level does not
|
|
16
|
+
# block it), so a level control for them would not mean what it says.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# PermissionForge::VirtualFields::Discovery.call(Customer) # => ["nickname", "theme"]
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
class Discovery
|
|
23
|
+
# One private method per source; add a source by adding a name here.
|
|
24
|
+
SOURCES = %i[attribute_api_names store_accessor_names].freeze
|
|
25
|
+
|
|
26
|
+
def self.call(klass) = new(klass).call
|
|
27
|
+
|
|
28
|
+
def initialize(klass)
|
|
29
|
+
@klass = klass
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Array<String>] discovered names, never a column
|
|
33
|
+
def call
|
|
34
|
+
SOURCES.flat_map { |source| send(source) }.uniq - @klass.column_names
|
|
35
|
+
rescue StandardError
|
|
36
|
+
[]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# `attribute :nickname, :string` with no column behind it.
|
|
42
|
+
def attribute_api_names
|
|
43
|
+
@klass.attribute_names
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# `store_accessor :settings, :theme`, by reader name.
|
|
47
|
+
def store_accessor_names
|
|
48
|
+
return [] unless @klass.respond_to?(:stored_attributes)
|
|
49
|
+
|
|
50
|
+
@klass.stored_attributes.flat_map do |store, keys|
|
|
51
|
+
keys.flat_map { |key| accessor_names(store, key) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# stored_attributes records keys, not reader names: with prefix: true the
|
|
56
|
+
# reader is `<store>_<key>`, with suffix: true `<key>_<store>`. Every
|
|
57
|
+
# spelling that is a method counts (a bare key can also be a column,
|
|
58
|
+
# which #call removes). Custom prefixes are not recorded anywhere, so
|
|
59
|
+
# those accessors are not found.
|
|
60
|
+
def accessor_names(store, key)
|
|
61
|
+
[key.to_s, "#{store}_#{key}", "#{key}_#{store}"].select { |name| @klass.method_defined?(name) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
# The virtual (non-column) fields of one model: names the host declared
|
|
5
|
+
# through extra_fields_method, plus discovered ones while
|
|
6
|
+
# config.virtual_field_discovery is on.
|
|
7
|
+
#
|
|
8
|
+
# The one place extra_fields_method is read. The engine reads it through
|
|
9
|
+
# VirtualFields.extra_names, which raises what the host method raises: a
|
|
10
|
+
# broken method must not silently change permissions. Read models, audits
|
|
11
|
+
# and reader wrapping go through #extra, which logs the error and carries on
|
|
12
|
+
# with no declared names.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# PermissionForge::VirtualFields.extra_names(Customer) # => ["full_address"]
|
|
16
|
+
#
|
|
17
|
+
# fields = PermissionForge::VirtualFields.new(Customer)
|
|
18
|
+
# fields.names # => ["full_address", "nickname"]
|
|
19
|
+
# fields.implicit_grant?('full_address') # => true under extra_fields_default_access = :grant
|
|
20
|
+
# fields.implicit_grant?('nickname') # => false: discovered, never granted
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
class VirtualFields
|
|
24
|
+
# Names the model declares through extra_fields_method, as Strings and
|
|
25
|
+
# exactly as declared (they may include column names). Empty for a class
|
|
26
|
+
# without the method, and for nil.
|
|
27
|
+
#
|
|
28
|
+
# @raise whatever the host's method raises
|
|
29
|
+
# @return [Array<String>]
|
|
30
|
+
def self.extra_names(klass, configuration = PermissionForge.configuration)
|
|
31
|
+
method = configuration.extra_fields_method
|
|
32
|
+
method && klass.respond_to?(method) ? Array(klass.public_send(method)).map(&:to_s) : []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The error the host's method raised when #extra read it, nil otherwise.
|
|
36
|
+
attr_reader :extra_error
|
|
37
|
+
|
|
38
|
+
def initialize(klass, configuration = PermissionForge.configuration)
|
|
39
|
+
@klass = klass
|
|
40
|
+
@configuration = configuration
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# VirtualFields.extra_names, except that an error is logged and gives [].
|
|
44
|
+
def extra
|
|
45
|
+
@extra ||= self.class.extra_names(@klass, @configuration)
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
@extra = extra_unreadable(e)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Declared names that are neither a column nor an ignored field.
|
|
51
|
+
def declared
|
|
52
|
+
@declared ||= extra - excluded
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Listed only while discovery is on; never implicitly granted.
|
|
56
|
+
def discovered
|
|
57
|
+
@discovered ||= @configuration.virtual_field_discovery ? discoverable - excluded - extra : []
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Array<String>] the declared and discovered virtual field names
|
|
61
|
+
def names
|
|
62
|
+
@names ||= (declared + discovered).uniq
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Names that get the implicit read + edit entry from ExtraFieldDefaults
|
|
66
|
+
# when no active ResourceField row configures them: every declared name
|
|
67
|
+
# under extra_fields_default_access = :grant, none otherwise.
|
|
68
|
+
def implicit
|
|
69
|
+
@configuration.grant_extra_fields_by_default? ? extra : []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def implicit_grant?(name)
|
|
73
|
+
implicit.include?(name.to_s)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Whether the model has `name` as a virtual field, listed or not (a
|
|
77
|
+
# field configured while discovery was on stays a virtual field).
|
|
78
|
+
def include?(name)
|
|
79
|
+
name = name.to_s
|
|
80
|
+
@klass.column_names.exclude?(name) && (extra.include?(name) || discoverable.include?(name))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def discoverable
|
|
86
|
+
@discoverable ||= Discovery.call(@klass)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def excluded
|
|
90
|
+
@klass.column_names + Array(@configuration.ignored_fields).map(&:to_s)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Keeps the error for callers that must say what it costs them (reader
|
|
94
|
+
# wrapping), logs it, and gives the empty list #extra then returns.
|
|
95
|
+
def extra_unreadable(error)
|
|
96
|
+
@extra_error = error
|
|
97
|
+
Rails.logger&.warn("[PermissionForge] #{@klass}.#{@configuration.extra_fields_method} raised " \
|
|
98
|
+
"#{error.class}: #{error.message}. Carrying on as if the model declared no virtual fields.")
|
|
99
|
+
[]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
require 'active_support'
|
|
2
|
+
require 'active_record'
|
|
3
|
+
|
|
4
|
+
require_relative 'permission_forge/version'
|
|
5
|
+
require_relative 'permission_forge/deprecation'
|
|
6
|
+
require_relative 'permission_forge/configuration/gdpr_settings'
|
|
7
|
+
require_relative 'permission_forge/configuration/masked_attributes_settings'
|
|
8
|
+
require_relative 'permission_forge/configuration/permits_attributes_settings'
|
|
9
|
+
require_relative 'permission_forge/configuration/query_scoping_settings'
|
|
10
|
+
require_relative 'permission_forge/configuration/admin_ui_settings'
|
|
11
|
+
require_relative 'permission_forge/configuration/virtual_attributes_settings'
|
|
12
|
+
require_relative 'permission_forge/configuration/controller_action_settings'
|
|
13
|
+
require_relative 'permission_forge/configuration/cache_settings'
|
|
14
|
+
require_relative 'permission_forge/configuration/audit_settings'
|
|
15
|
+
require_relative 'permission_forge/configuration/strategy_values'
|
|
16
|
+
require_relative 'permission_forge/configuration/presets'
|
|
17
|
+
require_relative 'permission_forge/configuration'
|
|
18
|
+
|
|
19
|
+
# Entry point of the gem: configuration, the current user, the cache and the table prefix.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# PermissionForge.configure do |config|
|
|
23
|
+
# config.current_user_resolver = -> { Current.user }
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
27
|
+
module PermissionForge
|
|
28
|
+
# Raised by Level 2 detection (config.unscoped_query_detection = :raise) when
|
|
29
|
+
# a protected model is queried outside a permission-scoped path.
|
|
30
|
+
#
|
|
31
|
+
# @api public
|
|
32
|
+
class UnscopedQueryError < StandardError; end
|
|
33
|
+
|
|
34
|
+
# Raised by Concerns::ControllerActionGuard when the current user may not run
|
|
35
|
+
# the requested controller action. The guard never renders — rescue this in
|
|
36
|
+
# the host app (rescue_from) and answer 403.
|
|
37
|
+
#
|
|
38
|
+
# `reason` is the Authorizer's reason symbol, so a handler can tell "nobody
|
|
39
|
+
# is signed in" (:nil_user_denied — send to login) from a real denial.
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
class ActionNotAuthorizedError < StandardError
|
|
43
|
+
# user: who was refused (nil for no user); record: the record the action
|
|
44
|
+
# was about, when the caller knows it (the guard runs before the action
|
|
45
|
+
# loads one, so it passes none).
|
|
46
|
+
attr_reader :resource, :action, :controller_path, :reason, :user, :record
|
|
47
|
+
|
|
48
|
+
def initialize(resource:, action:, controller_path:, reason: nil, user: nil, record: nil) # rubocop:disable Metrics/ParameterLists
|
|
49
|
+
@resource = resource
|
|
50
|
+
@action = action
|
|
51
|
+
@controller_path = controller_path
|
|
52
|
+
@reason = reason
|
|
53
|
+
@user = user
|
|
54
|
+
@record = record
|
|
55
|
+
super("Not authorized to run '#{action}' on #{resource} (#{controller_path})")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Locales the gem ships translation files for. Used as the fallback when
|
|
60
|
+
# config.available_locales is not set, so the admin UI never reflects
|
|
61
|
+
# locales registered by the host app (e.g. Faker) that PermissionForge
|
|
62
|
+
# has no translations for.
|
|
63
|
+
BUNDLED_LOCALES = %i[en sq mk].freeze
|
|
64
|
+
|
|
65
|
+
# A Resource name (Resource#name, a controller's declared resource): a Ruby
|
|
66
|
+
# constant path. The one definition — Resource::NAME_FORMAT and
|
|
67
|
+
# ControllerActions::RESOURCE_FORMAT point here.
|
|
68
|
+
RESOURCE_NAME_FORMAT = /\A[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*\z/
|
|
69
|
+
|
|
70
|
+
# The ActiveRecord models, in app/models/permission_forge. They are
|
|
71
|
+
# autoloaded (and in development reloaded) by the host application, not
|
|
72
|
+
# required by the gem, so a class body reads the configuration — the user
|
|
73
|
+
# class of an assignment, the audit user class, the tenant model — after the
|
|
74
|
+
# host's config/initializers have run, and there is nothing to rebind.
|
|
75
|
+
MODEL_NAMES = %i[
|
|
76
|
+
PermissionSet PermissionSetUser PermissionSetGroup PermissionSetGroupUser
|
|
77
|
+
Resource ResourceField ResourceAction PermissionSetRule AuditEvent
|
|
78
|
+
].freeze
|
|
79
|
+
|
|
80
|
+
# ExecutionState slot (fiber-local): when truthy, Level 1 auto-scoping and
|
|
81
|
+
# Level 2 detection are suspended for the running fiber. Set via
|
|
82
|
+
# without_permission_scope.
|
|
83
|
+
SKIP_SCOPE_KEY = :pf_skip_auto_scope
|
|
84
|
+
|
|
85
|
+
# Guards @permissions_cache_version increments; reads are lock-free.
|
|
86
|
+
PERMISSIONS_CACHE_VERSION_MUTEX = Mutex.new
|
|
87
|
+
|
|
88
|
+
class << self
|
|
89
|
+
# The models can only be resolved once the Rails application has set up
|
|
90
|
+
# its autoloader, which happens after every file in config/initializers
|
|
91
|
+
# has run (Rails 7.1+). Before that — the top level of an initializer, or
|
|
92
|
+
# no Rails application at all — say what to do instead of "uninitialized
|
|
93
|
+
# constant". Zeitwerk registers the autoloads itself, so this is never
|
|
94
|
+
# reached for a model name after boot.
|
|
95
|
+
def const_missing(name)
|
|
96
|
+
return super unless MODEL_NAMES.include?(name)
|
|
97
|
+
|
|
98
|
+
raise NameError.new(
|
|
99
|
+
"PermissionForge::#{name} is autoloaded by the Rails application once it has booted, so it cannot be " \
|
|
100
|
+
'referenced before that: not from the top level of a file in config/initializers, an engine or railtie ' \
|
|
101
|
+
'initializer or a file required from config/application.rb, nor outside a Rails application. Reference ' \
|
|
102
|
+
'it from a method (a model, a controller, a job, a rake task). To change the class — include a module, ' \
|
|
103
|
+
'add a callback — use Rails.application.config.to_prepare or ' \
|
|
104
|
+
"Rails.autoloaders.main.on_load('PermissionForge::#{name}'), which run again after every code reload; " \
|
|
105
|
+
'config.after_initialize runs once, so use it only to read.',
|
|
106
|
+
name
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [Configuration] the settings, created on first use
|
|
111
|
+
def configuration
|
|
112
|
+
@configuration ||= Configuration.new
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Configure in config/initializers: the models read the configuration as
|
|
116
|
+
# they load, right after. A setting a loaded model took is compared
|
|
117
|
+
# afterwards and a difference logged (ModelBindings).
|
|
118
|
+
def configure
|
|
119
|
+
yield(configuration)
|
|
120
|
+
configuration.reset_cache_store!
|
|
121
|
+
ModelBindings.warn!(configuration)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Replaces the configuration with a fresh default one (tests and console).
|
|
125
|
+
# @return [void]
|
|
126
|
+
def reset!
|
|
127
|
+
@configuration = Configuration.new
|
|
128
|
+
bump_permissions_cache_version!
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Process-local generation counter for cached permission hashes. Instances
|
|
132
|
+
# memoize their permissions_hash keyed by this value; bumping it (on any
|
|
133
|
+
# invalidation, regeneration, or cache-store swap) makes every memo in the
|
|
134
|
+
# process re-read from the cache store on next access. Cross-process
|
|
135
|
+
# coherence is unchanged — it still comes from the shared cache store.
|
|
136
|
+
def permissions_cache_version
|
|
137
|
+
@permissions_cache_version || 0
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Retires every per-instance `permissions_hash` memo (they compare against this version).
|
|
141
|
+
# Called by `invalidate_permissions`, `regenerate_permissions` and a cache-store swap.
|
|
142
|
+
# @return [Integer] the new version
|
|
143
|
+
# @api private
|
|
144
|
+
def bump_permissions_cache_version!
|
|
145
|
+
PERMISSIONS_CACHE_VERSION_MUTEX.synchronize do
|
|
146
|
+
@permissions_cache_version = permissions_cache_version + 1
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @return [Object, nil] whatever `config.current_user_resolver` answers
|
|
151
|
+
def current_user
|
|
152
|
+
configuration.current_user_resolver.call
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# @return [Cache::Base] the permission cache store (`config.cache_store`)
|
|
156
|
+
def cache
|
|
157
|
+
configuration.cache_store
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Id of the tenant acts_as_tenant has made ambient, when tenancy is on:
|
|
161
|
+
# the tenant a permission hash is built and cached for.
|
|
162
|
+
def ambient_tenant_id
|
|
163
|
+
return unless configuration.tenant_enabled? && defined?(ActsAsTenant)
|
|
164
|
+
|
|
165
|
+
ActsAsTenant.current_tenant&.id
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The tenant acts_as_tenant has made ambient (nil: none, or tenancy is off).
|
|
169
|
+
# Together with with_ambient_tenant it carries a request's tenant into work
|
|
170
|
+
# that runs after the request's own wrappers have ended (a streamed body).
|
|
171
|
+
def ambient_tenant
|
|
172
|
+
return unless configuration.tenant_enabled? && defined?(ActsAsTenant)
|
|
173
|
+
|
|
174
|
+
ActsAsTenant.current_tenant
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Runs the block with `tenant` ambient. Does nothing when there is none to
|
|
178
|
+
# pin, or it is the ambient one already. Restores the request-level tenant
|
|
179
|
+
# as it was (not acts_as_tenant's with_tenant, which writes the tenant it
|
|
180
|
+
# resolved — test or default included — back into ActsAsTenant::Current).
|
|
181
|
+
def with_ambient_tenant(tenant)
|
|
182
|
+
return yield unless tenant && configuration.tenant_enabled? && defined?(ActsAsTenant)
|
|
183
|
+
return yield if ActsAsTenant.current_tenant == tenant
|
|
184
|
+
|
|
185
|
+
previous = ActsAsTenant::Current.current_tenant
|
|
186
|
+
ActsAsTenant.current_tenant = tenant
|
|
187
|
+
begin
|
|
188
|
+
yield
|
|
189
|
+
ensure
|
|
190
|
+
ActsAsTenant::Current.current_tenant = previous
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# @return [String] the table prefix, `permission_forge_` unless configured
|
|
195
|
+
def table_name_prefix
|
|
196
|
+
configuration.table_name_prefix
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Escape hatch for intentional unscoped access (admin panels, reports,
|
|
200
|
+
# background maintenance). Inside the block, Level 1 auto-scoping and
|
|
201
|
+
# Level 2 detection are suspended on the running fiber (not in another
|
|
202
|
+
# fiber, an Enumerator's included: ExecutionState), and with nobody
|
|
203
|
+
# signed in find_by, find_by! and permission_scoped apply no rules,
|
|
204
|
+
# whatever nil_user_strategy says (rule.bypassed, scope_suspended); a
|
|
205
|
+
# signed-in user's find_by and permission_scoped still apply theirs. So
|
|
206
|
+
# inside the block nobody signed in is trusted more than a signed-in
|
|
207
|
+
# user: wrap trusted code only (jobs, seeds, rake tasks, sign-in lookups),
|
|
208
|
+
# never code an anonymous request reaches.
|
|
209
|
+
# Nesting-safe: restores the previous flag value on exit.
|
|
210
|
+
#
|
|
211
|
+
# PermissionForge.without_permission_scope { Customer.where(...) }
|
|
212
|
+
def without_permission_scope(&)
|
|
213
|
+
ExecutionState.with(SKIP_SCOPE_KEY, true, &)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# @return [Boolean] whether permission scoping is currently suspended on
|
|
217
|
+
# this thread (internal builds and without_permission_scope blocks).
|
|
218
|
+
def scope_suspended?
|
|
219
|
+
!!ExecutionState[SKIP_SCOPE_KEY]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Names who acts for every audit event recorded inside the block — a job,
|
|
223
|
+
# a rake task, a service run on someone's behalf — and where it comes
|
|
224
|
+
# from. Wins over the request context and PermissionForge.current_user.
|
|
225
|
+
#
|
|
226
|
+
# PermissionForge.with_audit_actor(user: admin, source: 'job') { cleaner.call }
|
|
227
|
+
# PermissionForge.with_audit_actor(label: ENV['ACTOR'], source: 'rake', reason: 'TCK-12') { ... }
|
|
228
|
+
#
|
|
229
|
+
# @param user [Object, nil] the acting user (actor_type 'user' unless type: says otherwise)
|
|
230
|
+
# @param label [String, nil] the name recorded; required without a user
|
|
231
|
+
# @param type [String, nil] user, api, basic_auth, system (default: 'user'
|
|
232
|
+
# with a user, 'system' without)
|
|
233
|
+
# @param source [String, nil] admin_ui, api, rake, job, console, service, runtime
|
|
234
|
+
# @param reason [String, nil] stored on every event of the block
|
|
235
|
+
# @return the value of the block
|
|
236
|
+
def with_audit_actor(user: nil, label: nil, type: nil, source: nil, reason: nil, &)
|
|
237
|
+
actor = if user
|
|
238
|
+
Audit::Actor.for_user(user, label: label, type: type || 'user')
|
|
239
|
+
elsif label || type
|
|
240
|
+
Audit::Actor.new(type: type || 'system', label: label)
|
|
241
|
+
end
|
|
242
|
+
Audit::Context.with(actor: actor, source: source, reason: reason, &)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Returns an array of regexes derived from the configured GDPR field patterns,
|
|
246
|
+
# suitable for appending to Rails.application.config.filter_parameters.
|
|
247
|
+
#
|
|
248
|
+
# Usage (manual):
|
|
249
|
+
# Rails.application.config.filter_parameters += PermissionForge.gdpr_filter_parameters
|
|
250
|
+
#
|
|
251
|
+
# Or set config.filter_parameters_integration = true for automatic injection.
|
|
252
|
+
def gdpr_filter_parameters
|
|
253
|
+
return [] unless configuration.gdpr_defaults
|
|
254
|
+
|
|
255
|
+
configuration.gdpr_field_patterns.keys
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
require_relative 'permission_forge/execution_state'
|
|
261
|
+
require_relative 'permission_forge/model_bindings'
|
|
262
|
+
require_relative 'permission_forge/cache/base'
|
|
263
|
+
require_relative 'permission_forge/cache/memory_cache'
|
|
264
|
+
require_relative 'permission_forge/cache/null_cache'
|
|
265
|
+
require_relative 'permission_forge/cache/handled_errors'
|
|
266
|
+
require_relative 'permission_forge/cache/rails_cache'
|
|
267
|
+
require_relative 'permission_forge/cache/failure'
|
|
268
|
+
require_relative 'permission_forge/cache/backlog'
|
|
269
|
+
require_relative 'permission_forge/cache/invalidation'
|
|
270
|
+
require_relative 'permission_forge/cache/bulk_invalidation'
|
|
271
|
+
require_relative 'permission_forge/cache/user_keys'
|
|
272
|
+
require_relative 'permission_forge/cache/holder_invalidation'
|
|
273
|
+
require_relative 'permission_forge/cache/variant_removal'
|
|
274
|
+
require_relative 'permission_forge/cache/withheld_notice'
|
|
275
|
+
require_relative 'permission_forge/cache/rebuild'
|
|
276
|
+
require_relative 'permission_forge/open_transaction'
|
|
277
|
+
require_relative 'permission_forge/cache/settlement'
|
|
278
|
+
|
|
279
|
+
# Audit event log
|
|
280
|
+
require_relative 'permission_forge/audit/event_catalog'
|
|
281
|
+
require_relative 'permission_forge/audit/actor'
|
|
282
|
+
require_relative 'permission_forge/audit/source'
|
|
283
|
+
require_relative 'permission_forge/audit/context'
|
|
284
|
+
require_relative 'permission_forge/audit/failure'
|
|
285
|
+
require_relative 'permission_forge/audit/column_values'
|
|
286
|
+
require_relative 'permission_forge/audit/recorder'
|
|
287
|
+
require_relative 'permission_forge/audit/subscribers'
|
|
288
|
+
require_relative 'permission_forge/audit/log_access'
|
|
289
|
+
require_relative 'permission_forge/audit/event_query'
|
|
290
|
+
require_relative 'permission_forge/audit/runtime_emission'
|
|
291
|
+
require_relative 'permission_forge/audit'
|
|
292
|
+
require_relative 'permission_forge/audit/gate'
|
|
293
|
+
require_relative 'permission_forge/audit/runtime'
|
|
294
|
+
require_relative 'permission_forge/audit/field_access'
|
|
295
|
+
require_relative 'permission_forge/audit/subscribers/base'
|
|
296
|
+
require_relative 'permission_forge/audit/subscribers/legacy_hooks'
|
|
297
|
+
require_relative 'permission_forge/audit/subscribers/log'
|
|
298
|
+
require_relative 'permission_forge/audit/actor_resolver'
|
|
299
|
+
require_relative 'permission_forge/audit/audited_views'
|
|
300
|
+
require_relative 'permission_forge/audit/request_context'
|
|
301
|
+
require_relative 'permission_forge/audit/detached'
|
|
302
|
+
require_relative 'permission_forge/audit/refusals'
|
|
303
|
+
require_relative 'permission_forge/audit/job_context'
|
|
304
|
+
require_relative 'permission_forge/audit/settings_observer'
|
|
305
|
+
require_relative 'permission_forge/audit/boot_warnings'
|
|
306
|
+
|
|
307
|
+
require_relative 'permission_forge/resource_chain'
|
|
308
|
+
require_relative 'permission_forge/virtual_fields'
|
|
309
|
+
require_relative 'permission_forge/virtual_fields/discovery'
|
|
310
|
+
|
|
311
|
+
require_relative 'permission_forge/concerns/has_active_scope'
|
|
312
|
+
require_relative 'permission_forge/concerns/has_tenant_scoping'
|
|
313
|
+
require_relative 'permission_forge/concerns/has_uuid'
|
|
314
|
+
require_relative 'permission_forge/concerns/has_audit_trail'
|
|
315
|
+
require_relative 'permission_forge/invalidation_batch'
|
|
316
|
+
require_relative 'permission_forge/concerns/invalidates_in_batch'
|
|
317
|
+
require_relative 'permission_forge/concerns/has_permission_set_users'
|
|
318
|
+
require_relative 'permission_forge/concerns/validates_dependency'
|
|
319
|
+
require_relative 'permission_forge/concerns/validates_column_lengths'
|
|
320
|
+
require_relative 'permission_forge/concerns/permissions_regenerator'
|
|
321
|
+
require_relative 'permission_forge/concerns/invalidates_managed_actions'
|
|
322
|
+
require_relative 'permission_forge/concerns/records_audit_events'
|
|
323
|
+
require_relative 'permission_forge/concerns/audited_collection'
|
|
324
|
+
require_relative 'permission_forge/conditional_sets'
|
|
325
|
+
require_relative 'permission_forge/concerns/permissions_memo'
|
|
326
|
+
require_relative 'permission_forge/concerns/permissions_per_context'
|
|
327
|
+
require_relative 'permission_forge/concerns/permission_cache_accessor'
|
|
328
|
+
require_relative 'permission_forge/concerns/resource_permission_checker'
|
|
329
|
+
require_relative 'permission_forge/concerns/field_mask_checker'
|
|
330
|
+
require_relative 'permission_forge/concerns/permissioned_user'
|
|
331
|
+
require_relative 'permission_forge/concerns/auto_permission_scoping'
|
|
332
|
+
require_relative 'permission_forge/concerns/permission_scoped_findable'
|
|
333
|
+
require_relative 'permission_forge/concerns/masked_attributes'
|
|
334
|
+
|
|
335
|
+
require_relative 'permission_forge/operators/constants'
|
|
336
|
+
require_relative 'permission_forge/operators/validations'
|
|
337
|
+
require_relative 'permission_forge/operators/value_casters'
|
|
338
|
+
require_relative 'permission_forge/operators/rule_column'
|
|
339
|
+
require_relative 'permission_forge/operators/value_typing'
|
|
340
|
+
require_relative 'permission_forge/services/mask_value'
|
|
341
|
+
|
|
342
|
+
# The models are not required here: they live in app/models/permission_forge
|
|
343
|
+
# and are autoloaded by the host application (see MODEL_NAMES above), so
|
|
344
|
+
# their class bodies read the configuration after config/initializers.
|
|
345
|
+
|
|
346
|
+
require_relative 'permission_forge/operators/registry'
|
|
347
|
+
|
|
348
|
+
require_relative 'permission_forge/controller_actions'
|
|
349
|
+
require_relative 'permission_forge/controller_actions/mapping'
|
|
350
|
+
require_relative 'permission_forge/controller_actions/managed_index'
|
|
351
|
+
require_relative 'permission_forge/controller_actions/authorizer'
|
|
352
|
+
require_relative 'permission_forge/concerns/controller_action_guard'
|
|
353
|
+
|
|
354
|
+
require_relative 'permission_forge/api/serializers/base_serializer'
|
|
355
|
+
require_relative 'permission_forge/api/serializers/permission_set_serializer'
|
|
356
|
+
require_relative 'permission_forge/api/serializers/resource_serializer'
|
|
357
|
+
require_relative 'permission_forge/api/serializers/resource_field_serializer'
|
|
358
|
+
require_relative 'permission_forge/api/serializers/resource_action_serializer'
|
|
359
|
+
require_relative 'permission_forge/api/serializers/permission_set_rule_serializer'
|
|
360
|
+
require_relative 'permission_forge/api/serializers/permission_set_user_serializer'
|
|
361
|
+
require_relative 'permission_forge/api/serializers/audit_event_serializer'
|
|
362
|
+
require_relative 'permission_forge/api/serializers/permission_set_group_serializer'
|
|
363
|
+
require_relative 'permission_forge/api/serializers/permission_set_group_user_serializer'
|
|
364
|
+
|
|
365
|
+
require_relative 'permission_forge/services/concerns/clonable'
|
|
366
|
+
require_relative 'permission_forge/services/concerns/audited_operation'
|
|
367
|
+
require_relative 'permission_forge/services/concerns/across_tenants'
|
|
368
|
+
require_relative 'permission_forge/services/bulk_permission_set_assignment'
|
|
369
|
+
require_relative 'permission_forge/services/consent_withdrawal_handler'
|
|
370
|
+
require_relative 'permission_forge/services/expired_assignment_cleaner'
|
|
371
|
+
require_relative 'permission_forge/services/permission_cache_clearer'
|
|
372
|
+
require_relative 'permission_forge/services/permission_set_condition_evaluator'
|
|
373
|
+
require_relative 'permission_forge/services/permission_set_cloner'
|
|
374
|
+
require_relative 'permission_forge/services/permission_set_group_cloner'
|
|
375
|
+
require_relative 'permission_forge/services/permission_rule_applier'
|
|
376
|
+
require_relative 'permission_forge/services/resource_with_fields_builder'
|
|
377
|
+
require_relative 'permission_forge/services/dashboard_stats'
|
|
378
|
+
require_relative 'permission_forge/services/extra_field_defaults'
|
|
379
|
+
require_relative 'permission_forge/services/field_permission_merger'
|
|
380
|
+
require_relative 'permission_forge/services/filters_response_attributes'
|
|
381
|
+
require_relative 'permission_forge/services/attribute_access_guard'
|
|
382
|
+
require_relative 'permission_forge/services/gdpr_field_auditor'
|
|
383
|
+
require_relative 'permission_forge/services/permission_hash_builder'
|
|
384
|
+
require_relative 'permission_forge/services/permits_attributes'
|
|
385
|
+
require_relative 'permission_forge/services/permits_attributes/nested_records'
|
|
386
|
+
require_relative 'permission_forge/services/data_subject_erasure'
|
|
387
|
+
require_relative 'permission_forge/services/host_key_guard'
|
|
388
|
+
require_relative 'permission_forge/services/stale_assignment_auditor'
|
|
389
|
+
require_relative 'permission_forge/services/audit_event_purger'
|
|
390
|
+
require_relative 'permission_forge/rake_audit'
|
|
391
|
+
require_relative 'permission_forge/services/resource_field_generator'
|
|
392
|
+
require_relative 'permission_forge/services/unscoped_query_detector'
|
|
393
|
+
require_relative 'permission_forge/services/rls_operator_sql'
|
|
394
|
+
require_relative 'permission_forge/services/rls_policy_generator'
|
|
395
|
+
require_relative 'permission_forge/rls'
|
|
396
|
+
|
|
397
|
+
# Admin UI support (read models + batched edits for the built-in admin screens)
|
|
398
|
+
require_relative 'permission_forge/admin/field_level'
|
|
399
|
+
require_relative 'permission_forge/admin/sensitive_columns'
|
|
400
|
+
require_relative 'permission_forge/admin/model_catalog'
|
|
401
|
+
require_relative 'permission_forge/admin/person_label'
|
|
402
|
+
require_relative 'permission_forge/admin/people_candidates'
|
|
403
|
+
require_relative 'permission_forge/admin/people_directory'
|
|
404
|
+
require_relative 'permission_forge/admin/record_id'
|
|
405
|
+
require_relative 'permission_forge/admin/editable_lock'
|
|
406
|
+
require_relative 'permission_forge/admin/expiry_presets'
|
|
407
|
+
require_relative 'permission_forge/admin/effective_access'
|
|
408
|
+
require_relative 'permission_forge/admin/effective_fields'
|
|
409
|
+
require_relative 'permission_forge/admin/settings_values'
|
|
410
|
+
require_relative 'permission_forge/admin/settings_catalog'
|
|
411
|
+
require_relative 'permission_forge/admin/csv'
|
|
412
|
+
require_relative 'permission_forge/admin/last_changes'
|
|
413
|
+
require_relative 'permission_forge/admin/audit_trail'
|
|
414
|
+
require_relative 'permission_forge/admin/audit_log_days'
|
|
415
|
+
require_relative 'permission_forge/admin/audit_log'
|
|
416
|
+
require_relative 'permission_forge/admin/audit_name_history'
|
|
417
|
+
require_relative 'permission_forge/admin/audit_lookups'
|
|
418
|
+
require_relative 'permission_forge/admin/exports'
|
|
419
|
+
require_relative 'permission_forge/admin/exports/document'
|
|
420
|
+
require_relative 'permission_forge/admin/exports/events'
|
|
421
|
+
require_relative 'permission_forge/admin/exports/holdings'
|
|
422
|
+
require_relative 'permission_forge/admin/attention_items'
|
|
423
|
+
require_relative 'permission_forge/admin/change_set_parts/context'
|
|
424
|
+
require_relative 'permission_forge/admin/change_set_parts/values'
|
|
425
|
+
require_relative 'permission_forge/admin/change_set_parts/base'
|
|
426
|
+
require_relative 'permission_forge/admin/change_set_parts/set_attrs'
|
|
427
|
+
require_relative 'permission_forge/admin/change_set_parts/resource_edit'
|
|
428
|
+
require_relative 'permission_forge/admin/change_set_parts/resource_flags'
|
|
429
|
+
require_relative 'permission_forge/admin/change_set_parts/field_edit'
|
|
430
|
+
require_relative 'permission_forge/admin/change_set_parts/field_levels'
|
|
431
|
+
require_relative 'permission_forge/admin/change_set_parts/new_fields'
|
|
432
|
+
require_relative 'permission_forge/admin/change_set_parts/actions'
|
|
433
|
+
require_relative 'permission_forge/admin/change_set_parts/new_actions'
|
|
434
|
+
require_relative 'permission_forge/admin/change_set'
|
|
435
|
+
require_relative 'permission_forge/admin/field_rows'
|
|
436
|
+
require_relative 'permission_forge/admin/field_detail'
|
|
437
|
+
require_relative 'permission_forge/admin/audit_event_detail'
|
|
438
|
+
require_relative 'permission_forge/admin/rule_builder'
|
|
439
|
+
require_relative 'permission_forge/admin/field_across_sets'
|
|
440
|
+
require_relative 'permission_forge/admin/controller_catalog'
|
|
441
|
+
require_relative 'permission_forge/admin/action_across_sets'
|
|
442
|
+
require_relative 'permission_forge/admin/action_rows'
|
|
443
|
+
require_relative 'permission_forge/admin/column_fields'
|
|
444
|
+
require_relative 'permission_forge/admin/conditions_form'
|
|
445
|
+
require_relative 'permission_forge/admin/permission_set_factory'
|
|
446
|
+
require_relative 'permission_forge/admin/rule_form_input'
|
|
447
|
+
require_relative 'permission_forge/admin/rule_form'
|
|
448
|
+
require_relative 'permission_forge/admin/bulk_people_matcher'
|
|
449
|
+
|
|
450
|
+
# Security review (rake permission_forge:audit)
|
|
451
|
+
require_relative 'permission_forge/services/security_audit'
|
|
452
|
+
require_relative 'permission_forge/services/security_audit/base_check'
|
|
453
|
+
require_relative 'permission_forge/services/security_audit/config_check'
|
|
454
|
+
require_relative 'permission_forge/services/security_audit/privilege_check'
|
|
455
|
+
require_relative 'permission_forge/services/security_audit/assignment_check'
|
|
456
|
+
require_relative 'permission_forge/services/security_audit/rule_check'
|
|
457
|
+
require_relative 'permission_forge/services/security_audit/integrity_check'
|
|
458
|
+
require_relative 'permission_forge/services/security_audit/personal_data_check'
|
|
459
|
+
require_relative 'permission_forge/services/security_audit/action_check'
|
|
460
|
+
require_relative 'permission_forge/services/security_audit/audit_trail_check'
|
|
461
|
+
require_relative 'permission_forge/services/security_audit/user_model_check'
|
|
462
|
+
require_relative 'permission_forge/services/security_audit/text_report'
|
|
463
|
+
|
|
464
|
+
require_relative 'permission_forge/engine' if defined?(Rails::Engine)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :permission_forge do
|
|
4
|
+
desc 'Report fields that match the personal-data patterns but are not masked, and matching model columns with no ResourceField'
|
|
5
|
+
task audit_masks: :environment do
|
|
6
|
+
report = PermissionForge::RakeAudit.reporting('permission_forge:audit_masks', counts: ->(r) { { findings: r.size } }) do
|
|
7
|
+
PermissionForge::Services::GdprFieldAuditor.call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
puts PermissionForge::Services::GdprFieldAuditor::TextReport.new(report)
|
|
11
|
+
exit 1 if report.any?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
namespace :permission_forge do
|
|
16
|
+
desc 'Read-only security review of the PermissionForge setup. FORMAT=json, FAIL_ON=critical|high|medium|low'
|
|
17
|
+
task audit: :environment do
|
|
18
|
+
report = PermissionForge::RakeAudit.reporting('permission_forge:audit', counts: :counts.to_proc) do
|
|
19
|
+
PermissionForge::Services::SecurityAudit.call
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if ENV['FORMAT'] == 'json'
|
|
23
|
+
puts JSON.pretty_generate(report.to_h)
|
|
24
|
+
else
|
|
25
|
+
puts PermissionForge::Services::SecurityAuditChecks::TextReport.new(report)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
fail_on = ENV.fetch('FAIL_ON', nil).presence
|
|
29
|
+
exit 1 if fail_on && report.at_least(fail_on).any?
|
|
30
|
+
end
|
|
31
|
+
end
|