permission_forge 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1138 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +356 -0
  5. data/app/controllers/concerns/permission_forge/acting_admin.rb +17 -0
  6. data/app/controllers/concerns/permission_forge/api/crud_actions.rb +84 -0
  7. data/app/controllers/concerns/permission_forge/api/nested_resource_lookup.rb +18 -0
  8. data/app/controllers/concerns/permission_forge/api/pagination.rb +50 -0
  9. data/app/controllers/concerns/permission_forge/api/response_renderer.rb +65 -0
  10. data/app/controllers/concerns/permission_forge/api/serializer_resolver.rb +30 -0
  11. data/app/controllers/concerns/permission_forge/api_authentication.rb +40 -0
  12. data/app/controllers/concerns/permission_forge/assignment_authorization.rb +97 -0
  13. data/app/controllers/concerns/permission_forge/audit_log_gate.rb +55 -0
  14. data/app/controllers/concerns/permission_forge/basic_authentication.rb +77 -0
  15. data/app/controllers/concerns/permission_forge/cache_confirmation.rb +32 -0
  16. data/app/controllers/concerns/permission_forge/forgery_protection.rb +29 -0
  17. data/app/controllers/concerns/permission_forge/html_format.rb +40 -0
  18. data/app/controllers/concerns/permission_forge/param_shapes.rb +87 -0
  19. data/app/controllers/concerns/permission_forge/tenant_required.rb +42 -0
  20. data/app/controllers/permission_forge/api/audit_events_controller.rb +33 -0
  21. data/app/controllers/permission_forge/api/base_controller.rb +220 -0
  22. data/app/controllers/permission_forge/api/dashboard_controller.rb +30 -0
  23. data/app/controllers/permission_forge/api/permission_set_group_users_controller.rb +51 -0
  24. data/app/controllers/permission_forge/api/permission_set_groups_controller.rb +71 -0
  25. data/app/controllers/permission_forge/api/permission_set_rules_controller.rb +37 -0
  26. data/app/controllers/permission_forge/api/permission_set_users_controller.rb +105 -0
  27. data/app/controllers/permission_forge/api/permission_sets_controller.rb +87 -0
  28. data/app/controllers/permission_forge/api/resource_actions_controller.rb +58 -0
  29. data/app/controllers/permission_forge/api/resource_fields_controller.rb +30 -0
  30. data/app/controllers/permission_forge/api/resources_controller.rb +56 -0
  31. data/app/controllers/permission_forge/application_controller.rb +198 -0
  32. data/app/controllers/permission_forge/assets_controller.rb +97 -0
  33. data/app/controllers/permission_forge/assignments_controller.rb +266 -0
  34. data/app/controllers/permission_forge/audit_events_controller.rb +94 -0
  35. data/app/controllers/permission_forge/bulk_assignments_controller.rb +108 -0
  36. data/app/controllers/permission_forge/field_lookups_controller.rb +38 -0
  37. data/app/controllers/permission_forge/home_controller.rb +24 -0
  38. data/app/controllers/permission_forge/maintenance_controller.rb +13 -0
  39. data/app/controllers/permission_forge/people_controller.rb +190 -0
  40. data/app/controllers/permission_forge/permission_set_editor.rb +71 -0
  41. data/app/controllers/permission_forge/permission_set_group_users_controller.rb +57 -0
  42. data/app/controllers/permission_forge/permission_set_groups_controller.rb +111 -0
  43. data/app/controllers/permission_forge/permission_set_rules_controller.rb +97 -0
  44. data/app/controllers/permission_forge/permission_sets_controller.rb +227 -0
  45. data/app/controllers/permission_forge/preferences_controller.rb +26 -0
  46. data/app/controllers/permission_forge/resource_actions_controller.rb +106 -0
  47. data/app/controllers/permission_forge/resource_fields_controller.rb +129 -0
  48. data/app/controllers/permission_forge/resources_controller.rb +189 -0
  49. data/app/controllers/permission_forge/settings_controller.rb +33 -0
  50. data/app/frontend/permission_forge/brand/apple-touch-icon.png +0 -0
  51. data/app/frontend/permission_forge/brand/chevron.svg +1 -0
  52. data/app/frontend/permission_forge/brand/favicon.ico +0 -0
  53. data/app/frontend/permission_forge/brand/favicon.svg +1 -0
  54. data/app/frontend/permission_forge/brand/logo-horizontal-dark.svg +1 -0
  55. data/app/frontend/permission_forge/brand/logo-horizontal-light.svg +1 -0
  56. data/app/frontend/permission_forge/brand/mark-small.svg +1 -0
  57. data/app/frontend/permission_forge/brand/mark.svg +1 -0
  58. data/app/frontend/permission_forge/pf_admin.css +766 -0
  59. data/app/frontend/permission_forge/pf_admin.js +482 -0
  60. data/app/helpers/permission_forge/application_helper.rb +167 -0
  61. data/app/helpers/permission_forge/audit_helper.rb +230 -0
  62. data/app/helpers/permission_forge/audit_sentences_helper.rb +84 -0
  63. data/app/helpers/permission_forge/badges_helper.rb +134 -0
  64. data/app/helpers/permission_forge/changes_helper.rb +117 -0
  65. data/app/helpers/permission_forge/people_helper.rb +118 -0
  66. data/app/helpers/permission_forge/sentences_helper.rb +175 -0
  67. data/app/helpers/permission_forge/settings_helper.rb +152 -0
  68. data/app/helpers/permission_forge/ui_helper.rb +184 -0
  69. data/app/models/permission_forge/audit_event.rb +159 -0
  70. data/app/models/permission_forge/permission_set.rb +100 -0
  71. data/app/models/permission_forge/permission_set_group.rb +83 -0
  72. data/app/models/permission_forge/permission_set_group_user.rb +43 -0
  73. data/app/models/permission_forge/permission_set_rule.rb +99 -0
  74. data/app/models/permission_forge/permission_set_user.rb +158 -0
  75. data/app/models/permission_forge/resource.rb +100 -0
  76. data/app/models/permission_forge/resource_action.rb +58 -0
  77. data/app/models/permission_forge/resource_field.rb +69 -0
  78. data/app/views/layouts/permission_forge/application.html.erb +30 -0
  79. data/app/views/layouts/permission_forge/fragment.html.erb +2 -0
  80. data/app/views/permission_forge/assignments/_person_options.html.erb +26 -0
  81. data/app/views/permission_forge/assignments/confirm_destroy.html.erb +24 -0
  82. data/app/views/permission_forge/assignments/new.html.erb +99 -0
  83. data/app/views/permission_forge/audit_events/index.html.erb +161 -0
  84. data/app/views/permission_forge/audit_events/show.html.erb +146 -0
  85. data/app/views/permission_forge/bulk_assignments/confirm_revoke.html.erb +21 -0
  86. data/app/views/permission_forge/bulk_assignments/new.html.erb +69 -0
  87. data/app/views/permission_forge/field_lookups/show.html.erb +32 -0
  88. data/app/views/permission_forge/home/_audit_card.html.erb +19 -0
  89. data/app/views/permission_forge/home/_ending_card.html.erb +32 -0
  90. data/app/views/permission_forge/home/_expired_card.html.erb +34 -0
  91. data/app/views/permission_forge/home/_privacy_card.html.erb +67 -0
  92. data/app/views/permission_forge/home/_rules_card.html.erb +23 -0
  93. data/app/views/permission_forge/home/_stale_card.html.erb +39 -0
  94. data/app/views/permission_forge/home/_tidy_card.html.erb +18 -0
  95. data/app/views/permission_forge/home/index.html.erb +69 -0
  96. data/app/views/permission_forge/people/_access_tab.html.erb +58 -0
  97. data/app/views/permission_forge/people/_audit_tab.html.erb +93 -0
  98. data/app/views/permission_forge/people/_effective_field_source.html.erb +11 -0
  99. data/app/views/permission_forge/people/_effective_tab.html.erb +118 -0
  100. data/app/views/permission_forge/people/_effective_why.html.erb +48 -0
  101. data/app/views/permission_forge/people/_why_item.html.erb +19 -0
  102. data/app/views/permission_forge/people/confirm_remove_all.html.erb +27 -0
  103. data/app/views/permission_forge/people/consent_preview.html.erb +39 -0
  104. data/app/views/permission_forge/people/erasure_preview.html.erb +28 -0
  105. data/app/views/permission_forge/people/index.html.erb +78 -0
  106. data/app/views/permission_forge/people/show.html.erb +54 -0
  107. data/app/views/permission_forge/permission_set_groups/confirm_destroy.html.erb +12 -0
  108. data/app/views/permission_forge/permission_set_groups/index.html.erb +55 -0
  109. data/app/views/permission_forge/permission_set_groups/show.html.erb +100 -0
  110. data/app/views/permission_forge/permission_set_rules/_builder.html.erb +145 -0
  111. data/app/views/permission_forge/permission_set_rules/_value_input.html.erb +42 -0
  112. data/app/views/permission_forge/permission_set_rules/confirm_destroy.html.erb +13 -0
  113. data/app/views/permission_forge/permission_set_rules/edit.html.erb +3 -0
  114. data/app/views/permission_forge/permission_set_rules/new.html.erb +3 -0
  115. data/app/views/permission_forge/permission_sets/_assignments_tab.html.erb +74 -0
  116. data/app/views/permission_forge/permission_sets/_audit_tab.html.erb +80 -0
  117. data/app/views/permission_forge/permission_sets/_conditions_tab.html.erb +83 -0
  118. data/app/views/permission_forge/permission_sets/_header.html.erb +36 -0
  119. data/app/views/permission_forge/permission_sets/_records_summary.html.erb +16 -0
  120. data/app/views/permission_forge/permission_sets/_resource_summary.html.erb +11 -0
  121. data/app/views/permission_forge/permission_sets/_resources_tab.html.erb +49 -0
  122. data/app/views/permission_forge/permission_sets/confirm_destroy.html.erb +23 -0
  123. data/app/views/permission_forge/permission_sets/confirm_switch_mode.html.erb +15 -0
  124. data/app/views/permission_forge/permission_sets/index.html.erb +83 -0
  125. data/app/views/permission_forge/permission_sets/new.html.erb +63 -0
  126. data/app/views/permission_forge/permission_sets/review.html.erb +26 -0
  127. data/app/views/permission_forge/permission_sets/show.html.erb +50 -0
  128. data/app/views/permission_forge/resource_actions/_across_sets.html.erb +24 -0
  129. data/app/views/permission_forge/resource_actions/_add_select.html.erb +12 -0
  130. data/app/views/permission_forge/resource_actions/_effect.html.erb +25 -0
  131. data/app/views/permission_forge/resource_actions/_endpoints.html.erb +16 -0
  132. data/app/views/permission_forge/resource_actions/_form.html.erb +44 -0
  133. data/app/views/permission_forge/resource_actions/_managed_note.html.erb +11 -0
  134. data/app/views/permission_forge/resource_actions/confirm_destroy.html.erb +13 -0
  135. data/app/views/permission_forge/resource_actions/new.html.erb +31 -0
  136. data/app/views/permission_forge/resource_actions/show.html.erb +39 -0
  137. data/app/views/permission_forge/resource_fields/_explain.html.erb +24 -0
  138. data/app/views/permission_forge/resource_fields/show.html.erb +186 -0
  139. data/app/views/permission_forge/resources/_actions_tab.html.erb +88 -0
  140. data/app/views/permission_forge/resources/_fields_tab.html.erb +89 -0
  141. data/app/views/permission_forge/resources/_rules_tab.html.erb +55 -0
  142. data/app/views/permission_forge/resources/_sync.html.erb +57 -0
  143. data/app/views/permission_forge/resources/confirm_destroy.html.erb +17 -0
  144. data/app/views/permission_forge/resources/new.html.erb +57 -0
  145. data/app/views/permission_forge/resources/show.html.erb +117 -0
  146. data/app/views/permission_forge/settings/_ladder.html.erb +15 -0
  147. data/app/views/permission_forge/settings/_row.html.erb +38 -0
  148. data/app/views/permission_forge/settings/show.html.erb +67 -0
  149. data/app/views/permission_forge/shared/_across_sets.html.erb +31 -0
  150. data/app/views/permission_forge/shared/_audit_events.html.erb +36 -0
  151. data/app/views/permission_forge/shared/_audit_footer.html.erb +16 -0
  152. data/app/views/permission_forge/shared/_audit_off_note.html.erb +15 -0
  153. data/app/views/permission_forge/shared/_audit_pagination.html.erb +19 -0
  154. data/app/views/permission_forge/shared/_audit_refused_note.html.erb +7 -0
  155. data/app/views/permission_forge/shared/_audit_timeline.html.erb +29 -0
  156. data/app/views/permission_forge/shared/_crud_cell.html.erb +18 -0
  157. data/app/views/permission_forge/shared/_dialog_frame.html.erb +11 -0
  158. data/app/views/permission_forge/shared/_expiry_picker.html.erb +27 -0
  159. data/app/views/permission_forge/shared/_head_extra.html.erb +3 -0
  160. data/app/views/permission_forge/shared/_help_banner.html.erb +10 -0
  161. data/app/views/permission_forge/shared/_level_control.html.erb +18 -0
  162. data/app/views/permission_forge/shared/_mode_banners.html.erb +22 -0
  163. data/app/views/permission_forge/shared/_nav.html.erb +12 -0
  164. data/app/views/permission_forge/shared/_preferences.html.erb +24 -0
  165. data/app/views/permission_forge/shared/_reason_field.html.erb +20 -0
  166. data/app/views/permission_forge/shared/_savebar.html.erb +13 -0
  167. data/app/views/permission_forge/shared/_sheet_frame.html.erb +22 -0
  168. data/app/views/permission_forge/shared/_sidebar.html.erb +18 -0
  169. data/app/views/permission_forge/shared/_tech_details.html.erb +20 -0
  170. data/app/views/permission_forge/shared/_toasts.html.erb +27 -0
  171. data/app/views/permission_forge/shared/_topbar.html.erb +15 -0
  172. data/app/views/permission_forge/shared/_user_select.html.erb +16 -0
  173. data/app/views/permission_forge/shared/audit_refused.html.erb +7 -0
  174. data/config/locales/permission_forge.en.yml +2115 -0
  175. data/config/locales/permission_forge.mk.yml +2166 -0
  176. data/config/locales/permission_forge.sq.yml +2164 -0
  177. data/config/routes.rb +143 -0
  178. data/lib/generators/permission_forge/install/install_generator.rb +105 -0
  179. data/lib/generators/permission_forge/install/templates/initializer.rb.tt +148 -0
  180. data/lib/generators/permission_forge/install/templates/migration.rb.tt +198 -0
  181. data/lib/generators/permission_forge/rls/rls_generator.rb +57 -0
  182. data/lib/generators/permission_forge/rls/templates/enable_permission_forge_rls.rb.tt +15 -0
  183. data/lib/generators/permission_forge/upgrade/templates/add_admin_ui_columns.rb.tt +8 -0
  184. data/lib/generators/permission_forge/upgrade/templates/add_audit_events.rb.tt +98 -0
  185. data/lib/generators/permission_forge/upgrade/templates/add_resource_fields_unique_index.rb.tt +69 -0
  186. data/lib/generators/permission_forge/upgrade/upgrade_generator.rb +66 -0
  187. data/lib/permission_forge/admin/action_across_sets.rb +76 -0
  188. data/lib/permission_forge/admin/action_rows.rb +156 -0
  189. data/lib/permission_forge/admin/attention_items.rb +207 -0
  190. data/lib/permission_forge/admin/audit_event_detail.rb +75 -0
  191. data/lib/permission_forge/admin/audit_log.rb +193 -0
  192. data/lib/permission_forge/admin/audit_log_days.rb +41 -0
  193. data/lib/permission_forge/admin/audit_lookups.rb +105 -0
  194. data/lib/permission_forge/admin/audit_name_history.rb +85 -0
  195. data/lib/permission_forge/admin/audit_trail.rb +169 -0
  196. data/lib/permission_forge/admin/bulk_people_matcher.rb +109 -0
  197. data/lib/permission_forge/admin/change_set.rb +123 -0
  198. data/lib/permission_forge/admin/change_set_parts/actions.rb +27 -0
  199. data/lib/permission_forge/admin/change_set_parts/base.rb +58 -0
  200. data/lib/permission_forge/admin/change_set_parts/context.rb +50 -0
  201. data/lib/permission_forge/admin/change_set_parts/field_edit.rb +67 -0
  202. data/lib/permission_forge/admin/change_set_parts/field_levels.rb +71 -0
  203. data/lib/permission_forge/admin/change_set_parts/new_actions.rb +91 -0
  204. data/lib/permission_forge/admin/change_set_parts/new_fields.rb +63 -0
  205. data/lib/permission_forge/admin/change_set_parts/resource_edit.rb +55 -0
  206. data/lib/permission_forge/admin/change_set_parts/resource_flags.rb +19 -0
  207. data/lib/permission_forge/admin/change_set_parts/set_attrs.rb +45 -0
  208. data/lib/permission_forge/admin/change_set_parts/values.rb +20 -0
  209. data/lib/permission_forge/admin/column_fields.rb +48 -0
  210. data/lib/permission_forge/admin/conditions_form.rb +78 -0
  211. data/lib/permission_forge/admin/controller_catalog.rb +166 -0
  212. data/lib/permission_forge/admin/csv.rb +41 -0
  213. data/lib/permission_forge/admin/editable_lock.rb +157 -0
  214. data/lib/permission_forge/admin/effective_access.rb +158 -0
  215. data/lib/permission_forge/admin/effective_fields.rb +176 -0
  216. data/lib/permission_forge/admin/expiry_presets.rb +51 -0
  217. data/lib/permission_forge/admin/exports/document.rb +89 -0
  218. data/lib/permission_forge/admin/exports/events.rb +51 -0
  219. data/lib/permission_forge/admin/exports/holdings.rb +118 -0
  220. data/lib/permission_forge/admin/exports.rb +37 -0
  221. data/lib/permission_forge/admin/field_across_sets.rb +51 -0
  222. data/lib/permission_forge/admin/field_detail.rb +112 -0
  223. data/lib/permission_forge/admin/field_level.rb +74 -0
  224. data/lib/permission_forge/admin/field_rows.rb +137 -0
  225. data/lib/permission_forge/admin/last_changes.rb +94 -0
  226. data/lib/permission_forge/admin/model_catalog.rb +216 -0
  227. data/lib/permission_forge/admin/people_candidates.rb +108 -0
  228. data/lib/permission_forge/admin/people_directory.rb +158 -0
  229. data/lib/permission_forge/admin/permission_set_factory.rb +48 -0
  230. data/lib/permission_forge/admin/person_label.rb +95 -0
  231. data/lib/permission_forge/admin/record_id.rb +19 -0
  232. data/lib/permission_forge/admin/rule_builder.rb +58 -0
  233. data/lib/permission_forge/admin/rule_form.rb +175 -0
  234. data/lib/permission_forge/admin/rule_form_input.rb +74 -0
  235. data/lib/permission_forge/admin/sensitive_columns.rb +68 -0
  236. data/lib/permission_forge/admin/settings_catalog.rb +261 -0
  237. data/lib/permission_forge/admin/settings_values.rb +130 -0
  238. data/lib/permission_forge/api/serializers/audit_event_serializer.rb +27 -0
  239. data/lib/permission_forge/api/serializers/base_serializer.rb +36 -0
  240. data/lib/permission_forge/api/serializers/permission_set_group_serializer.rb +12 -0
  241. data/lib/permission_forge/api/serializers/permission_set_group_user_serializer.rb +11 -0
  242. data/lib/permission_forge/api/serializers/permission_set_rule_serializer.rb +25 -0
  243. data/lib/permission_forge/api/serializers/permission_set_serializer.rb +13 -0
  244. data/lib/permission_forge/api/serializers/permission_set_user_serializer.rb +11 -0
  245. data/lib/permission_forge/api/serializers/resource_action_serializer.rb +13 -0
  246. data/lib/permission_forge/api/serializers/resource_field_serializer.rb +14 -0
  247. data/lib/permission_forge/api/serializers/resource_serializer.rb +15 -0
  248. data/lib/permission_forge/audit/actor.rb +144 -0
  249. data/lib/permission_forge/audit/actor_resolver.rb +103 -0
  250. data/lib/permission_forge/audit/audited_views.rb +208 -0
  251. data/lib/permission_forge/audit/boot_warnings.rb +58 -0
  252. data/lib/permission_forge/audit/column_values.rb +56 -0
  253. data/lib/permission_forge/audit/context.rb +98 -0
  254. data/lib/permission_forge/audit/detached.rb +62 -0
  255. data/lib/permission_forge/audit/event_catalog.rb +110 -0
  256. data/lib/permission_forge/audit/event_query.rb +192 -0
  257. data/lib/permission_forge/audit/failure.rb +47 -0
  258. data/lib/permission_forge/audit/field_access.rb +186 -0
  259. data/lib/permission_forge/audit/gate.rb +167 -0
  260. data/lib/permission_forge/audit/job_context.rb +32 -0
  261. data/lib/permission_forge/audit/log_access.rb +36 -0
  262. data/lib/permission_forge/audit/recorder.rb +168 -0
  263. data/lib/permission_forge/audit/refusals.rb +61 -0
  264. data/lib/permission_forge/audit/request_context.rb +152 -0
  265. data/lib/permission_forge/audit/runtime.rb +178 -0
  266. data/lib/permission_forge/audit/runtime_emission.rb +135 -0
  267. data/lib/permission_forge/audit/settings_observer.rb +70 -0
  268. data/lib/permission_forge/audit/source.rb +54 -0
  269. data/lib/permission_forge/audit/subscribers/base.rb +69 -0
  270. data/lib/permission_forge/audit/subscribers/legacy_hooks.rb +97 -0
  271. data/lib/permission_forge/audit/subscribers/log.rb +45 -0
  272. data/lib/permission_forge/audit/subscribers.rb +58 -0
  273. data/lib/permission_forge/audit.rb +302 -0
  274. data/lib/permission_forge/boot_checks.rb +184 -0
  275. data/lib/permission_forge/cache/backlog.rb +142 -0
  276. data/lib/permission_forge/cache/base.rb +200 -0
  277. data/lib/permission_forge/cache/bulk_invalidation.rb +131 -0
  278. data/lib/permission_forge/cache/failure.rb +171 -0
  279. data/lib/permission_forge/cache/handled_errors.rb +82 -0
  280. data/lib/permission_forge/cache/holder_invalidation.rb +85 -0
  281. data/lib/permission_forge/cache/invalidation.rb +180 -0
  282. data/lib/permission_forge/cache/memory_cache.rb +77 -0
  283. data/lib/permission_forge/cache/null_cache.rb +40 -0
  284. data/lib/permission_forge/cache/rails_cache.rb +231 -0
  285. data/lib/permission_forge/cache/rebuild.rb +149 -0
  286. data/lib/permission_forge/cache/settlement.rb +63 -0
  287. data/lib/permission_forge/cache/user_keys.rb +206 -0
  288. data/lib/permission_forge/cache/variant_removal.rb +55 -0
  289. data/lib/permission_forge/cache/withheld_notice.rb +60 -0
  290. data/lib/permission_forge/concerns/audited_collection.rb +34 -0
  291. data/lib/permission_forge/concerns/auto_permission_scoping.rb +158 -0
  292. data/lib/permission_forge/concerns/controller_action_guard.rb +106 -0
  293. data/lib/permission_forge/concerns/field_mask_checker.rb +45 -0
  294. data/lib/permission_forge/concerns/has_active_scope.rb +12 -0
  295. data/lib/permission_forge/concerns/has_audit_trail.rb +59 -0
  296. data/lib/permission_forge/concerns/has_permission_set_users.rb +115 -0
  297. data/lib/permission_forge/concerns/has_tenant_scoping.rb +86 -0
  298. data/lib/permission_forge/concerns/has_uuid.rb +24 -0
  299. data/lib/permission_forge/concerns/invalidates_in_batch.rb +207 -0
  300. data/lib/permission_forge/concerns/invalidates_managed_actions.rb +80 -0
  301. data/lib/permission_forge/concerns/masked_attributes/alias_access.rb +280 -0
  302. data/lib/permission_forge/concerns/masked_attributes/store_access.rb +100 -0
  303. data/lib/permission_forge/concerns/masked_attributes/validation_reads.rb +153 -0
  304. data/lib/permission_forge/concerns/masked_attributes/virtual_readers.rb +242 -0
  305. data/lib/permission_forge/concerns/masked_attributes.rb +576 -0
  306. data/lib/permission_forge/concerns/permission_cache_accessor.rb +416 -0
  307. data/lib/permission_forge/concerns/permission_scoped_findable.rb +337 -0
  308. data/lib/permission_forge/concerns/permissioned_user.rb +78 -0
  309. data/lib/permission_forge/concerns/permissions_memo.rb +160 -0
  310. data/lib/permission_forge/concerns/permissions_per_context.rb +288 -0
  311. data/lib/permission_forge/concerns/permissions_regenerator.rb +43 -0
  312. data/lib/permission_forge/concerns/records_audit_events.rb +286 -0
  313. data/lib/permission_forge/concerns/resource_permission_checker.rb +116 -0
  314. data/lib/permission_forge/concerns/validates_column_lengths.rb +69 -0
  315. data/lib/permission_forge/concerns/validates_dependency.rb +54 -0
  316. data/lib/permission_forge/conditional_sets.rb +361 -0
  317. data/lib/permission_forge/configuration/admin_ui_settings.rb +108 -0
  318. data/lib/permission_forge/configuration/audit_settings.rb +184 -0
  319. data/lib/permission_forge/configuration/cache_settings.rb +76 -0
  320. data/lib/permission_forge/configuration/controller_action_settings.rb +112 -0
  321. data/lib/permission_forge/configuration/gdpr_settings.rb +70 -0
  322. data/lib/permission_forge/configuration/masked_attributes_settings.rb +83 -0
  323. data/lib/permission_forge/configuration/permits_attributes_settings.rb +26 -0
  324. data/lib/permission_forge/configuration/presets.rb +139 -0
  325. data/lib/permission_forge/configuration/query_scoping_settings.rb +107 -0
  326. data/lib/permission_forge/configuration/strategy_values.rb +57 -0
  327. data/lib/permission_forge/configuration/virtual_attributes_settings.rb +65 -0
  328. data/lib/permission_forge/configuration.rb +624 -0
  329. data/lib/permission_forge/controller_actions/authorizer.rb +171 -0
  330. data/lib/permission_forge/controller_actions/managed_index.rb +213 -0
  331. data/lib/permission_forge/controller_actions/mapping.rb +167 -0
  332. data/lib/permission_forge/controller_actions.rb +122 -0
  333. data/lib/permission_forge/deprecation.rb +86 -0
  334. data/lib/permission_forge/engine.rb +113 -0
  335. data/lib/permission_forge/execution_state.rb +318 -0
  336. data/lib/permission_forge/invalidation_batch.rb +206 -0
  337. data/lib/permission_forge/masked_models.rb +226 -0
  338. data/lib/permission_forge/masking_sync.rb +271 -0
  339. data/lib/permission_forge/model_bindings.rb +90 -0
  340. data/lib/permission_forge/open_transaction.rb +303 -0
  341. data/lib/permission_forge/operators/constants.rb +41 -0
  342. data/lib/permission_forge/operators/registry.rb +300 -0
  343. data/lib/permission_forge/operators/rule_column.rb +120 -0
  344. data/lib/permission_forge/operators/validations.rb +111 -0
  345. data/lib/permission_forge/operators/value_casters/number.rb +68 -0
  346. data/lib/permission_forge/operators/value_casters/temporal.rb +69 -0
  347. data/lib/permission_forge/operators/value_casters.rb +80 -0
  348. data/lib/permission_forge/operators/value_typing.rb +115 -0
  349. data/lib/permission_forge/query_scoping_warnings.rb +68 -0
  350. data/lib/permission_forge/rake_audit.rb +65 -0
  351. data/lib/permission_forge/resource_chain.rb +190 -0
  352. data/lib/permission_forge/rls.rb +209 -0
  353. data/lib/permission_forge/services/attribute_access_guard.rb +138 -0
  354. data/lib/permission_forge/services/audit_event_purger.rb +150 -0
  355. data/lib/permission_forge/services/bulk_permission_set_assignment.rb +108 -0
  356. data/lib/permission_forge/services/concerns/across_tenants.rb +69 -0
  357. data/lib/permission_forge/services/concerns/audited_operation.rb +43 -0
  358. data/lib/permission_forge/services/concerns/clonable.rb +29 -0
  359. data/lib/permission_forge/services/consent_withdrawal_handler.rb +135 -0
  360. data/lib/permission_forge/services/dashboard_stats.rb +43 -0
  361. data/lib/permission_forge/services/data_subject_erasure.rb +306 -0
  362. data/lib/permission_forge/services/expired_assignment_cleaner.rb +152 -0
  363. data/lib/permission_forge/services/extra_field_defaults.rb +71 -0
  364. data/lib/permission_forge/services/field_permission_merger.rb +83 -0
  365. data/lib/permission_forge/services/filters_response_attributes.rb +144 -0
  366. data/lib/permission_forge/services/gdpr_field_auditor/text_report.rb +63 -0
  367. data/lib/permission_forge/services/gdpr_field_auditor.rb +223 -0
  368. data/lib/permission_forge/services/host_key_guard.rb +110 -0
  369. data/lib/permission_forge/services/mask_value.rb +93 -0
  370. data/lib/permission_forge/services/permission_cache_clearer.rb +174 -0
  371. data/lib/permission_forge/services/permission_hash_builder.rb +305 -0
  372. data/lib/permission_forge/services/permission_rule_applier.rb +192 -0
  373. data/lib/permission_forge/services/permission_set_cloner.rb +89 -0
  374. data/lib/permission_forge/services/permission_set_condition_evaluator.rb +102 -0
  375. data/lib/permission_forge/services/permission_set_group_cloner.rb +52 -0
  376. data/lib/permission_forge/services/permits_attributes/nested_records.rb +106 -0
  377. data/lib/permission_forge/services/permits_attributes.rb +270 -0
  378. data/lib/permission_forge/services/resource_field_generator.rb +93 -0
  379. data/lib/permission_forge/services/resource_with_fields_builder.rb +47 -0
  380. data/lib/permission_forge/services/rls_operator_sql.rb +131 -0
  381. data/lib/permission_forge/services/rls_policy_generator.rb +365 -0
  382. data/lib/permission_forge/services/security_audit/action_check.rb +157 -0
  383. data/lib/permission_forge/services/security_audit/assignment_check.rb +120 -0
  384. data/lib/permission_forge/services/security_audit/audit_trail_check.rb +166 -0
  385. data/lib/permission_forge/services/security_audit/base_check.rb +57 -0
  386. data/lib/permission_forge/services/security_audit/config_check.rb +212 -0
  387. data/lib/permission_forge/services/security_audit/integrity_check.rb +105 -0
  388. data/lib/permission_forge/services/security_audit/personal_data_check.rb +79 -0
  389. data/lib/permission_forge/services/security_audit/privilege_check.rb +67 -0
  390. data/lib/permission_forge/services/security_audit/rule_check.rb +141 -0
  391. data/lib/permission_forge/services/security_audit/text_report.rb +46 -0
  392. data/lib/permission_forge/services/security_audit/user_model_check.rb +67 -0
  393. data/lib/permission_forge/services/security_audit.rb +75 -0
  394. data/lib/permission_forge/services/stale_assignment_auditor.rb +68 -0
  395. data/lib/permission_forge/services/unscoped_query_detector.rb +132 -0
  396. data/lib/permission_forge/version.rb +3 -0
  397. data/lib/permission_forge/virtual_fields/discovery.rb +65 -0
  398. data/lib/permission_forge/virtual_fields.rb +102 -0
  399. data/lib/permission_forge.rb +464 -0
  400. data/lib/tasks/permission_forge.rake +31 -0
  401. data/lib/tasks/permission_forge_audit_events.rake +28 -0
  402. data/lib/tasks/permission_forge_cache.rake +26 -0
  403. data/lib/tasks/permission_forge_expiration.rake +30 -0
  404. data/lib/tasks/permission_forge_gdpr.rake +69 -0
  405. data/lib/tasks/permission_forge_rls.rake +38 -0
  406. metadata +515 -0
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ class Configuration
5
+ # `config.preset`: a named set of values for the options that decide
6
+ # whether something fails open, applied as a layer of defaults under
7
+ # the settings the host makes itself.
8
+ #
9
+ # :compat (default) — the library defaults, as every version had them
10
+ # :strict — every fail-open default of the README's
11
+ # "Production hardening checklist" closed, and the
12
+ # settings that are already closed kept so
13
+ #
14
+ # A setting made explicitly always wins, before or after `preset =`, in
15
+ # the same configure block or a later one: the preset only fills the
16
+ # options nobody set. A String is taken as its Symbol; anything else
17
+ # raises ConfigurationError. SecurityAudit reports the preset and, under
18
+ # :strict, every option set looser than the preset.
19
+ #
20
+ # The options that need host code to close (an authenticated
21
+ # base_controller_class, admin_assignment_authorization,
22
+ # audit_log_authorization, a request_context_resolver, user_active_check)
23
+ # are not in a preset. Nor is masked_attributes_nil_user_strategy
24
+ # (OPT_IN): :hide makes jobs and mailers that read masked models with no
25
+ # user see hidden values, a cost each app must choose; it stays :bypass
26
+ # under every preset.
27
+ #
28
+ # @api public
29
+ module Presets
30
+ # The library defaults of every option a preset decides: what
31
+ # Configuration.new starts with (a test pins the two together).
32
+ COMPAT = {
33
+ # fail open by default (the checklist)
34
+ nil_user_strategy: :bypass,
35
+ response_filter_nil_user_strategy: :bypass,
36
+ auto_query_scoping: :off,
37
+ unconfigured_action_strategy: :allow,
38
+ action_guard_nil_user_strategy: nil,
39
+ conditional_sets_strict_mode: false,
40
+ mask_virtual_attributes: false,
41
+ extra_fields_default_access: :grant,
42
+ cache_backend: :memory,
43
+ # closed by default
44
+ api_require_authorization: true,
45
+ admin_tenantless_access: :deny,
46
+ nested_attributes_parent_fallback: false,
47
+ unconfigured_field_strategy: :block
48
+ }.freeze
49
+
50
+ STRICT = COMPAT.merge(
51
+ nil_user_strategy: :deny,
52
+ response_filter_nil_user_strategy: :block,
53
+ auto_query_scoping: :filter_only,
54
+ unconfigured_action_strategy: :block,
55
+ action_guard_nil_user_strategy: :deny,
56
+ conditional_sets_strict_mode: true,
57
+ mask_virtual_attributes: :auto,
58
+ extra_fields_default_access: :unconfigured,
59
+ cache_backend: :rails_cache
60
+ ).freeze
61
+
62
+ PRESETS = { compat: COMPAT, strict: STRICT }.freeze
63
+
64
+ # Fail-open defaults of the hardening checklist that no preset closes:
65
+ # an explicit choice with a documented cost.
66
+ OPT_IN = %i[masked_attributes_nil_user_strategy].freeze
67
+ OPTIONS = COMPAT.keys.freeze
68
+
69
+ # When an option counts as closed with a value other than the :strict
70
+ # one: receives (value, configuration).
71
+ ALSO_CLOSED = {
72
+ nil_user_strategy: ->(value, _config) { value == :raise },
73
+ # nil follows nil_user_strategy
74
+ action_guard_nil_user_strategy: ->(_value, config) { !config.action_guard_nil_user_bypass? },
75
+ mask_virtual_attributes: ->(value, _config) { [true, 'auto'].include?(value) },
76
+ # no cache at all, or a shared store the host built
77
+ cache_backend: ->(value, _config) { value == :null || value.is_a?(ActiveSupport::Cache::Store) }
78
+ }.freeze
79
+
80
+ # Each writer of an option a preset decides marks the option as set by
81
+ # the host (after the value passed validation).
82
+ OPTIONS.each do |option|
83
+ define_method(:"#{option}=") do |value|
84
+ super(value)
85
+ @explicit_options << option
86
+ end
87
+ end
88
+
89
+ # @return [Symbol] :compat or :strict
90
+ attr_reader :preset
91
+
92
+ # @raise [PermissionForge::ConfigurationError] for anything but :strict
93
+ # or :compat (or its String)
94
+ def preset=(value)
95
+ name = StrategyValues.as_symbol(value)
96
+ unless PRESETS.key?(name)
97
+ raise PermissionForge::ConfigurationError,
98
+ "preset must be one of #{PRESETS.keys.map(&:inspect).join(', ')} (or its String); got #{value.inspect}"
99
+ end
100
+
101
+ backend = @cache_backend
102
+ @preset = name
103
+ PRESETS.fetch(name).each do |option, preset_value|
104
+ instance_variable_set(:"@#{option}", preset_value) unless @explicit_options.include?(option)
105
+ end
106
+ # A store built from the old backend (preset set outside configure,
107
+ # which rebuilds it anyway) is rebuilt.
108
+ reset_cache_store! if @cache_store && !backend.equal?(@cache_backend)
109
+ end
110
+
111
+ # @param option [Symbol]
112
+ # @return [Boolean] whether the host set the option itself (not the preset)
113
+ def explicitly_set?(option)
114
+ @explicit_options.include?(option.to_sym)
115
+ end
116
+
117
+ # The options whose value is looser than the :strict preset's — under
118
+ # :strict, the ones the host overrode.
119
+ #
120
+ # @return [Hash{Symbol => Object}] option => current value
121
+ def options_looser_than_strict
122
+ STRICT.each_with_object({}) do |(option, strict_value), looser|
123
+ value = public_send(option)
124
+ value = value.to_sym if value.is_a?(String) # 'block' is :block, as the strategy writers take it
125
+ next if value == strict_value || ALSO_CLOSED[option]&.call(value, self)
126
+
127
+ looser[option] = value
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def set_preset_defaults
134
+ @preset = :compat
135
+ @explicit_options = Set.new
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ class Configuration
5
+ # Query-scoping configuration (the three defense levels) extracted for SRP.
6
+ #
7
+ # Level 1 — ENFORCE: auto_query_scoping applies filter-type row rules to
8
+ # every relation built on a protected model.
9
+ # Level 2 — DETECT: unscoped_query_detection reports queries on protected
10
+ # tables that bypassed a permission-scoped path.
11
+ # Level 3 — DATABASE: database_row_level_security enables Postgres RLS
12
+ # session-variable plumbing (policies via the
13
+ # permission_forge:rls generator + rls:sync rake task).
14
+ #
15
+ # All options default OFF — behavior is unchanged unless a level is enabled.
16
+ #
17
+ # @api public
18
+ module QueryScopingSettings
19
+ AUTO_QUERY_SCOPING_VALUES = %i[off filter_only].freeze
20
+ UNSCOPED_QUERY_DETECTION_VALUES = %i[off log raise].freeze
21
+
22
+ # Level 1 — ENFORCE.
23
+ # :off — (default) only find_by/find_by!/permission_scoped apply rules
24
+ # :filter_only — a default_scope applies the current user's FILTER-type rules
25
+ # to every relation (where/all/find/first/count/pluck and
26
+ # association traversal). Join-type rules are NOT auto-applied
27
+ # and still require permission_scoped (a boot warning fires
28
+ # when join rules exist while this is enabled).
29
+ # Notes: view_all/modify_all bypasses auto-scoping (parity with find_by);
30
+ # nil current_user follows nil_user_strategy (:bypass -> unscoped,
31
+ # :deny/:raise -> none — fail closed, never raising from inside a scope);
32
+ # denied rows are silently filtered (find -> RecordNotFound).
33
+ # Unknown values raise ConfigurationError at assignment (see below).
34
+ attr_reader :auto_query_scoping
35
+
36
+ # Level 2 — DETECT.
37
+ # :off — (default) no detection
38
+ # :log — Rails.logger.warn for each unscoped query on a protected table
39
+ # :raise — raise PermissionForge::UnscopedQueryError instead (CI guard)
40
+ # Detection only activates in environments listed in
41
+ # unscoped_query_detection_environments. Queries carrying the pf_scoped
42
+ # annotation (permission_scoped, auto-scoping, gem internals) are never
43
+ # flagged; INSERTs and schema queries are ignored.
44
+ attr_reader :unscoped_query_detection
45
+
46
+ # Environments (strings) where unscoped_query_detection is active.
47
+ # Default: %w[development test]. Adding 'production' is allowed but a
48
+ # boot warning fires when :raise is configured for production.
49
+ attr_accessor :unscoped_query_detection_environments
50
+
51
+ # Level 3 — DATABASE (Postgres only). When true, PermissionForge::RLS
52
+ # helpers set/clear the session variable used by generated policies.
53
+ # Policies themselves are installed via `rails generate permission_forge:rls`
54
+ # and kept in sync with `rake permission_forge:rls:sync`.
55
+ attr_accessor :database_row_level_security
56
+
57
+ # Postgres session variable (set via SET LOCAL) that generated policies
58
+ # read through current_setting(..., true). Must contain a dot (Postgres
59
+ # requires namespaced custom settings).
60
+ attr_accessor :rls_session_variable
61
+
62
+ def auto_query_scoping=(value)
63
+ @auto_query_scoping = normalize_scoping_value(
64
+ value, AUTO_QUERY_SCOPING_VALUES, 'auto_query_scoping'
65
+ )
66
+ end
67
+
68
+ def unscoped_query_detection=(value)
69
+ @unscoped_query_detection = normalize_scoping_value(
70
+ value, UNSCOPED_QUERY_DETECTION_VALUES, 'unscoped_query_detection'
71
+ )
72
+ end
73
+
74
+ def auto_query_scoping_enabled?
75
+ @auto_query_scoping == :filter_only
76
+ end
77
+
78
+ def unscoped_query_detection_active?
79
+ return false if @unscoped_query_detection == :off
80
+ return false unless defined?(Rails) && Rails.respond_to?(:env)
81
+
82
+ @unscoped_query_detection_environments.include?(Rails.env.to_s)
83
+ end
84
+
85
+ private
86
+
87
+ def set_query_scoping_defaults
88
+ @auto_query_scoping = :off
89
+ @unscoped_query_detection = :off
90
+ @unscoped_query_detection_environments = %w[development test]
91
+ @database_row_level_security = false
92
+ @rls_session_variable = 'permission_forge.user_id'
93
+ end
94
+
95
+ # Fail closed on typos: an unknown value must never silently enable a
96
+ # weaker mode than the dev intended, so we raise at assignment time
97
+ # (initializer) rather than guessing at query time.
98
+ def normalize_scoping_value(value, allowed, option)
99
+ sym = value.to_sym
100
+ return sym if allowed.include?(sym)
101
+
102
+ raise PermissionForge::ConfigurationError,
103
+ "Invalid #{option}: #{value.inspect}. Allowed: #{allowed.map(&:inspect).join(', ')}"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ class Configuration
5
+ # The strategy options that decide whether something is allowed. Each
6
+ # writer takes a Symbol from its list, or that Symbol's String
7
+ # ('block' is :block), and raises ConfigurationError for anything else,
8
+ # at assignment time (the initializer). Before, a String was compared
9
+ # with a Symbol and read as an unknown value: for some options that
10
+ # failed open ('block' for response_filter_nil_user_strategy was
11
+ # :bypass). Prepended, so it wraps the plain writers declared in
12
+ # Configuration and its sub-settings.
13
+ #
14
+ # @api private
15
+ module StrategyValues
16
+ ALLOWED = {
17
+ nil_user_strategy: %i[bypass raise deny],
18
+ response_filter_nil_user_strategy: %i[bypass block],
19
+ masked_write_protection: %i[skip raise off],
20
+ masked_attributes_nil_user_strategy: %i[bypass hide],
21
+ action_guard_nil_user_strategy: %i[bypass deny raise],
22
+ admin_tenantless_access: %i[deny allow],
23
+ unconfigured_action_strategy: %i[allow block],
24
+ extra_fields_default_access: %i[grant unconfigured]
25
+ }.freeze
26
+
27
+ # nil or blank means "follow nil_user_strategy".
28
+ BLANK_ALLOWED = %i[action_guard_nil_user_strategy].freeze
29
+
30
+ ALLOWED.each_key do |option|
31
+ define_method(:"#{option}=") do |value|
32
+ super(StrategyValues.strategy(option, value))
33
+ end
34
+ end
35
+
36
+ # @return [Symbol, nil]
37
+ # @raise [PermissionForge::ConfigurationError] for a value not in ALLOWED[option]
38
+ def self.strategy(option, value)
39
+ blank_allowed = BLANK_ALLOWED.include?(option)
40
+ return nil if blank_allowed && value.to_s.strip.empty?
41
+
42
+ allowed = ALLOWED.fetch(option)
43
+ symbol = as_symbol(value)
44
+ return symbol if allowed.include?(symbol)
45
+
46
+ raise PermissionForge::ConfigurationError,
47
+ "#{option} must be one of #{allowed.map(&:inspect).join(', ')} (or its String)" \
48
+ "#{' or nil' if blank_allowed}; got #{value.inspect}"
49
+ end
50
+
51
+ # A Symbol as is, a String as its Symbol, anything else nil.
52
+ def self.as_symbol(value)
53
+ value.to_s.to_sym if value.is_a?(String) || value.is_a?(Symbol)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ class Configuration
5
+ # Options for virtual (non-column) fields: names a model declares through
6
+ # `extra_fields_method`, plus names the admin can discover.
7
+ #
8
+ # @api public
9
+ module VirtualAttributesSettings
10
+ EXTRA_FIELDS_DEFAULT_ACCESS = %i[grant unconfigured].freeze
11
+
12
+ # What a declared virtual field gets when no ResourceField row configures it:
13
+ #
14
+ # :grant — readable and editable, unmasked (default, backward-compatible)
15
+ # :unconfigured — no implicit entry; the field follows unconfigured_field_strategy
16
+ # until an admin gives it a level (fail closed with :block)
17
+ #
18
+ # A String is taken as its Symbol; any other value raises
19
+ # ConfigurationError (StrategyValues).
20
+ attr_accessor :extra_fields_default_access
21
+
22
+ # When true, the admin also lists attribute-API attributes and
23
+ # store_accessor keys as virtual fields. Discovered names never get
24
+ # implicit access — they stay unconfigured until an admin sets a level.
25
+ # Default: false.
26
+ attr_accessor :virtual_field_discovery
27
+
28
+ # Model-level masking of virtual readers (methods, store accessors,
29
+ # attribute-API attributes), on top of the column masking MaskedAttributes
30
+ # already does. Off by default: the model's own callbacks and validations
31
+ # then see the masked value too (see pf_without_virtual_masking).
32
+ #
33
+ # false / nil — off; virtual fields are enforced only by
34
+ # PermitsAttributes and FiltersResponseAttributes (default)
35
+ # :auto / true — every model that has MaskedAttributes
36
+ # ['Customer', 'Order'] — only these models
37
+ attr_accessor :mask_virtual_attributes
38
+
39
+ # Sets this group's defaults; called from Configuration#initialize.
40
+ # @return [void]
41
+ # @api private
42
+ def set_virtual_attributes_defaults
43
+ @extra_fields_default_access = :grant
44
+ @virtual_field_discovery = false
45
+ @mask_virtual_attributes = false
46
+ end
47
+
48
+ # @return [Boolean] whether declared virtual fields get the implicit
49
+ # read + edit entry. Only the exact :grant value grants.
50
+ def grant_extra_fields_by_default?
51
+ extra_fields_default_access.to_s == 'grant'
52
+ end
53
+
54
+ # @param model_name [String, Symbol, Class]
55
+ # @return [Boolean] whether virtual readers of this model are masked.
56
+ def mask_virtual_attributes_for?(model_name)
57
+ setting = mask_virtual_attributes
58
+ return false if [false, nil].include?(setting)
59
+ return setting.map(&:to_s).include?(model_name.to_s) if setting.is_a?(Array)
60
+
61
+ [true, :auto, 'auto'].include?(setting)
62
+ end
63
+ end
64
+ end
65
+ end