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
@@ -2,21 +2,21 @@
2
2
  <% resource = @resource %>
3
3
  <% pf_audit_preload_last_changes([resource, *(@tab == 'rules' ? @rules : [])]) %>
4
4
  <% muting = set.is_muting %>
5
- <% locked = pf_locked_record?(set) || pf_locked_record?(resource) %>
5
+ <% locked = pf_locked?(resource) %>
6
6
  <% label = pf_resource_label(resource) %>
7
7
  <% plural = pf_resource_plural(resource.name) %>
8
8
  <% base = "pf[resources][#{resource.id}]" %>
9
- <% content_for :pf_title, "#{label} · #{set.name}" %>
9
+ <% content_for :pf_title, "#{label} · #{pf_record_name(set)}" %>
10
10
  <%= pf_breadcrumb([t('permission_forge.nav.permission_sets'), permission_forge.permission_sets_path],
11
- [set.name, permission_forge.permission_set_path(set)], label) %>
11
+ [pf_record_name(set), permission_forge.permission_set_path(set)], label) %>
12
12
 
13
13
  <% if muting %>
14
14
  <div class="pf-banner pf-banner--muting" role="note">
15
15
  <span class="pf-banner__glyph" aria-hidden="true">⊘</span>
16
- <div class="pf-banner__body"><span><b><%= t('permission_forge.ui.badges.muting') %>.</b> <%= t('.muting_note', set: set.name) %></span></div>
16
+ <div class="pf-banner__body"><span><b><%= t('permission_forge.ui.badges.muting') %>.</b> <%= t('.muting_note', set: pf_record_name(set)) %></span></div>
17
17
  </div>
18
18
  <% end %>
19
- <%= render 'permission_forge/shared/mode_banners', record: (pf_locked_record?(resource) ? resource : set), muting: false,
19
+ <%= render 'permission_forge/shared/mode_banners', record: resource, muting: false,
20
20
  duplicate_path: permission_forge.duplicate_permission_set_path(set) %>
21
21
  <%= pf_help_banner('resource', term: t('.help_term'), text: t('.help_text', model: resource.name)) %>
22
22
 
@@ -27,7 +27,7 @@
27
27
  submit button on the page — a bulk "Set to" button, proposing that level for the ticked rows.
28
28
  Out of sight but rendered, not `hidden`: Safari before 16.4 skips a display:none button
29
29
  when it picks the default button (webkit.org/b/247545) and would press a bulk button. %>
30
- <button type="submit" class="pf-sr-only" tabindex="-1" aria-hidden="true"><%= t('permission_forge.shared.savebar.review') %></button>
30
+ <button type="submit" class="pf-sr-only" tabindex="-1" aria-hidden="true" <%= 'disabled' if locked %>><%= t('permission_forge.shared.savebar.review') %></button>
31
31
  <% end %>
32
32
 
33
33
  <div class="pf-page-header">
@@ -16,7 +16,7 @@
16
16
  </div>
17
17
  <div class="pf-setting__value">
18
18
  <%= pf_setting_value(row) %>
19
- <span class="pf-fs-12 pf-text-3"><%= t('.default', value: pf_setting_default(row)) %></span>
19
+ <span class="pf-fs-12 pf-text-3"><%= t('.default_html', value: tag.code(pf_setting_default(row), class: 'pf-mono')) %></span>
20
20
  </div>
21
21
  <details class="pf-setting__how" <%= 'open' if focused %>>
22
22
  <summary class="pf-btn pf-btn--sm"><%= t('.how_to_change') %></summary>
@@ -32,7 +32,7 @@
32
32
  <li>2. <%= t('.step_deploy') %></li>
33
33
  <li>3. <%= t('.step_reload') %></li>
34
34
  </ol>
35
- <%= link_to t('.docs', key: key), "#{PermissionForge.configuration.admin_docs_url}##{key}", class: 'pf-fs-12', target: '_blank', rel: 'noopener noreferrer' %>
35
+ <%= link_to t('.docs_html', key: tag.code(key)), "#{PermissionForge.configuration.admin_docs_url}##{key}", class: 'pf-fs-12', target: '_blank', rel: 'noopener noreferrer' %>
36
36
  </div>
37
37
  </details>
38
38
  </div>
@@ -24,8 +24,17 @@
24
24
  <%= link_to t('.how_to_fix'), permission_forge.settings_path(focus: row.key, anchor: "set-#{row.key}"), class: 'pf-btn pf-btn--sm' %>
25
25
  </div>
26
26
  <% end %>
27
+ <%# Home's items count here too: Settings never says the setup looks good while Home warns. %>
28
+ <% attention = @catalog.attention_count %>
29
+ <% if attention.positive? %>
30
+ <div class="pf-card__row pf-card__row--top">
31
+ <span class="pf-text-warn" aria-hidden="true">⚠</span>
32
+ <span class="pf-grow"><%= t('.attention', count: attention) %></span>
33
+ <%= link_to t('.open_home'), permission_forge.root_path, class: 'pf-btn pf-btn--sm' %>
34
+ </div>
35
+ <% end %>
27
36
  <div class="pf-card__row">
