permission_forge 0.14.1 → 0.15.0

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.
Files changed (347) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2372 -0
  3. data/README.md +76 -25
  4. data/app/controllers/concerns/permission_forge/api/crud_actions.rb +3 -2
  5. data/app/controllers/concerns/permission_forge/api/input_shapes.rb +200 -0
  6. data/app/controllers/concerns/permission_forge/api/pagination.rb +12 -5
  7. data/app/controllers/concerns/permission_forge/api/response_renderer.rb +37 -1
  8. data/app/controllers/concerns/permission_forge/api_authentication.rb +13 -2
  9. data/app/controllers/concerns/permission_forge/cache_confirmation.rb +42 -11
  10. data/app/controllers/concerns/permission_forge/change_review.rb +42 -0
  11. data/app/controllers/concerns/permission_forge/copy_refusal.rb +39 -0
  12. data/app/controllers/concerns/permission_forge/param_shapes.rb +207 -7
  13. data/app/controllers/concerns/permission_forge/save_notice.rb +69 -0
  14. data/app/controllers/permission_forge/api/audit_events_controller.rb +1 -1
  15. data/app/controllers/permission_forge/api/base_controller.rb +163 -12
  16. data/app/controllers/permission_forge/api/dashboard_controller.rb +29 -4
  17. data/app/controllers/permission_forge/api/permission_set_group_users_controller.rb +5 -2
  18. data/app/controllers/permission_forge/api/permission_set_groups_controller.rb +5 -4
  19. data/app/controllers/permission_forge/api/permission_set_rules_controller.rb +7 -5
  20. data/app/controllers/permission_forge/api/permission_set_users_controller.rb +80 -19
  21. data/app/controllers/permission_forge/api/permission_sets_controller.rb +12 -6
  22. data/app/controllers/permission_forge/api/resource_actions_controller.rb +1 -1
  23. data/app/controllers/permission_forge/api/resource_fields_controller.rb +3 -4
  24. data/app/controllers/permission_forge/api/resources_controller.rb +26 -12
  25. data/app/controllers/permission_forge/api/unmatched_routes_controller.rb +20 -0
  26. data/app/controllers/permission_forge/application_controller.rb +39 -5
  27. data/app/controllers/permission_forge/assignments_controller.rb +36 -34
  28. data/app/controllers/permission_forge/bulk_assignments_controller.rb +8 -4
  29. data/app/controllers/permission_forge/field_lookups_controller.rb +1 -1
  30. data/app/controllers/permission_forge/maintenance_controller.rb +6 -2
  31. data/app/controllers/permission_forge/people_controller.rb +14 -14
  32. data/app/controllers/permission_forge/permission_set_editor.rb +20 -6
  33. data/app/controllers/permission_forge/permission_set_group_users_controller.rb +3 -1
  34. data/app/controllers/permission_forge/permission_set_groups_controller.rb +23 -9
  35. data/app/controllers/permission_forge/permission_set_rules_controller.rb +20 -10
  36. data/app/controllers/permission_forge/permission_sets_controller.rb +23 -24
  37. data/app/controllers/permission_forge/resource_actions_controller.rb +11 -6
  38. data/app/controllers/permission_forge/resource_fields_controller.rb +37 -19
  39. data/app/controllers/permission_forge/resources_controller.rb +12 -11
  40. data/app/frontend/permission_forge/pf_admin.css +99 -17
  41. data/app/frontend/permission_forge/pf_admin.js +36 -0
  42. data/app/helpers/permission_forge/application_helper.rb +22 -72
  43. data/app/helpers/permission_forge/audit_attribution_helper.rb +77 -0
  44. data/app/helpers/permission_forge/audit_helper.rb +57 -119
  45. data/app/helpers/permission_forge/audit_records_helper.rb +103 -0
  46. data/app/helpers/permission_forge/audit_sentences_helper.rb +56 -2
  47. data/app/helpers/permission_forge/badges_helper.rb +74 -17
  48. data/app/helpers/permission_forge/changes_helper.rb +24 -6
  49. data/app/helpers/permission_forge/conditions_helper.rb +119 -0
  50. data/app/helpers/permission_forge/labels_helper.rb +134 -0
  51. data/app/helpers/permission_forge/people_helper.rb +11 -19
  52. data/app/helpers/permission_forge/rule_problems_helper.rb +73 -0
  53. data/app/helpers/permission_forge/sentences_helper.rb +119 -98
  54. data/app/helpers/permission_forge/settings_helper.rb +2 -2
  55. data/app/helpers/permission_forge/ui_helper.rb +65 -4
  56. data/app/models/permission_forge/audit_event.rb +5 -1
  57. data/app/models/permission_forge/permission_set.rb +208 -18
  58. data/app/models/permission_forge/permission_set_group.rb +10 -4
  59. data/app/models/permission_forge/permission_set_group_user.rb +3 -1
  60. data/app/models/permission_forge/permission_set_rule.rb +254 -17
  61. data/app/models/permission_forge/permission_set_user.rb +38 -3
  62. data/app/models/permission_forge/resource.rb +18 -8
  63. data/app/models/permission_forge/resource_action.rb +15 -6
  64. data/app/models/permission_forge/resource_field.rb +97 -11
  65. data/app/views/permission_forge/assignments/confirm_destroy.html.erb +4 -4
  66. data/app/views/permission_forge/assignments/new.html.erb +10 -6
  67. data/app/views/permission_forge/audit_events/index.html.erb +1 -1
  68. data/app/views/permission_forge/audit_events/show.html.erb +16 -9
  69. data/app/views/permission_forge/bulk_assignments/confirm_revoke.html.erb +3 -3
  70. data/app/views/permission_forge/bulk_assignments/new.html.erb +3 -3
  71. data/app/views/permission_forge/field_lookups/show.html.erb +2 -2
  72. data/app/views/permission_forge/home/_broken_rules_card.html.erb +35 -0
  73. data/app/views/permission_forge/home/_conditions_card.html.erb +28 -0
  74. data/app/views/permission_forge/home/_ending_card.html.erb +11 -5
  75. data/app/views/permission_forge/home/_expired_card.html.erb +1 -1
  76. data/app/views/permission_forge/home/_mask_strategies_card.html.erb +27 -0
  77. data/app/views/permission_forge/home/_privacy_card.html.erb +21 -7
  78. data/app/views/permission_forge/home/_rules_card.html.erb +1 -1
  79. data/app/views/permission_forge/home/_secret_rules_card.html.erb +33 -0
  80. data/app/views/permission_forge/home/_stale_card.html.erb +7 -5
  81. data/app/views/permission_forge/home/_tidy_card.html.erb +1 -1
  82. data/app/views/permission_forge/home/index.html.erb +5 -6
  83. data/app/views/permission_forge/people/_access_tab.html.erb +26 -19
  84. data/app/views/permission_forge/people/_audit_tab.html.erb +8 -8
  85. data/app/views/permission_forge/people/_effective_field_source.html.erb +13 -7
  86. data/app/views/permission_forge/people/_effective_tab.html.erb +16 -15
  87. data/app/views/permission_forge/people/_effective_why.html.erb +20 -16
  88. data/app/views/permission_forge/people/_why_item.html.erb +1 -1
  89. data/app/views/permission_forge/people/confirm_remove_all.html.erb +3 -3
  90. data/app/views/permission_forge/people/erasure_preview.html.erb +1 -1
  91. data/app/views/permission_forge/permission_set_groups/confirm_destroy.html.erb +3 -3
  92. data/app/views/permission_forge/permission_set_groups/index.html.erb +3 -3
  93. data/app/views/permission_forge/permission_set_groups/show.html.erb +28 -19
  94. data/app/views/permission_forge/permission_set_rules/_builder.html.erb +35 -15
  95. data/app/views/permission_forge/permission_set_rules/_preview.html.erb +17 -0
  96. data/app/views/permission_forge/permission_set_rules/confirm_destroy.html.erb +3 -3
  97. data/app/views/permission_forge/permission_set_rules/preview.html.erb +12 -0
  98. data/app/views/permission_forge/permission_sets/_assignments_tab.html.erb +37 -22
  99. data/app/views/permission_forge/permission_sets/_audit_tab.html.erb +4 -4
  100. data/app/views/permission_forge/permission_sets/_condition_problems.html.erb +15 -0
  101. data/app/views/permission_forge/permission_sets/_conditions_tab.html.erb +14 -8
  102. data/app/views/permission_forge/permission_sets/_header.html.erb +6 -5
  103. data/app/views/permission_forge/permission_sets/_records_summary.html.erb +2 -2
  104. data/app/views/permission_forge/permission_sets/_resources_tab.html.erb +4 -4
  105. data/app/views/permission_forge/permission_sets/confirm_destroy.html.erb +4 -4
  106. data/app/views/permission_forge/permission_sets/confirm_switch_mode.html.erb +3 -3
  107. data/app/views/permission_forge/permission_sets/index.html.erb +6 -6
  108. data/app/views/permission_forge/permission_sets/new.html.erb +2 -2
  109. data/app/views/permission_forge/permission_sets/review.html.erb +13 -3
  110. data/app/views/permission_forge/permission_sets/show.html.erb +7 -5
  111. data/app/views/permission_forge/resource_actions/_across_sets.html.erb +7 -10
  112. data/app/views/permission_forge/resource_actions/_effect.html.erb +5 -5
  113. data/app/views/permission_forge/resource_actions/_form.html.erb +1 -1
  114. data/app/views/permission_forge/resource_actions/_managed_note.html.erb +5 -3
  115. data/app/views/permission_forge/resource_actions/confirm_destroy.html.erb +1 -1
  116. data/app/views/permission_forge/resource_actions/new.html.erb +1 -1
  117. data/app/views/permission_forge/resource_actions/show.html.erb +2 -2
  118. data/app/views/permission_forge/resource_fields/_explain.html.erb +19 -15
  119. data/app/views/permission_forge/resource_fields/show.html.erb +21 -12
  120. data/app/views/permission_forge/resources/_actions_tab.html.erb +4 -4
  121. data/app/views/permission_forge/resources/_fields_tab.html.erb +17 -9
  122. data/app/views/permission_forge/resources/_rules_tab.html.erb +9 -6
  123. data/app/views/permission_forge/resources/_sync.html.erb +8 -5
  124. data/app/views/permission_forge/resources/confirm_destroy.html.erb +3 -3
  125. data/app/views/permission_forge/resources/new.html.erb +3 -2
  126. data/app/views/permission_forge/resources/show.html.erb +6 -6
  127. data/app/views/permission_forge/settings/_row.html.erb +2 -2
  128. data/app/views/permission_forge/settings/show.html.erb +10 -1
  129. data/app/views/permission_forge/shared/_across_sets.html.erb +8 -9
  130. data/app/views/permission_forge/shared/_audit_events.html.erb +2 -2
  131. data/app/views/permission_forge/shared/_audit_footer.html.erb +13 -2
  132. data/app/views/permission_forge/shared/_crud_cell.html.erb +6 -6
  133. data/app/views/permission_forge/shared/_mode_banners.html.erb +1 -1
  134. data/app/views/permission_forge/shared/_savebar.html.erb +4 -2
  135. data/config/locales/permission_forge.en.yml +480 -69
  136. data/config/locales/permission_forge.mk.yml +499 -79
  137. data/config/locales/permission_forge.sq.yml +509 -80
  138. data/config/routes.rb +38 -23
  139. data/lib/generators/permission_forge/install/install_generator.rb +21 -0
  140. data/lib/generators/permission_forge/install/templates/initializer.rb.tt +35 -34
  141. data/lib/generators/permission_forge/install/templates/migration.rb.tt +5 -5
  142. data/lib/permission_forge/activation_conditions.rb +200 -0
  143. data/lib/permission_forge/admin/action_label.rb +33 -0
  144. data/lib/permission_forge/admin/action_rows.rb +3 -1
  145. data/lib/permission_forge/admin/actor_label.rb +52 -0
  146. data/lib/permission_forge/admin/attention_items.rb +152 -31
  147. data/lib/permission_forge/admin/audit_log.rb +4 -7
  148. data/lib/permission_forge/admin/audit_trail.rb +33 -17
  149. data/lib/permission_forge/admin/bulk_people_matcher.rb +32 -14
  150. data/lib/permission_forge/admin/chain_entries.rb +42 -0
  151. data/lib/permission_forge/admin/change_set.rb +12 -1
  152. data/lib/permission_forge/admin/change_set_parts/base.rb +5 -4
  153. data/lib/permission_forge/admin/change_set_parts/context.rb +3 -1
  154. data/lib/permission_forge/admin/change_set_parts/field_edit.rb +4 -1
  155. data/lib/permission_forge/admin/change_set_parts/field_levels.rb +1 -1
  156. data/lib/permission_forge/admin/change_set_parts/new_actions.rb +1 -1
  157. data/lib/permission_forge/admin/change_set_parts/new_fields.rb +38 -11
  158. data/lib/permission_forge/admin/change_set_parts/resource_edit.rb +17 -1
  159. data/lib/permission_forge/admin/change_set_parts/resource_flags.rb +1 -1
  160. data/lib/permission_forge/admin/change_set_parts/set_attrs.rb +4 -0
  161. data/lib/permission_forge/admin/change_set_parts/values.rb +10 -4
  162. data/lib/permission_forge/admin/column_fields.rb +22 -21
  163. data/lib/permission_forge/admin/conditions_form.rb +47 -15
  164. data/lib/permission_forge/admin/controller_catalog.rb +42 -14
  165. data/lib/permission_forge/admin/editable_lock.rb +83 -3
  166. data/lib/permission_forge/admin/effective_access.rb +147 -48
  167. data/lib/permission_forge/admin/effective_fields.rb +75 -67
  168. data/lib/permission_forge/admin/error_sentence.rb +47 -0
  169. data/lib/permission_forge/admin/expiry_presets.rb +17 -0
  170. data/lib/permission_forge/admin/exports/holdings.rb +6 -3
  171. data/lib/permission_forge/admin/field_across_sets.rb +7 -4
  172. data/lib/permission_forge/admin/field_chain.rb +109 -0
  173. data/lib/permission_forge/admin/field_detail.rb +24 -11
  174. data/lib/permission_forge/admin/field_label.rb +109 -0
  175. data/lib/permission_forge/admin/field_level.rb +4 -68
  176. data/lib/permission_forge/admin/field_provenance.rb +132 -0
  177. data/lib/permission_forge/admin/field_rows.rb +26 -11
  178. data/lib/permission_forge/admin/last_changes.rb +14 -0
  179. data/lib/permission_forge/admin/list_sentence.rb +32 -0
  180. data/lib/permission_forge/admin/mask_strategy_choices.rb +57 -0
  181. data/lib/permission_forge/admin/model_catalog.rb +83 -39
  182. data/lib/permission_forge/admin/model_label.rb +66 -0
  183. data/lib/permission_forge/admin/people_candidates.rb +21 -9
  184. data/lib/permission_forge/admin/people_directory.rb +23 -5
  185. data/lib/permission_forge/admin/permission_set_factory.rb +6 -15
  186. data/lib/permission_forge/admin/person_label.rb +105 -16
  187. data/lib/permission_forge/admin/prefill.rb +92 -0
  188. data/lib/permission_forge/admin/record_name.rb +45 -0
  189. data/lib/permission_forge/admin/rule_builder.rb +77 -3
  190. data/lib/permission_forge/admin/rule_form.rb +44 -10
  191. data/lib/permission_forge/admin/rule_form_input.rb +27 -11
  192. data/lib/permission_forge/admin/rule_notes.rb +43 -0
  193. data/lib/permission_forge/admin/rule_preview.rb +96 -0
  194. data/lib/permission_forge/admin/sensitive_columns.rb +10 -62
  195. data/lib/permission_forge/admin/settings_catalog.rb +64 -12
  196. data/lib/permission_forge/admin/state_label.rb +97 -0
  197. data/lib/permission_forge/admin_method_override.rb +44 -0
  198. data/lib/permission_forge/api/schema.rb +322 -0
  199. data/lib/permission_forge/api/serializers/base_serializer.rb +87 -1
  200. data/lib/permission_forge/api/serializers/permission_set_group_user_serializer.rb +1 -1
  201. data/lib/permission_forge/api/serializers/permission_set_rule_serializer.rb +1 -1
  202. data/lib/permission_forge/api/serializers/permission_set_serializer.rb +5 -2
  203. data/lib/permission_forge/api/serializers/permission_set_user_serializer.rb +1 -1
  204. data/lib/permission_forge/api/serializers/resource_action_serializer.rb +1 -1
  205. data/lib/permission_forge/api/serializers/resource_field_serializer.rb +1 -1
  206. data/lib/permission_forge/api/serializers/resource_serializer.rb +1 -1
  207. data/lib/permission_forge/api_authorizations.rb +59 -0
  208. data/lib/permission_forge/api_only_admin.rb +81 -0
  209. data/lib/permission_forge/audit/actor_codes.rb +32 -0
  210. data/lib/permission_forge/audit/actor_resolver.rb +1 -2
  211. data/lib/permission_forge/audit/boot_warnings.rb +29 -9
  212. data/lib/permission_forge/audit/event_catalog.rb +16 -6
  213. data/lib/permission_forge/audit/event_query.rb +3 -3
  214. data/lib/permission_forge/audit/reason_codes.rb +25 -0
  215. data/lib/permission_forge/audit/runtime.rb +9 -2
  216. data/lib/permission_forge/audit/settings_observer.rb +2 -2
  217. data/lib/permission_forge/audit/unmasked_without_user.rb +169 -0
  218. data/lib/permission_forge/audit.rb +24 -10
  219. data/lib/permission_forge/boot_checks.rb +180 -63
  220. data/lib/permission_forge/cache/base.rb +10 -0
  221. data/lib/permission_forge/cache/null_cache.rb +5 -0
  222. data/lib/permission_forge/cache/outcome.rb +119 -0
  223. data/lib/permission_forge/cache/rails_cache.rb +6 -0
  224. data/lib/permission_forge/cache/report.rb +150 -0
  225. data/lib/permission_forge/cache/user_keys.rb +8 -4
  226. data/lib/permission_forge/cache_warnings.rb +140 -0
  227. data/lib/permission_forge/concerns/auto_permission_scoping.rb +19 -2
  228. data/lib/permission_forge/concerns/has_tenant_scoping.rb +1 -1
  229. data/lib/permission_forge/concerns/has_uuid.rb +1 -1
  230. data/lib/permission_forge/concerns/keeps_copied_values.rb +48 -0
  231. data/lib/permission_forge/concerns/masked_attributes/alias_access.rb +12 -2
  232. data/lib/permission_forge/concerns/masked_attributes/virtual_readers.rb +6 -4
  233. data/lib/permission_forge/concerns/masked_attributes.rb +150 -34
  234. data/lib/permission_forge/concerns/normalizes_label.rb +78 -0
  235. data/lib/permission_forge/concerns/permission_cache_accessor.rb +3 -3
  236. data/lib/permission_forge/concerns/permission_scoped_findable.rb +121 -29
  237. data/lib/permission_forge/concerns/permissioned_user.rb +1 -1
  238. data/lib/permission_forge/concerns/records_audit_events.rb +13 -6
  239. data/lib/permission_forge/concerns/requires_name.rb +37 -0
  240. data/lib/permission_forge/concerns/validates_column_lengths.rb +24 -0
  241. data/lib/permission_forge/concerns/validates_dependency.rb +5 -2
  242. data/lib/permission_forge/concerns/validates_required_columns.rb +74 -0
  243. data/lib/permission_forge/conditional_sets.rb +13 -6
  244. data/lib/permission_forge/configuration/admin_ui_settings.rb +18 -4
  245. data/lib/permission_forge/configuration/audit_settings.rb +2 -1
  246. data/lib/permission_forge/configuration/controller_action_settings.rb +5 -4
  247. data/lib/permission_forge/configuration/masked_attributes_settings.rb +73 -19
  248. data/lib/permission_forge/configuration/presets.rb +20 -11
  249. data/lib/permission_forge/configuration/query_scoping_settings.rb +55 -0
  250. data/lib/permission_forge/configuration/strategy_values.rb +2 -1
  251. data/lib/permission_forge/configuration.rb +41 -18
  252. data/lib/permission_forge/controller_actions/managed_index.rb +8 -7
  253. data/lib/permission_forge/database_reach.rb +93 -0
  254. data/lib/permission_forge/deprecation.rb +1 -1
  255. data/lib/permission_forge/docs/api_samples.rb +250 -0
  256. data/lib/permission_forge/docs/configuration_reference.rb +172 -0
  257. data/lib/permission_forge/encrypted_attributes.rb +150 -0
  258. data/lib/permission_forge/engine.rb +46 -14
  259. data/lib/permission_forge/execution_state/restore.rb +74 -0
  260. data/lib/permission_forge/execution_state.rb +8 -1
  261. data/lib/permission_forge/field_level.rb +123 -0
  262. data/lib/permission_forge/field_names.rb +145 -0
  263. data/lib/permission_forge/field_variants.rb +96 -0
  264. data/lib/permission_forge/generated_text.rb +95 -0
  265. data/lib/permission_forge/inspection_filters.rb +83 -0
  266. data/lib/permission_forge/managed_fields.rb +66 -0
  267. data/lib/permission_forge/masked_models.rb +25 -19
  268. data/lib/permission_forge/masking_sync.rb +100 -25
  269. data/lib/permission_forge/model_bindings.rb +30 -19
  270. data/lib/permission_forge/model_lookup.rb +42 -0
  271. data/lib/permission_forge/operators/encrypted_alias.rb +67 -0
  272. data/lib/permission_forge/operators/registry.rb +6 -0
  273. data/lib/permission_forge/operators/rule_column.rb +242 -27
  274. data/lib/permission_forge/operators/rule_path.rb +179 -0
  275. data/lib/permission_forge/operators/rule_problem.rb +309 -0
  276. data/lib/permission_forge/operators/rule_sources.rb +96 -0
  277. data/lib/permission_forge/operators/value_casters/postgresql_types.rb +237 -0
  278. data/lib/permission_forge/operators/value_casters/temporal.rb +9 -20
  279. data/lib/permission_forge/operators/value_casters.rb +61 -13
  280. data/lib/permission_forge/operators/value_typing.rb +83 -7
  281. data/lib/permission_forge/query_scoping_warnings.rb +22 -17
  282. data/lib/permission_forge/rake_audit.rb +5 -4
  283. data/lib/permission_forge/rake_dry_run.rb +32 -0
  284. data/lib/permission_forge/rake_output.rb +189 -0
  285. data/lib/permission_forge/record_read_access.rb +51 -0
  286. data/lib/permission_forge/resource_chain.rb +14 -2
  287. data/lib/permission_forge/schema_read/ordered_types.rb +72 -0
  288. data/lib/permission_forge/schema_read.rb +335 -0
  289. data/lib/permission_forge/secret_columns.rb +49 -0
  290. data/lib/permission_forge/sensitive_columns.rb +114 -0
  291. data/lib/permission_forge/services/attribute_access_guard.rb +22 -14
  292. data/lib/permission_forge/services/bulk_permission_set_assignment.rb +16 -8
  293. data/lib/permission_forge/services/concerns/clonable.rb +9 -0
  294. data/lib/permission_forge/services/concerns/settles_cache.rb +58 -0
  295. data/lib/permission_forge/services/consent_withdrawal_handler.rb +37 -19
  296. data/lib/permission_forge/services/data_subject_erasure.rb +83 -45
  297. data/lib/permission_forge/services/expired_assignment_cleaner.rb +62 -25
  298. data/lib/permission_forge/services/extra_field_defaults.rb +1 -1
  299. data/lib/permission_forge/services/field_permission_merger.rb +44 -17
  300. data/lib/permission_forge/services/filters_response_attributes.rb +20 -5
  301. data/lib/permission_forge/services/gdpr_field_auditor/text_report.rb +33 -5
  302. data/lib/permission_forge/services/gdpr_field_auditor.rb +68 -48
  303. data/lib/permission_forge/services/host_key_guard.rb +8 -5
  304. data/lib/permission_forge/services/mask_value.rb +72 -11
  305. data/lib/permission_forge/services/permission_cache_clearer.rb +36 -15
  306. data/lib/permission_forge/services/permission_hash_builder.rb +48 -19
  307. data/lib/permission_forge/services/permission_rule_applier.rb +131 -68
  308. data/lib/permission_forge/services/permission_set_cloner.rb +32 -8
  309. data/lib/permission_forge/services/permission_set_condition_evaluator.rb +38 -36
  310. data/lib/permission_forge/services/permission_set_group_cloner.rb +2 -1
  311. data/lib/permission_forge/services/permits_attributes/result.rb +31 -0
  312. data/lib/permission_forge/services/permits_attributes.rb +48 -27
  313. data/lib/permission_forge/services/resource_field_generator/row_builder.rb +96 -0
  314. data/lib/permission_forge/services/resource_field_generator.rb +71 -42
  315. data/lib/permission_forge/services/resource_with_fields_builder.rb +2 -1
  316. data/lib/permission_forge/services/rls_operator_sql.rb +83 -24
  317. data/lib/permission_forge/services/rls_policy_generator.rb +83 -39
  318. data/lib/permission_forge/services/rls_read_access_sql.rb +131 -0
  319. data/lib/permission_forge/services/security_audit/action_check.rb +49 -5
  320. data/lib/permission_forge/services/security_audit/assignment_check.rb +2 -1
  321. data/lib/permission_forge/services/security_audit/audit_trail_check.rb +1 -3
  322. data/lib/permission_forge/services/security_audit/conditions_check.rb +92 -0
  323. data/lib/permission_forge/services/security_audit/config_check.rb +41 -18
  324. data/lib/permission_forge/services/security_audit/integrity_check.rb +71 -4
  325. data/lib/permission_forge/services/security_audit/masking_without_user.rb +66 -0
  326. data/lib/permission_forge/services/security_audit/personal_data_check.rb +7 -11
  327. data/lib/permission_forge/services/security_audit/rule_check.rb +180 -50
  328. data/lib/permission_forge/services/security_audit/text_report.rb +14 -2
  329. data/lib/permission_forge/services/security_audit/unmanaged_actions.rb +145 -0
  330. data/lib/permission_forge/services/security_audit/user_model_check.rb +4 -3
  331. data/lib/permission_forge/services/security_audit.rb +16 -4
  332. data/lib/permission_forge/services/stale_assignment_auditor.rb +8 -5
  333. data/lib/permission_forge/timestamp_fields.rb +60 -0
  334. data/lib/permission_forge/unique_conflict.rb +117 -0
  335. data/lib/permission_forge/version.rb +1 -1
  336. data/lib/permission_forge/virtual_fields/discovery.rb +2 -1
  337. data/lib/permission_forge/virtual_fields.rb +15 -5
  338. data/lib/permission_forge/warnings.rb +263 -0
  339. data/lib/permission_forge/web_request.rb +248 -0
  340. data/lib/permission_forge.rb +54 -0
  341. data/lib/tasks/permission_forge.rake +26 -6
  342. data/lib/tasks/permission_forge_audit_events.rake +8 -4
  343. data/lib/tasks/permission_forge_cache.rake +12 -12
  344. data/lib/tasks/permission_forge_docs.rake +48 -0
  345. data/lib/tasks/permission_forge_expiration.rake +11 -5
  346. data/lib/tasks/permission_forge_gdpr.rake +36 -20
  347. metadata +84 -1
