open-loam 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +148 -8
  3. data/LICENSE +1 -1
  4. data/README.md +27 -25
  5. data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
  6. data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
  7. data/app/jobs/open_loam/event_log_prune_job.rb +15 -0
  8. data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
  9. data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +17 -8
  10. data/app/models/open_loam/api_token.rb +63 -0
  11. data/app/models/{loam → open_loam}/audit_record.rb +3 -3
  12. data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
  13. data/app/models/{loam → open_loam}/business_rule.rb +10 -10
  14. data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
  15. data/app/models/{loam → open_loam}/comment.rb +9 -9
  16. data/app/models/{loam → open_loam}/config.rb +7 -7
  17. data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
  18. data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
  19. data/app/models/open_loam/dictionary.rb +20 -0
  20. data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
  21. data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
  22. data/app/models/open_loam/event_record.rb +42 -0
  23. data/app/models/{loam → open_loam}/field_definition.rb +8 -8
  24. data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
  25. data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
  26. data/app/models/{loam → open_loam}/membership.rb +6 -6
  27. data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
  28. data/app/models/{loam → open_loam}/notification.rb +10 -10
  29. data/app/models/{loam → open_loam}/pending_action.rb +70 -26
  30. data/app/models/{loam → open_loam}/perspective.rb +5 -5
  31. data/app/models/{loam → open_loam}/progress_job.rb +7 -7
  32. data/app/models/{loam → open_loam}/record_lock.rb +5 -5
  33. data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
  34. data/app/models/open_loam/search_token.rb +9 -0
  35. data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
  36. data/app/models/open_loam/sso_provider.rb +66 -0
  37. data/app/models/open_loam/tenant.rb +20 -0
  38. data/app/models/{loam → open_loam}/translation.rb +5 -5
  39. data/app/models/open_loam/webhook_endpoint.rb +39 -0
  40. data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
  41. data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
  42. data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +6 -2
  43. data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
  44. data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +2 -1
  45. data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +32 -22
  46. data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
  47. data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
  48. data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
  49. data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
  50. data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +8 -5
  51. data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
  52. data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
  53. data/lib/generators/open_loam/entity/templates/views/index.html.erb +94 -0
  54. data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
  55. data/lib/generators/open_loam/entity/templates/views/show.html.erb +38 -0
  56. data/lib/generators/{loam → open_loam}/install/install_generator.rb +58 -47
  57. data/lib/generators/open_loam/install/templates/AGENTS.md +345 -0
  58. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
  59. data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
  60. data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +48 -0
  61. data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +23 -0
  62. data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +101 -38
  63. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
  64. data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
  65. data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
  66. data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
  67. data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +12 -0
  68. data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
  69. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
  70. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
  71. data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
  72. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
  73. data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
  74. data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +7 -5
  75. data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
  76. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
  77. data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
  78. data/lib/generators/open_loam/install/templates/admin/history_controller.rb +64 -0
  79. data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +2 -2
  80. data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +25 -13
  81. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +7 -7
  82. data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -4
  83. data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
  84. data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +11 -6
  85. data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +5 -3
  86. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
  87. data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
  88. data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
  89. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +13 -7
  90. data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
  91. data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +23 -0
  92. data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
  93. data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
  94. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
  95. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
  96. data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
  97. data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +7 -5
  98. data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +33 -34
  99. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
  100. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
  101. data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +8 -1
  102. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +7 -5
  103. data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
  104. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
  105. data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
  106. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
  107. data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
  108. data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
  109. data/lib/generators/open_loam/install/templates/api_base_controller.rb +127 -0
  110. data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
  111. data/lib/generators/open_loam/install/templates/import_job.rb +38 -0
  112. data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
  113. data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +87 -75
  114. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
  115. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
  116. data/lib/generators/{loam/install/templates/migrations/create_loam_auth_attempts.rb → open_loam/install/templates/migrations/create_open_loam_auth_attempts.rb} +3 -3
  117. data/lib/generators/{loam/install/templates/migrations/create_loam_business_rules.rb → open_loam/install/templates/migrations/create_open_loam_business_rules.rb} +10 -10
  118. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
  119. data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
  120. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
  121. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
  122. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
  123. data/lib/generators/{loam/install/templates/migrations/create_loam_dictionary_entries.rb → open_loam/install/templates/migrations/create_open_loam_dictionary_entries.rb} +6 -6
  124. data/lib/generators/{loam/install/templates/migrations/create_loam_event_deliveries.rb → open_loam/install/templates/migrations/create_open_loam_event_deliveries.rb} +5 -5
  125. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
  126. data/lib/generators/{loam/install/templates/migrations/create_loam_field_definitions.rb → open_loam/install/templates/migrations/create_open_loam_field_definitions.rb} +4 -4
  127. data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -10
  128. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
  129. data/lib/generators/{loam/install/templates/migrations/create_loam_mfa_credentials.rb → open_loam/install/templates/migrations/create_open_loam_mfa_credentials.rb} +4 -4
  130. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
  131. data/lib/generators/{loam/install/templates/migrations/create_loam_pending_actions.rb → open_loam/install/templates/migrations/create_open_loam_pending_actions.rb} +9 -9
  132. data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
  133. data/lib/generators/{loam/install/templates/migrations/create_loam_progress_jobs.rb → open_loam/install/templates/migrations/create_open_loam_progress_jobs.rb} +5 -5
  134. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
  135. data/lib/generators/{loam/install/templates/migrations/create_loam_scheduled_jobs.rb → open_loam/install/templates/migrations/create_open_loam_scheduled_jobs.rb} +5 -5
  136. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
  137. data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +15 -12
  138. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
  139. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
  140. data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
  141. data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
  142. data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
  143. data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
  144. data/lib/open-loam.rb +2 -2
  145. data/lib/{loam → open_loam}/attachable.rb +4 -4
  146. data/lib/{loam → open_loam}/auditable.rb +23 -23
  147. data/lib/{loam → open_loam}/auth_throttle.rb +20 -12
  148. data/lib/{loam → open_loam}/base32.rb +1 -1
  149. data/lib/{loam → open_loam}/bulk.rb +7 -7
  150. data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
  151. data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
  152. data/lib/{loam → open_loam}/business_rules.rb +19 -19
  153. data/lib/{loam → open_loam}/commentable.rb +6 -6
  154. data/lib/{loam → open_loam}/configs.rb +19 -19
  155. data/lib/{loam → open_loam}/cron.rb +2 -2
  156. data/lib/{loam → open_loam}/csv.rb +2 -2
  157. data/lib/{loam → open_loam}/current.rb +5 -5
  158. data/lib/{loam → open_loam}/custom_field_index.rb +33 -30
  159. data/lib/{loam → open_loam}/custom_fields.rb +17 -17
  160. data/lib/{loam → open_loam}/dashboard.rb +5 -5
  161. data/lib/{loam → open_loam}/dictionaries.rb +12 -12
  162. data/lib/{loam → open_loam}/durable_events.rb +19 -19
  163. data/lib/{loam → open_loam}/encryptable.rb +39 -34
  164. data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
  165. data/lib/{loam → open_loam}/encryption/key_provider.rb +19 -3
  166. data/lib/{loam → open_loam}/encryption.rb +58 -15
  167. data/lib/open_loam/engine.rb +65 -0
  168. data/lib/{loam → open_loam}/enrichers.rb +5 -5
  169. data/lib/{loam → open_loam}/errors.rb +20 -10
  170. data/lib/{loam → open_loam}/eval.rb +2 -2
  171. data/lib/open_loam/event_log.rb +77 -0
  172. data/lib/{loam → open_loam}/event_stream.rb +14 -14
  173. data/lib/{loam → open_loam}/eventful.rb +12 -12
  174. data/lib/{loam → open_loam}/events.rb +5 -5
  175. data/lib/{loam → open_loam}/export.rb +12 -12
  176. data/lib/{loam → open_loam}/features.rb +15 -15
  177. data/lib/{loam → open_loam}/generated_key.rb +6 -6
  178. data/lib/{loam → open_loam}/import.rb +12 -15
  179. data/lib/{loam → open_loam}/inbound_webhooks.rb +19 -18
  180. data/lib/{loam → open_loam}/lifecycle.rb +54 -25
  181. data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
  182. data/lib/{loam → open_loam}/mcp/server.rb +9 -9
  183. data/lib/{loam → open_loam}/mcp.rb +31 -22
  184. data/lib/{loam → open_loam}/notifications.rb +5 -5
  185. data/lib/{loam → open_loam}/open_api.rb +16 -16
  186. data/lib/open_loam/outbound_url.rb +100 -0
  187. data/lib/{loam → open_loam}/overrides.rb +10 -10
  188. data/lib/{loam → open_loam}/pending_actions.rb +9 -9
  189. data/lib/{loam → open_loam}/permissions.rb +8 -8
  190. data/lib/{loam → open_loam}/perspectives.rb +9 -9
  191. data/lib/{loam → open_loam}/policy.rb +21 -8
  192. data/lib/{loam → open_loam}/progress.rb +6 -6
  193. data/lib/{loam → open_loam}/record_locks.rb +9 -9
  194. data/lib/{loam → open_loam}/scheduler.rb +20 -20
  195. data/lib/{loam → open_loam}/search/driver.rb +3 -3
  196. data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
  197. data/lib/{loam → open_loam}/search/token_driver.rb +16 -16
  198. data/lib/{loam → open_loam}/search.rb +5 -5
  199. data/lib/{loam → open_loam}/searchable.rb +22 -22
  200. data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
  201. data/lib/{loam → open_loam}/sso/claims.rb +1 -1
  202. data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
  203. data/lib/open_loam/sso/http_client.rb +51 -0
  204. data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
  205. data/lib/{loam → open_loam}/sso.rb +39 -18
  206. data/lib/{loam → open_loam}/telemetry.rb +6 -6
  207. data/lib/{loam → open_loam}/tenant_record.rb +10 -10
  208. data/lib/{loam → open_loam}/test_helpers.rb +4 -4
  209. data/lib/{loam → open_loam}/totp.rb +3 -3
  210. data/lib/{loam → open_loam}/translatable.rb +24 -24
  211. data/lib/{loam → open_loam}/undo.rb +11 -11
  212. data/lib/open_loam/version.rb +3 -0
  213. data/lib/{loam → open_loam}/webhooks.rb +4 -4
  214. data/lib/{loam → open_loam}/widgets.rb +9 -9
  215. data/lib/{loam → open_loam}/workflow.rb +45 -45
  216. data/lib/open_loam.rb +124 -0
  217. data/lib/tasks/open_loam.rake +203 -0
  218. data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
  219. data/lib/tasks/open_loam_mcp.rake +20 -0
  220. metadata +247 -242
  221. data/app/models/loam/api_token.rb +0 -40
  222. data/app/models/loam/dictionary.rb +0 -20
  223. data/app/models/loam/search_token.rb +0 -9
  224. data/app/models/loam/sso_provider.rb +0 -35
  225. data/app/models/loam/tenant.rb +0 -20
  226. data/app/models/loam/webhook_endpoint.rb +0 -26
  227. data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
  228. data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
  229. data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
  230. data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
  231. data/lib/generators/loam/install/templates/AGENTS.md +0 -341
  232. data/lib/generators/loam/install/templates/admin/api_tokens_controller.rb +0 -30
  233. data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
  234. data/lib/generators/loam/install/templates/admin/history_controller.rb +0 -38
  235. data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
  236. data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
  237. data/lib/generators/loam/install/templates/api_base_controller.rb +0 -90
  238. data/lib/generators/loam/install/templates/import_job.rb +0 -25
  239. data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
  240. data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
  241. data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
  242. data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
  243. data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
  244. data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
  245. data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
  246. data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
  247. data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
  248. data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
  249. data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
  250. data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
  251. data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
  252. data/lib/loam/engine.rb +0 -57
  253. data/lib/loam/sso/http_client.rb +0 -42
  254. data/lib/loam/version.rb +0 -3
  255. data/lib/loam.rb +0 -122
  256. data/lib/tasks/loam.rake +0 -171
  257. data/lib/tasks/loam_mcp.rake +0 -20
  258. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
  259. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
  260. /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
  261. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
  262. /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
  263. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
  264. /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
  265. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
  266. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
  267. /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
  268. /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
  269. /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
  270. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
  271. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
  272. /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
  273. /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
  274. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
  275. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
  276. /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
  277. /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
  278. /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
  279. /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
  280. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
  281. /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
  282. /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
  283. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
  284. /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
  285. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
  286. /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
  287. /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
