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
@@ -258,6 +258,9 @@ module PermissionForge
258
258
  end
259
259
 
260
260
  require_relative 'permission_forge/execution_state'
261
+ require_relative 'permission_forge/execution_state/restore'
262
+ require_relative 'permission_forge/web_request'
263
+ require_relative 'permission_forge/inspection_filters'
261
264
  require_relative 'permission_forge/model_bindings'
262
265
  require_relative 'permission_forge/cache/base'
263
266
  require_relative 'permission_forge/cache/memory_cache'
@@ -288,6 +291,7 @@ require_relative 'permission_forge/audit/subscribers'
288
291
  require_relative 'permission_forge/audit/log_access'
289
292
  require_relative 'permission_forge/audit/event_query'
290
293
  require_relative 'permission_forge/audit/runtime_emission'
294
+ require_relative 'permission_forge/database_reach'
291
295
  require_relative 'permission_forge/audit'
292
296
  require_relative 'permission_forge/audit/gate'
293
297
  require_relative 'permission_forge/audit/runtime'
@@ -295,32 +299,53 @@ require_relative 'permission_forge/audit/field_access'
295
299
  require_relative 'permission_forge/audit/subscribers/base'
296
300
  require_relative 'permission_forge/audit/subscribers/legacy_hooks'
297
301
  require_relative 'permission_forge/audit/subscribers/log'
302
+ require_relative 'permission_forge/audit/actor_codes'
303
+ require_relative 'permission_forge/audit/reason_codes'
298
304
  require_relative 'permission_forge/audit/actor_resolver'
299
305
  require_relative 'permission_forge/audit/audited_views'
300
306
  require_relative 'permission_forge/audit/request_context'
307
+ require_relative 'permission_forge/audit/unmasked_without_user'
301
308
  require_relative 'permission_forge/audit/detached'
302
309
  require_relative 'permission_forge/audit/refusals'
303
310
  require_relative 'permission_forge/audit/job_context'
304
311
  require_relative 'permission_forge/audit/settings_observer'
305
312
  require_relative 'permission_forge/audit/boot_warnings'
306
313
 
314
+ require_relative 'permission_forge/schema_read'
315
+ require_relative 'permission_forge/schema_read/ordered_types'
307
316
  require_relative 'permission_forge/resource_chain'
317
+ require_relative 'permission_forge/record_read_access'
318
+ require_relative 'permission_forge/unique_conflict'
308
319
  require_relative 'permission_forge/virtual_fields'
320
+ require_relative 'permission_forge/managed_fields'
309
321
  require_relative 'permission_forge/virtual_fields/discovery'
322
+ require_relative 'permission_forge/field_variants'
323
+ require_relative 'permission_forge/field_names'
324
+ require_relative 'permission_forge/timestamp_fields'
325
+ require_relative 'permission_forge/field_level'
326
+ require_relative 'permission_forge/sensitive_columns'
327
+ require_relative 'permission_forge/secret_columns'
328
+ require_relative 'permission_forge/encrypted_attributes'
329
+ require_relative 'permission_forge/generated_text'
310
330
 
311
331
  require_relative 'permission_forge/concerns/has_active_scope'
312
332
  require_relative 'permission_forge/concerns/has_tenant_scoping'
313
333
  require_relative 'permission_forge/concerns/has_uuid'
334
+ require_relative 'permission_forge/concerns/keeps_copied_values'
314
335
  require_relative 'permission_forge/concerns/has_audit_trail'
315
336
  require_relative 'permission_forge/invalidation_batch'
316
337
  require_relative 'permission_forge/concerns/invalidates_in_batch'
317
338
  require_relative 'permission_forge/concerns/has_permission_set_users'
318
339
  require_relative 'permission_forge/concerns/validates_dependency'
319
340
  require_relative 'permission_forge/concerns/validates_column_lengths'
341
+ require_relative 'permission_forge/concerns/requires_name'
342
+ require_relative 'permission_forge/concerns/normalizes_label'
343
+ require_relative 'permission_forge/concerns/validates_required_columns'
320
344
  require_relative 'permission_forge/concerns/permissions_regenerator'
321
345
  require_relative 'permission_forge/concerns/invalidates_managed_actions'
322
346
  require_relative 'permission_forge/concerns/records_audit_events'
323
347
  require_relative 'permission_forge/concerns/audited_collection'
