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
data/config/routes.rb
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
PermissionForge::Engine.routes.draw do
|
|
2
|
+
# Engine-served CSS/JS/brand files — no host asset pipeline required.
|
|
3
|
+
get 'pf-assets/:file', to: 'assets#show', as: :pf_asset, constraints: { file: /[\w.-]+/ }, format: false
|
|
4
|
+
|
|
5
|
+
root to: 'home#index'
|
|
6
|
+
|
|
7
|
+
# Per-browser admin preferences (theme, dismissed helper banners).
|
|
8
|
+
resource :preferences, only: :update
|
|
9
|
+
|
|
10
|
+
get 'settings', to: 'settings#show', as: :settings
|
|
11
|
+
get 'field_lookup', to: 'field_lookups#show', as: :field_lookup
|
|
12
|
+
|
|
13
|
+
# The audit log (read-only): the filtered log and its CSV / JSON export, one
|
|
14
|
+
# event by uuid, and the "who holds what" export.
|
|
15
|
+
resources :audit_events, path: 'audit', only: %i[index show] do
|
|
16
|
+
collection do
|
|
17
|
+
get :holdings
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Home "needs attention": remove expired assignments (dry run shown inline on Home).
|
|
22
|
+
delete 'maintenance/cleanup', to: 'maintenance#cleanup', as: :maintenance_cleanup
|
|
23
|
+
|
|
24
|
+
resources :people, only: %i[index show] do
|
|
25
|
+
member do
|
|
26
|
+
get :remove_all, action: :confirm_remove_all
|
|
27
|
+
delete :remove_all
|
|
28
|
+
post :refresh_cache
|
|
29
|
+
get :consent, action: :consent_preview
|
|
30
|
+
post :consent, action: :withdraw_consent
|
|
31
|
+
get :erasure, action: :erasure_preview
|
|
32
|
+
post :erasure, action: :erase
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
resources :assignments, only: %i[new create update destroy] do
|
|
37
|
+
member do
|
|
38
|
+
get :remove, action: :confirm_destroy
|
|
39
|
+
post :keep
|
|
40
|
+
end
|
|
41
|
+
collection do
|
|
42
|
+
post :restore
|
|
43
|
+
post :undo
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
resources :permission_sets, only: %i[index new create show destroy] do
|
|
48
|
+
member do
|
|
49
|
+
post :review
|
|
50
|
+
patch :apply
|
|
51
|
+
post :duplicate
|
|
52
|
+
get :delete, action: :confirm_destroy
|
|
53
|
+
get :switch_mode, action: :confirm_switch_mode
|
|
54
|
+
patch :switch_mode
|
|
55
|
+
patch :conditions
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
resource :bulk_assignment, only: %i[new create] do
|
|
59
|
+
post :preview
|
|
60
|
+
get :revoke, action: :confirm_revoke
|
|
61
|
+
delete :revoke
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
resources :resources, param: :model, only: %i[new create show destroy],
|
|
65
|
+
constraints: { model: %r{[^/]+} } do
|
|
66
|
+
member do
|
|
67
|
+
post :review
|
|
68
|
+
patch :apply
|
|
69
|
+
post :sync
|
|
70
|
+
get :delete, action: :confirm_destroy
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
resources :fields, controller: 'resource_fields', param: :key, only: %i[show update],
|
|
74
|
+
constraints: { key: %r{[^/]+} } do
|
|
75
|
+
member do
|
|
76
|
+
post :acknowledge
|
|
77
|
+
post :apply_recommendation
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
resources :actions, controller: 'resource_actions', only: %i[new create show update destroy] do
|
|
81
|
+
member do
|
|
82
|
+
get :delete, action: :confirm_destroy
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
resources :rules, controller: 'permission_set_rules', only: %i[new create edit update destroy] do
|
|
86
|
+
collection do
|
|
87
|
+
post :refresh
|
|
88
|
+
end
|
|
89
|
+
member do
|
|
90
|
+
patch :toggle
|
|
91
|
+
get :delete, action: :confirm_destroy
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
resources :groups, controller: 'permission_set_groups', only: %i[index create show update destroy] do
|
|
98
|
+
member do
|
|
99
|
+
post :duplicate
|
|
100
|
+
get :delete, action: :confirm_destroy
|
|
101
|
+
post :add_set
|
|
102
|
+
delete :remove_set
|
|
103
|
+
end
|
|
104
|
+
resources :members, controller: 'permission_set_group_users', only: %i[create destroy]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# JSON Admin API — routes are always drawn; access is guarded at runtime
|
|
108
|
+
# by BaseController checking api_enabled + api_auth configuration.
|
|
109
|
+
namespace :api, defaults: { format: :json } do
|
|
110
|
+
resource :stats, only: [:show], controller: 'dashboard'
|
|
111
|
+
|
|
112
|
+
# The audit log is read-only over the API: no write route exists.
|
|
113
|
+
resources :audit_events, only: %i[index show], param: :identifier
|
|
114
|
+
|
|
115
|
+
resources :permission_sets, param: :identifier do
|
|
116
|
+
member do
|
|
117
|
+
post :clone
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
resources :resources, param: :identifier do
|
|
121
|
+
member do
|
|
122
|
+
post :generate_fields
|
|
123
|
+
end
|
|
124
|
+
resources :fields, param: :identifier, controller: 'resource_fields'
|
|
125
|
+
resources :actions, param: :identifier, controller: 'resource_actions'
|
|
126
|
+
resources :rules, param: :identifier, controller: 'permission_set_rules'
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
resources :users, param: :identifier, controller: 'permission_set_users', only: %i[index create destroy] do
|
|
130
|
+
collection do
|
|
131
|
+
post :bulk
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
resources :permission_set_groups, param: :identifier do
|
|
137
|
+
member do
|
|
138
|
+
post :clone
|
|
139
|
+
end
|
|
140
|
+
resources :users, param: :identifier, controller: 'permission_set_group_users', only: %i[index create destroy]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/active_record'
|
|
3
|
+
|
|
4
|
+
module PermissionForge
|
|
5
|
+
module Generators
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
include ActiveRecord::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
class_option :skip_migration, type: :boolean, default: false,
|
|
12
|
+
desc: 'Skip migration generation (for existing tables)'
|
|
13
|
+
class_option :table_prefix, type: :string, default: 'permission_forge_',
|
|
14
|
+
desc: 'Table name prefix for all permission_forge tables'
|
|
15
|
+
class_option :tenant, type: :string, default: nil,
|
|
16
|
+
desc: 'Tenant model name for acts_as_tenant (e.g., Account)'
|
|
17
|
+
class_option :user_id_type, type: :string, default: nil,
|
|
18
|
+
desc: 'Column type for user ids: bigint, integer, uuid or string ' \
|
|
19
|
+
'(default: the primary key type of the configured user model)'
|
|
20
|
+
class_option :tenant_id_type, type: :string, default: nil,
|
|
21
|
+
desc: 'Column type for the tenant column ' \
|
|
22
|
+
'(default: the primary key type of the --tenant model)'
|
|
23
|
+
|
|
24
|
+
KEY_TYPES = %w[bigint integer uuid string].freeze
|
|
25
|
+
DETECTED_KEY_TYPES = { integer: 'bigint', uuid: 'uuid', string: 'string' }.freeze
|
|
26
|
+
|
|
27
|
+
def copy_initializer
|
|
28
|
+
template 'initializer.rb.tt', 'config/initializers/permission_forge.rb'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def copy_migration
|
|
32
|
+
return if options[:skip_migration]
|
|
33
|
+
|
|
34
|
+
migration_template 'migration.rb.tt', 'db/migrate/create_permission_forge_tables.rb'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def table_prefix
|
|
40
|
+
options[:table_prefix]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tenant_model
|
|
44
|
+
options[:tenant].presence
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 'Account' and 'Billing::Account' both map to account_id — the column
|
|
48
|
+
# name cannot carry a namespace.
|
|
49
|
+
def tenant_column
|
|
50
|
+
return nil unless tenant_model
|
|
51
|
+
|
|
52
|
+
"#{tenant_model.demodulize.underscore}_id"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Columns that store a host primary key (user ids, audit user ids, the
|
|
56
|
+
# tenant id) take the type of that key. A bigint column silently
|
|
57
|
+
# truncates a UUID to its leading digits, which makes different users
|
|
58
|
+
# (or tenants) share permission data.
|
|
59
|
+
def user_id_type
|
|
60
|
+
@user_id_type ||= key_type(options[:user_id_type], PermissionForge.configuration.user_class, '--user-id-type')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def tenant_id_type
|
|
64
|
+
return 'bigint' unless tenant_model
|
|
65
|
+
|
|
66
|
+
@tenant_id_type ||= key_type(options[:tenant_id_type], tenant_model, '--tenant-id-type')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def key_type(explicit, model_name, flag)
|
|
70
|
+
return validated_key_type(explicit, flag) if explicit.present?
|
|
71
|
+
|
|
72
|
+
detected_key_type(model_name) || assume_bigint(model_name, flag)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def validated_key_type(type, flag)
|
|
76
|
+
return type if KEY_TYPES.include?(type)
|
|
77
|
+
|
|
78
|
+
raise Thor::Error, "#{flag} must be one of #{KEY_TYPES.join(', ')} (got #{type.inspect})"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# nil when the model is not loadable, has no table yet, or has a
|
|
82
|
+
# composite primary key.
|
|
83
|
+
def detected_key_type(model_name)
|
|
84
|
+
model = model_name.to_s.safe_constantize
|
|
85
|
+
return unless model.respond_to?(:columns_hash)
|
|
86
|
+
|
|
87
|
+
primary_key = model.primary_key
|
|
88
|
+
column = model.columns_hash[primary_key] if primary_key.is_a?(String)
|
|
89
|
+
DETECTED_KEY_TYPES[column&.type]
|
|
90
|
+
rescue ActiveRecord::ActiveRecordError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def assume_bigint(model_name, flag)
|
|
95
|
+
say_status :warning, "could not read the primary key type of #{model_name}; assuming bigint. " \
|
|
96
|
+
"Pass #{flag}=uuid (or string) if its keys are not integers.", :yellow
|
|
97
|
+
'bigint'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def migration_version
|
|
101
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# PermissionForge — the options you are most likely to set. Every option, its
|
|
2
|
+
# default and what it does:
|
|
3
|
+
# https://github.com/fetijashari/permission_forge/blob/main/docs/configuration.md
|
|
4
|
+
# Before production, go through the Production hardening checklist
|
|
5
|
+
# (https://github.com/fetijashari/permission_forge#production-hardening-checklist);
|
|
6
|
+
# `bin/rails permission_forge:audit` reports what is still open.
|
|
7
|
+
PermissionForge.configure do |config|
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
# Closed for new installs (the library defaults stay open for existing apps)
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
# A guarded controller action (ControllerActionGuard) with nobody signed in is refused.
|
|
12
|
+
config.action_guard_nil_user_strategy = :deny
|
|
13
|
+
# Conditional Permission Sets fail closed without a request context (see below).
|
|
14
|
+
config.conditional_sets_strict_mode = true
|
|
15
|
+
# The cache is Rails.cache: use a shared store in production (Redis, Solid Cache, Memcached).
|
|
16
|
+
config.cache_backend = :rails_cache
|
|
17
|
+
|
|
18
|
+
# config.preset = :strict closes every fail-open default at once
|
|
19
|
+
# (docs/configuration.md#presets; a setting you make here still wins):
|
|
20
|
+
# nil_user_strategy = :deny, auto_query_scoping = :filter_only,
|
|
21
|
+
# response_filter_nil_user_strategy = :block, unconfigured_action_strategy = :block,
|
|
22
|
+
# mask_virtual_attributes = :auto, extra_fields_default_access = :unconfigured,
|
|
23
|
+
# and the three settings above.
|
|
24
|
+
# What it costs: with nil_user_strategy :deny and auto_query_scoping :filter_only,
|
|
25
|
+
# code that runs with no current user (jobs, mailers, seeds, webhooks, the
|
|
26
|
+
# console, sign-in lookups on a user model that includes PermissionScopedFindable)
|
|
27
|
+
# sees no protected rows, and a belongs_to presence check on a protected parent
|
|
28
|
+
# fails ("must exist"). Run such code with a user, or inside
|
|
29
|
+
# PermissionForge.without_permission_scope { }, where nobody signed in gets no rules:
|
|
30
|
+
# never around code an anonymous request reaches. Never include PermissionScopedFindable in the user model.
|
|
31
|
+
# config.preset = :strict
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Who is signed in
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# The class name of your User model (string, lazily constantized), and the
|
|
37
|
+
# class that created_by / updated_by point to.
|
|
38
|
+
config.user_class = 'User'
|
|
39
|
+
config.audit_user_class = 'User'
|
|
40
|
+
|
|
41
|
+
# Returns the signed-in user, or nil. As generated it reads Current.user when
|
|
42
|
+
# the app has a Current class that answers `user` (Rails 8's authentication
|
|
43
|
+
# generator creates one) and nil otherwise: until you wire it, nobody is
|
|
44
|
+
# signed in.
|
|
45
|
+
config.current_user_resolver = -> { ::Current.user if defined?(::Current) && ::Current.respond_to?(:user) }
|
|
46
|
+
|
|
47
|
+
# Whether a user is active. It does not remove access: the audit task
|
|
48
|
+
# reports the assignments still held by users it calls inactive.
|
|
49
|
+
config.user_active_check = ->(user) { user.respond_to?(:active_for_authentication?) ? user.active_for_authentication? : true }
|
|
50
|
+
|
|
51
|
+
# The profile cached with each user's permissions (uuid: nil when users have none).
|
|
52
|
+
config.profile_builder = ->(user) {
|
|
53
|
+
{ id: user.id, uuid: user.try(:uuid), name: user.try(:name), email: user.try(:email) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Prefix of the PermissionForge tables ('' for none).
|
|
57
|
+
config.table_name_prefix = '<%= table_prefix %>'
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Multi-tenancy (optional, requires the acts_as_tenant gem)
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Every PermissionForge table needs the tenant column (the install migration
|
|
63
|
+
# adds it with --tenant=Account). Boot fails if this is set while
|
|
64
|
+
# acts_as_tenant is not loaded.
|
|
65
|
+
<% if tenant_model %> config.tenant_model = '<%= tenant_model %>'
|
|
66
|
+
<% else %> # config.tenant_model = 'Account'
|
|
67
|
+
<% end %>
|
|
68
|
+
# The admin screens and the admin API serve one tenant at a time: a request
|
|
69
|
+
# with no current tenant is refused (403). Set the tenant in
|
|
70
|
+
# base_controller_class / api_base_controller_class (or in middleware).
|
|
71
|
+
# config.admin_tenantless_access = :deny
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Activation conditions (session flag / allowed IPs / login hours)
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# With conditional_sets_strict_mode (set above), where the request context is
|
|
77
|
+
# blank (no resolver, a job, the console) a conditional GRANTING set does not
|
|
78
|
+
# apply and a conditional MUTING set (a lockdown) does. Trade-off: an MFA- or
|
|
79
|
+
# IP-gated grant never applies in jobs or the console, nor in requests until
|
|
80
|
+
# this resolver returns the request's IP and session flags:
|
|
81
|
+
# config.request_context_resolver = -> {
|
|
82
|
+
# { ip: Current.ip, session_flags: Current.session_flags, time: Time.current }
|
|
83
|
+
# }
|
|
84
|
+
# It runs on every permission read of a user who holds a conditional set:
|
|
85
|
+
# keep it cheap, never read permissions inside it, return {} with no request.
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Masked attributes with nobody signed in (not part of the preset)
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# :bypass (default) returns the stored values; :hide hides every guarded
|
|
91
|
+
# attribute. Trade-off: jobs, mailers and console code that read masked
|
|
92
|
+
# models with no user then see hidden values: set the current user there.
|
|
93
|
+
# The user model (user_class and its subclasses) stays readable for sign-in,
|
|
94
|
+
# and validations always read the stored values.
|
|
95
|
+
# config.masked_attributes_nil_user_strategy = :hide
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Admin screens
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# HTTP Basic auth is on and answers 401 until credentials are set. Never turn
|
|
101
|
+
# it off without the second option: the admin can grant anything to anyone.
|
|
102
|
+
#
|
|
103
|
+
# Option A — Basic auth credentials (from ENV or credentials, never literals):
|
|
104
|
+
# config.http_basic_auth_user = ENV['PF_ADMIN_USER']
|
|
105
|
+
# config.http_basic_auth_password = ENV['PF_ADMIN_PASSWORD']
|
|
106
|
+
#
|
|
107
|
+
# Option B — your own authenticated admin controller:
|
|
108
|
+
# config.http_basic_auth_enabled = false
|
|
109
|
+
# config.base_controller_class = '::Admin::BaseController' # must authenticate admins
|
|
110
|
+
#
|
|
111
|
+
# Who may grant, revoke, extend or review an assignment, or change a group's
|
|
112
|
+
# members (admin screens and API). Example: no self-assignment.
|
|
113
|
+
# config.admin_assignment_authorization = ->(controller, target, user_ids, action) {
|
|
114
|
+
# admin = controller.current_user
|
|
115
|
+
# admin&.admin? && !user_ids.include?(admin.id)
|
|
116
|
+
# }
|
|
117
|
+
#
|
|
118
|
+
# Who may read the audit log pages, exports and API (nil: anyone with admin access).
|
|
119
|
+
# config.audit_log_authorization = ->(controller) { controller.current_user&.auditor? }
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Audit log (docs/audit-log.md)
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# Every permission change is recorded in the audit_events table, inside the
|
|
125
|
+
# change's own transaction. Each option with its default (an example where that is nil):
|
|
126
|
+
# config.audit_events = :auto # true: always; false: never (events are still published)
|
|
127
|
+
# config.audit_actor_resolver = ->(controller) { controller.current_user } # nil: current_user_resolver
|
|
128
|
+
# config.audit_request_context = ->(controller) { { request_id: controller.request.request_id, ip: controller.request.remote_ip } }
|
|
129
|
+
# config.audit_change_failure_strategy = :raise # the change rolls back; or :report, :log
|
|
130
|
+
# config.audit_hook_failure_strategy = :report # or :log, :raise
|
|
131
|
+
# config.audit_runtime_events = %i[denied] # also: bypass field_read params_filtered cache
|
|
132
|
+
# config.audit_log_subscriber = false # true: also write every event to Rails.logger
|
|
133
|
+
# config.audit_retention_period = 2.years # nil keeps events forever; purge: bin/rails permission_forge:audit_events:purge
|
|
134
|
+
# In jobs and rake tasks: PermissionForge.with_audit_actor(user: admin, source: 'job') { ... }
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# JSON admin API (off by default)
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# config.api_enabled = true
|
|
140
|
+
# config.api_base_controller_class = '::Api::BaseController' # your auth chain
|
|
141
|
+
# config.api_auth = ->(controller) { controller.current_user&.admin? }
|
|
142
|
+
# config.api_action_authorization = ->(controller, action, record) { controller.current_user&.admin? }
|
|
143
|
+
# config.api_assignment_authorization = ->(controller, permission_set, user_ids) { controller.current_user&.admin? }
|
|
144
|
+
#
|
|
145
|
+
# api_auth proves identity only: until both authorization lambdas are set,
|
|
146
|
+
# every API change and assignment is refused (403).
|
|
147
|
+
# config.api_require_authorization = true # default
|
|
148
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
class CreatePermissionForgeTables < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
def change
|
|
3
|
+
create_table :<%= table_prefix %>permission_set_groups do |t|
|
|
4
|
+
t.string :uuid, null: false
|
|
5
|
+
t.string :name, null: false, default: 'Untitled Permission Set Group'
|
|
6
|
+
t.string :description
|
|
7
|
+
t.boolean :is_active, null: false, default: true
|
|
8
|
+
t.boolean :is_editable, null: false, default: true
|
|
9
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
10
|
+
t.index :<%= tenant_column %>
|
|
11
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
12
|
+
t.<%= user_id_type %> :updated_by_id
|
|
13
|
+
t.timestamps
|
|
14
|
+
|
|
15
|
+
t.index :uuid, unique: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
create_table :<%= table_prefix %>permission_sets do |t|
|
|
19
|
+
t.string :uuid, null: false
|
|
20
|
+
t.references :permission_set_group, foreign_key: { to_table: :<%= table_prefix %>permission_set_groups }, null: true
|
|
21
|
+
t.string :name, null: false, default: 'Untitled Permission Set'
|
|
22
|
+
t.string :description
|
|
23
|
+
t.boolean :is_active, null: false, default: true
|
|
24
|
+
t.boolean :is_editable, null: false, default: true
|
|
25
|
+
t.boolean :is_muting, null: false, default: false
|
|
26
|
+
t.string :requires_session_flag
|
|
27
|
+
t.string :allowed_ips
|
|
28
|
+
t.integer :allowed_hours_start
|
|
29
|
+
t.integer :allowed_hours_end
|
|
30
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
31
|
+
t.index :<%= tenant_column %>
|
|
32
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
33
|
+
t.<%= user_id_type %> :updated_by_id
|
|
34
|
+
t.timestamps
|
|
35
|
+
|
|
36
|
+
t.index :uuid, unique: true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
create_table :<%= table_prefix %>resources do |t|
|
|
40
|
+
t.string :uuid, null: false
|
|
41
|
+
t.references :permission_set, foreign_key: { to_table: :<%= table_prefix %>permission_sets }, null: false
|
|
42
|
+
t.string :name, null: false, default: 'Untitled Resource'
|
|
43
|
+
t.string :label
|
|
44
|
+
t.string :description
|
|
45
|
+
t.boolean :is_active, null: false, default: true
|
|
46
|
+
t.boolean :is_editable, null: false, default: true
|
|
47
|
+
t.boolean :read_access, null: false, default: false
|
|
48
|
+
t.boolean :create_access, null: false, default: false
|
|
49
|
+
t.boolean :edit_access, null: false, default: false
|
|
50
|
+
t.boolean :delete_access, null: false, default: false
|
|
51
|
+
t.boolean :view_all, null: false, default: false
|
|
52
|
+
t.boolean :modify_all, null: false, default: false
|
|
53
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
54
|
+
t.index :<%= tenant_column %>
|
|
55
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
56
|
+
t.<%= user_id_type %> :updated_by_id
|
|
57
|
+
t.timestamps
|
|
58
|
+
|
|
59
|
+
t.index :uuid, unique: true
|
|
60
|
+
t.index [:permission_set_id, :name], unique: true, name: 'idx_pf_resources_unique'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
create_table :<%= table_prefix %>resource_fields do |t|
|
|
64
|
+
t.string :uuid, null: false
|
|
65
|
+
t.references :resource, foreign_key: { to_table: :<%= table_prefix %>resources }, null: false
|
|
66
|
+
t.string :name, null: false, default: 'untitled_field'
|
|
67
|
+
t.string :label, null: false, default: 'Untitled Field'
|
|
68
|
+
t.string :description
|
|
69
|
+
t.boolean :is_active, null: false, default: true
|
|
70
|
+
t.boolean :is_editable, null: false, default: true
|
|
71
|
+
t.boolean :read_access, null: false, default: false
|
|
72
|
+
t.boolean :edit_access, null: false, default: false
|
|
73
|
+
t.boolean :mask_access, null: false, default: false
|
|
74
|
+
t.string :mask_strategy, null: false, default: 'full'
|
|
75
|
+
t.datetime :sensitive_acknowledged_at
|
|
76
|
+
t.<%= user_id_type %> :sensitive_acknowledged_by_id
|
|
77
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
78
|
+
t.index :<%= tenant_column %>
|
|
79
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
80
|
+
t.<%= user_id_type %> :updated_by_id
|
|
81
|
+
t.timestamps
|
|
82
|
+
|
|
83
|
+
t.index :uuid, unique: true
|
|
84
|
+
t.index [:resource_id, :name], unique: true, name: 'idx_pf_resource_fields_unique'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
create_table :<%= table_prefix %>resource_actions do |t|
|
|
88
|
+
t.string :uuid, null: false
|
|
89
|
+
t.references :resource, foreign_key: { to_table: :<%= table_prefix %>resources }, null: false
|
|
90
|
+
t.string :name, null: false
|
|
91
|
+
t.string :label
|
|
92
|
+
t.string :description
|
|
93
|
+
t.boolean :is_permitted, null: false, default: false
|
|
94
|
+
t.boolean :is_active, null: false, default: true
|
|
95
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
96
|
+
t.index :<%= tenant_column %>
|
|
97
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
98
|
+
t.<%= user_id_type %> :updated_by_id
|
|
99
|
+
t.timestamps
|
|
100
|
+
|
|
101
|
+
t.index :uuid, unique: true
|
|
102
|
+
t.index [:resource_id, :name], unique: true, name: 'idx_pf_resource_actions_unique'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
create_table :<%= table_prefix %>permission_set_rules do |t|
|
|
106
|
+
t.string :uuid, null: false
|
|
107
|
+
t.references :permission_set, foreign_key: { to_table: :<%= table_prefix %>permission_sets }, null: false
|
|
108
|
+
t.references :resource, foreign_key: { to_table: :<%= table_prefix %>resources }, null: false
|
|
109
|
+
t.string :name, null: false, default: 'Untitled Rule'
|
|
110
|
+
t.string :failure_message, default: 'You do not have permission to perform this action.'
|
|
111
|
+
t.string :join_type
|
|
112
|
+
t.string :field_name, null: false
|
|
113
|
+
t.string :field_value, null: false
|
|
114
|
+
t.string :field_accessor_path
|
|
115
|
+
t.string :operator, null: false, default: 'equal'
|
|
116
|
+
t.boolean :include_nulls, null: false, default: true
|
|
117
|
+
t.boolean :is_active, null: false, default: true
|
|
118
|
+
t.boolean :is_editable, null: false, default: true
|
|
119
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
120
|
+
t.index :<%= tenant_column %>
|
|
121
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
122
|
+
t.<%= user_id_type %> :updated_by_id
|
|
123
|
+
t.timestamps
|
|
124
|
+
|
|
125
|
+
t.index :uuid, unique: true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
create_table :<%= table_prefix %>permission_set_users do |t|
|
|
129
|
+
t.string :uuid, null: false
|
|
130
|
+
t.references :permission_set, foreign_key: { to_table: :<%= table_prefix %>permission_sets }, null: false
|
|
131
|
+
t.references :user, null: false, type: :<%= user_id_type %>
|
|
132
|
+
t.datetime :expires_at
|
|
133
|
+
t.datetime :reviewed_at
|
|
134
|
+
t.<%= user_id_type %> :reviewed_by_id
|
|
135
|
+
t.string :reason
|
|
136
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
137
|
+
t.index :<%= tenant_column %>
|
|
138
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
139
|
+
t.<%= user_id_type %> :updated_by_id
|
|
140
|
+
t.timestamps
|
|
141
|
+
|
|
142
|
+
t.index :uuid, unique: true
|
|
143
|
+
t.index [:permission_set_id, :user_id], unique: true, name: 'idx_pf_pset_users_unique'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
create_table :<%= table_prefix %>permission_set_group_users do |t|
|
|
147
|
+
t.string :uuid, null: false
|
|
148
|
+
t.references :permission_set_group, foreign_key: { to_table: :<%= table_prefix %>permission_set_groups }, null: false
|
|
149
|
+
t.references :user, null: false, type: :<%= user_id_type %>
|
|
150
|
+
t.string :reason
|
|
151
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
152
|
+
t.index :<%= tenant_column %>
|
|
153
|
+
<% end %> t.<%= user_id_type %> :created_by_id
|
|
154
|
+
t.<%= user_id_type %> :updated_by_id
|
|
155
|
+
t.timestamps
|
|
156
|
+
|
|
157
|
+
t.index :uuid, unique: true
|
|
158
|
+
t.index [:permission_set_group_id, :user_id], unique: true, name: 'idx_pf_pset_group_users_unique'
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Insert-only audit log. No foreign keys: events outlive the records they describe.
|
|
162
|
+
create_table :<%= table_prefix %>audit_events do |t|
|
|
163
|
+
t.string :uuid, null: false
|
|
164
|
+
t.datetime :occurred_at, null: false, precision: 6
|
|
165
|
+
t.string :event, null: false
|
|
166
|
+
t.string :category, null: false
|
|
167
|
+
t.string :actor_type, null: false
|
|
168
|
+
t.string :actor_id
|
|
169
|
+
t.string :actor_label
|
|
170
|
+
t.string :source, null: false
|
|
171
|
+
t.string :record_type
|
|
172
|
+
t.string :record_id
|
|
173
|
+
t.string :record_uuid
|
|
174
|
+
t.string :record_label
|
|
175
|
+
t.bigint :permission_set_id
|
|
176
|
+
t.string :permission_set_uuid
|
|
177
|
+
t.string :subject_user_id
|
|
178
|
+
t.text :data
|
|
179
|
+
t.string :reason
|
|
180
|
+
t.string :request_id
|
|
181
|
+
t.string :ip
|
|
182
|
+
t.string :correlation_id
|
|
183
|
+
<% if tenant_column %> t.<%= tenant_id_type %> :<%= tenant_column %>
|
|
184
|
+
t.index :<%= tenant_column %>, name: 'idx_pf_audit_tenant'
|
|
185
|
+
<% end %> t.datetime :created_at, null: false
|
|
186
|
+
|
|
187
|
+
t.index :uuid, unique: true, name: 'idx_pf_audit_uuid'
|
|
188
|
+
t.index :occurred_at, name: 'idx_pf_audit_occurred'
|
|
189
|
+
t.index [:event, :occurred_at], name: 'idx_pf_audit_event'
|
|
190
|
+
t.index [:subject_user_id, :occurred_at], name: 'idx_pf_audit_subject'
|
|
191
|
+
t.index [:permission_set_id, :occurred_at], name: 'idx_pf_audit_pset'
|
|
192
|
+
t.index [:permission_set_uuid, :occurred_at], name: 'idx_pf_audit_pset_uuid'
|
|
193
|
+
t.index [:record_type, :record_id, :occurred_at], name: 'idx_pf_audit_record'
|
|
194
|
+
t.index [:actor_id, :occurred_at], name: 'idx_pf_audit_actor'
|
|
195
|
+
t.index :correlation_id, name: 'idx_pf_audit_correlation'
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/active_record'
|
|
3
|
+
|
|
4
|
+
module PermissionForge
|
|
5
|
+
# @api private
|
|
6
|
+
module Generators
|
|
7
|
+
# Installs Postgres Row-Level Security for PermissionForge (Level 3).
|
|
8
|
+
#
|
|
9
|
+
# bin/rails generate permission_forge:rls
|
|
10
|
+
#
|
|
11
|
+
# Creates a migration that enables (and forces) RLS on every protected
|
|
12
|
+
# table and installs policies generated from the current rule state via
|
|
13
|
+
# PermissionForge::RLS.sync!. After editing rules, re-sync with
|
|
14
|
+
# `rake permission_forge:rls:sync` (drift is warned about at boot).
|
|
15
|
+
#
|
|
16
|
+
# Requires PostgreSQL and config.database_row_level_security = true for the
|
|
17
|
+
# runtime session-variable plumbing.
|
|
18
|
+
class RlsGenerator < Rails::Generators::Base
|
|
19
|
+
include ActiveRecord::Generators::Migration
|
|
20
|
+
|
|
21
|
+
source_root File.expand_path('templates', __dir__)
|
|
22
|
+
|
|
23
|
+
def check_adapter
|
|
24
|
+
return if PermissionForge::RLS.postgres?
|
|
25
|
+
|
|
26
|
+
say_status :error, 'PermissionForge RLS requires PostgreSQL — aborting.', :red
|
|
27
|
+
raise Thor::Error, 'database adapter is not PostgreSQL'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def copy_migration
|
|
31
|
+
migration_template 'enable_permission_forge_rls.rb.tt',
|
|
32
|
+
'db/migrate/enable_permission_forge_rls.rb'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def show_post_install
|
|
36
|
+
say <<~MSG
|
|
37
|
+
|
|
38
|
+
RLS migration created. Next steps:
|
|
39
|
+
1. bin/rails db:migrate
|
|
40
|
+
2. Set config.database_row_level_security = true in the initializer
|
|
41
|
+
3. Wrap request/job execution: PermissionForge::RLS.with_user(user) { ... }
|
|
42
|
+
(or set_user!/clear_user! in an around_action)
|
|
43
|
+
4. After editing permission rules: rake permission_forge:rls:sync
|
|
44
|
+
|
|
45
|
+
IMPORTANT: connect as a NON-superuser role without BYPASSRLS —
|
|
46
|
+
superusers bypass row-level security entirely.
|
|
47
|
+
MSG
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def migration_version
|
|
53
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Enables Postgres Row-Level Security on all PermissionForge-protected tables
|
|
2
|
+
# and installs policies generated from the current rule state.
|
|
3
|
+
#
|
|
4
|
+
# Policies check set membership dynamically (assign/revoke needs NO resync) but
|
|
5
|
+
# bake rule content statically — after editing permission rules, run:
|
|
6
|
+
# rake permission_forge:rls:sync
|
|
7
|
+
class EnablePermissionForgeRls < ActiveRecord::Migration<%= migration_version %>
|
|
8
|
+
def up
|
|
9
|
+
PermissionForge::RLS.sync!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def down
|
|
13
|
+
PermissionForge::RLS.remove!
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class PermissionForgeAddAdminUiColumns < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
# Nullable and additive: safe to run on a live table. The admin UI checks for
|
|
3
|
+
# the column and simply hides "This is intended" until it exists.
|
|
4
|
+
def change
|
|
5
|
+
add_column :<%= table_prefix %>resource_fields, :sensitive_acknowledged_at, :datetime,
|
|
6
|
+
if_not_exists: true
|
|
7
|
+
end
|
|
8
|
+
end
|