@@ -1,8 +1,8 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Text search over an entity's own columns:
3
3
  #
4
- # class Equipment < Loam::TenantRecord
5
- # include Loam::Searchable
4
+ # class Equipment < OpenLoam::TenantRecord
5
+ # include OpenLoam::Searchable
6
6
  # searchable_by :name, :status
7
7
  # end
8
8
  #
@@ -12,19 +12,19 @@ module Loam
12
12
  # to remember: the default scope is already on it, and the result composes
13
13
  # with ordering, pagination and anything else.
14
14
  #
15
- # HOW a query matches is a swappable driver (Loam::Search.driver): the default
15
+ # HOW a query matches is a swappable driver (OpenLoam::Search.driver): the default
16
16
  # LikeDriver is a substring LIKE; the TokenDriver is a portable word-level
17
17
  # index; an external engine is a third. This concern is only the DECLARATION
18
18
  # (`searchable_by`) and the untouched call site (`Model.search(q)`) — swapping
19
- # the driver changes neither. See Loam::Search.
19
+ # the driver changes neither. See OpenLoam::Search.
20
20
  module Searchable
21
21
  extend ActiveSupport::Concern
22
22
 
23
23
  included do
24
- class_attribute :loam_searchable_columns, default: [].freeze, instance_writer: false
24
+ class_attribute :open_loam_searchable_columns, default: [].freeze, instance_writer: false
25
25
 