348
+ require_relative 'permission_forge/activation_conditions'
324
349
  require_relative 'permission_forge/conditional_sets'
325
350
  require_relative 'permission_forge/concerns/permissions_memo'
326
351
  require_relative 'permission_forge/concerns/permissions_per_context'
@@ -335,7 +360,12 @@ require_relative 'permission_forge/concerns/masked_attributes'
335
360
  require_relative 'permission_forge/operators/constants'
336
361
  require_relative 'permission_forge/operators/validations'
337
362
  require_relative 'permission_forge/operators/value_casters'
363
+ require_relative 'permission_forge/operators/rule_path'
364
+ require_relative 'permission_forge/operators/rule_sources'
365
+ require_relative 'permission_forge/model_lookup'
338
366
  require_relative 'permission_forge/operators/rule_column'
367
+ require_relative 'permission_forge/operators/encrypted_alias'
368
+ require_relative 'permission_forge/operators/rule_problem'
339
369
  require_relative 'permission_forge/operators/value_typing'
340
370
  require_relative 'permission_forge/services/mask_value'
341
371
 
@@ -361,9 +391,11 @@ require_relative 'permission_forge/api/serializers/permission_set_user_serialize
361
391
  require_relative 'permission_forge/api/serializers/audit_event_serializer'
362
392
  require_relative 'permission_forge/api/serializers/permission_set_group_serializer'
363
393
  require_relative 'permission_forge/api/serializers/permission_set_group_user_serializer'
394
+ require_relative 'permission_forge/api/schema'
364
395
 
365
396
  require_relative 'permission_forge/services/concerns/clonable'
366
397
  require_relative 'permission_forge/services/concerns/audited_operation'
398
+ require_relative 'permission_forge/services/concerns/settles_cache'
367
399
  require_relative 'permission_forge/services/concerns/across_tenants'
368
400
  require_relative 'permission_forge/services/bulk_permission_set_assignment'
369
401
  require_relative 'permission_forge/services/consent_withdrawal_handler'
@@ -383,14 +415,17 @@ require_relative 'permission_forge/services/gdpr_field_auditor'
383
415
  require_relative 'permission_forge/services/permission_hash_builder'
384
416
  require_relative 'permission_forge/services/permits_attributes'
385
417
  require_relative 'permission_forge/services/permits_attributes/nested_records'
418
+ require_relative 'permission_forge/services/permits_attributes/result'
386
419
  require_relative 'permission_forge/services/data_subject_erasure'
387
420
  require_relative 'permission_forge/services/host_key_guard'
388
421
  require_relative 'permission_forge/services/stale_assignment_auditor'
389
422
  require_relative 'permission_forge/services/audit_event_purger'
390
423
  require_relative 'permission_forge/rake_audit'
424
+ require_relative 'permission_forge/rake_dry_run'
391
425
  require_relative 'permission_forge/services/resource_field_generator'
392
426
  require_relative 'permission_forge/services/unscoped_query_detector'
393
427
  require_relative 'permission_forge/services/rls_operator_sql'
428
+ require_relative 'permission_forge/services/rls_read_access_sql'
394
429
  require_relative 'permission_forge/services/rls_policy_generator'
395
430
  require_relative 'permission_forge/rls'
396
431
 
@@ -399,12 +434,17 @@ require_relative 'permission_forge/admin/field_level'
399
434
  require_relative 'permission_forge/admin/sensitive_columns'
400
435
  require_relative 'permission_forge/admin/model_catalog'
401
436
  require_relative 'permission_forge/admin/person_label'
437
+ require_relative 'permission_forge/admin/state_label'
438
+ require_relative 'permission_forge/admin/actor_label'
402
439
  require_relative 'permission_forge/admin/people_candidates'
403
440
  require_relative 'permission_forge/admin/people_directory'
441
+ require_relative 'permission_forge/admin/list_sentence'
404
442
  require_relative 'permission_forge/admin/record_id'
405
443
  require_relative 'permission_forge/admin/editable_lock'
406
444
  require_relative 'permission_forge/admin/expiry_presets'
407
445
  require_relative 'permission_forge/admin/effective_access'
446
+ require_relative 'permission_forge/admin/field_provenance'
447
+ require_relative 'permission_forge/admin/field_chain'
408
448
  require_relative 'permission_forge/admin/effective_fields'
