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,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ # What a model took from the configuration as it loaded — the user class of
5
+ # an assignment (PermissionSetUser#user, PermissionSetGroupUser#user), the
6
+ # audit user class (created_by / updated_by) and the tenant model
7
+ # (HasTenantScoping) — against what the configuration says now. A setting
8
+ # changed after the class loaded (a PermissionForge.configure or a setter
9
+ # in after_initialize, a console) changes nothing in the class: a
10
+ # development reload picks it up, an eager-loaded production process never
11
+ # does. The engine logs each difference once the host's after_initialize
12
+ # blocks have run, and PermissionForge.configure logs it too. Nothing here
13
+ # loads a model. (table_name_prefix is not compared: table_name is a
14
+ # method that reads it on every call.)
15
+ #
16
+ # @api private
17
+ module ModelBindings
18
+ # The settings a model declares as it loads, each with the association
19
+ # that carries it (the tenant is read from HasTenantScoping).
20
+ ASSOCIATIONS = { user_class: :user, audit_user_class: :created_by }.freeze
21
+
22
+ class << self
23
+ # One sentence per setting that loaded models declared differently.
24
+ #
25
+ # @param config [Configuration]
26
+ # @return [Array<String>]
27
+ def mismatches(config = PermissionForge.configuration)
28
+ found = Hash.new { |hash, key| hash[key] = [] }
29
+ loaded_models.each do |model|
30
+ declared_otherwise(model, config).each do |setting, declared|
31
+ found[[setting, config.public_send(setting), declared]] << model.name
32
+ end
33
+ end
34
+ found.map { |difference, models| sentence(difference, models) }
35
+ end
36
+
37
+ # Logs every mismatch.
38
+ #
39
+ # @param config [Configuration]
40
+ # @param logger [Logger, nil]
41
+ # @return [void]
42
+ def warn!(config = PermissionForge.configuration, logger: default_logger)
43
+ mismatches(config).each { |sentence| logger&.warn("[PermissionForge] #{sentence}") }
44
+ end
45
+
46
+ # The model classes already loaded, without loading one.
47
+ #
48
+ # @return [Array<Class>]
49
+ def loaded_models
50
+ MODEL_NAMES.select { |name| PermissionForge.autoload?(name).nil? && PermissionForge.const_defined?(name, false) }
51
+ .map { |name| PermissionForge.const_get(name) }
52
+ end
53
+
54
+ private
55
+
56
+ # [setting, what the model declared] for each setting the model
57
+ # declared and the configuration now says differently. A model without
58
+ # the association (a PermissionSet has no :user) declares nothing.
59
+ def declared_otherwise(model, config)
60
+ declared(model).reject { |setting, value| value == current(config, setting) }
61
+ end
62
+
63
+ def declared(model)
64
+ pairs = ASSOCIATIONS.filter_map do |setting, association|
65
+ reflection = model.reflect_on_association(association)
66
+ [setting, reflection.class_name] if reflection
67
+ end
68
+ pairs << [:tenant_model, model.tenant_scoping_model]
69
+ end
70
+
71
+ def current(config, setting)
72
+ return (config.tenant_model.to_s if config.tenant_enabled?) if setting == :tenant_model
73
+
74
+ config.public_send(setting).to_s
75
+ end
76
+
77
+ def sentence(difference, models)
78
+ setting, now, declared = difference
79
+ "config.#{setting} is #{now.inspect} but #{models.join(', ')} loaded with " \
80
+ "#{declared.inspect}: a loaded class keeps what it declared until it is loaded again (a code reload in " \
81
+ 'development; never in an eager-loaded production process). Configure PermissionForge in ' \
82
+ 'config/initializers.'
83
+ end
84
+
85
+ def default_logger
86
+ Rails.logger if defined?(Rails) && Rails.respond_to?(:logger)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ # The database transaction the calling code runs in, on the connection of
5
+ # the permission models — if one is open whose rows nobody else can see yet
6
+ # and which may still be rolled back.
7
+ #
8
+ # Open means: a joinable transaction is on the connection's stack. A
9
+ # transaction that is not joinable does not count by itself — that is how
10
+ # Rails wraps a test (and how test-prof's `before_all` and DatabaseCleaner
11
+ # wrap theirs): everything a test does runs inside it, and Rails treats it
12
+ # as no transaction at all (commit callbacks run inside it). Whatever is
13
+ # opened inside one counts, and so does a non-joinable transaction inside
14
+ # an ordinary one. `rails console --sandbox` is one non-joinable
15
+ # transaction too, and there it does count: nothing done in a sandbox is
16
+ # ever committed.
17
+ #
18
+ # What is asked of it:
19
+ # - #withhold?: whether a permission hash built inside it has to stay
20
+ # out of the shared cache — the transaction reads from a snapshot
21
+ # (#snapshot?, `config.snapshot_transactions`), or it is the sandbox.
22
+ # - #finished?: whether it has ended since, committed or rolled back —
23
+ # the outermost transaction that counted, that is.
24
+ # - #rolled_back?: whether the transaction it was asked in, or one around
25
+ # it, was rolled back since. What was read inside it is void then, also
26
+ # while a transaction around it is still open.
27
+ # - #after_commit: runs a block once everybody can see what the
28
+ # transaction did — after the outermost one has committed — and never
29
+ # after this transaction, or one around it, was rolled back.
30
+ #
31
+ # All of it is asked of the adapter's own transaction objects, on every
32
+ # Rails version: the stack of the connection's transaction manager (Rails
33
+ # has no reader for it; it is `@stack` from 7.1 to 8.1), each transaction's
34
+ # `joinable?`, `state` and `isolation_level`, and `add_record` for the
35
+ # block, which is enlisted the way Rails enlists a record that has commit
36
+ # callbacks. The public API of Rails 7.2+ (ActiveRecord::Transaction) is not
37
+ # used: it does not see a transaction that is not joinable, it does not say
38
+ # how a transaction ended, and Rails skips a block registered through it
39
+ # when a commit callback of any record in the transaction raises.
40
+ #
41
+ # @api private
42
+ class OpenTransaction
43
+ # Isolation levels at which a transaction reads from one snapshot.
44
+ SNAPSHOT_LEVELS = %i[repeatable_read serializable].freeze
45
+
46
+ # Adapters whose transactions read committed rows unless they were opened
47
+ # with a stronger isolation level. Every other adapter is taken to read
48
+ # from a snapshot (MySQL at its default, SQLite) under `:auto`.
49
+ READ_COMMITTED_ADAPTERS = %w[PostgreSQL PostGIS].freeze
50
+
51
+ class << self
52
+ # @param model [Class] a model on the connection in question
53
+ # @return [OpenTransaction, nil] nil when no transaction is open
54
+ def current(model = PermissionForge::PermissionSet)
55
+ pool = model.connection_pool
56
+ # a thread that holds no connection has no transaction
57
+ return unless pool.active_connection?
58
+
59
+ pool.with_connection { |connection| on(connection) }
60
+ end
61
+
62
+ # The open transaction, when what is built inside it must stay out of
63
+ # the shared cache. The question a host asks before it writes a cache
64
+ # entry of its own that is derived from the permissions.
65
+ #
66
+ # @return [OpenTransaction, nil]
67
+ def withholding(model = PermissionForge::PermissionSet)
68
+ return if PermissionForge.configuration.snapshot_transactions == false && !sandbox?
69
+
70
+ transaction = current(model)
71
+ transaction if transaction&.withhold?
72
+ end
73
+
74
+ # @return [Array] the adapter's open transactions, outermost first
75
+ # @raise [PermissionForge::ConfigurationError] when this Rails keeps
76
+ # them elsewhere. Looking at the innermost transaction alone would
77
+ # answer, wrongly and silently: a hash a rollback takes back would be
78
+ # cached.
79
+ def stack_of(connection)
80
+ manager = connection.transaction_manager
81
+ return manager.instance_variable_get(:@stack) if manager.instance_variable_defined?(:@stack)
82
+
83
+ raise PermissionForge::ConfigurationError,
84
+ "PermissionForge cannot read the open transactions of #{manager.class} (ActiveRecord " \
85
+ "#{ActiveRecord.version}): this version of the gem does not support that Rails version"
86
+ end
87
+
88
+ # @return [Boolean] whether this process is `rails console --sandbox`
89
+ def sandbox?
90
+ return false unless defined?(Rails) && Rails.respond_to?(:application)
91
+
92
+ application = Rails.application
93
+ application.respond_to?(:sandbox?) && application.sandbox? == true
94
+ end
95
+
96
+ private
97
+
98
+ def on(connection)
99
+ stack = stack_of(connection)
100
+ root = sandbox? ? stack.first : stack.find(&:joinable?)
101
+ new(connection, root, stack.last) if root
102
+ end
103
+ end
104
+
105
+ # What identifies the transaction the handle was asked for (the innermost
106
+ # one), for as long as it is referenced.
107
+ #
108
+ # @return [Object] the adapter's transaction
109
+ attr_reader :subject
110
+
111
+ # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter]
112
+ # @param root [Object] the outermost transaction that counts
113
+ # @param subject [Object] the innermost transaction
114
+ def initialize(connection, root, subject)
115
+ @connection = connection
116
+ @root = root
117
+ @subject = subject
118
+ end
119
+
120
+ # @return [Boolean]
121
+ def withhold?
122
+ self.class.sandbox? || snapshot?
123
+ end
124
+
125
+ # Whether the transaction is treated as reading from a snapshot:
126
+ # `config.snapshot_transactions`, and under `:auto` the adapter and the
127
+ # isolation level the transaction was opened with. The server is not
128
+ # asked.
129
+ #
130
+ # @return [Boolean]
131
+ def snapshot?
132
+ setting = PermissionForge.configuration.snapshot_transactions
133
+ return setting if [true, false].include?(setting)
134
+
135
+ !READ_COMMITTED_ADAPTERS.include?(@connection.adapter_name) || snapshot_isolation?
136
+ end
137
+
138
+ # Also true when the transaction is not on the connection's stack any
139
+ # more without having ended (the connection was reset or closed), and for
140
+ # a copy that was marshalled.
141
+ #
142
+ # @return [Boolean]
143
+ def finished?
144
+ return true unless @root
145
+
146
+ !!@root.state.finalized? || self.class.stack_of(@connection).none? { |transaction| transaction.equal?(@root) }
147
+ end
148
+
149
+ # (A transaction that rolls back marks the ones inside it as rolled back,
150
+ # also those that had been released before.)
151
+ #
152
+ # @return [Boolean]
153
+ def rolled_back?
154
+ !!@subject&.state&.rolledback?
155
+ end
156
+
157
+ # Whether what was read inside it is still good: it has neither ended
158
+ # nor been rolled back to a savepoint.
159
+ #
160
+ # @return [Boolean]
161
+ def stands?
162
+ !(finished? || rolled_back?)
163
+ end
164
+
165
+ # Whether what was read inside it can be used where it is asked: it
166
+ # stands, and the calling thread (fiber) works on its connection —
167
+ # another connection does not see its rows, which may still be rolled
168
+ # back.
169
+ #
170
+ # @return [Boolean]
171
+ def visible?
172
+ own_connection? && stands?
173
+ end
174
+
175
+ # Whether the calling thread (fiber) works on its connection.
176
+ #
177
+ # @return [Boolean]
178
+ def own_connection?
179
+ pool = @connection&.pool
180
+ return false unless pool&.active_connection?
181
+
182
+ pool.with_connection { |connection| connection.equal?(@connection) }
183
+ end
184
+
185
+ # Whether what is tied to it must not be replaced, from the calling
186
+ # thread, by what is tied to `other` (another transaction, or none): it
187
+ # is still open, on a connection the calling thread does not work on.
188
+ #
189
+ # @param other [OpenTransaction, nil]
190
+ # @return [Boolean]
191
+ def held_elsewhere?(other)
192
+ !(equal?(other) || finished? || own_connection?)
193
+ end
194
+
195
+ # @return [void]
196
+ def after_commit(&block)
197
+ @subject.add_record(CommitHook.new(after: block))
198
+ end
199
+
200
+ # Runs a block just before the outermost transaction commits — after the
201
+ # transaction it was registered in, and every one around it, has decided
202
+ # to commit. A block that raises makes the commit fail: Rails rolls the
203
+ # transaction back and raises the error to the code that opened it. That
204
+ # is how a change whose audit event could not be written is kept from
205
+ # being committed by a caller that rescued the error (Audit).
206
+ #
207
+ # Never runs after this transaction, or one around it, was rolled back.
208
+ #
209
+ # @return [void]
210
+ def before_commit(&block)
211
+ @subject.add_record(CommitHook.new(before: block))
212
+ end
213
+
214
+ # Runs a block once, when what this transaction did is decided either
215
+ # way: after the outermost transaction has committed, or once it — or
216
+ # this one, or one in between — has rolled back and no transaction that
217
+ # counts is open any more. A savepoint that rolls back while
218
+ # transactions around it are still open hands the block to them; one
219
+ # that is released takes it along (Rails moves its records). For what
220
+ # must be written whichever way the caller's transaction ends: a
221
+ # refusal or a failed sign-in (Audit::Detached), a runtime decision
222
+ # (Audit.instrument).
223
+ #
224
+ # The block runs from Rails' commit or rollback, where raising would
225
+ # replace the caller's own exception or fail a transaction that has
226
+ # already committed: an error is reported (Rails.error) and logged,
227
+ # never raised — also when the connection cannot say whether a
228
+ # transaction is still open (the block then runs). Nothing is kept on a
229
+ # record.
230
+ #
231
+ # @return [void]
232
+ def after_end(&block)
233
+ hand_on = lambda do
234
+ enclosing = CommitHook.run_detached(-> { self.class.send(:on, @connection) if @connection&.active? })
235
+ enclosing ? CommitHook.run_detached(-> { enclosing.after_end(&block) }) : CommitHook.run_detached(block)
236
+ end
237
+ @subject.add_record(CommitHook.new(after: -> { CommitHook.run_detached(block) }, rollback: hand_on))
238
+ end
239
+
240
+ # A user that still holds the hash it built inside a transaction can be
241
+ # marshalled (cached). Nothing of a transaction can be dumped, and the
242
+ # copy is in no transaction: for it, this one has finished.
243
+ def marshal_dump = nil
244
+
245
+ def marshal_load(_state)
246
+ @connection = @root = @subject = nil
247
+ end
248
+
249
+ private
250
+
251
+ def snapshot_isolation?
252
+ self.class.stack_of(@connection).any? { |transaction| SNAPSHOT_LEVELS.include?(transaction.isolation_level&.to_sym) }
253
+ end
254
+
255
+ # Blocks in the place of a record: what Rails calls on the records of a
256
+ # transaction when it commits or rolls back. The record moves to the
257
+ # enclosing transaction when a nested one commits. `before_committed!` is
258
+ # called by the transaction that runs the commit callbacks before its
259
+ # COMMIT — an error there makes Rails roll back instead. `committed!` is
260
+ # called after the COMMIT, for every record, also when an after_commit of
261
+ # another one raised, and before the blocks registered through
262
+ # ActiveRecord::Transaction#after_commit. `rolledback!` is called by the
263
+ # transaction that rolls back (a savepoint too), and again for records left
264
+ # when a callback of another one raised: each block runs once.
265
+ class CommitHook
266
+ # Runs a block where nothing may be raised (OpenTransaction#after_end).
267
+ def self.run_detached(block)
268
+ block.call
269
+ rescue StandardError => e
270
+ Rails.logger&.error("[PermissionForge] transaction end hook failed — #{e.class}: #{e.message}") if defined?(Rails)
271
+ Rails.error.report(e, handled: true, source: 'permission_forge.open_transaction') if defined?(Rails) && Rails.respond_to?(:error)
272
+ nil
273
+ end
274
+
275
+ def initialize(after: nil, before: nil, rollback: nil)
276
+ @after = after
277
+ @before = before
278
+ @rollback = rollback
279
+ end
280
+
281
+ def trigger_transactional_callbacks?
282
+ true
283
+ end
284
+
285
+ def before_committed!
286
+ @before&.call
287
+ end
288
+
289
+ def committed!(*, **)
290
+ @rollback = nil
291
+ block = @after
292
+ @after = nil
293
+ block&.call
294
+ end
295
+
296
+ def rolledback!(*, **)
297
+ block = @rollback
298
+ @rollback = nil
299
+ block&.call
300
+ end
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PermissionForge
4
+ module Operators
5
+ # Operator and join-type constants used by PermissionSetRule and Registry.
6
+ # Extracted to keep PermissionSetRule's constant count manageable.
7
+ # Include this module to access constants directly (e.g., EQUAL, JOINS).
8
+ #
9
+ # @api public
10
+ module Constants
11
+ # Join types
12
+ LEFT_JOINS = 'left_joins'
13
+ LEFT_OUTER_JOINS = 'left_outer_joins'
14
+ JOINS = 'joins'
15
+
16
+ JOIN_TYPES = [LEFT_JOINS, LEFT_OUTER_JOINS, JOINS].freeze
17
+
18
+ # Operators
19
+ EQUAL = 'equal'
20
+ NOT_EQUAL = 'not_equal'
21
+ LESS_THAN = 'less_than'
22
+ GREATER_THAN = 'greater_than'
23
+ LESS_THAN_OR_EQUAL = 'less_than_or_equal'
24
+ GREATER_THAN_OR_EQUAL = 'greater_than_or_equal'
25
+ IN = 'in'
26
+ NOT_IN = 'not_in'
27
+ CONTAINS = 'contains'
28
+ NOT_CONTAINS = 'not_contains'
29
+ STARTS_WITH = 'starts_with'
30
+ ENDS_WITH = 'ends_with'
31
+ BETWEEN = 'between'
32
+ IS_NULL = 'is_null'
33
+ IS_NOT_NULL = 'is_not_null'
34
+
35
+ OPERATORS = [
36
+ EQUAL, NOT_EQUAL, LESS_THAN, GREATER_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL,
37
+ IN, NOT_IN, CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH, BETWEEN, IS_NULL, IS_NOT_NULL
38
+ ].freeze
39
+ end
40
+ end
41
+ end