26
26
  # Keep the active driver's index current. A no-op under the default
27
- # LikeDriver; the TokenDriver maintains loam_search_tokens here. Guarded so
27
+ # LikeDriver; the TokenDriver maintains open_loam_search_tokens here. Guarded so
28
28
  # non-searchable models pay nothing, and skipped when no searchable column
29
29
  # actually changed — so a soft-delete (which touches only deleted_at)
30
30
  # leaves the tokens in place, and the base scope hides the row anyway.
@@ -33,8 +33,8 @@ module Loam
33
33
  # exercise the `searchable_by` DSL (its encrypted-field check), with no
34
34
  # ActiveRecord underneath — only a real model gets the callbacks.
35
35
  if respond_to?(:after_save)
36
- after_save :loam_update_search_index, if: :loam_search_index_stale?
37
- after_destroy :loam_remove_from_search_index, if: -> { self.class.loam_searchable? }
36
+ after_save :open_loam_update_search_index, if: :open_loam_search_index_stale?
37
+ after_destroy :open_loam_remove_from_search_index, if: -> { self.class.open_loam_searchable? }
38
38
  end
39
39
  end
40
40
 
@@ -45,24 +45,24 @@ module Loam
45
45
  def searchable_by(*columns)
46
46
  columns = columns.map(&:to_s)
47
47
 
48
- # The mirror of Loam::Encryptable's check: an encrypted column is
48
+ # The mirror of OpenLoam::Encryptable's check: an encrypted column is
49
49
  # ciphertext at rest, which LIKE cannot match. Caught here when
50
50
  # `searchable_by` is the later declaration (encrypts catches the reverse).
51
- if respond_to?(:loam_encrypted_attributes)
52
- conflict = columns & loam_encrypted_attributes
51
+ if respond_to?(:open_loam_encrypted_attributes)
52
+ conflict = columns & open_loam_encrypted_attributes
53
53
  if conflict.any?
54
- raise Loam::Error,
54
+ raise OpenLoam::Error,
55
55
  "#{name}: cannot `searchable_by` encrypted field(s) #{conflict.join(', ')} — " \