409
449
  require_relative 'permission_forge/admin/settings_values'
410
450
  require_relative 'permission_forge/admin/settings_catalog'
@@ -432,7 +472,15 @@ require_relative 'permission_forge/admin/change_set_parts/new_fields'
432
472
  require_relative 'permission_forge/admin/change_set_parts/actions'
433
473
  require_relative 'permission_forge/admin/change_set_parts/new_actions'
434
474
  require_relative 'permission_forge/admin/change_set'
475
+ require_relative 'permission_forge/admin/field_label'
476
+ require_relative 'permission_forge/admin/model_label'
477
+ require_relative 'permission_forge/admin/action_label'
478
+ require_relative 'permission_forge/admin/error_sentence'
479
+ require_relative 'permission_forge/admin/prefill'
480
+ require_relative 'permission_forge/admin/record_name'
481
+ require_relative 'permission_forge/admin/chain_entries'
435
482
  require_relative 'permission_forge/admin/field_rows'
483
+ require_relative 'permission_forge/admin/mask_strategy_choices'
436
484
  require_relative 'permission_forge/admin/field_detail'
437
485
  require_relative 'permission_forge/admin/audit_event_detail'
438
486
  require_relative 'permission_forge/admin/rule_builder'
@@ -445,17 +493,23 @@ require_relative 'permission_forge/admin/conditions_form'
445
493
  require_relative 'permission_forge/admin/permission_set_factory'
446
494
  require_relative 'permission_forge/admin/rule_form_input'
447
495
  require_relative 'permission_forge/admin/rule_form'
496
+ require_relative 'permission_forge/admin/rule_notes'
497
+ require_relative 'permission_forge/admin/rule_preview'
448
498
  require_relative 'permission_forge/admin/bulk_people_matcher'
449
499
 
450
500
  # Security review (rake permission_forge:audit)
501
+ require_relative 'permission_forge/api_authorizations'
451
502
  require_relative 'permission_forge/services/security_audit'
452
503
  require_relative 'permission_forge/services/security_audit/base_check'
504
+ require_relative 'permission_forge/services/security_audit/masking_without_user'
453
505
  require_relative 'permission_forge/services/security_audit/config_check'
454
506
  require_relative 'permission_forge/services/security_audit/privilege_check'
455
507
  require_relative 'permission_forge/services/security_audit/assignment_check'
456
508
  require_relative 'permission_forge/services/security_audit/rule_check'
457
509
  require_relative 'permission_forge/services/security_audit/integrity_check'
510
+ require_relative 'permission_forge/services/security_audit/conditions_check'
458
511
  require_relative 'permission_forge/services/security_audit/personal_data_check'
512
+ require_relative 'permission_forge/services/security_audit/unmanaged_actions'
459
513
  require_relative 'permission_forge/services/security_audit/action_check'
460
514
  require_relative 'permission_forge/services/security_audit/audit_trail_check'
461
515
  require_relative 'permission_forge/services/security_audit/user_model_check'
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :permission_forge do
4
- desc 'Report fields that match the personal-data patterns but are not masked, and matching model columns with no ResourceField'
4
+ desc 'Report personal-data fields that are not masked, and matching model columns with no field row. ' \
5
+ 'ACTOR= names who ran it, REASON= why (recorded as report.generated).'
5
6
  task audit_masks: :environment do
6
7
  report = PermissionForge::RakeAudit.reporting('permission_forge:audit_masks', counts: ->(r) { { findings: r.size } }) do
7
8
  PermissionForge::Services::GdprFieldAuditor.call
@@ -13,19 +14,38 @@ namespace :permission_forge do
13
14
  end
14
15
 
15
16
  namespace :permission_forge do
16
- desc 'Read-only security review of the PermissionForge setup. FORMAT=json, FAIL_ON=critical|high|medium|low'
17
+ # Exits 1 when FAIL_ON is given and a finding of that severity or worse
18
+ # exists (saying so on stderr, so FORMAT=json output stays parseable), and 2
19
+ # when FAIL_ON names no severity (before the review runs).
20
+ # It changes no permission data; every run is recorded (report.generated)
21
+ # when the audit log is on.
22
+ desc 'Security review of the setup: changes no permission data, records the run in the audit log. ' \
23
+ 'FORMAT=json, FAIL_ON=critical|high|medium|low|info; ACTOR= names who ran it, REASON= why.'
17
24
  task audit: :environment do
