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,94 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# The audit log: every recorded event, filtered and paged, one event in
|
|
3
|
+
# full, and the exports (the filtered log as CSV or JSON, and who holds
|
|
4
|
+
# what). Read-only: nothing here writes an event — viewing and exporting
|
|
5
|
+
# are recorded by Audit::AuditedViews, a refusal by AuditLogGate.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class AuditEventsController < ApplicationController
|
|
9
|
+
include PermissionForge::AuditLogGate
|
|
10
|
+
|
|
11
|
+
EXPORT_FORMATS = %w[csv json].freeze
|
|
12
|
+
MAX_KEY_LENGTH = 255
|
|
13
|
+
|
|
14
|
+
pf_exports EXPORT_FORMATS.map(&:to_sym), only: %i[index holdings]
|
|
15
|
+
before_action :pf_require_audit_log!
|
|
16
|
+
|
|
17
|
+
def index
|
|
18
|
+
@log = PermissionForge::Admin::AuditLog.new(params, include_views: export?)
|
|
19
|
+
return pf_stream(PermissionForge::Admin::Exports::Events.call(@log.relation, filters: @log.filters), 'audit-log') if export?
|
|
20
|
+
|
|
21
|
+
@page = @log.page(params[:page])
|
|
22
|
+
@lookups = PermissionForge::Admin::AuditLookups.new(@page.events)
|
|
23
|
+
rescue PermissionForge::Audit::EventQuery::Invalid => e
|
|
24
|
+
invalid_filters(e)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def show
|
|
28
|
+
@event = find_event || pf_not_found
|
|
29
|
+
@lookups = PermissionForge::Admin::AuditLookups.new([@event])
|
|
30
|
+
@detail = PermissionForge::Admin::AuditEventDetail.new(@event, @lookups)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Who holds what: every assignment as it stands, with its review.
|
|
34
|
+
def holdings
|
|
35
|
+
return head(:not_acceptable) unless export?
|
|
36
|
+
|
|
37
|
+
pf_stream(PermissionForge::Admin::Exports::Holdings.call, 'who-holds-what')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def export?
|
|
43
|
+
EXPORT_FORMATS.include?(request.format.symbol.to_s)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# By uuid (what the pages link to), or by id. A key no column can hold
|
|
47
|
+
# (a NUL byte, longer than a column) is not looked for.
|
|
48
|
+
def find_event
|
|
49
|
+
key = params[:id].to_s
|
|
50
|
+
return nil if key.include?("\0") || key.length > MAX_KEY_LENGTH
|
|
51
|
+
|
|
52
|
+
events = PermissionForge::AuditEvent
|
|
53
|
+
events.find_by(uuid: key) || (key.match?(/\A\d{1,18}\z/) ? events.find_by(id: key) : nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def invalid_filters(error)
|
|
57
|
+
return head(:unprocessable_entity) if export?
|
|
58
|
+
|
|
59
|
+
@errors = error.errors
|
|
60
|
+
@page = nil
|
|
61
|
+
render :index, status: :unprocessable_entity
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Hands the file over as it is written (batch by batch), with no cap.
|
|
65
|
+
def pf_stream(document, name)
|
|
66
|
+
format = request.format.symbol.to_s
|
|
67
|
+
headers.merge!(pf_download_headers(format, PermissionForge::Admin::Exports.filename(name, format)))
|
|
68
|
+
self.response_body = pf_in_request_context(document.chunks(format))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The body is written after the action's around_actions have ended: the
|
|
72
|
+
# record rules are no longer suspended (the people of the rows would be
|
|
73
|
+
# looked up under the admin's own rules) and the host's tenant may be gone.
|
|
74
|
+
# Each is captured here, in the action, and put back while the body is written.
|
|
75
|
+
def pf_in_request_context(chunks)
|
|
76
|
+
tenant = PermissionForge.ambient_tenant
|
|
77
|
+
Enumerator.new do |out|
|
|
78
|
+
PermissionForge.with_ambient_tenant(tenant) do
|
|
79
|
+
PermissionForge.without_permission_scope { chunks.each { |chunk| out << chunk } }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Not buffered or cached on the way: no ETag digest (Last-Modified set),
|
|
85
|
+
# no proxy buffering, nothing stored.
|
|
86
|
+
def pf_download_headers(format, filename)
|
|
87
|
+
{
|
|
88
|
+
'Content-Type' => Mime[format.to_sym].to_s,
|
|
89
|
+
'Content-Disposition' => ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: filename),
|
|
90
|
+
'Cache-Control' => 'no-store', 'Last-Modified' => Time.current.httpdate, 'X-Accel-Buffering' => 'no'
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Bulk assign (paste emails/names and/or pick people) and bulk revoke for
|
|
3
|
+
# one Permission Set. Both apply immediately, through the model callbacks
|
|
4
|
+
# (one audit event per person, one correlation id per action), after
|
|
5
|
+
# config.admin_assignment_authorization (AssignmentAuthorization).
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class BulkAssignmentsController < ApplicationController
|
|
9
|
+
include PermissionForge::AssignmentAuthorization
|
|
10
|
+
include PermissionForge::ActingAdmin
|
|
11
|
+
|
|
12
|
+
before_action :set_permission_set
|
|
13
|
+
# A locked set refuses what widens access (EditableLock.widening_lock):
|
|
14
|
+
# new holders of a granting set, removing holders of a Muting one.
|
|
15
|
+
before_action :widening_grant_refused?, only: %i[new preview create]
|
|
16
|
+
before_action :widening_revoke_refused?, only: %i[confirm_revoke revoke]
|
|
17
|
+
|
|
18
|
+
def new
|
|
19
|
+
load_form
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Shows who would be assigned, who already has it and what wasn't found.
|
|
23
|
+
def preview
|
|
24
|
+
load_form
|
|
25
|
+
@result = matcher_result
|
|
26
|
+
render :new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create
|
|
30
|
+
load_form
|
|
31
|
+
@result = matcher_result
|
|
32
|
+
@expiry_error = pf_resolve_expiry
|
|
33
|
+
@nobody = @result.to_assign.empty?
|
|
34
|
+
return render(:new, status: :unprocessable_entity) if @expiry_error || @nobody
|
|
35
|
+
return if grant_refused?
|
|
36
|
+
|
|
37
|
+
outcome = assign(@result.to_assign)
|
|
38
|
+
redirect_to assignments_path,
|
|
39
|
+
notice: t('permission_forge.flash.bulk_assignments.assigned', count: outcome[:assigned], set: @permission_set.name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def confirm_revoke
|
|
43
|
+
@people = selected_people
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def revoke
|
|
47
|
+
ids = selected_people.map(&:id)
|
|
48
|
+
return if pf_assignment_refused?(@permission_set, ids, :revoke, assignments_path)
|
|
49
|
+
|
|
50
|
+
outcome = PermissionForge::Services::BulkPermissionSetAssignment.call(
|
|
51
|
+
permission_set: @permission_set, user_ids: ids, action: :revoke
|
|
52
|
+
)
|
|
53
|
+
redirect_to assignments_path,
|
|
54
|
+
notice: t('permission_forge.flash.bulk_assignments.revoked', count: outcome[:revoked], set: @permission_set.name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def set_permission_set
|
|
60
|
+
@permission_set = PermissionForge::PermissionSet.find(params[:permission_set_id])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def grant_refused?
|
|
64
|
+
pf_assignment_refused?(@permission_set, @result.to_assign.map(&:id), :grant, assignments_path)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def assignments_path
|
|
68
|
+
permission_forge.permission_set_path(@permission_set, tab: 'assignments')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def widening_grant_refused?
|
|
72
|
+
pf_widening_refused?(pf_widening_lock(@permission_set, :grant), permission_forge.permission_set_path(@permission_set))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def widening_revoke_refused?
|
|
76
|
+
pf_widening_refused?(pf_widening_lock(@permission_set, :revoke), assignments_path)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def load_form
|
|
80
|
+
@text = params[:identifiers].to_s
|
|
81
|
+
@query = params[:q].to_s.strip
|
|
82
|
+
@picked_ids = Array(params[:user_ids]).compact_blank.map(&:to_s)
|
|
83
|
+
@people = people_directory.pick(query: @query, limit: 8)
|
|
84
|
+
pf_load_expiry
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def assign(people)
|
|
88
|
+
PermissionForge::Services::BulkPermissionSetAssignment.call(
|
|
89
|
+
permission_set: @permission_set, user_ids: people.map(&:id), action: :assign,
|
|
90
|
+
created_by: pf_acting_admin_record, expires_at: @expires_at
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def matcher_result
|
|
95
|
+
PermissionForge::Admin::BulkPeopleMatcher.new(directory: people_directory)
|
|
96
|
+
.call(text: @text, user_ids: @picked_ids, permission_set: @permission_set)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# One per request: the picker and the matcher share its resolved people.
|
|
100
|
+
def people_directory
|
|
101
|
+
@people_directory ||= PermissionForge::Admin::PeopleDirectory.new
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def selected_people
|
|
105
|
+
people_directory.find_all(params[:user_ids])
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# "Who can see iban?" — pick a resource and a field, see its level in every
|
|
3
|
+
# Permission Set that includes the resource.
|
|
4
|
+
#
|
|
5
|
+
# @api private
|
|
6
|
+
class FieldLookupsController < ApplicationController
|
|
7
|
+
# Labels are computed here from the allow-listed resource name (via
|
|
8
|
+
# ModelCatalog, which only resolves real models) so the view never
|
|
9
|
+
# constantizes request input.
|
|
10
|
+
def show
|
|
11
|
+
@resource_names = PermissionForge::Resource.distinct.order(:name).pluck(:name)
|
|
12
|
+
@resource_name = @resource_names.include?(params[:resource]) ? params[:resource] : nil
|
|
13
|
+
model = @resource_name && PermissionForge::Admin::ModelCatalog.new.find(@resource_name)
|
|
14
|
+
@fields = @resource_name ? field_names(@resource_name, model) : []
|
|
15
|
+
@field_options = @fields.map { |name| [name, field_label(model, name)] }
|
|
16
|
+
load_field(model)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def load_field(model)
|
|
22
|
+
@field = @fields.include?(params[:field]) ? params[:field] : nil
|
|
23
|
+
@field_label = field_label(model, @field) if @field
|
|
24
|
+
@rows = @field ? PermissionForge::Admin::FieldAcrossSets.call(@resource_name, @field) : []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def field_names(resource_name, model)
|
|
28
|
+
configured = PermissionForge::ResourceField.joins(:resource)
|
|
29
|
+
.where(PermissionForge::Resource.table_name => { name: resource_name })
|
|
30
|
+
.distinct.pluck(:name)
|
|
31
|
+
(configured + (model ? model.managed_column_names + model.virtual_field_names : [])).uniq.sort
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def field_label(model, name)
|
|
35
|
+
model&.klass&.human_attribute_name(name) || name.humanize
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# "Needs attention": what the owner should look at, plus quick lookups.
|
|
3
|
+
#
|
|
4
|
+
# @api private
|
|
5
|
+
class HomeController < ApplicationController
|
|
6
|
+
PRIVACY_PREVIEW = 5
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@attention = pf_attention
|
|
10
|
+
@first_run = !PermissionForge::PermissionSet.exists?
|
|
11
|
+
@show_all_privacy = params[:privacy] == 'all'
|
|
12
|
+
@cleanup_preview = params[:cleanup].present?
|
|
13
|
+
@people_with_access = live_assignments.distinct.count(:user_id)
|
|
14
|
+
@active_sets = PermissionForge::PermissionSet.active.count
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def live_assignments
|
|
20
|
+
PermissionForge::PermissionSetUser.active_assignments
|
|
21
|
+
.where(permission_set_id: PermissionForge::PermissionSet.active.select(:id))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Housekeeping triggered from the home page.
|
|
3
|
+
#
|
|
4
|
+
# @api private
|
|
5
|
+
class MaintenanceController < ApplicationController
|
|
6
|
+
# Removes expired assignments (the dry run is shown on Home first).
|
|
7
|
+
def cleanup
|
|
8
|
+
result = PermissionForge::Services::ExpiredAssignmentCleaner.call
|
|
9
|
+
redirect_to permission_forge.root_path,
|
|
10
|
+
notice: t('permission_forge.flash.maintenance.cleaned', count: result[:removed])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# People: everyone in the host app, what they can access, and why.
|
|
3
|
+
#
|
|
4
|
+
# @api private
|
|
5
|
+
class PeopleController < ApplicationController
|
|
6
|
+
include PermissionForge::CacheConfirmation
|
|
7
|
+
include PermissionForge::AssignmentAuthorization
|
|
8
|
+
include PermissionForge::AuditLogGate
|
|
9
|
+
|
|
10
|
+
PER_PAGE = 25
|
|
11
|
+
TABS = %w[access effective audit].freeze
|
|
12
|
+
# Records listed per type under "Changes made by" (the CSV has them all).
|
|
13
|
+
CHANGES_SHOWN = 100
|
|
14
|
+
TIMELINE_PER_PAGE = 20
|
|
15
|
+
|
|
16
|
+
pf_exports %i[csv], only: :show # the person's changes
|
|
17
|
+
before_action :set_person, except: %i[index]
|
|
18
|
+
|
|
19
|
+
def index
|
|
20
|
+
@query = params[:q].to_s.strip
|
|
21
|
+
@filter = filter_param
|
|
22
|
+
@page = directory.search(query: @query, filter: @filter, page: params[:page], per_page: PER_PAGE)
|
|
23
|
+
@counts = directory.counts(query: @query)
|
|
24
|
+
@assignments = assignments_for(@page.people.map(&:id))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def show
|
|
28
|
+
@tab = TABS.include?(params[:tab]) ? params[:tab] : 'access'
|
|
29
|
+
@assignments = @person_assignments
|
|
30
|
+
@groups = PermissionForge::PermissionSetGroupUser.where(user_id: @person.id).includes(:permission_set_group)
|
|
31
|
+
return pf_refuse_audit_tab unless pf_audit_tab_allowed?
|
|
32
|
+
|
|
33
|
+
send("load_#{@tab}_tab")
|
|
34
|
+
respond_to do |format|
|
|
35
|
+
format.html
|
|
36
|
+
format.csv { send_changes_csv }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# --- offboarding ------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def confirm_remove_all
|
|
43
|
+
load_removable
|
|
44
|
+
@lost = lost_resource_labels(@removable)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Each assignment is destroyed through its callbacks (one
|
|
48
|
+
# assignment.revoked event each, sharing one correlation id). Granting
|
|
49
|
+
# sets lose the person also when locked; a locked Muting Permission Set
|
|
50
|
+
# keeps them (removing them would give access back — EditableLock).
|
|
51
|
+
def remove_all
|
|
52
|
+
load_removable
|
|
53
|
+
sets = @removable.map(&:permission_set).uniq
|
|
54
|
+
path = permission_forge.person_path(@person)
|
|
55
|
+
return if pf_assignment_refused?(sets, @person.id, :revoke, path)
|
|
56
|
+
|
|
57
|
+
count = @removable.size
|
|
58
|
+
confirmed = cache_confirmed? { PermissionForge::Audit.correlate { @removable.each(&:destroy!) } }
|
|
59
|
+
redirect_to permission_forge.person_path(@person),
|
|
60
|
+
**cache_flash(confirmed, { notice: :removed_all, alert: :removed_all_cache_kept }, count: count, name: person_name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def refresh_cache
|
|
64
|
+
confirmed = cache_confirmed? { @person.invalidate_permissions if @person.respond_to?(:invalidate_permissions) }
|
|
65
|
+
redirect_to permission_forge.person_path(@person, tab: pf_tab_param(TABS)),
|
|
66
|
+
**cache_flash(confirmed, { notice: :cache_refreshed, alert: :cache_refresh_failed }, name: person_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- consent withdrawal -------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def consent_preview
|
|
72
|
+
@flags = consent_flags
|
|
73
|
+
@flag = params[:flag].presence || @flags.first
|
|
74
|
+
@affected = @person_assignments.select { |assignment| @flag.present? && assignment.permission_set.requires_session_flag == @flag }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def withdraw_consent
|
|
78
|
+
flag = params[:flag].to_s
|
|
79
|
+
return redirect_to(permission_forge.person_path(@person)) unless consent_flags.include?(flag)
|
|
80
|
+
|
|
81
|
+
count, invalidated = consent_service(flag, dry_run: false).values_at(:revoked_sets, :invalidated)
|
|
82
|
+
# nothing revoked means nothing to clear: that is not a failure
|
|
83
|
+
redirect_to permission_forge.person_path(@person),
|
|
84
|
+
**cache_flash(count.zero? || invalidated, { notice: :consent_withdrawn, alert: :consent_withdrawn_cache_kept },
|
|
85
|
+
count: count, flag: flag)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# --- erasure ----------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def erasure_preview
|
|
91
|
+
@preview = erasure_service(dry_run: true)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def erase
|
|
95
|
+
if params[:confirmation].to_s.strip != person_name
|
|
96
|
+
@preview = erasure_service(dry_run: true)
|
|
97
|
+
@confirmation_error = true
|
|
98
|
+
return render :erasure_preview, status: :unprocessable_entity
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
result = erasure_service(dry_run: false)
|
|
102
|
+
# nil: erased inside a transaction of the host's, the cache follows at its commit
|
|
103
|
+
confirmed = result[:cache_cleared] != false
|
|
104
|
+
redirect_to permission_forge.people_path,
|
|
105
|
+
**cache_flash(confirmed, { notice: :erased, alert: :erased_cache_kept }, name: person_name)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def directory
|
|
111
|
+
@directory ||= PermissionForge::Admin::PeopleDirectory.new
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def filter_param
|
|
115
|
+
filter = params[:filter]
|
|
116
|
+
PermissionForge::Admin::PeopleDirectory::FILTERS.include?(filter) ? filter : 'all'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def set_person
|
|
120
|
+
@person = directory.find(params[:id]) || pf_not_found
|
|
121
|
+
@person_assignments = PermissionForge::PermissionSetUser.where(user_id: @person.id)
|
|
122
|
+
.includes(:permission_set).order(:created_at).to_a
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def person_name
|
|
126
|
+
PermissionForge::Admin::PersonLabel.new(@person).name
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @removable: the assignments "Remove all access" removes; @kept: those
|
|
130
|
+
# of locked Muting Permission Sets, where removing widens access.
|
|
131
|
+
def load_removable
|
|
132
|
+
@kept, @removable = @person_assignments.partition do |assignment|
|
|
133
|
+
pf_widening_lock(assignment.permission_set, :revoke)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def assignments_for(user_ids)
|
|
138
|
+
PermissionForge::PermissionSetUser.where(user_id: user_ids).includes(:permission_set).group_by(&:user_id)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def load_access_tab; end
|
|
142
|
+
|
|
143
|
+
def load_effective_tab
|
|
144
|
+
service = PermissionForge::Admin::EffectiveAccess.new(user: @person)
|
|
145
|
+
@effective = service.call
|
|
146
|
+
@conditional_sets = service.conditional_sets
|
|
147
|
+
@open = Array(params[:open]).map(&:to_s)
|
|
148
|
+
@why = params[:why].to_s
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def load_audit_tab
|
|
152
|
+
@changes = audit.changes_by(@person.id, limit: CHANGES_SHOWN)
|
|
153
|
+
@changes_capped = audit.capped?
|
|
154
|
+
@timeline_log = PermissionForge::Admin::AuditLog.new({ views: params[:views] },
|
|
155
|
+
scope: PermissionForge::Admin::AuditLog.for_person(@person.id))
|
|
156
|
+
@timeline = @timeline_log.page(params[:page], per_page: TIMELINE_PER_PAGE)
|
|
157
|
+
@timeline_lookups = PermissionForge::Admin::AuditLookups.new(@timeline.events)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def audit
|
|
161
|
+
@audit ||= PermissionForge::Admin::AuditTrail.new
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def send_changes_csv
|
|
165
|
+
entries = audit.changes_by(@person.id).values.flatten
|
|
166
|
+
csv = audit.to_csv(entries, describe: helpers.method(:pf_audit_describe), person: helpers.method(:pf_audit_people))
|
|
167
|
+
response.headers['Cache-Control'] = 'no-store' # names of people, like the log's exports
|
|
168
|
+
send_data csv, filename: "permission-forge-changes-#{@person.id}.csv", type: 'text/csv'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def lost_resource_labels(assignments)
|
|
172
|
+
assignments.map(&:permission_set).reject(&:is_muting)
|
|
173
|
+
.flat_map { |ps| ps.resources.select(&:is_active).map { |r| helpers.pf_resource_plural(r.name) } }.uniq
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Flags gating sets this person has, plus configured flags.
|
|
177
|
+
def consent_flags
|
|
178
|
+
flags = @person_assignments.map { |assignment| assignment.permission_set.requires_session_flag }.compact_blank
|
|
179
|
+
(flags + (PermissionForge.configuration.admin_session_flags || {}).keys.map(&:to_s)).uniq
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def consent_service(flag, dry_run:)
|
|
183
|
+
PermissionForge::Services::ConsentWithdrawalHandler.call(user: @person, consent_flag: flag, dry_run: dry_run)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def erasure_service(dry_run:)
|
|
187
|
+
PermissionForge::Services::DataSubjectErasure.call(user: @person, dry_run: dry_run)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Loads the one-page Permission Set editor and its tabs (resources matrix,
|
|
3
|
+
# assignments, conditions, audit) for PermissionSetsController. Tabs are
|
|
4
|
+
# dispatched as load_<tab>_tab.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
module PermissionSetEditor
|
|
8
|
+
AUDIT_TIMELINE_PER_PAGE = 20
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def load_editor
|
|
13
|
+
@resources = @permission_set.resources.includes(:resource_fields, :resource_actions, :permission_set_rules).order(:name).to_a
|
|
14
|
+
@groups = PermissionForge::PermissionSetGroup.order(:name)
|
|
15
|
+
load_impact
|
|
16
|
+
@assignment_count = @permission_set.permission_set_users.count
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load_impact
|
|
20
|
+
live = @permission_set.permission_set_users.active_assignments.includes(:user).order(:created_at)
|
|
21
|
+
@people_count = live.count
|
|
22
|
+
@impact_people = live.limit(3).map(&:user).compact
|
|
23
|
+
@lost = @permission_set.is_muting ? [] : @permission_set.resources.select(&:is_active).map { |r| helpers.pf_resource_plural(r.name) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load_resources_tab
|
|
27
|
+
@catalog = PermissionForge::Admin::ModelCatalog.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def load_assignments_tab
|
|
31
|
+
@assignments = @permission_set.permission_set_users.includes(:user).order(:created_at).to_a
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def load_conditions_tab
|
|
35
|
+
@ip_text ||= helpers.pf_ip_list(@permission_set.allowed_ips).join("\n")
|
|
36
|
+
configured = (PermissionForge.configuration.admin_session_flags || {}).keys.map(&:to_s)
|
|
37
|
+
used = PermissionForge::PermissionSet.where.not(requires_session_flag: [nil, '']).distinct.pluck(:requires_session_flag)
|
|
38
|
+
@flag_options = (configured + used).uniq
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def load_audit_tab
|
|
42
|
+
load_audit_filters
|
|
43
|
+
audit = PermissionForge::Admin::AuditTrail.new
|
|
44
|
+
@editors = PermissionForge::Admin::AuditTrail.editors(audit.for_permission_set(@permission_set))
|
|
45
|
+
@entries = audit.for_permission_set(@permission_set, who: @who, type: @type, days: @days, sort: @sort)
|
|
46
|
+
load_audit_timeline unless request.format.csv?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The set's events, newest first (the log filtered by this set).
|
|
50
|
+
def load_audit_timeline
|
|
51
|
+
@timeline_log = PermissionForge::Admin::AuditLog.new({ views: params[:views] },
|
|
52
|
+
scope: PermissionForge::Admin::AuditLog.for_permission_set(@permission_set))
|
|
53
|
+
@timeline = @timeline_log.page(params[:page], per_page: AUDIT_TIMELINE_PER_PAGE)
|
|
54
|
+
@timeline_lookups = PermissionForge::Admin::AuditLookups.new(@timeline.events)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def load_audit_filters
|
|
58
|
+
@who = params[:who].presence
|
|
59
|
+
@type = params[:type].presence
|
|
60
|
+
@days = params[:days].presence
|
|
61
|
+
@sort = params[:sort] == 'asc' ? 'asc' : 'desc'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def send_audit_csv
|
|
65
|
+
audit = PermissionForge::Admin::AuditTrail.new
|
|
66
|
+
csv = audit.to_csv(@entries, describe: helpers.method(:pf_audit_describe), person: helpers.method(:pf_audit_people))
|
|
67
|
+
response.headers['Cache-Control'] = 'no-store' # names of people, like the log's exports
|
|
68
|
+
send_data csv, filename: "permission-forge-#{@permission_set.name.parameterize}-audit.csv", type: 'text/csv'
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# Group membership. Organisational only: it never gives Permission Sets.
|
|
3
|
+
# Adding and removing go through the model callbacks (group_membership.*
|
|
4
|
+
# audit events) after config.admin_assignment_authorization.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
class PermissionSetGroupUsersController < ApplicationController
|
|
8
|
+
include PermissionForge::AssignmentAuthorization
|
|
9
|
+
|
|
10
|
+
before_action :set_group
|
|
11
|
+
before_action :reject_locked
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
user = member_user
|
|
15
|
+
return redirect_to(group_path, alert: t('permission_forge.flash.permission_set_group_users.user_not_found')) unless user
|
|
16
|
+
return if pf_assignment_refused?(@group, user.id, :add_member, group_path)
|
|
17
|
+
|
|
18
|
+
membership = PermissionForge::PermissionSetGroupUser.new(permission_set_group: @group, user: user)
|
|
19
|
+
return redirect_to(group_path, alert: t('permission_forge.flash.permission_set_group_users.add_failed')) unless membership.save
|
|
20
|
+
|
|
21
|
+
redirect_to group_path, notice: t('permission_forge.flash.groups.member_added', name: person_name(user))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The member must be somebody config.user_list_resolver lists (404 otherwise).
|
|
25
|
+
def destroy
|
|
26
|
+
membership = @group.permission_set_group_users.find(params[:id])
|
|
27
|
+
PermissionForge::Admin::PeopleDirectory.new.find(membership.user_id) || pf_not_found
|
|
28
|
+
return if pf_assignment_refused?(@group, membership.user_id, :remove_member, group_path)
|
|
29
|
+
|
|
30
|
+
membership.destroy!
|
|
31
|
+
redirect_to group_path, notice: t('permission_forge.flash.permission_set_group_users.removed')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def set_group
|
|
37
|
+
@group = PermissionForge::PermissionSetGroup.find(params[:group_id])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# A locked group's members are frozen.
|
|
41
|
+
def reject_locked
|
|
42
|
+
pf_reject_locked!(@group, group_path)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def member_user
|
|
46
|
+
PermissionForge::Admin::PeopleDirectory.new.find(params[:user_id])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def person_name(user)
|
|
50
|
+
PermissionForge::Admin::PersonLabel.new(user).name
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def group_path
|
|
54
|
+
permission_forge.group_path(@group)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|