56
56
  "ciphertext cannot be LIKE-searched. Use `encrypts :field, searchable: true` " \
57
57
  "for exact-match lookup instead."
58
58
  end
59
59
  end
60
60
 
61
- self.loam_searchable_columns = columns.freeze
61
+ self.open_loam_searchable_columns = columns.freeze
62
62
  end
63
63
 
64
- def loam_searchable?
65
- loam_searchable_columns.any?
64
+ def open_loam_searchable?
65
+ open_loam_searchable_columns.any?
66
66
  end
67
67
 
68
68
  # Delegates to the active driver, handing it the current relation as the
@@ -70,21 +70,21 @@ module Loam
70
70
  # (a class method called on a relation runs with `all` == that relation).
71
71
  # A blank query returns everything: an empty search box means "no filter".
72
72
  def search(query)
73
- Loam::Search.search(self, query, scope: all)
73
+ OpenLoam::Search.search(self, query, scope: all)
74
74
  end
75
75
  end
76
76
 
77
77
  private
78
78
 
79
- def loam_update_search_index = Loam::Search.index(self)
80
- def loam_remove_from_search_index = Loam::Search.remove(self)
79
+ def open_loam_update_search_index = OpenLoam::Search.index(self)
80
+ def open_loam_remove_from_search_index = OpenLoam::Search.remove(self)
81
81
 
82
82
  # On create every column counts as changed, so a new record is always
83
83
  # indexed; on update, reindex only when a searchable column actually moved.
84
- def loam_search_index_stale?
85
- return false unless self.class.loam_searchable?
84
+ def open_loam_search_index_stale?
85
+ return false unless self.class.open_loam_searchable?
86
86
 
87
- saved_changes.keys.intersect?(self.class.loam_searchable_columns)
87
+ saved_changes.keys.intersect?(self.class.open_loam_searchable_columns)
88
88
  end
89
89
  end
90
90
  end
@@ -1,7 +1,7 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Soft-delete: destroying a business record hides it instead of erasing it.
3
3
  # A recycle bin — for GDPR retention, for undo, for audits that still read
4
- # cleanly after the fact. Loam is "safe by default", so a soft-deleted record
4
+ # cleanly after the fact. OpenLoam is "safe by default", so a soft-deleted record
5
5
  # is EXCLUDED from every ordinary query; seeing it is something you opt INTO,
6
6
  # never a filter you must remember to add.
7
7
  #
@@ -34,19 +34,19 @@ module Loam
34
34
  end
35
35
 
36
36
  def soft_delete
37
- loam_toggle_deleted(deleted: true, bang: false)
37
+ open_loam_toggle_deleted(deleted: true, bang: false)
38
38
  end
39
39
 
40
40
  def soft_delete!
41
- loam_toggle_deleted(deleted: true, bang: true)
41
+ open_loam_toggle_deleted(deleted: true, bang: true)
42
42
  end
43
43
 
44
44
  def restore
45
- loam_toggle_deleted(deleted: false, bang: false)
45
+ open_loam_toggle_deleted(deleted: false, bang: false)
46
46
  end
47
47
 
48
48
  def restore!
49
- loam_toggle_deleted(deleted: false, bang: true)
49
+ open_loam_toggle_deleted(deleted: false, bang: true)
50
50
  end
51
51
 
52
52
  private
@@ -55,18 +55,18 @@ module Loam
55
55
  # record to the state it is already in (a double-clicked button) is a no-op,
56
56
  # so the trail never fills with deleted_at -> deleted_at noise.
57
57
  #
58
- # A soft-delete is an UPDATE, so Loam::Auditable's after_destroy never fires
58
+ # A soft-delete is an UPDATE, so OpenLoam::Auditable's after_destroy never fires
59
59
  # for it. Rather than write our own audit row we borrow Auditable's single
60
- # audit path via `loam_audit_as`, which relabels this save's audit
60
+ # audit path via `open_loam_audit_as`, which relabels this save's audit
61
61
  # "soft_delete"/"restore" instead of the "update" it would otherwise record.
62
- def loam_toggle_deleted(deleted:, bang:)
62
+ def open_loam_toggle_deleted(deleted:, bang:)
63
63
  return true if deleted == deleted?
64
64
 
65
65
  self.deleted_at = deleted ? Time.current : nil
66
66
  action = deleted ? "soft_delete" : "restore"
67
67
  write = -> { bang ? save! : save }
68
68
 
69
- respond_to?(:loam_audit_as, true) ? loam_audit_as(action, &write) : write.call
69
+ respond_to?(:open_loam_audit_as, true) ? open_loam_audit_as(action, &write) : write.call
70
70
  end
71
71
  end
72
72
  end
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Sso
3
3
  # Normalized identity claims, protocol-independent. Every provider's
4
4
  # `exchange` returns one of these so the rest of the flow never knows whether
@@ -1,6 +1,6 @@
1
1
  require "base64"
2
2
 
3
- module Loam
3
+ module OpenLoam
4
4
  module Sso
5
5
  # An OFFLINE stand-in for a real IdP — for the demo and the test suite ONLY,
6
6
  # NEVER production. `authorization_url` loops straight back to our own