18
- report = PermissionForge::RakeAudit.reporting('permission_forge:audit', counts: :counts.to_proc) do
19
- PermissionForge::Services::SecurityAudit.call
25
+ audit = PermissionForge::Services::SecurityAudit
26
+ given = ENV.fetch('FAIL_ON', nil).presence
27
+ fail_on = given && audit.severity_named(given)
28
+ if given && fail_on.nil?
29
+ warn "FAIL_ON must be one of #{audit::SEVERITIES.join(', ')} (got #{given.inspect}). " \
30
+ 'Usage: rake permission_forge:audit FAIL_ON=high'
31
+ exit 2
20
32
  end
21
33
 
34
+ # A rake task does not eager-load: the review must see every model (an
35
+ # STI subclass's `encrypts`, the models on a table a rule joins).
36
+ PermissionForge::Admin::ModelCatalog.eager_load_app_models
37
+ report = PermissionForge::RakeAudit.reporting('permission_forge:audit', counts: :counts.to_proc) { audit.call }
38
+
22
39
  if ENV['FORMAT'] == 'json'
23
40
  puts JSON.pretty_generate(report.to_h)
24
41
  else
25
42
  puts PermissionForge::Services::SecurityAuditChecks::TextReport.new(report)
26
43
  end
27
44
 
28
- fail_on = ENV.fetch('FAIL_ON', nil).presence
29
- exit 1 if fail_on && report.at_least(fail_on).any?
45
+ failing = fail_on ? report.at_least(fail_on).size : 0
46
+ if failing.positive?
47
+ warn "Failing: #{failing} finding(s) at or above #{fail_on} (FAIL_ON=#{given})."
48
+ exit 1
49
+ end
30
50
  end
31
51
  end
@@ -2,10 +2,12 @@
2
2
 
3
3
  namespace :permission_forge do
4
4
  namespace :audit_events do
5
- desc 'Delete audit events older than config.audit_retention_period (DRY_RUN=1 counts only). ACTOR= names who ran it.'
5
+ # Prints the result the audit.purged events record (RakeOutput).
6
+ desc 'Delete audit events older than config.audit_retention_period. Deletes when DRY_RUN is unset or ' \
7
+ 'false (or 0, no); any other DRY_RUN value (1, true, yes) only counts. ACTOR= names who ran it, REASON= why.'
6
8
  task purge: :environment do
7
9
  task_name = 'permission_forge:audit_events:purge'
8
- dry_run = %w[1 true].include?(ENV.fetch('DRY_RUN', nil))
10
+ dry_run = PermissionForge::RakeDryRun.dry_run?(ENV.fetch('DRY_RUN', nil), default: false)
9
11
  purge = -> { PermissionForge::Services::AuditEventPurger.call(dry_run: dry_run) }
10
12
  result = begin
11
13
  dry_run ? purge.call : PermissionForge::RakeAudit.changing(task_name, &purge)
@@ -19,9 +21,11 @@ namespace :permission_forge do
19
21
  if !result[:retention_configured]
20
22
  puts 'Retention is not configured (config.audit_retention_period is nil): nothing was deleted.'
21
23
  elsif dry_run
22
- puts "Dry run — #{result[:purged]} audit event(s) older than #{result[:cutoff].iso8601} would be deleted."
24
+ PermissionForge::RakeOutput.report("Dry run — would delete #{result[:purged]} audit event(s) older than " \
25
+ "#{result[:cutoff].iso8601}:", result)
23
26
  else
24
- puts "Deleted #{result[:purged]} audit event(s) older than #{result[:cutoff].iso8601}."
27
+ PermissionForge::RakeOutput.report("Deleted #{result[:purged]} audit event(s) older than #{result[:cutoff].iso8601}:",
28
+ result.except(:dry_run))
25
29
  end
26
30
  end
27
31
  end
@@ -2,25 +2,25 @@
2
2
 
3
3
  namespace :permission_forge do
4
4
  namespace :cache do
