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/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Feti Jashari and Confidee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/fetijashari/permission_forge/main/docs/assets/logo-horizontal-dark.svg">
|
|
4
|
+
<img alt="PermissionForge" src="https://raw.githubusercontent.com/fetijashari/permission_forge/main/docs/assets/logo-horizontal-light.svg" width="420">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://github.com/fetijashari/permission_forge/actions/workflows/ci.yml"><img src="https://github.com/fetijashari/permission_forge/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://rubygems.org/gems/permission_forge"><img src="https://img.shields.io/gem/v/permission_forge" alt="Gem Version"></a>
|
|
11
|
+
<a href="https://www.ruby-lang.org"><img src="https://img.shields.io/badge/ruby-%3E%3D%203.2-ruby.svg" alt="Ruby"></a>
|
|
12
|
+
<a href="https://rubyonrails.org"><img src="https://img.shields.io/badge/rails-7.1%20to%208.1-red.svg" alt="Rails"></a>
|
|
13
|
+
<a href="https://github.com/fetijashari/permission_forge/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
Database-backed permissions for Ruby on Rails.<br>
|
|
18
|
+
Resource CRUD and custom actions, field-level read/edit access with masking, and row-level query rules, edited at runtime and cached per user.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
PermissionForge implements a **three-tier permission model**:
|
|
26
|
+
|
|
27
|
+
| Tier | Scope | Controls |
|
|
28
|
+
|------|-------|----------|
|
|
29
|
+
| **Resource** | Model | Can this user read/create/edit/delete Customers? Run the `export` action? |
|
|
30
|
+
| **Field** | Column | Can this user see `email`? Masked or in full? Edit `name`? |
|
|
31
|
+
| **Rule** | Row | Only records where `owner_id = current_user.id` |
|
|
32
|
+
|
|
33
|
+
Permissions live in your database as **Permission Sets** (collections of resources, fields, actions and rules) that are
|
|
34
|
+
assigned to users, optionally grouped, and edited in the built-in admin while the app runs. Each user's merged
|
|
35
|
+
permissions are cached as one hash.
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
- **Resource-level CRUD and custom actions** per model (`can_read?`, `can?(:export, :Customer)`), and an optional
|
|
40
|
+
controller guard that checks them before an action runs ([Controller Actions](https://github.com/fetijashari/permission_forge/blob/main/docs/controller-actions.md))
|
|
41
|
+
- **Field-level read/edit access**, enforced on the model's attribute readers and serialization (`MaskedAttributes`), on
|
|
42
|
+
incoming params (`PermitsAttributes`) and on response hashes (`FiltersResponseAttributes`)
|
|
43
|
+
- **Field masking** with four built-in strategies (`full`, `email`, `last4`, `first1_last4`) and your own
|
|
44
|
+
- **Row-level query rules**: 15 operators, 3 join types, values compared in the column's type on PostgreSQL, MySQL and
|
|
45
|
+
SQLite; applied by `Model.find_by` / `find_by!` and `Model.permission_scoped`
|
|
46
|
+
- **Optional query scoping**: a `default_scope` for every query, detection of queries that skipped the rules, and
|
|
47
|
+
PostgreSQL Row-Level Security policies ([Query Scoping](https://github.com/fetijashari/permission_forge/blob/main/docs/query-scoping.md))
|
|
48
|
+
- **Muting Permission Sets** that subtract access, **time-bound assignments**, and **activation conditions** (session
|
|
49
|
+
flag, IP allowlist, login hours), evaluated for each request
|
|
50
|
+
- **Audit log** of every permission change made through the models, written in the change's transaction
|
|
51
|
+
- **Admin UI** with no host asset dependencies, and an **opt-in JSON admin API**
|
|
52
|
+
- **Personal-data helpers**: masks suggested for personal-data columns when fields are generated, a field auditor, and
|
|
53
|
+
log parameter filtering for those columns (on by default)
|
|
54
|
+
- **Any Rails cache store** (`Rails.cache` over Redis, Solid Cache, Memcached, file; or in-memory for tests)
|
|
55
|
+
- **Multi-tenancy** through `acts_as_tenant`, and three locales (English, Albanian, Macedonian)
|
|
56
|
+
- **No hard dependencies** on Devise, Pundit, Rolify or any auth framework: every integration point is a lambda
|
|
57
|
+
|
|
58
|
+
### How it differs from Pundit, CanCanCan and Action Policy
|
|
59
|
+
|
|
60
|
+
Those are policy libraries: you write rules in Ruby and call them where you decide. PermissionForge is a permissions
|
|
61
|
+
platform: rules are data that admins edit at runtime, and the gem hooks into your app to apply them. It overrides
|
|
62
|
+
`find_by` on models you protect, wraps attribute readers and serialization, can add a `default_scope`, runs checks at
|
|
63
|
+
boot, and mounts an admin. That reaches further than a policy library and couples more tightly to your models; it can
|
|
64
|
+
sit next to one (for example Pundit for controller decisions, PermissionForge for fields and rows).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
Add to your `Gemfile`:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
gem 'permission_forge'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then run:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bundle install
|
|
80
|
+
bin/rails generate permission_forge:install
|
|
81
|
+
bin/rails db:migrate
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This creates `config/initializers/permission_forge.rb` and a migration with 9 tables: the 8 permission tables and the
|
|
85
|
+
audit log, prefixed with `permission_forge_` by default.
|
|
86
|
+
|
|
87
|
+
Generator options:
|
|
88
|
+
- `--skip-migration`: no migration (for apps with existing tables)
|
|
89
|
+
- `--table-prefix=PREFIX`: table name prefix (default `permission_forge_`; `''` for none)
|
|
90
|
+
- `--user-id-type=TYPE`: type of the user id columns, `bigint`, `integer`, `uuid` or `string` (default: detected from
|
|
91
|
+
your user model's primary key, so run the generator after your users table exists)
|
|
92
|
+
- `--tenant=MODEL`: add a tenant column for [multi-tenancy](https://github.com/fetijashari/permission_forge/blob/main/docs/multi-tenancy.md)
|
|
93
|
+
(`--tenant-id-type=TYPE` to override its detected type)
|
|
94
|
+
|
|
95
|
+
Requirements: Ruby 3.2+, Rails 7.1 to 8.1, PostgreSQL, MySQL or SQLite.
|
|
96
|
+
|
|
97
|
+
Upgrading? Read the [CHANGELOG](https://github.com/fetijashari/permission_forge/blob/main/CHANGELOG.md) and the
|
|
98
|
+
[migration guide](https://github.com/fetijashari/permission_forge/blob/main/docs/migration-guide.md).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Quick Start
|
|
103
|
+
|
|
104
|
+
### 1. Tell it who is signed in
|
|
105
|
+
|
|
106
|
+
The generated initializer reads `Current.user` when your app has a `Current` class that answers `user` (as Rails 8's
|
|
107
|
+
authentication generator creates). Otherwise point it at your own lookup:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# config/initializers/permission_forge.rb
|
|
111
|
+
PermissionForge.configure do |config|
|
|
112
|
+
config.user_class = 'User'
|
|
113
|
+
# As generated; or however your app knows the user
|
|
114
|
+
config.current_user_resolver = -> { ::Current.user if defined?(::Current) && ::Current.respond_to?(:user) }
|
|
115
|
+
config.cache_backend = :rails_cache
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Until it returns a user, nobody is signed in and `nil_user_strategy` decides (see the checklist below).
|
|
120
|
+
|
|
121
|
+
Configure PermissionForge in `config/initializers` only. The gem's models (`PermissionForge::PermissionSet`, ...) are
|
|
122
|
+
autoloaded by your application right after the initializers and read `user_class`, `audit_user_class` and
|
|
123
|
+
`tenant_model` as they load; a setting changed later (in `after_initialize`, a console) does not reach a loaded class
|
|
124
|
+
(PermissionForge logs a warning). Reference a model from a method (your models, controllers, jobs and rake tasks), not
|
|
125
|
+
from the top level of an initializer, an engine or railtie initializer, or a file required from
|
|
126
|
+
`config/application.rb`: the boot then fails with a `NameError` that says so. To change one of its classes (include a
|
|
127
|
+
module, add a callback), use `Rails.application.config.to_prepare` or
|
|
128
|
+
`Rails.autoloaders.main.on_load('PermissionForge::PermissionSet')`, which run again after every code reload;
|
|
129
|
+
`config.after_initialize` runs once, so use it only to read.
|
|
130
|
+
|
|
131
|
+
### 2. Include the concern in your User model
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
class User < ApplicationRecord
|
|
135
|
+
include PermissionForge::Concerns::PermissionedUser
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Every private method PermissionForge adds to your User class starts with `pf_`, so a method of your own never
|
|
140
|
+
replaces one of the gem's. The public API is the documented one (`permissions_hash`, `regenerate_permissions`,
|
|
141
|
+
`invalidate_permissions`, `permission_cache_key`, `permission_invalidation_stamp`, `can?`, `can_*?`,
|
|
142
|
+
`permissions_for`, `permissions_hash_for`, `masked_field?`, `mask_strategy_for`, `field_permissions`); override
|
|
143
|
+
those, not the private helpers.
|
|
144
|
+
|
|
145
|
+
### 3. Protect models with row-level rules (optional)
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
class Customer < ApplicationRecord
|
|
149
|
+
include PermissionForge::Concerns::PermissionScopedFindable
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This applies the user's rules to the class-level `Customer.find_by` / `find_by!` and adds a chainable
|
|
154
|
+
`Customer.permission_scoped` relation. Nothing else is scoped by default: `find`, `where`, `scope.find_by` and
|
|
155
|
+
association traversal return every row. Start from `permission_scoped`, or turn on one of the
|
|
156
|
+
[query-scoping levels](https://github.com/fetijashari/permission_forge/blob/main/docs/query-scoping.md). A user with no
|
|
157
|
+
rules for a model gets every row.
|
|
158
|
+
|
|
159
|
+
### 4. Create a Permission Set and assign it
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
pset = PermissionForge::PermissionSet.create!(name: 'Sales Team', is_active: true)
|
|
163
|
+
|
|
164
|
+
resource = PermissionForge::Resource.create!(
|
|
165
|
+
permission_set: pset,
|
|
166
|
+
name: 'Customer',
|
|
167
|
+
read_access: true, create_access: true,
|
|
168
|
+
edit_access: true, delete_access: false
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
PermissionForge::ResourceField.create!(
|
|
172
|
+
resource: resource, name: 'name', label: 'Name',
|
|
173
|
+
read_access: true, edit_access: true
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
PermissionForge::PermissionSetUser.create!(permission_set: pset, user: user)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Each change invalidates the cached permissions of the users it affects; the next read rebuilds them. The admin UI
|
|
180
|
+
(mounted as described in [Admin UI](https://github.com/fetijashari/permission_forge/blob/main/docs/admin-ui.md)) does
|
|
181
|
+
the same through forms.
|
|
182
|
+
|
|
183
|
+
### 5. Check permissions at runtime
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
user.can_read?('Customer') # => true
|
|
187
|
+
user.can_delete?('Customer') # => false
|
|
188
|
+
user.permissions_for(:Customer)
|
|
189
|
+
# => { read_access: true, create_access: true, edit_access: true, delete_access: false,
|
|
190
|
+
# view_all: false, modify_all: false }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Resource CRUD flags are answers to these calls. They do not by themselves stop a getter or `find_by`: enforce them
|
|
194
|
+
where you call `can_*?`, or with `ControllerActionGuard`. Field access is decided by the field rows.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Production hardening checklist
|
|
199
|
+
|
|
200
|
+
Several defaults fail open so that installing the gem changes nothing until you configure it. Before production, go
|
|
201
|
+
through this list. `bin/rails permission_forge:audit` reports most of these settings.
|
|
202
|
+
|
|
203
|
+
`config.preset = :strict` closes every row below that a setting can close, except masking with nobody signed in (an
|
|
204
|
+
explicit choice: see its row); a setting you make yourself always wins over the preset. New installs get the safe
|
|
205
|
+
part (`action_guard_nil_user_strategy = :deny`, `conditional_sets_strict_mode = true`, `cache_backend = :rails_cache`)
|
|
206
|
+
and the preset as a commented line with what it costs. The rows that need your code (`base_controller_class`,
|
|
207
|
+
`admin_assignment_authorization`, `audit_log_authorization`, `user_active_check`) are not in a preset.
|
|
208
|
+
`bin/rails permission_forge:audit` prints the preset and, under `:strict`, every option set looser. See
|
|
209
|
+
[Presets](https://github.com/fetijashari/permission_forge/blob/main/docs/configuration.md#presets).
|
|
210
|
+
|
|
211
|
+
| Default | What it means | To close it |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `nil_user_strategy = :bypass` | With no current user (jobs, console, a resolver that returns nil), `find_by`, `permission_scoped`, auto-scoping and the RLS policy apply no rules; so does the controller guard unless `action_guard_nil_user_strategy` is set | `:deny` (not found) or `:raise`. Do not include `PermissionScopedFindable` in the user model: under `:deny` sign-in lookups return nil (`permission_forge:audit` reports `user_model_scoped`) |
|
|
214
|
+
| `masked_attributes_nil_user_strategy = :bypass` | Attribute readers, `attributes` and `as_json` of a masked model return the stored values when nobody is signed in, whatever `nil_user_strategy` says | `:hide` (every guarded attribute reads as hidden with no user; the primary key and the raw paths are untouched), and set the current user in the jobs and mailers that read masked models. Not part of `config.preset = :strict`. The user model (`user_class` and its subclasses) stays readable for sign-in; validations read stored values |
|
|
215
|
+
| `response_filter_nil_user_strategy = :bypass` | `FiltersResponseAttributes` returns every attribute when there is no user | `:block` |
|
|
216
|
+
| `auto_query_scoping = :off` | Only class-level `find_by` / `find_by!` and `permission_scoped` apply row rules | `:filter_only`, and use `permission_scoped` for join rules; consider RLS on PostgreSQL |
|
|
217
|
+
| `unconfigured_action_strategy = :allow` | A controller action no Permission Set manages is allowed; unmapped actions are always allowed | `:block` |
|
|
218
|
+
| `action_guard_nil_user_strategy = nil` | The guard follows `nil_user_strategy` (`:bypass`) for signed-out requests to mapped actions | `:deny` (new installs get it from the generator) |
|
|
219
|
+
| `conditional_sets_strict_mode = false` | With an empty request context (no resolver configured), every conditional Permission Set applies, MFA- or IP-gated grants included | A `request_context_resolver` that returns the request's IP and session flags, and `true`: without a request context, conditional granting sets are then dropped and conditional muting sets still apply |
|
|
220
|
+
| `mask_virtual_attributes = false` | Virtual readers (methods, store accessors, attribute-API attributes) are not masked on the model; only `PermitsAttributes` and `FiltersResponseAttributes` apply their field rows | `:auto` (or a list of models), after checking that the models' own callbacks do not need the raw values |
|
|
221
|
+
| `extra_fields_default_access = :grant` | A declared virtual field no row configures gets the resource's read and edit access, unmasked | `:unconfigured` |
|
|
222
|
+
| `cache_backend = :memory` | Per process: invalidations and auto-applied masking do not reach other processes (boot warning in production) | `:rails_cache` over a shared store (Redis, Solid Cache, Memcached) |
|
|
223
|
+
| `http_basic_auth_enabled = true` | Without credentials the admin answers 401 (closed). With credentials it is one shared login with full admin power. With Basic auth off and `base_controller_class` left as `ActionController::Base`, the admin is open to anyone | Set `base_controller_class` to your authenticated admin controller, then turn Basic auth off |
|
|
224
|
+
| `admin_assignment_authorization = nil` | Every admin who reaches the admin can assign anyone to anything | A lambda that checks the acting admin |
|
|
225
|
+
| `audit_log_authorization = nil` | Every admin (and every API caller `api_auth` admits) can read and export the whole audit log | A lambda that returns exactly `true` for those allowed |
|
|
226
|
+
| `base_controller_class` / `api_base_controller_class` | With tenancy on, a request with no current tenant is refused (403) | Controllers that authenticate and set the tenant |
|
|
227
|
+
| `user_active_check` | Only reported by the security audit; an inactive user keeps every grant | Revoke the assignments of users you deactivate |
|
|
228
|
+
|
|
229
|
+
Upgrading from 0.13: once the last 0.13 process is gone, run `bin/rails permission_forge:cache:clear` (see the
|
|
230
|
+
[CHANGELOG](https://github.com/fetijashari/permission_forge/blob/main/CHANGELOG.md)).
|
|
231
|
+
|
|
232
|
+
Already closed by default, keep them so: `api_require_authorization = true` (API changes need
|
|
233
|
+
`api_action_authorization` and `api_assignment_authorization`), `admin_tenantless_access = :deny`,
|
|
234
|
+
`nested_attributes_parent_fallback = false`, `unconfigured_field_strategy = :block`, and the API is off
|
|
235
|
+
(`api_enabled = false`). If you use PostgreSQL RLS, run `bin/rails permission_forge:rls:sync` after each upgrade.
|
|
236
|
+
|
|
237
|
+
See [Configuration](https://github.com/fetijashari/permission_forge/blob/main/docs/configuration.md) for every option.
|
|
238
|
+
|
|
239
|
+
## What is not intercepted
|
|
240
|
+
|
|
241
|
+
Masking and row rules work at the model layer. These paths read the database or the raw attributes directly:
|
|
242
|
+
|
|
243
|
+
- **Values:** `pluck`, `pick`, calculations (`minimum(:email)`, `maximum`, `sum`, `group(:email).count`),
|
|
244
|
+
`select("email AS contact")` and other SQL aliases (also through `find_by_sql`), `connection.select_*`,
|
|
245
|
+
`*_before_type_cast` / `read_attribute_before_type_cast` / `attributes_before_type_cast`, `*_in_database` /
|
|
246
|
+
`attribute_in_database`, `_read_attribute`, `attributes_for_database`, the dirty-tracking helpers (`*_was`,
|
|
247
|
+
`changes`, `changes_to_save`), `Marshal.dump` and `to_yaml` / `YAML.dump`. Use `FiltersResponseAttributes` on
|
|
248
|
+
hashes you build this way.
|
|
249
|
+
- **Presence:** `where(email: value).exists?` tells whether a value is stored, and `attribute_present?(:email)`
|
|
250
|
+
answers for the stored value even when the column is hidden (the `email?` reader follows the guard).
|
|
251
|
+
- **Rows:** with `auto_query_scoping = :off`, everything except class-level `find_by` / `find_by!` and
|
|
252
|
+
`permission_scoped` (for example `find`, `where`, `scope.find_by`, `association.find_by`). With auto-scoping on,
|
|
253
|
+
`unscoped`, `find_by_sql` and raw SQL still skip it; PostgreSQL RLS covers them for connections that set the user's
|
|
254
|
+
session variable (with `nil_user_strategy = :bypass`, a connection without it sees every row).
|
|
255
|
+
- **Writes that skip callbacks** (`update_all`, `delete_all`, raw SQL) are not audited.
|
|
256
|
+
|
|
257
|
+
Details: [Masked Attributes](https://github.com/fetijashari/permission_forge/blob/main/docs/masked-attributes.md),
|
|
258
|
+
[Query Scoping](https://github.com/fetijashari/permission_forge/blob/main/docs/query-scoping.md).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Audit log
|
|
263
|
+
|
|
264
|
+
Every create, update and destroy, made through the models, of a Permission Set, Resource, field, action, rule, group,
|
|
265
|
+
assignment or membership is written to the `audit_events` table inside the change's own transaction. Under the default
|
|
266
|
+
`audit_change_failure_strategy = :raise`, a change whose event cannot be written rolls back. Each event stores the
|
|
267
|
+
actor, source, request id, IP, an optional reason, a correlation id, and the changes or a snapshot of the deleted row.
|
|
268
|
+
Name the person who acts, otherwise HTTP Basic admins share one name:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
PermissionForge.configure do |config|
|
|
272
|
+
config.audit_actor_resolver = ->(controller) { controller.current_admin }
|
|
273
|
+
config.audit_retention_period = 2.years # purge: bin/rails permission_forge:audit_events:purge
|
|
274
|
+
end
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Read it at `<mount path>/audit`, export it as CSV or JSON, or call `GET /api/audit_events`. It supports evidence for
|
|
278
|
+
access-control and accountability requirements; it is not a certification. It does not record writes that skip
|
|
279
|
+
callbacks or, by default, who read data. The model refuses updates and deletes, but `update_all`, `delete_all` and
|
|
280
|
+
direct database access still reach the table, so protect it at database level, forward it, synchronise clocks and set
|
|
281
|
+
retention. See [Audit Log](https://github.com/fetijashari/permission_forge/blob/main/docs/audit-log.md).
|
|
282
|
+
|
|
283
|
+
## Documentation
|
|
284
|
+
|
|
285
|
+
| Guide | Description |
|
|
286
|
+
|-------|-------------|
|
|
287
|
+
| [Getting Started](https://github.com/fetijashari/permission_forge/blob/main/docs/getting-started.md) | Install, first Permission Set, first checks |
|
|
288
|
+
| [Configuration](https://github.com/fetijashari/permission_forge/blob/main/docs/configuration.md) | Every option and its default |
|
|
289
|
+
| [Architecture](https://github.com/fetijashari/permission_forge/blob/main/docs/architecture.md) | Data model, merge rules, cache and invalidation |
|
|
290
|
+
| [Usage](https://github.com/fetijashari/permission_forge/blob/main/docs/usage.md) | Checking access, custom actions, fields, masking, rules, filtering |
|
|
291
|
+
| [Masked Attributes](https://github.com/fetijashari/permission_forge/blob/main/docs/masked-attributes.md) | Model-level field enforcement and what it does not cover |
|
|
292
|
+
| [Field Filtering](https://github.com/fetijashari/permission_forge/blob/main/docs/field-filtering.md) | Read-path and write-path filtering, nested attributes, virtual fields |
|
|
293
|
+
| [Row-Level Rules](https://github.com/fetijashari/permission_forge/blob/main/docs/row-level-rules.md) | Operators, join types, association paths, STI |
|
|
294
|
+
| [Query Scoping](https://github.com/fetijashari/permission_forge/blob/main/docs/query-scoping.md) | Auto-scoping, unscoped-query detection, PostgreSQL RLS |
|
|
295
|
+
| [Muting Permission Sets](https://github.com/fetijashari/permission_forge/blob/main/docs/muting-permission-sets.md) | Subtracting access |
|
|
296
|
+
| [Activation Conditions](https://github.com/fetijashari/permission_forge/blob/main/docs/activation-conditions.md) | Session flags, IP allowlists, login hours |
|
|
297
|
+
| [Assignment Expiration](https://github.com/fetijashari/permission_forge/blob/main/docs/assignment-expiration.md) | Time-bound assignments and clean-up |
|
|
298
|
+
| [Permission Set Groups](https://github.com/fetijashari/permission_forge/blob/main/docs/permission-set-groups.md) | Grouping Permission Sets |
|
|
299
|
+
| [Controller Actions](https://github.com/fetijashari/permission_forge/blob/main/docs/controller-actions.md) | Permit or block controller actions per Permission Set |
|
|
300
|
+
| [Admin UI](https://github.com/fetijashari/permission_forge/blob/main/docs/admin-ui.md) | Mounting, authentication, screens |
|
|
301
|
+
| [Admin JSON API](https://github.com/fetijashari/permission_forge/blob/main/docs/admin-api.md) | Authentication, CSRF, endpoints, pagination, serializers |
|
|
302
|
+
| [Audit Log](https://github.com/fetijashari/permission_forge/blob/main/docs/audit-log.md) | What is recorded and what is not, exports, retention, protecting the table |
|
|
303
|
+
| [Multi-Tenancy](https://github.com/fetijashari/permission_forge/blob/main/docs/multi-tenancy.md) | `acts_as_tenant` integration |
|
|
304
|
+
| [Security Audit](https://github.com/fetijashari/permission_forge/blob/main/docs/security-audit.md) | `rake permission_forge:audit` and its findings |
|
|
305
|
+
| [GDPR Data Handling](https://github.com/fetijashari/permission_forge/blob/main/docs/gdpr-data-handling.md) | What the gem evidences and what the host must add |
|
|
306
|
+
| [HIPAA](https://github.com/fetijashari/permission_forge/blob/main/docs/hipaa-compliance.md) | Technical safeguards: what the gem evidences and what the host must add |
|
|
307
|
+
| [Masking Disclaimer](https://github.com/fetijashari/permission_forge/blob/main/docs/gdpr_masking_disclaimer.md) | Masking is not anonymization |
|
|
308
|
+
| [Table Name Prefix](https://github.com/fetijashari/permission_forge/blob/main/docs/table-prefix.md) | Custom prefixes, moving between prefixes |
|
|
309
|
+
| [Internationalization](https://github.com/fetijashari/permission_forge/blob/main/docs/i18n.md) | Locales and overrides |
|
|
310
|
+
| [Performance](https://github.com/fetijashari/permission_forge/blob/main/docs/benchmarks.md) | Overhead and cache costs |
|
|
311
|
+
| [Testing](https://github.com/fetijashari/permission_forge/blob/main/docs/testing.md) | Testing permissions in your app |
|
|
312
|
+
| [Migration Guide](https://github.com/fetijashari/permission_forge/blob/main/docs/migration-guide.md) | Upgrading between versions |
|
|
313
|
+
| [Tips & Helpers](https://github.com/fetijashari/permission_forge/blob/main/docs/tips-and-helpers.md) | Common patterns and pitfalls |
|
|
314
|
+
| [AI assistant instructions](https://github.com/fetijashari/permission_forge/blob/main/docs/claude-instructions.md) | A section to paste into your app's `CLAUDE.md` / `AGENTS.md` |
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Versioning and stability
|
|
319
|
+
|
|
320
|
+
PermissionForge follows [Semantic Versioning](https://semver.org/) and is pre-1.0. Until 1.0, a minor version (0.x.0)
|
|
321
|
+
may change defaults and behaviour; every such change is listed under **Changed** in the
|
|
322
|
+
[CHANGELOG](https://github.com/fetijashari/permission_forge/blob/main/CHANGELOG.md) with its opt-out and upgrade
|
|
323
|
+
steps. Patch versions (0.x.y) only fix bugs. The supported surface is the configuration options, the concerns your
|
|
324
|
+
models include, the services documented in the guides, the error classes and the permission hash format
|
|
325
|
+
([Architecture](https://github.com/fetijashari/permission_forge/blob/main/docs/architecture.md)). Other classes are
|
|
326
|
+
internal and may change in any release: classes marked `@api private` in the source (and hidden in the YARD docs) are
|
|
327
|
+
internal. A public API is never renamed or removed in one step: the release that changes it keeps the old name
|
|
328
|
+
working for at least one minor version and warns through `PermissionForge.deprecator` (registered with
|
|
329
|
+
`Rails.application.deprecators`, so `config.active_support.deprecation` governs it), and the CHANGELOG lists it under
|
|
330
|
+
**Deprecated** with the version that removes it. Security fixes go into the latest release only.
|
|
331
|
+
|
|
332
|
+
## Security
|
|
333
|
+
|
|
334
|
+
Please report vulnerabilities privately, as described in
|
|
335
|
+
[SECURITY.md](https://github.com/fetijashari/permission_forge/blob/main/SECURITY.md).
|
|
336
|
+
|
|
337
|
+
## Contributing
|
|
338
|
+
|
|
339
|
+
Bug reports and pull requests are welcome. See
|
|
340
|
+
[CONTRIBUTING.md](https://github.com/fetijashari/permission_forge/blob/main/CONTRIBUTING.md) for the setup, the test
|
|
341
|
+
suites and the rules the code follows, and the
|
|
342
|
+
[Code of Conduct](https://github.com/fetijashari/permission_forge/blob/main/CODE_OF_CONDUCT.md).
|
|
343
|
+
|
|
344
|
+
## Acknowledgements
|
|
345
|
+
|
|
346
|
+
PermissionForge's permission model (resource-level CRUD, field-level visibility and row-level query rules) was
|
|
347
|
+
inspired by [Salesforce's Profile and Permission Set architecture](https://help.salesforce.com/s/articleView?id=sf.perm_sets_overview.htm&type=5),
|
|
348
|
+
adapted for Ruby on Rails.
|
|
349
|
+
|
|
350
|
+
Built at [Confidee](https://confidee.com), where it controls access across internal operations, customer portals and
|
|
351
|
+
supplier portals.
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
The gem is available as open source under the terms of the
|
|
356
|
+
[MIT License](https://github.com/fetijashari/permission_forge/blob/main/LICENSE.txt).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
# The acting admin as a record whose id can be stored (reviewed_by_id,
|
|
3
|
+
# sensitive_acknowledged_by_id, created_by_id), or nil — under HTTP Basic
|
|
4
|
+
# or a resolver that returns something that is not a record. The audit
|
|
5
|
+
# event's actor comes from the audit context either way.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module ActingAdmin
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# @return [Object, nil] pf_current_admin when it has an id
|
|
12
|
+
def pf_acting_admin_record
|
|
13
|
+
admin = pf_current_admin
|
|
14
|
+
admin.respond_to?(:id) ? admin : nil
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PermissionForge
|
|
4
|
+
module Api
|
|
5
|
+
# Template Method concern providing standard CRUD actions for API controllers.
|
|
6
|
+
#
|
|
7
|
+
# Include this concern and override the private template methods to customize
|
|
8
|
+
# behavior per controller. Override any action method for non-standard behavior.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# class ResourceFieldsController < BaseController
|
|
12
|
+
# include CrudActions
|
|
13
|
+
#
|
|
14
|
+
# private
|
|
15
|
+
#
|
|
16
|
+
# def collection_scope = @resource.resource_fields
|
|
17
|
+
# def current_record = @field
|
|
18
|
+
# def serializer_type = :resource_field
|
|
19
|
+
# def record_params = pf_require_hash(:resource_field).permit(:name, :label)
|
|
20
|
+
# end
|
|
21
|
+
module CrudActions
|
|
22
|
+
extend ActiveSupport::Concern
|
|
23
|
+
|
|
24
|
+
def index
|
|
25
|
+
render_collection(collection_scope.order(sort_column), serializer_for(serializer_type))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def show
|
|
29
|
+
render_record(current_record, serializer_for(serializer_type))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create
|
|
33
|
+
record = build_record
|
|
34
|
+
guard_mutation!(:create, record)
|
|
35
|
+
return if performed?
|
|
36
|
+
|
|
37
|
+
if record.save
|
|
38
|
+
render_created(record, serializer_for(serializer_type))
|
|
39
|
+
else
|
|
40
|
+
render_validation_errors(record)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update
|
|
45
|
+
guard_mutation!(:update, current_record)
|
|
46
|
+
return if performed?
|
|
47
|
+
|
|
48
|
+
if current_record.update(record_params)
|
|
49
|
+
render_record(current_record, serializer_for(serializer_type))
|
|
50
|
+
else
|
|
51
|
+
render_validation_errors(current_record)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def destroy
|
|
56
|
+
guard_mutation!(:destroy, current_record)
|
|
57
|
+
return if performed?
|
|
58
|
+
|
|
59
|
+
current_record.destroy!
|
|
60
|
+
head :no_content
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @return [ActiveRecord::Relation] the parent collection scope
|
|
66
|
+
def collection_scope = raise(NotImplementedError, "#{self.class}#collection_scope")
|
|
67
|
+
|
|
68
|
+
# @return [ActiveRecord::Base] the record for show/update/destroy
|
|
69
|
+
def current_record = raise(NotImplementedError, "#{self.class}#current_record")
|
|
70
|
+
|
|
71
|
+
# @return [Symbol] key for serializer_for()
|
|
72
|
+
def serializer_type = raise(NotImplementedError, "#{self.class}#serializer_type")
|
|
73
|
+
|
|
74
|
+
# @return [ActionController::Parameters] permitted params
|
|
75
|
+
def record_params = raise(NotImplementedError, "#{self.class}#record_params")
|
|
76
|
+
|
|
77
|
+
# @return [ActiveRecord::Base] new record built from collection_scope and params
|
|
78
|
+
def build_record = collection_scope.build(record_params)
|
|
79
|
+
|
|
80
|
+
# @return [Symbol] default sort column for index
|
|
81
|
+
def sort_column = :name
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
module Api
|
|
3
|
+
module NestedResourceLookup
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def set_resource
|
|
9
|
+
@permission_set = find_record(PermissionForge::PermissionSet, :permission_set_identifier)
|
|
10
|
+
@resource = find_record(@permission_set.resources, :resource_identifier)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def find_nested_record(scope)
|
|
14
|
+
find_record(scope)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
module Api
|
|
3
|
+
module Pagination
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
# Beyond this the offset overflows the database's integer; the page is
|
|
7
|
+
# empty long before.
|
|
8
|
+
MAX_PAGE = (2**31) - 1
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def paginate_scope(scope, paginator)
|
|
13
|
+
page = current_page
|
|
14
|
+
per = clamped_per_page
|
|
15
|
+
paginator.call(scope, page, per)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def default_paginate_scope(scope)
|
|
19
|
+
page = current_page
|
|
20
|
+
per = clamped_per_page
|
|
21
|
+
total = scope.count
|
|
22
|
+
records = scope.limit(per).offset((page - 1) * per)
|
|
23
|
+
records.define_singleton_method(:total_count) { total }
|
|
24
|
+
records
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def pagination_meta(paginated)
|
|
28
|
+
{
|
|
29
|
+
pagination: {
|
|
30
|
+
page: current_page,
|
|
31
|
+
per_page: clamped_per_page,
|
|
32
|
+
total_count: paginated.respond_to?(:total_count) ? paginated.total_count : paginated.size
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Anything that is not a number (a Hash, an Array) reads as page 1.
|
|
38
|
+
def current_page
|
|
39
|
+
params[:page].to_s.to_i.clamp(1, MAX_PAGE)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def clamped_per_page
|
|
43
|
+
config = PermissionForge.configuration
|
|
44
|
+
requested = params[:per_page]
|
|
45
|
+
requested = requested.nil? ? config.api_default_per_page : requested.to_s.to_i
|
|
46
|
+
requested.clamp(1, config.api_max_per_page)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
module Api
|
|
3
|
+
module ResponseRenderer
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def render_record(record, serializer_class)
|
|
9
|
+
if ams_serializer?(serializer_class)
|
|
10
|
+
render json: record, serializer: serializer_class, root: :data, status: :ok
|
|
11
|
+
else
|
|
12
|
+
render json: { data: serializer_class.new(record).as_json }, status: :ok
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render_created(record, serializer_class)
|
|
17
|
+
if ams_serializer?(serializer_class)
|
|
18
|
+
render json: record, serializer: serializer_class, root: :data, status: :created
|
|
19
|
+
else
|
|
20
|
+
render json: { data: serializer_class.new(record).as_json }, status: :created
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render_collection(scope, serializer_class)
|
|
25
|
+
paginator = PermissionForge.configuration.api_pagination_handler
|
|
26
|
+
records = paginator ? paginate_scope(scope, paginator) : default_paginate_scope(scope)
|
|
27
|
+
meta = pagination_meta(records)
|
|
28
|
+
|
|
29
|
+
if ams_serializer?(serializer_class)
|
|
30
|
+
render json: records, each_serializer: serializer_class, root: :data, meta: meta, status: :ok
|
|
31
|
+
else
|
|
32
|
+
data = serializer_class.collection(records)
|
|
33
|
+
render json: { data: data, meta: meta }, status: :ok
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def render_validation_errors(record)
|
|
38
|
+
render json: { error: { status: 422, messages: record.errors.messages } }, status: :unprocessable_entity
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Saves a record and renders the created resource or its validation errors.
|
|
42
|
+
def save_and_render(record, serializer_type)
|
|
43
|
+
if record.save
|
|
44
|
+
render_created(record, serializer_for(serializer_type))
|
|
45
|
+
else
|
|
46
|
+
render_validation_errors(record)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_not_authorized(message = nil)
|
|
51
|
+
msg = message || I18n.t('permission_forge.api.errors.forbidden')
|
|
52
|
+
render json: { error: { status: 403, messages: msg } }, status: :forbidden
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render_not_found(message = nil)
|
|
56
|
+
msg = message || I18n.t('permission_forge.api.errors.not_found')
|
|
57
|
+
render json: { error: { status: 404, messages: msg } }, status: :not_found
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ams_serializer?(serializer_class)
|
|
61
|
+
defined?(ActiveModel::Serializer) && serializer_class < ActiveModel::Serializer
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module PermissionForge
|
|
2
|
+
module Api
|
|
3
|
+
module SerializerResolver
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def serializer_for(resource_type)
|
|
9
|
+
overrides = PermissionForge.configuration.api_serializer_overrides
|
|
10
|
+
return overrides[resource_type] if overrides.key?(resource_type)
|
|
11
|
+
|
|
12
|
+
default_serializers.fetch(resource_type)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def default_serializers
|
|
16
|
+
{
|
|
17
|
+
permission_set: PermissionForge::Api::Serializers::PermissionSetSerializer,
|
|
18
|
+
resource: PermissionForge::Api::Serializers::ResourceSerializer,
|
|
19
|
+
resource_field: PermissionForge::Api::Serializers::ResourceFieldSerializer,
|
|
20
|
+
resource_action: PermissionForge::Api::Serializers::ResourceActionSerializer,
|
|
21
|
+
permission_set_rule: PermissionForge::Api::Serializers::PermissionSetRuleSerializer,
|
|
22
|
+
permission_set_user: PermissionForge::Api::Serializers::PermissionSetUserSerializer,
|
|
23
|
+
permission_set_group: PermissionForge::Api::Serializers::PermissionSetGroupSerializer,
|
|
24
|
+
permission_set_group_user: PermissionForge::Api::Serializers::PermissionSetGroupUserSerializer,
|
|
25
|
+
audit_event: PermissionForge::Api::Serializers::AuditEventSerializer
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|