@@ -0,0 +1,51 @@
1
+ require "net/http"
2
+ require "json"
3
+
4
+ module OpenLoam
5
+ module Sso
6
+ # The one place SSO talks to the network — kept tiny and isolated so the rest
7
+ # of the flow is pure. Only OpenLoam::Sso::OidcProvider uses it, and the test
8
+ # suite injects a FakeProvider instead, so this is never exercised offline.
9
+ module HttpClient
10
+ module_function
11
+
12
+ def get_json(url, bearer: nil)
13
+ uri, address = checked(url)
14
+ request = Net::HTTP::Get.new(uri)
15
+ request["Authorization"] = "Bearer #{bearer}" if bearer
16
+ parse(perform(uri, address, request))
17
+ end
18
+
19
+ def post_form(url, params)
20
+ uri, address = checked(url)
21
+ request = Net::HTTP::Post.new(uri)
22
+ request.set_form_data(params)
23
+ request["Accept"] = "application/json"
24
+ parse(perform(uri, address, request))
25
+ end
26
+
27
+ # The issuer is typed by a tenant manager, so these URLs are as untrusted
28
+ # as a webhook endpoint's. https is mandatory here rather than merely
29
+ # allowed: the client_secret and the code exchange travel over it.
30
+ def checked(url)
31
+ OpenLoam::OutboundUrl.resolve!(url, require_https: true)
32
+ rescue OpenLoam::OutboundUrl::BlockedError => error
33
+ raise OpenLoam::Sso::Error, "identity provider URL refused: #{error.message}"
34
+ end
35
+
36
+ def perform(uri, address, request)
37
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true, ipaddr: address) do |http|
38
+ http.request(request)
39
+ end
40
+ end
41
+
42
+ def parse(response)
43
+ unless response.is_a?(Net::HTTPSuccess)
44
+ raise OpenLoam::Sso::Error, "identity provider returned #{response.code}"
45
+ end
46
+
47
+ JSON.parse(response.body)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,4 +1,4 @@
1
- module Loam
1
+ module OpenLoam
2
2
  module Sso
3
3
  # The real OIDC Authorization Code provider. Discovers the issuer's endpoints
4
4
  # from its .well-known document, sends the user to the authorization endpoint,
@@ -13,7 +13,7 @@ module Loam
13
13
  # JWKS verification for the prototype. Roadmap: full id_token + JWKS.
14
14
  #
15
15
  # NOT exercised by the test suite (a FakeProvider is injected via
16
- # Loam::Sso.builder); live OIDC against a real IdP is verified manually.
16
+ # OpenLoam::Sso.builder); live OIDC against a real IdP is verified manually.
17
17
  class OidcProvider
18
18
  SCOPE = "openid email profile".freeze
19
19
 
@@ -1,25 +1,25 @@
1
- require "loam/sso/claims"
2
- require "loam/sso/http_client"
3
- require "loam/sso/oidc_provider"
4
- require "loam/sso/fake_provider"
1
+ require "open_loam/sso/claims"
2
+ require "open_loam/sso/http_client"
3
+ require "open_loam/sso/oidc_provider"
4
+ require "open_loam/sso/fake_provider"
5
5
 
6
- module Loam
6
+ module OpenLoam
7
7
  # Single sign-on. SHIPPED: OIDC Authorization Code flow end-to-end —
8
8
  # home-realm discovery by email domain, just-in-time user provisioning, IdP
9
9
  # group -> role mapping, and account linking to an existing User. SEAMS
10
10
  # (documented, not built): SAML (another protocol behind the same interface,
11
11
  # raises NotImplementedError until implemented) and SCIM 2.0 provisioning
12
- # (see docs/_foundation/overview.md). See Loam::SsoProvider for the per-tenant config.
12
+ # (see docs/_foundation/overview.md). See OpenLoam::SsoProvider for the per-tenant config.
13
13
  #
14
14
  # A protocol provider implements two methods:
15
15
  # authorization_url(state:, login_hint: nil) -> the IdP URL to redirect to
16
- # exchange(code:) -> Loam::Sso::Claims
16
+ # exchange(code:) -> OpenLoam::Sso::Claims
17
17
  #
18
18
  # The provider is built through a swappable `builder` so tests and the demo
19
- # inject a FakeProvider and NOTHING touches the network. Loam::Sso::OidcProvider
19
+ # inject a FakeProvider and NOTHING touches the network. OpenLoam::Sso::OidcProvider
20
20
  # is the real one; it is never constructed in the test suite.
21
21
  module Sso
22
- class Error < Loam::Error; end
22
+ class Error < OpenLoam::Error; end
23
23
 
24
24
  # The identity provider did not assert a verified email. Linking or creating
25
25
  # an account on an unverified email is an account-takeover vector, so the
@@ -32,6 +32,11 @@ module Loam
32
32
  # (cross-domain account takeover), so the flow refuses it.
33
33
  class DomainMismatchError < Error; end
34
34
 
35
+ # The provider has not proven it owns its `domain`, so it may not adopt an
36
+ # existing account. Ownership is stamped out of band by an operator
37
+ # (rake open_loam:sso:verify_domain).
38
+ class UnverifiedDomainError < Error; end
39
+
35
40
  class << self
36
41
  attr_writer :builder
37
42
 
@@ -39,7 +44,7 @@ module Loam
39
44
  @builder ||= method(:default_builder)
40
45
  end
41
46
 
42
- # Wrap a Loam::SsoProvider record in its protocol provider.
47
+ # Wrap a OpenLoam::SsoProvider record in its protocol provider.
43
48
  def build(record, redirect_uri:)
44
49
  builder.call(record, redirect_uri)
45
50
  end