5
- desc 'Remove every cached permission hash of every user (by key pattern, or every user through the bulk ' \
6
- 'invalidation) — once the last process of the previous release is gone (0.14), or to revoke everything ' \
7
- 'cached at once; the cache is cold afterwards. ACTOR=, REASON='
5
+ # Upgrading from 0.13 or earlier: run it once the last process of the
6
+ # previous release is gone (it also removes the previous key namespace).
7
+ # What it prints is the result the cache.cleared event records
8
+ # (RakeOutput), the cache outcome included: on an in-memory store only
9
+ # this process's store is cleared, and the output says so.
10
+ desc "Remove every user's cached permissions at once; each user rebuilds on their next request. " \
11
+ 'ACTOR=, REASON= (After an upgrade from 0.13: run it once the old processes are gone.)'
8
12
  task clear: :environment do
9
13
  task_name = 'permission_forge:cache:clear'
10
14
  result = PermissionForge::RakeAudit.changing(task_name) { PermissionForge::Services::PermissionCacheClearer.call }
11
15
  PermissionForge::RakeAudit.log(task_name, result.map { |key, value| "#{key}=#{value.inspect}" }.join(' '))
12
- unconfirmed = result[:unconfirmed].positive? ? " (#{result[:unconfirmed]} not confirmed by the cache store)" : ''
16
+ # what it means for the users is the outcome's "Cache scope" line (a shared store: every user rebuilds on their
17
+ # next request; a per-process store: this process's only), never a second claim here
13
18
  done = if result[:strategy] == :pattern
14
- 'Removed every cached permission hash by key pattern (users not counted)'
19
+ 'Removed every cached permission hash by key pattern:'
15
20
  else
16
- "Invalidated the cached permissions of #{result[:users]} user visit(s) in #{result[:tenants]} tenant(s)"
21
+ 'Invalidated the cached permissions of every user:'
17
22
  end
18
- puts "#{done}#{unconfirmed}. The cache is cold: each user rebuilds on their next request."
19
- if PermissionForge.cache.process_local?
20
- puts 'Only the cache store of this rake process was cleared: an in-memory store (cache_backend :memory, or ' \
21
- ':memory_store) lives in each process, so the web and job processes keep their entries until they ' \
22
- 'restart or the entries expire (cache_ttl). Invalidate the users from inside those processes, or restart them.'
23
- end
23
+ PermissionForge::RakeOutput.report(done, result)
24
24
  end
25
25
  end
26
26
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The gem's own documentation generators, run from the gem's Rakefile
4
+ # (`bundle exec rake docs:configuration`, `docs:api`). They are defined only in a checkout
5
+ # of the gem (where docs/ is) and never inside a Rails application — not in a
6
+ # host's `rails -T` (the gem ships without docs/), not in the dummy app's.
7
+ docs_root = File.expand_path('../../docs', __dir__)
8
+ inside_rails = defined?(Rails) && Rails.respond_to?(:application) && Rails.application
9
+ if File.directory?(docs_root) && !inside_rails
10
+ namespace :docs do
11
+ desc 'Regenerate the Option reference of docs/configuration.md from SettingsCatalog and Configuration.new'
12
+ task :configuration do
13
+ lib = File.expand_path('../../lib', __dir__)
14
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
15
+ require 'permission_forge'
16
+ require 'permission_forge/docs/configuration_reference'
17
+
18
+ path = File.join(docs_root, 'configuration.md')
19
+ reference = PermissionForge::Docs::ConfigurationReference.new(document: File.read(path))
20
+ if reference.stale?
21
+ File.write(path, reference.render)
22
+ puts 'docs/configuration.md: Option reference regenerated'
23
+ else
24
+ puts 'docs/configuration.md: Option reference is up to date'
25
+ end
26
+ end
27
+
28
+ desc 'Regenerate the json sample blocks (sample=<name>) of docs/admin-api.md from Api::Schema and the serializers'
29
+ task :api do
30
+ lib = File.expand_path('../../lib', __dir__)
31
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
32
+ require 'permission_forge'
33
+ require 'permission_forge/docs/api_samples'
34
+ # The one parser of the docs' fenced blocks (a checkout has test/).
35
+ require File.expand_path('../../test/support/doc_blocks', __dir__)
36
+
37
+ path = File.join(docs_root, 'admin-api.md')
38
+ document = File.read(path)
39
+ samples = PermissionForge::Docs::ApiSamples.new(document: document, blocks: DocBlocks.parse(document, file: 'docs/admin-api.md'))
40
+ if samples.stale?
41
+ File.write(path, samples.render)
42
+ puts 'docs/admin-api.md: json samples regenerated'
43
+ else
44
+ puts 'docs/admin-api.md: json samples are up to date'
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :permission_forge do
4
- desc 'Remove expired permission set assignments and invalidate affected users\' cached permissions'
4
+ # Prints the result the cleanup.expired_assignments event records
5
+ # (RakeOutput): the people by name, the sets, the cache outcome.
6
+ desc "Remove expired permission set assignments and invalidate affected users' cached permissions. " \
7
+ 'ACTOR= names who ran it, REASON= why.'
5
8
  task cleanup_expired_assignments: :environment do