28
- <span class="pf-text-grant">✓ <%= t('.secure_count', count: @catalog.secure_count) %></span>
37
+ <span class="pf-text-grant">✓ <%= @catalog.healthy? ? t('.secure_count', count: @catalog.secure_count) : t('.secure_count_set', count: @catalog.secure_count) %></span>
29
38
  </div>
30
39
  </section>
31
40
 
@@ -3,27 +3,26 @@
3
3
  <% if rows.empty? %>
4
4
  <p class="pf-fs-13 pf-text-3"><%= t('.none') %></p>
5
5
  <% else %>
6
+ <% chain = rows.map { |row| row[:resource].name }.uniq %>
6
7
  <div class="pf-listbox">
7
8
  <% rows.each do |row| %>
8
9
  <% set = row[:set] %>
9
10
  <% field = row[:field] %>
10
- <% level = field && PermissionForge::Admin::FieldLevel.level_of(field, muting: set.is_muting) %>
11
+ <% level_model = PermissionForge::Admin::FieldLevel %>
12
+ <%# No row: a Muting Permission Set is on Unchanged (it mutes nothing), as on its Fields tab. %>
13
+ <% level = field ? level_model.level_of(field, muting: set.is_muting) : level_model.unconfigured_level(muting: set.is_muting) %>
11
14
  <%= link_to permission_forge.permission_set_resource_field_path(set, row[:resource].name, key),
12
15
  class: 'pf-option', 'aria-current': (local_assigns[:current_set] == set ? 'true' : nil) do %>
13
16
  <span class="pf-option__main">
14
17
  <span class="pf-row pf-row--tight">
15
18
  <% if set.is_muting %><span class="pf-text-restrict" aria-hidden="true">⊘</span><% end %>
16
- <span class="pf-fw-600"><%= set.name %></span>
19
+ <%# "<set> · <Model>" when the set holds more than one Resource of the chain (B#35). %>
20
+ <span class="pf-fw-600"><%= pf_set_entry_name(set, row[:resource].name, chain) %></span>
17
21
  <%= pf_off_badge unless row[:in_effect] %>
18
22
  </span>
19
23
  </span>
20
- <% if level.nil? %>
21
- <%= pf_level_badge('none', label: t('permission_forge.ui.badges.not_configured')) %>
22
- <% elsif set.is_muting %>
23
- <%= pf_level_badge(level == 'hide' ? 'muted' : level, label: (t('.muted_hidden') if level == 'hide')) %>
24
- <% else %>
25
- <%= pf_level_badge(level, strategy: (field.mask_strategy if level == 'masked')) %>
26
- <% end %>
24
+ <%# The set's level, worded as its Fields tab and the field drawer word it. %>
25
+ <%= pf_level_badge(level || 'none', strategy: (field.mask_strategy if %w[masked mask].include?(level))) %>
27
26
  <span class="pf-option__aside"><%= pf_people_text(row[:people]) %></span>
28
27
  <% end %>
29
28
  <% end %>
@@ -21,9 +21,9 @@
21
21
  title: t('permission_forge.audit_log.filter_actor') %>
22
22
  <span aria-hidden="true">·</span>
23
23
  <span><%= pf_audit_source_label(event.source) %></span>
24
- <% if event.reason.present? %>
24
+ <% if (reason = pf_audit_reason(event)) %>
25
25
  <span aria-hidden="true">·</span>
26
- <span class="pf-audit-event__reason"><%= t('permission_forge.audit_log.reason_inline', reason: event.reason) %></span>
26
+ <span class="pf-audit-event__reason"><%= t('permission_forge.audit_log.reason_inline', reason: reason) %></span>
27
27
  <% end %>
28
28
  </div>
29
29
  </div>
@@ -1,15 +1,26 @@
1
1
  <footer class="pf-audit-footer">
2
2
  <% if PermissionForge.configuration.audit_trail_enabled %>
3
3
  <% last_event = pf_audit_last_change(record) %>
4
+ <% creations = pf_audit_creations([record]) unless @pf_creations&.covers?(record) %>
5
+ <% creation = pf_audit_creation_event(record, creations) %>
6
+ <%# "Unknown" is said once: an edit by nobody known after a creation by nobody known names no actor again. %>
7
+ <% unknown_twice = pf_audit_unknown_actor?(creation) && pf_audit_unknown_actor?(last_event) %>
4
8
  <%= prefix %>
5
- <%= t('.created', created_by: pf_person_name(record.try(:created_by)), created_at: pf_date(record.created_at)) %>
9
+ <%= t('.created', created_by: pf_audit_creator(record, creations), created_at: pf_date(record.created_at)) %>
6
10
  ·