@@ -3,7 +3,7 @@
3
3
  module PermissionForge
4
4
  module Api
5
5
  class DashboardController < BaseController
6
- before_action :require_admin!, only: :show
6
+ before_action :authorize_stats!, only: :show
7
7
 
8
8
  def show
9
9
  render json: {
@@ -20,10 +20,35 @@ module PermissionForge
20
20
 
21
21
  private
22
22
 
23
- def require_admin!
24
- return unless PermissionForge.configuration.api_stats_require_admin
23
+ # While config.api_stats_require_admin is on (the default), only a
24
+ # caller config.api_stats_authorization allows reads the stats; a
25
+ # refusal is a 403 recorded as api.auth_failed (stats_not_authorized).
26
+ # Without the lambda the 403 names it, as for the other lambdas.
27
+ def authorize_stats!
28
+ configuration = PermissionForge.configuration
29
+ return unless configuration.api_stats_require_admin
25
30
 
26
- head :forbidden unless @admin
31
+ authorizer = configuration.api_stats_authorization
32
+ return if authorizer ? authorizer.call(self) : legacy_admin_flag?
33
+
34
+ pf_audit_refused('api.auth_failed', data: { 'reason' => 'stats_not_authorized',
35
+ 'hook' => authorizer ? 'api_stats_authorization' : 'api_stats_require_admin' })
36
+ render_not_authorized(authorizer ? nil : I18n.t('permission_forge.api.errors.stats_authorization_missing'))
37
+ end
38
+
39
+ # Through 0.15 the stats endpoint read an @admin = true that api_auth
40
+ # set on the controller (documented in 0.14). Still honoured when
41
+ # api_stats_authorization is not set, with a deprecation warning;
42
+ # removed in 0.16.
43
+ def legacy_admin_flag?
44
+ return false unless @admin
45
+
46
+ PermissionForge::Deprecation.warn(
47
+ 'Setting @admin on the API controller (in api_auth) to allow GET /api/stats is deprecated; ' \
48
+ 'set config.api_stats_authorization = ->(controller) { ... } instead',
49
+ key: :api_stats_admin_ivar
50
+ )
51
+ true
27
52
  end
28
53
  end
29
54
  end
@@ -15,14 +15,17 @@ module PermissionForge
15
15
  render_collection(scope, serializer_for(:permission_set_group_user))
16
16
  end
17
17
 
18
+ # A user_id that is missing, not an id, or names nobody
19
+ # config.user_list_resolver lists is a 422 keyed by user_id
20
+ # (BaseController#requested_user), after the authorization hooks.
18
21
  def create
19
22
  user_id = params[:user_id]
20
23
  authorize_assignment!(@group, [user_id])
21
24
  enforce_editable!(@group) unless performed?
22
25
  return if performed?
23
26
 
24
- user = find_host_user(user_id)
25
- return if performed? || pf_assignment_refused_api?(@group, user.id, :add_member)
27
+ user = requested_user
28
+ return if pf_assignment_refused_api?(@group, user.id, :add_member)
26
29
 
27
30
  gu = @group.permission_set_group_users.build(user: user)
28
31
  save_and_render(gu, :permission_set_group_user)
@@ -49,10 +49,11 @@ module PermissionForge
49
49
  authorize_action!(:clone, @group)
50
50
  return if performed?
51
51
 
52
- # As the admin's Duplicate: the copy of the group and of its sets is editable.
52
+ # As the admin's Duplicate: the copy of the group and of its sets is
53
+ # editable, and named "<name> (copy)" in the request's locale.
54
+ name = pf_copy_name.presence || I18n.t('permission_forge.permission_sets.copy_name', name: @group.name)
53
55
  cloned = PermissionForge::Services::PermissionSetGroupCloner.call(
54
- permission_set_group: @group, attrs: { name: pf_text_param(:name).presence || "#{@group.name} (Copy)", is_editable: true },
55
- unlock: true
56
+ permission_set_group: @group, attrs: { name: name, is_editable: true }, unlock: true
56
57
  )
57
58
  render_created(cloned, serializer_for(:permission_set_group))
58
59
  end
@@ -64,7 +65,7 @@ module PermissionForge
64
65
  end
65
66
 
66
67
  def group_params
67
- pf_require_hash(:permission_set_group).permit(:name, :description, :is_active, :is_editable)
68
+ pf_record_attributes(:permission_set_group, PermissionForge::PermissionSetGroup, :name, :description, :is_active, :is_editable)
68
69
  end
69
70
  end
70
71
  end
@@ -25,12 +25,14 @@ module PermissionForge
25
25
  @rule = find_nested_record(@resource.permission_set_rules)
26
26
  end
27
27
 
28
+ # A list for field_value under in / not_in / between is joined with
29
+ # commas; the stored rule's operator decides on an update that sends
30
+ # none (Api::InputShapes).
28
31
  def record_params
29
- pf_require_hash(:permission_set_rule).permit(
30
- :name, :field_name, :field_value, :operator,
31
- :join_type, :field_accessor_path, :include_nulls,
32
- :is_active, :is_editable, :failure_message
33
- )
32
+ pf_record_attributes(:permission_set_rule, PermissionForge::PermissionSetRule,
33
+ :name, :field_name, :field_value, :operator,
34
+ :join_type, :field_accessor_path, :include_nulls,
35
+ :is_active, :is_editable, :failure_message, record: current_record)
34
36
  end
35
37
  end
36
38
  end
@@ -19,13 +19,16 @@ module PermissionForge
19
19
  render_collection(scope, serializer_for(:permission_set_user))
20
20
  end
21
21
 
22
+ # A user_id that is missing, not an id, or names nobody
23
+ # config.user_list_resolver lists is a 422 keyed by user_id
24
+ # (BaseController#requested_user), after the authorization hooks.
22
25
  def create
23
26
  authorize_assignment!(@permission_set, [params[:user_id]])
24
27
  reject_widening!(:grant) unless performed?
25
28
  return if performed?
26
29
 
27
- user = find_host_user(params[:user_id])
28
- return if performed? || pf_assignment_refused_api?(@permission_set, user.id, :grant)
30
+ user = requested_user
31
+ return if pf_assignment_refused_api?(@permission_set, user.id, :grant)
29
32
 
30
33
  create_assignment(user)
31
34
  end
@@ -41,20 +44,20 @@ module PermissionForge
41
44
  reject_widening!(:revoke) unless performed?
42
45
  return if performed? || pf_assignment_refused_api?(@permission_set, psu.user_id, :revoke)
43
46
 
44
- psu.destroy!
45
- head :no_content
47
+ # The revoked assignment and what the cache store confirmed
48
+ # (meta.cache, as the bulk endpoint answers it); through 0.14.x a 204.
49
+ outcome = PermissionForge::Cache::Report.of { psu.destroy! }
50
+ render_record_with_cache(psu, outcome, status: :ok)
46
51
  end
47
52
 
48
53
  def bulk
49
54
  authorize_assignment!(@permission_set, params[:user_ids])
50
55
  # On a locked set only what narrows access passes (an unknown
51
56
  # bulk_action is judged as a grant, then refused as a 422).
52
- reject_widening!(BULK_ACTIONS.fetch(params[:bulk_action].to_s, :grant)) unless performed?
57
+ reject_widening!(BULK_ACTIONS.fetch(bulk_action_param, :grant)) unless performed?
53
58
  return if performed? || bulk_refused?
54
59
 
55
60
  render json: { data: bulk_assignment }, status: :ok
56
- rescue ArgumentError => e
57
- render json: { error: { status: 422, messages: e.message } }, status: :unprocessable_entity
58
61
  end
59
62
 
60
63
  private
@@ -65,34 +68,92 @@ module PermissionForge
65
68
  reject_locked!(pf_widening_lock(@permission_set, change))
66
69
  end
67
70
 
71
+ # The new assignment and what the cache store confirmed (meta.cache).
68
72
  def create_assignment(user)
69
- psu = @permission_set.permission_set_users.build(user: user, expires_at: pf_time_param(:expires_at))
70
- psu.save ? render_created(psu, serializer_for(:permission_set_user)) : render_validation_errors(psu)
73
+ psu = @permission_set.permission_set_users.build(user: user, expires_at: future_expires_at)
74
+ saved = false
75
+ outcome = PermissionForge::Cache::Report.of { saved = psu.save }
76
+ return render_validation_errors(psu) unless saved
77
+
78
+ render_record_with_cache(psu, outcome, status: :created)
71
79
  end
72
80
 
73
- # An unknown bulk_action raises ArgumentError (422, rescued in #bulk)
74
- # before anything is authorized or changed.
81
+ # params[:expires_at] (ParamShapes#pf_time_param) for a new
82
+ # assignment. An end that is not after now would create an assignment
83
+ # that grants nothing: refused (422) before anybody is assigned. The
84
+ # admin's Undo restores a removed assignment with the end it had, also
85
+ # one that has passed, and does not come through here.
86
+ def future_expires_at
87
+ expires_at = pf_time_param(:expires_at)
88
+ return expires_at if expires_at.nil? || expires_at > Time.current
89
+
90
+ raise ParamShapes::InvalidParam.new(:expires_at, I18n.t('permission_forge.api.errors.expires_at_past'))
91
+ end
92
+
93
+ # An unknown bulk_action, and a request that names nobody, are a 422
94
+ # ({ "bulk_action": [...] }, { "user_ids": [...] }) before anything is
95
+ # changed: a 200 that did nothing would hide a broken payload.
75
96
  def bulk_refused?
76
- action = BULK_ACTIONS.fetch(params[:bulk_action].to_s) do |name|
77
- PermissionForge::Services::BulkPermissionSetAssignment.validate_action!(name)
78
- raise ArgumentError, "Invalid action: #{name}" # a service action this controller does not map
97
+ action = BULK_ACTIONS.fetch(bulk_action_param) do
98
+ raise ParamShapes::InvalidParam.new(:bulk_action, I18n.t('permission_forge.api.errors.bulk_action_invalid'))
79
99
  end
100
+ require_user_ids!
80
101
  pf_assignment_refused_api?(@permission_set, params[:user_ids], action)
81
102
  end
82
103
 
104
+ # params[:bulk_action] when it is text ('' otherwise: a list or an
105
+ # object is no action, never its #to_s).
106
+ def bulk_action_param
107
+ action = params[:bulk_action]
108
+ action.is_a?(String) ? action : ''
109
+ end
110
+
111
+ # user_ids missing, [], "" or only blanks: "must name at least one
112
+ # user". Anything else that is not a list of ids — one string such as
113
+ # "1,3", a number, an object, a list holding an object, a list, a
114
+ # fraction or a boolean —: "must be a list" — through 0.14.1 "1,3" was
115
+ # read as one id and answered 200 with nobody assigned, and an element
116
+ # that was no id was left out without a word. A blank string beside an
117
+ # id is ignored, as Rails' own `collection_singular_ids=` ignores it
118
+ # (`collection_check_boxes` sends one with every form); Ids.cast leaves
119
+ # it out. Ids that name nobody are not refused: they are reported as
120
+ # `skipped`. (A JSON null among them never gets here: Rails' deep_munge
121
+ # removes it from the parameters.)
122
+ def require_user_ids!
123
+ user_ids = params[:user_ids]
124
+ message = if Array.wrap(user_ids).all?(&:blank?) then I18n.t('permission_forge.api.errors.user_ids_missing')
125
+ elsif !user_id_list?(user_ids) then I18n.t('permission_forge.api.errors.user_ids_not_list')
126
+ end
127
+ raise ParamShapes::InvalidParam.new(:user_ids, message) if message
128
+ end
129
+
130
+ def user_id_list?(user_ids)
131
+ user_ids.is_a?(Array) && user_ids.all? { |id| id.is_a?(String) || id.is_a?(Integer) }
132
+ end
133
+
134
+ # The service's counts and its cache outcome (`cache`: `confirmed`,
135
+ # `process_local`, `unconfirmed` — Cache::Outcome, as every
136
+ # cache-touching operation answers it) plus `skipped`: the ids of the
137
+ # request that name nobody config.user_list_resolver lists. A revoke
138
+ # ignores expires_at (it removes the assignments whatever their end),
139
+ # also one that cannot be read.
83
140
  def bulk_assignment
84
- PermissionForge::Services::BulkPermissionSetAssignment.call(
141
+ user_ids = bulk_user_ids
142
+ result = PermissionForge::Services::BulkPermissionSetAssignment.call(
85
143
  permission_set: @permission_set,
86
- user_ids: bulk_user_ids,
87
- action: params[:bulk_action],
144
+ user_ids: user_ids,
145
+ action: bulk_action_param,
88
146
  created_by: PermissionForge.current_user,
89
- expires_at: pf_time_param(:expires_at)
147
+ expires_at: (future_expires_at if bulk_action_param == 'assign')
90
148
  )
149
+ # Someone outside user_list_resolver and an id that names no user
150
+ # read the same: the answer says nothing about who exists.
151
+ result.merge(skipped: PermissionForge::Admin::PeopleDirectory::Ids.unmatched(params[:user_ids], user_ids))
91
152
  end
92
153
 
93
154
  # The people of the request within config.user_list_resolver
94
155
  # (Admin::PeopleDirectory#find_all); an id naming nobody there is
95
- # skipped, as an unknown id always was.
156
+ # skipped (and reported).
96
157
  def bulk_user_ids
97
158
  PermissionForge::Admin::PeopleDirectory.new.find_all(params[:user_ids]).map(&:id)
98
159
  end
@@ -45,8 +45,10 @@ module PermissionForge
45
45
  return if performed?
46
46
 
47
47
  # As the admin's Duplicate: an editable copy, outside a locked group.
48
+ # Names are unique per tenant: the default one is the admin's
49
+ # "<name> (copy)" in the request's locale, numbered when taken.
48
50
  cloned = PermissionForge::Admin::PermissionSetFactory.duplicate(
49
- @permission_set, name: pf_text_param(:name).presence || "#{@permission_set.name} (Copy)"
51
+ @permission_set, name: pf_copy_name.presence || PermissionForge::PermissionSet.copy_name(@permission_set.name)
50
52
  )
51
53
  render_created(cloned, serializer_for(:permission_set))
52
54
  end
@@ -75,12 +77,16 @@ module PermissionForge
75
77
  @permission_set = find_record(PermissionForge::PermissionSet)
76
78
  end
77
79
 
80
+ # The activation conditions take their shapes from Api::InputShapes
81
+ # (allowed_ips a String or a list of Strings, an hour digits, a flag
82
+ # text; null or "" clears); permission_set_group_id takes the group's
83
+ # id or, under api_record_identifier = :uuid, its uuid
84
+ # (BaseController#with_reference_id).
78
85
  def permission_set_params
79
- pf_require_hash(:permission_set).permit(
80
- :name, :description, :is_active, :is_editable, :is_muting,
81
- :requires_session_flag, :allowed_ips, :allowed_hours_start, :allowed_hours_end,
82
- :permission_set_group_id
83
- )
86
+ attrs = pf_record_attributes(:permission_set, PermissionForge::PermissionSet,
87
+ :name, :description, :is_active, :is_editable, :is_muting, :permission_set_group_id,
88
+ :requires_session_flag, :allowed_hours_start, :allowed_hours_end, :allowed_ips)
89
+ with_reference_id(attrs, :permission_set_group_id, PermissionForge::PermissionSetGroup)
84
90
  end
85
91
  end
86
92
  end
@@ -20,7 +20,7 @@ module PermissionForge
20
20
  end
21
21
 
22
22
  def record_params
23
- pf_require_hash(:resource_action).permit(:name, :label, :description, :is_permitted, :is_active)
23
+ pf_record_attributes(:resource_action, PermissionForge::ResourceAction, :name, :label, :description, :is_permitted, :is_active)
24
24
  end
25
25
 
26
26
  # A create or a rename must also name a valid action key. Checked after
@@ -20,10 +20,9 @@ module PermissionForge
20
20
  end
21
21
 
22
22
  def record_params
23
- pf_require_hash(:resource_field).permit(
24
- :name, :label, :description, :is_active, :is_editable,
25
- :read_access, :edit_access, :mask_access, :mask_strategy
26
- )
23
+ pf_record_attributes(:resource_field, PermissionForge::ResourceField,
24
+ :name, :label, :description, :is_active, :is_editable,
25
+ :read_access, :edit_access, :mask_access, :mask_strategy)
27
26
  end
28
27
  end
29
28
  end
@@ -8,26 +8,34 @@ module PermissionForge
8
8
  before_action :set_permission_set
9
9
  before_action :set_resource, only: %i[show update destroy generate_fields]
10
10
 
11
- # Grants read (and, with grant_all, edit) on every unconfigured column,
12
- # so it passes the same gates as the admin UI's "Sync fields": the
13
- # authorization hook, then the lock on the resource and its set.
11
+ # Creates a field row for every unconfigured column: with grant_all,
12
+ # readable and editable, unmasked; without it, no access (under
13
+ # gdpr_defaults, personal-data columns readable and masked). It passes
14
+ # the same gates as the admin UI's "Sync fields": the authorization
15
+ # hook, then the lock on the resource and its set. grant_all is read
16
+ # by ParamShapes#pf_boolean_param (true, "true", 1, "1"; absent, false,
17
+ # "false", 0, "0"): anything else is a 422 keyed by grant_all, never
18
+ # fields without edit. A Resource named after no model this app can
19
+ # load (PermissionForge::ModelLookup: `Kernel`, a model without its
20
+ # table) has no columns: a 422 keyed by name.
14
21
  def generate_fields
15
22
  guard_mutation!(:generate_fields, @resource)
23
+ refuse_no_model unless performed?
16
24
  return if performed?
17
25
 
18
26
  fields = PermissionForge::Services::ResourceFieldGenerator.call(
19
27
  resource: @resource,
20
- grant_all: %w[true 1].include?(params[:grant_all])
28
+ grant_all: pf_boolean_param(:grant_all)
21
29
  )
22
30
  data = PermissionForge::Api::Serializers::ResourceFieldSerializer.collection(fields)
23
31
  render json: { data: data }, status: :created
32
+ rescue ParamShapes::InvalidParam
33
+ raise # rendered by BaseController like every input error
24
34
  rescue StandardError => e
25
35
  # Log the internal detail; return a generic message so model/column names
26
36
  # and adapter errors are not leaked to API clients.
27
37
  Rails.logger.error("[PermissionForge] generate_fields failed for #{@resource&.name}: #{e.class}: #{e.message}")
28
- render json: {
29
- error: { status: 422, messages: I18n.t('permission_forge.api.errors.field_generation_failed') }
30
- }, status: :unprocessable_entity
38
+ render_unprocessable('base' => [I18n.t('permission_forge.api.errors.field_generation_failed')])
31
39
  end
32
40
 
33
41
  private
@@ -36,6 +44,13 @@ module PermissionForge
36
44
  def current_record = @resource
37
45
  def serializer_type = :resource
38
46
 
47
+ # 422 on name for a Resource named after no model this app can load.
48
+ def refuse_no_model
49
+ return if PermissionForge::ModelLookup.model?(@resource.name)
50
+
51
+ render_unprocessable('name' => [I18n.t('permission_forge.api.errors.resource_names_no_model')])
52
+ end
53
+
39
54
  def set_permission_set
40
55
  @permission_set = find_record(PermissionForge::PermissionSet, :permission_set_identifier)
41
56
  end
@@ -45,11 +60,10 @@ module PermissionForge
45
60
  end
46
61
 
47
62
  def record_params
48
- pf_require_hash(:resource).permit(
49
- :name, :description, :is_active, :is_editable,
50
- :read_access, :create_access, :edit_access, :delete_access,
51
- :view_all, :modify_all
52
- )
63
+ pf_record_attributes(:resource, PermissionForge::Resource,
64
+ :name, :description, :is_active, :is_editable,
65
+ :read_access, :create_access, :edit_access, :delete_access,
66
+ :view_all, :modify_all)
53
67
  end
54
68
  end
55
69
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ module Api
5
+ # Any path and method under the API mount that no endpoint answers
6
+ # (`GET /api/unknown`, `POST /api/audit_events`): a 404 in the API's JSON
7
+ # envelope instead of Rails' HTML page. Drawn after every API route, and
8
+ # only under /api. It runs the API's guards first: while api_enabled is
9
+ # off it is the same bodiless 404 as every API route; a caller api_auth
10
+ # refuses gets the 401 and a request with no tenant the 403, each
11
+ # recorded as api.auth_failed like on any endpoint.
12
+ #
13
+ # @api private
14
+ class UnmatchedRoutesController < BaseController
15
+ def show
16
+ render_not_found(I18n.t('permission_forge.api.errors.route_not_found'))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -26,6 +26,7 @@ module PermissionForge
26
26
  include PermissionForge::TenantRequired
27
27
  include PermissionForge::ParamShapes
28
28
  include PermissionForge::HtmlFormat
29
+ include PermissionForge::SaveNotice
29
30
 
30
31
  THEMES = %w[auto light dark].freeze
31
32
  FRAGMENT_HEADER = 'X-PF-Fragment'.freeze
@@ -38,19 +39,56 @@ module PermissionForge
38
39
  # inside the scope suspension (an actor resolver may query).
39
40
  around_action :pf_audit_request
40
41
 
42
+ # The uniqueness race (two requests passed the uniqueness validation
43
+ # together; the unique index refused this one's INSERT): back to the
44
+ # page the form was on (the admin's home without a Referer, never
45
+ # another host) with the validation's message for the index the error
46
+ # names (UniqueConflict), or the generic one. 303: the browser follows
47
+ # with a GET whatever the form's method was. Through 0.14.1 a 500.
48
+ rescue_from ActiveRecord::RecordNotUnique do |error|
49
+ detail = PermissionForge::UniqueConflict.sentence(error)
50
+ alert = detail ? t('permission_forge.flash.record_not_unique_detail', detail: detail) : t('permission_forge.flash.record_not_unique')
51
+ redirect_back_or_to permission_forge.root_path, alert: alert, allow_other_host: false, status: :see_other
52
+ end
53
+
54
+ # A NOT NULL column no validation foresaw (ValidatesRequiredColumns
55
+ # covers the gem's own columns): back to the page like the uniqueness
56
+ # race, never a 500.
57
+ rescue_from ActiveRecord::NotNullViolation do
58
+ redirect_back_or_to permission_forge.root_path, alert: t('permission_forge.flash.not_null'), allow_other_host: false,
59
+ status: :see_other
60
+ end
61
+
62
+ # A flag the admin cannot read (ParamShapes::InvalidFlag: not true or
63
+ # false, where the forms post "0" and "1"): back to the page like the
64
+ # uniqueness race, saying which flag, nothing changed. ActiveModel's
65
+ # cast read any other word as true; this is never that, nor a 500. (Any
66
+ # other InvalidParam, a date the admin cannot read, stays a 400.)
67
+ rescue_from PermissionForge::ParamShapes::InvalidFlag do |error|
68
+ redirect_back_or_to permission_forge.root_path,
69
+ alert: t('permission_forge.flash.invalid_param', param: error.param, detail: error.detail),
70
+ allow_other_host: false, status: :see_other
71
+ end
72
+
41
73
  helper PermissionForge::ApplicationHelper unless self < PermissionForge::ApplicationHelper
42
74
  helper_method :pf_fragment?, :pf_theme, :pf_help_visible?, :pf_current_admin,
43
75
  :pf_attention, :pf_settings_catalog, :pf_back_or
44
76
 
45
77
  private
46
78
 
79
+ # A Permission Set's, group's or rule's name for a flash (Admin::RecordName:
80
+ # an install-default English name in the request's language).
81
+ def pf_record_name(record)
82
+ PermissionForge::Admin::RecordName.for(record)
83
+ end
84
+
47
85
  # Memoized per request: drives the Home badge and the home page.
48
86
  def pf_attention
49
87
  @pf_attention ||= PermissionForge::Admin::AttentionItems.new
50
88
  end
51
89
 
52
90
  def pf_settings_catalog
53
- @pf_settings_catalog ||= PermissionForge::Admin::SettingsCatalog.new
91
+ @pf_settings_catalog ||= PermissionForge::Admin::SettingsCatalog.new(attention: pf_attention)
54
92
  end
55
93
 
56
94
  def set_locale
@@ -178,10 +216,6 @@ module PermissionForge
178
216
  raise ActionController::RoutingError, 'Not Found'
179
217
  end
180
218
 
181
- def pf_locked?(record)
182
- PermissionForge::Admin::EditableLock.locked?(record)
183
- end
184
-
185
219
  # Redirects with an alert when the record (or any of `records`), or what
186
220
  # it belongs to (Admin::EditableLock: its Resource, Permission Set, the
187
221
  # Group of a membership), is locked, and records the refusal