6
9
  task_name = 'permission_forge:cleanup_expired_assignments'
7
10
  result = PermissionForge::RakeAudit.changing(task_name) { PermissionForge::Services::ExpiredAssignmentCleaner.call }
@@ -9,11 +12,12 @@ namespace :permission_forge do
9
12
  if result[:removed].zero?
10
13
  puts 'No expired assignments found.'
11
14
  else
12
- puts "Removed #{result[:removed]} expired assignment(s), invalidated #{result[:invalidated]} user(s)."
15
+ PermissionForge::RakeOutput.report("Removed #{result[:removed]} expired assignment(s):", result)
13
16
  end
14
17
  end
15
18
 
16
- desc 'Show expired permission set assignments without removing (dry run)'
19
+ desc 'List expired permission set assignments without removing them, as a dry run. ' \
20
+ 'ACTOR= names who ran it, REASON= why (recorded as report.generated).'
17
21
  task expired_assignments: :environment do
18
22
  result = PermissionForge::RakeAudit.reporting(
19
23
  'permission_forge:expired_assignments',
@@ -22,8 +26,10 @@ namespace :permission_forge do
22
26
  if result[:expired_count].zero?
23
27
  puts 'No expired assignments found.'
24
28
  else
25
- puts "Found #{result[:expired_count]} expired assignment(s) affecting #{result[:affected_user_ids].size} user(s)."
26
- puts "User IDs: #{result[:affected_user_ids].join(', ')}"
29
+ PermissionForge::RakeOutput.report(
30
+ "Dry run — would remove #{result[:expired_count]} expired assignment(s) affecting #{result[:affected_user_ids].size} person(s):",
31
+ result.slice(:expired_count, :affected_user_ids, :cache).merge(dry_run: true)
32
+ )
27
33
  puts 'Run `rake permission_forge:cleanup_expired_assignments` to remove them.'
28
34
  end
29
35
  end
@@ -1,20 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :permission_forge do
4
- desc 'Find permanent permission assignments not reviewed within threshold (default: 90 days)'
4
+ # Exits 1 when it finds stale assignments, 2 when THRESHOLD_DAYS is not a
5
+ # number of days (rather than reading it as 0, which flags everything).
6
+ # People are named as the admin names them (RakeOutput.people).
7
+ desc 'Find permanent assignments not reviewed in THRESHOLD_DAYS, by default admin_stale_assignment_days. ' \
8
+ 'Exits 1 when it finds any. ACTOR= names who ran it, REASON= why (recorded as report.generated).'
5
9
  task stale_assignments: :environment do
6
- threshold = ENV.fetch('THRESHOLD_DAYS', 90).to_i
10
+ task_name = 'permission_forge:stale_assignments'
11
+ threshold = ENV.fetch('THRESHOLD_DAYS', nil)&.then do |given|
12
+ digits = given.strip
13
+ # ASCII digits only: Integer() would also take Ruby's underscores ("1_0").
14
+ days = Integer(digits, 10) if digits.match?(/\A[0-9]+\z/)
15
+ next days if days&.between?(1, 36_500)
16
+
17
+ message = "THRESHOLD_DAYS must be a whole number of days from 1 to 36500 (got #{given.inspect}). " \
18
+ "Usage: rake #{task_name} THRESHOLD_DAYS=90"
19
+ PermissionForge::RakeAudit.log(task_name, "refused: #{message}")
20
+ warn message
21
+ exit 2
22
+ end
7
23
  result = PermissionForge::RakeAudit.reporting(
8
- 'permission_forge:stale_assignments',
9
- counts: ->(r) { { stale: r[:stale_count], users: r[:affected_user_ids].size, threshold_days: threshold } }
24
+ task_name,
25
+ counts: ->(r) { { stale: r[:stale_count], users: r[:affected_user_ids].size, threshold_days: r[:threshold_days] } }
10
26
  ) { PermissionForge::Services::StaleAssignmentAuditor.call(threshold_days: threshold) }
11
27
 
12
28
  if result[:stale_count].zero?
13
- puts "No stale assignments found (threshold: #{threshold} days)."
29
+ puts "No stale assignments found (threshold: #{result[:threshold_days]} days)."
14
30
  else
15
- puts "Found #{result[:stale_count]} stale assignment(s) affecting #{result[:affected_user_ids].size} user(s):"
31
+ puts "Found #{result[:stale_count]} stale assignment(s) affecting #{result[:affected_user_ids].size} person(s):"
32
+ names = PermissionForge::RakeOutput.people(result[:assignments].map { |a| a[:user_id] })
16
33
  result[:assignments].each do |a|
17
- puts " User #{a[:user_id]} — #{a[:permission_set] || '(set not found)'} (#{a[:days_stale]} days since last review or grant)"
34
+ puts " #{names.fetch(a[:user_id].to_s)} — #{a[:permission_set] || '(set not found)'} (#{a[:days_stale]} days since last review or grant)"
18
35
  end
19
36
  puts "\nConsider reviewing these assignments or setting expires_at for time-bound access."
20
37
  exit 1
@@ -28,12 +45,16 @@ namespace :permission_forge do
28
45
  # request id, reason and data of every audit event, the *_by_id columns on
29
46
  # rows the person created, edited or reviewed, and the person's own record
30
47
  # in the app. Assignments in other tenants stay when a tenant is current.
31
- desc "Remove a user's assignments, group memberships and cached permissions, and clear their name labels " \
32
- 'in the audit log (supports GDPR Art. 17). Ids and other event data stay. USER_ID required; DRY_RUN=true ' \
33
- 'by default.'
48
+ # rake -T shows a description up to its first full stop after a word: the
49
+ # first sentence carries no abbreviation ("Article 17", not "Art. 17") and
50
+ # ends on a word, not a parenthesis. What it prints is the result the
51
+ # erasure.performed event records (RakeOutput), the cache outcome included.
52
+ desc "Erase a person's permission data (assignments, cache, audit-log names) for a GDPR Article 17 request. " \
53
+ 'Ids and other event data stay. USER_ID= required; a dry run unless DRY_RUN=false (or 0, no); ' \
54
+ 'ACTOR= names who ran it, REASON= why.'
34
55
  task erasure: :environment do
35
56
  user_id = ENV.fetch('USER_ID', nil)
36
- dry_run = ENV.fetch('DRY_RUN', 'true') == 'true'
57
+ dry_run = PermissionForge::RakeDryRun.dry_run?(ENV.fetch('DRY_RUN', nil), default: true)
37
58
 
38
59
  abort 'USER_ID is required. Usage: rake permission_forge:erasure USER_ID=123' if user_id.blank?
39
60
 
@@ -51,18 +72,13 @@ namespace :permission_forge do
51
72
  end
52
73
  PermissionForge::RakeAudit.log(task_name, "user #{user_id}, dry_run=#{dry_run}: #{result.except(:permission_sets).to_json}")
53
74
 
75
+ person = PermissionForge::RakeOutput.person(user)
54
76
  if dry_run
55
- puts "Dry run — would remove for user #{user_id}:"
56
- puts " Permission set assignments: #{result[:assignments_count]}"
57
- puts " Group assignments: #{result[:group_assignments_count]}"
58
- puts " Permission sets: #{result[:permission_sets].join(', ')}" if result[:permission_sets].any?
77
+ PermissionForge::RakeOutput.report("Dry run — would erase for #{person}:", result)
78
+ puts ' (plus the name labels on the events the erasure itself writes)'
59
79
  puts "\nRun with DRY_RUN=false to execute."
60
80
  else
61
- puts "Erasure complete for user #{user_id}:"
62
- puts " Assignments removed: #{result[:assignments_removed]}"
63
- puts " Group assignments removed: #{result[:group_assignments_removed]}"
64
- puts " Cache cleared: #{result[:cache_cleared]}"
65
- puts " Assignments remaining in other tenants: #{result[:assignments_remaining_elsewhere]}"
81
+ PermissionForge::RakeOutput.report("Erasure complete for #{person}:", result)
66
82
  puts ' Kept: audit event ids, ip, request id, reason and data; *_by_id columns; the user record itself.'
67
83
  end
68
84
  end