@@ -50,25 +55,28 @@ module Loam
50
55
  OidcProvider.new(record, redirect_uri: redirect_uri)
51
56
  when "saml"
52
57
  raise NotImplementedError,
53
- "SAML SSO is a documented seam — implement Loam::Sso::SamlProvider behind this interface."
58
+ "SAML SSO is a documented seam — implement OpenLoam::Sso::SamlProvider behind this interface."
54
59
  else
55
60
  raise Error, "Unsupported SSO protocol: #{record.protocol.inspect}"
56
61
  end
57
62
  end
58
63
 
59
64
  # Home-realm discovery. THE blessed cross-tenant lookup for SSO (like
60
- # Loam::Membership.tenants_for): "which tenant's IdP owns this email
65
+ # OpenLoam::Membership.tenants_for): "which tenant's IdP owns this email
61
66
  # domain?" is asked at the sign-in page, before any tenant is chosen, so it
62
67
  # reaches across tenants via `unscoped` — something host code must never do.
68
+ #
69
+ # Verified providers only: the domain is manager-typed, so an unverified
70
+ # claim would capture every sign-in on a domain the tenant does not own.
63
71
  def provider_for(email:)
64
72
  domain = email.to_s.split("@").last.to_s.strip.downcase
65
73
  return nil if domain.blank?
66
74
 
67
- Loam::SsoProvider.unscoped.where(domain: domain, active: true).first
75
+ OpenLoam::SsoProvider.unscoped.domain_verified.where(domain: domain, active: true).first
68
76
  end
69
77
 
70
78
  # Resolve verified IdP claims to a signed-in User, in the provider's tenant.
71
- # MUST be called inside Loam.as_tenant(provider.tenant). Order matters:
79
+ # MUST be called inside OpenLoam.as_tenant(provider.tenant). Order matters:
72
80
  # match the durable (provider, sub) identity first, then an existing User by
73
81
  # verified email (link), else just-in-time create — always with a tenant
74
82
  # membership at the mapped role.
@@ -86,13 +94,13 @@ module Loam
86
94
 
87
95
  # Resolve the user: the durable (provider, sub) identity first, then an
88
96
  # existing User by verified email (link), else just-in-time create.
89
- identity = Loam::SsoIdentity.find_by(sso_provider_id: provider.id, sub: claims.sub)
90
- user = identity&.user || User.find_by(email: claims.email) || jit_create_user(claims)
97
+ identity = OpenLoam::SsoIdentity.find_by(sso_provider_id: provider.id, sub: claims.sub)
98
+ user = identity&.user || link_or_create_user(provider, claims)
91
99
 
92
100
  # Re-map claims -> role on EVERY login (including a returning identity),
93
101
  # so an IdP role change takes effect.
94
102
  ensure_membership(provider, user, claims)
95
- Loam::SsoIdentity.create!(user: user, sso_provider: provider, sub: claims.sub) unless identity
103
+ OpenLoam::SsoIdentity.create!(user: user, sso_provider: provider, sub: claims.sub) unless identity
96
104
  user
97
105
  end
98
106
 
@@ -106,6 +114,19 @@ module Loam
106
114
 
107
115
  private
108
116
 
117
+ # `User` is global, so linking adopts an account this tenant did not create
118
+ # — allowed only once the provider proved it owns the domain. JIT-creating
119
+ # needs no proof: the new user lands in the claimant's own tenant.
120
+ def link_or_create_user(provider, claims)
121
+ existing = User.find_by(email: claims.email)
122
+ return jit_create_user(claims) if existing.nil?
123
+ return existing if provider.domain_verified?
124
+
125
+ raise UnverifiedDomainError,
126
+ "provider for #{provider.domain.inspect} has not proven it owns that domain — " \
127
+ "refusing to link the existing account #{claims.email.inspect}"
128
+ end
129
+
109
130
  def jit_create_user(claims)
110
131
  # SSO users authenticate at the IdP; the random password satisfies
111
132
  # has_secure_password and is unguessable, never used to log in.
@@ -120,7 +141,7 @@ module Loam
120
141
  # (a promotion reflects, a removed group downgrades) rather than freezing
121
142
  # at whatever the role was when the membership was first created.
122
143
  def ensure_membership(provider, user, claims)
123
- membership = Loam::Membership.find_or_initialize_by(user_id: user.id)
144
+ membership = OpenLoam::Membership.find_or_initialize_by(user_id: user.id)
124
145
  membership.role = role_for(provider, claims)
125
146
  membership.save!
126
147
  end
@@ -1,16 +1,16 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # A dependency-free observability seam (L-712). Wrap a unit of work in a span;
3
3
  # by default it emits an `ActiveSupport::Notifications` event
4
- # ("loam.span.<name>") carrying the attributes and duration, so ANY subscriber
4
+ # ("open_loam.span.<name>") carrying the attributes and duration, so ANY subscriber
5
5
  # — an OpenTelemetry bridge, StatsD, a log line — picks it up with no hard
6
- # dependency on Loam's side. An app that wants real OTLP spans replaces the
6
+ # dependency on OpenLoam's side. An app that wants real OTLP spans replaces the
7
7
  # whole strategy:
8
8
  #
9
- # Loam::Telemetry.backend = ->(name, attributes, work) do
9
+ # OpenLoam::Telemetry.backend = ->(name, attributes, work) do
10
10
  # Tracer.in_span(name, attributes: attributes) { work.call }
11
11
  # end
12
12
  #