7
- <% if last_event %>
11
+ <% if unknown_twice %>
12
+ <%= t('.edited_at', updated_at: pf_relative(last_event.occurred_at)) %>
13
+ · <%= link_to t('.history'), pf_audit_log_path(record_type: last_event.record_type, record_id: last_event.record_id) %>
14
+ <% elsif last_event %>
8
15
  <%= t('.edited', updated_by: pf_audit_actor_name(last_event), updated_at: pf_relative(last_event.occurred_at)) %>
9
16
  · <%= link_to t('.history'), pf_audit_log_path(record_type: last_event.record_type, record_id: last_event.record_id) %>
10
17
  <% else %>
11
18
  <%= t('.edited', updated_by: pf_person_name(record.try(:updated_by)), updated_at: pf_relative(record.updated_at)) %>
12
19
  <% end %>
20
+ <%# "Unknown" is the audit log's own name for an event with no actor; say where it comes from. %>
21
+ <% if pf_audit_unknown_actor?(last_event) || pf_audit_unknown_actor?(creation) %>
22
+ <span class="pf-audit-footer__hint"><%= pf_audit_unknown_actor_hint %></span>
23
+ <% end %>
13
24
  <% else %>
14
25
  <%= t('.disabled') %> <%= pf_setting_link('audit_trail_enabled') %>
15
26
  <% end %>
@@ -3,16 +3,16 @@
3
3
  <% form_attr = local_assigns[:form] ? { form: form } : {} %>
4
4
  <% requires = local_assigns[:requires] %>
5
5
  <label class="pf-cell<%= ' pf-cell--muting' if muting %><%= ' pf-cell--lg' if local_assigns[:size] == :lg %>"
6
- title="<%= t('.needs_read') if requires && !muting %>">
6
+ title="<%= t('permission_forge.ui.needs_read_first') if requires && !muting %>">
7
7
  <%= tag.input(type: 'hidden', name: name, value: '0', **form_attr) %>
8
8
  <%= tag.input(type: 'checkbox', name: name, value: '1', checked: checked, disabled: locked,
9
9
  'aria-label': aria, data: { pf_flag: flag, pf_requires: (muting ? nil : requires), pf_locked: (locked || nil) }, **form_attr) %>
10
10
  <% if muting %>
11
- <span class="pf-cell__on"><span class="pf-cell__glyph" aria-hidden="true">⊘</span><%= t('.muted') %></span>
12
- <span class="pf-cell__off"><span class="pf-cell__glyph" aria-hidden="true">–</span><%= t('.unchanged') %></span>
11
+ <span class="pf-cell__on"><%= pf_state_parts(pf_state(:crud, :muted)) %></span>
12
+ <span class="pf-cell__off"><%= pf_state_parts(pf_state(:crud, :unchanged)) %></span>
13
13
  <% else %>
14
- <span class="pf-cell__on"><span class="pf-cell__glyph" aria-hidden="true">✓</span><%= t('.allowed') %></span>
15
- <span class="pf-cell__off"><span class="pf-cell__glyph" aria-hidden="true">–</span><%= t('.no') %></span>
16
- <span class="pf-cell__needs"><span class="pf-cell__glyph" aria-hidden="true">–</span><%= t('.needs_read_short') %></span>
14
+ <span class="pf-cell__on"><%= pf_state_parts(pf_state(:crud, :allowed)) %></span>
15
+ <span class="pf-cell__off"><%= pf_state_parts(pf_state(:crud, :no)) %></span>
16
+ <span class="pf-cell__needs"><%= pf_state_parts(pf_state(:crud, :needs_read)) %></span>
17
17
  <% end %>
18
18
  </label>
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
  </div>
10
10
  <% end %>
11
- <% if pf_locked_record?(record) %>
11
+ <% if pf_locked?(record) %>
12
12
  <div class="pf-banner" role="note">
13
13
  <span class="pf-banner__glyph" aria-hidden="true"><%= pf_lock_icon %></span>
14
14
  <div class="pf-banner__body">
@@ -1,5 +1,7 @@
1
- <%# Unsaved-changes bar: the submit area of a batched-edit form (form="id"). %>
2
- <div id="<%= id %>" class="pf-savebar" role="region" aria-label="<%= t('.label') %>" data-pf-dirty="false">
1
+ <%# Unsaved-changes bar: the submit area of a batched-edit form (form="id").
2
+ `quiet: true` — the page's own controls apply at once, so with JS the bar shows only once
3
+ something joined to the form (the header) has changed; without JS it stays, as the only way to save that. %>
4
+ <div id="<%= id %>" class="pf-savebar<%= ' pf-savebar--quiet' if local_assigns[:quiet] %>" role="region" aria-label="<%= t('.label') %>" data-pf-dirty="false">
3
5
  <div class="pf-savebar__text">
4
6
  <span class="pf-savebar__count pf-savebar__count--dirty" data-pf-dirty-count
5
7
  data-pf-one="<%= t('.count.one', count: '%{count}') %>" data-pf-other="<%= t('.count.other', count: '%{count}') %>"></span>