13
- # Loam instruments its async/background hot paths (scheduler tick, durable
13
+ # OpenLoam instruments its async/background hot paths (scheduler tick, durable
14
14
  # event delivery, inbound webhook ingest) so an operator sees them without
15
15
  # wiring anything per-call.
16
16
  module Telemetry
@@ -24,7 +24,7 @@ module Loam
24
24
  if backend
25
25
  backend.call(name.to_s, attributes, block)
26
26
  else
27
- ActiveSupport::Notifications.instrument("loam.span.#{name}", **attributes, &block)
27
+ ActiveSupport::Notifications.instrument("open_loam.span.#{name}", **attributes, &block)
28
28
  end
29
29
  end
30
30
 
@@ -1,8 +1,8 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Base class for every tenant-scoped model. Inheriting from it is what makes
3
- # a model a "Loam entity":
3
+ # a model a "OpenLoam entity":
4
4
  #
5
- # * every query is scoped to Loam::Current.tenant via default_scope
5
+ # * every query is scoped to OpenLoam::Current.tenant via default_scope
6
6
  # * building/creating a record assigns the current tenant automatically
7
7
  # * touching the model with NO tenant in context raises MissingTenantError
8
8
  #
@@ -12,24 +12,24 @@ module Loam
12
12
  class TenantRecord < ActiveRecord::Base
13
13
  self.abstract_class = true
14
14
 
15
- # Generates the key when it is not an integer (see Loam::GeneratedKey).
16
- include Loam::GeneratedKey
15
+ # Generates the key when it is not an integer (see OpenLoam::GeneratedKey).
16
+ include OpenLoam::GeneratedKey
17
17
 
18
- belongs_to :tenant, class_name: "Loam::Tenant"
18
+ belongs_to :tenant, class_name: "OpenLoam::Tenant"
19
19
 
20
- default_scope { where(tenant_id: Loam.tenant!.id) }
20
+ default_scope { where(tenant_id: OpenLoam.tenant!.id) }
21
21
 
22
22
  validates :tenant_id, presence: true
23
23
 
24
24
  before_validation on: :create do
25
- self.tenant_id ||= Loam.tenant!.id
25
+ self.tenant_id ||= OpenLoam.tenant!.id
26
26
  end
27
27
 
28
28
  # Guardrail for writes: a record can never be saved into a foreign tenant.
29
29
  before_save do
30
- if tenant_id != Loam.tenant!.id
30
+ if tenant_id != OpenLoam.tenant!.id
31
31
  raise MissingTenantError, "Attempted to write #{self.class.name} into tenant #{tenant_id} " \
32
- "while Loam::Current.tenant is #{Loam.tenant!.id}"
32
+ "while OpenLoam::Current.tenant is #{OpenLoam.tenant!.id}"
33
33
  end
34
34
  end
35
35
  end
@@ -1,15 +1,15 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Mixed into ActiveSupport::TestCase by the install generator.
3
3
  module TestHelpers
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- setup { Loam::Current.reset }
8
- teardown { Loam::Current.reset }
7
+ setup { OpenLoam::Current.reset }
8
+ teardown { OpenLoam::Current.reset }
9
9
  end
10
10
 
11
11
  def with_tenant(tenant, actor: nil, &block)
12
- Loam.as_tenant(tenant, actor: actor, &block)
12
+ OpenLoam.as_tenant(tenant, actor: actor, &block)
13
13
  end
14
14
  end
15
15
  end
@@ -3,7 +3,7 @@ require "securerandom"
3
3
  require "uri"
4
4
  require "erb"
5
5
 
6
- module Loam
6
+ module OpenLoam
7
7
  # RFC 6238 time-based one-time passwords — the shipped second factor.
8
8
  # HMAC-SHA1, 30-second step, 6 digits, ±1 step of drift tolerance (so a code
9
9
  # entered a few seconds either side of a boundary still verifies). Hand-rolled
@@ -17,7 +17,7 @@ module Loam
17
17
  # A fresh random secret, base32-encoded for authenticator apps. 20 bytes
18
18
  # (160 bits) is the RFC-recommended size for SHA1.
19
19
  def self.generate_secret(bytes: 20)
20
- Loam::Base32.encode(SecureRandom.random_bytes(bytes))
20
+ OpenLoam::Base32.encode(SecureRandom.random_bytes(bytes))
21
21
  end
22
22
 
23
23
  # True if `code` is valid for `secret` right now.
@@ -44,7 +44,7 @@ module Loam
44
44
 
45
45
  # The HOTP code for a given step counter (RFC 4226 dynamic truncation).
46
46
  def self.code_at(secret, counter, digits: DIGITS)
47
- hmac = OpenSSL::HMAC.digest("SHA1", Loam::Base32.decode(secret), [counter].pack("Q>"))
47
+ hmac = OpenSSL::HMAC.digest("SHA1", OpenLoam::Base32.decode(secret), [counter].pack("Q>"))
48
48
  offset = hmac.bytes.last & 0x0f
49
49
  truncated = hmac[offset, 4].unpack1("N") & 0x7fffffff
50
50
  (truncated % (10**digits)).to_s.rjust(digits, "0")
@@ -1,32 +1,32 @@
1
- module Loam
1
+ module OpenLoam
2
2
  # Per-record, per-locale translations of DATA fields — a product name, a
3
3
  # category label — distinct from Rails i18n (developer UI strings, still
4
4
  # Rails-native and out of scope here).
5
5
  #
6
- # class Equipment < Loam::TenantRecord
7
- # include Loam::Translatable
6
+ # class Equipment < OpenLoam::TenantRecord
7
+ # include OpenLoam::Translatable
8
8
  # translates :name, :description
9
9
  # end
10
10
  #
11
11
  # READ OVERLAY: the field reader returns the translation for the CURRENT locale
12
- # (Loam.locale) when one exists, else the record's own COLUMN value — the base
12
+ # (OpenLoam.locale) when one exists, else the record's own COLUMN value — the base
13
13
  # is the fallback and stays authoritative for the default locale:
14
14
  #
15
- # equipment.name # => overlay for Loam.locale, else base
15
+ # equipment.name # => overlay for OpenLoam.locale, else base
16
16
  # equipment.name(locale: "de") # => explicit locale, else base
17
17
  # equipment.set_translation(:name, "de", "Bagger")
18
18
  # equipment.translations_for(:name) # => { "de" => "Bagger", ... }
19
19
  #
20
20
  # Writes still go through the ordinary `name=` writer to the base column, so
21
- # translations are purely additive rows in loam_translations — the base value
21
+ # translations are purely additive rows in open_loam_translations — the base value
22
22
  # is never overwritten or lost.
23
23
  module Translatable
24
24
  extend ActiveSupport::Concern
25
25
 
26
26
  included do
27
- class_attribute :loam_translatable_attributes, default: [].freeze, instance_writer: false
27
+ class_attribute :open_loam_translatable_attributes, default: [].freeze, instance_writer: false
28
28
 
29
- has_many :loam_translations, class_name: "Loam::Translation",
29
+ has_many :open_loam_translations, class_name: "OpenLoam::Translation",
30
30
  as: :translatable, dependent: :delete_all
31
31
  end
32
32
 
@@ -35,54 +35,54 @@ module Loam
35
35
  names = names.map(&:to_s)
36
36
 
37
37
  # An encrypted field must NOT be translatable: a translation row would
38
- # store its PLAINTEXT in loam_translations, recreating exactly the leak
39
- # Loam::Encryptable closes (same posture as searchable_by's refusal).
40
- if respond_to?(:loam_encrypted_attributes)
41
- conflict = names & loam_encrypted_attributes
38
+ # store its PLAINTEXT in open_loam_translations, recreating exactly the leak
39
+ # OpenLoam::Encryptable closes (same posture as searchable_by's refusal).
40
+ if respond_to?(:open_loam_encrypted_attributes)
41
+ conflict = names & open_loam_encrypted_attributes
42
42
  if conflict.any?
43
- raise Loam::Error,
43
+ raise OpenLoam::Error,
44
44
  "#{name}: cannot `translates` encrypted field(s) #{conflict.join(', ')} — " \
45
45
  "a translation would store plaintext. Encrypted data is not translatable."
46
46
  end
47
47
  end
48
48
 
49
- self.loam_translatable_attributes = (loam_translatable_attributes + names).freeze
49
+ self.open_loam_translatable_attributes = (open_loam_translatable_attributes + names).freeze
50
50
 
51
51
  names.each do |field|
52
52
  # The overlay reader. `read_attribute` (not super) is the base value,
53
53
  # so it works regardless of AR's lazy attribute-method definition.
54
54
  define_method(field) do |locale: nil|
55
- loam_translation_value(field, locale) || read_attribute(field)
55
+ open_loam_translation_value(field, locale) || read_attribute(field)
56
56
  end
57
57
  end
58
58
  end
59
59
 
60
- def loam_translatable?(field)
61
- loam_translatable_attributes.include?(field.to_s)
60
+ def open_loam_translatable?(field)
61
+ open_loam_translatable_attributes.include?(field.to_s)
62
62
  end
63
63
  end
64
64
 
65
65
  # The stored translation for a field in a locale (default: the current one),
66
66
  # or nil when there is none.
67
- def loam_translation_value(field, locale = nil)
68
- locale = (locale || Loam.locale).to_s
67
+ def open_loam_translation_value(field, locale = nil)
68
+ locale = (locale || OpenLoam.locale).to_s
69
69
  return nil if locale.blank?
70
70
 
71
- loam_translations.detect { |t| t.field == field.to_s && t.locale == locale }&.value
71
+ open_loam_translations.detect { |t| t.field == field.to_s && t.locale == locale }&.value
72
72
  end
73
73
 
74
74
  def set_translation(field, locale, value)
75
- raise Loam::Error, "#{field} is not translatable on #{self.class.name}" unless self.class.loam_translatable?(field)
75
+ raise OpenLoam::Error, "#{field} is not translatable on #{self.class.name}" unless self.class.open_loam_translatable?(field)
76
76
 
77
- row = loam_translations.find_or_initialize_by(field: field.to_s, locale: locale.to_s)
77
+ row = open_loam_translations.find_or_initialize_by(field: field.to_s, locale: locale.to_s)
78
78
  row.value = value
79
79
  row.save!
80
- loam_translations.reset
80
+ open_loam_translations.reset
81
81
  value
82
82
  end
83
83
 
84
84
  def translations_for(field)
85
- loam_translations.select { |t| t.field == field.to_s }.to_h { |t| [ t.locale, t.value ] }
85
+ open_loam_translations.select { |t| t.field == field.to_s }.to_h { |t| [ t.locale, t.value ] }
86
86
  end
87
87
  end
88
88
  end