open-loam 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -8
- data/LICENSE +1 -1
- data/README.md +24 -24
- data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
- data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
- data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
- data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +7 -7
- data/app/models/{loam → open_loam}/api_token.rb +7 -7
- data/app/models/{loam → open_loam}/audit_record.rb +3 -3
- data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
- data/app/models/{loam → open_loam}/business_rule.rb +10 -10
- data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
- data/app/models/{loam → open_loam}/comment.rb +9 -9
- data/app/models/{loam → open_loam}/config.rb +7 -7
- data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
- data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
- data/app/models/open_loam/dictionary.rb +20 -0
- data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
- data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/field_definition.rb +8 -8
- data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
- data/app/models/{loam → open_loam}/membership.rb +6 -6
- data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
- data/app/models/{loam → open_loam}/notification.rb +10 -10
- data/app/models/{loam → open_loam}/pending_action.rb +23 -23
- data/app/models/{loam → open_loam}/perspective.rb +5 -5
- data/app/models/{loam → open_loam}/progress_job.rb +7 -7
- data/app/models/{loam → open_loam}/record_lock.rb +5 -5
- data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
- data/app/models/open_loam/search_token.rb +9 -0
- data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
- data/app/models/{loam → open_loam}/sso_provider.rb +8 -8
- data/app/models/open_loam/tenant.rb +20 -0
- data/app/models/{loam → open_loam}/translation.rb +5 -5
- data/app/models/{loam → open_loam}/webhook_endpoint.rb +6 -6
- data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
- data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
- data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
- data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +21 -21
- data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
- data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
- data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
- data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +4 -4
- data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
- data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/index.html.erb +90 -0
- data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/show.html.erb +36 -0
- data/lib/generators/{loam → open_loam}/install/install_generator.rb +47 -47
- data/lib/generators/open_loam/install/templates/AGENTS.md +341 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_controller.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +38 -38
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
- data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +10 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +12 -12
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -2
- data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +19 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +32 -32
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/api_base_controller.rb +15 -15
- data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
- data/lib/generators/{loam → open_loam}/install/templates/import_job.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +74 -74
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
- 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
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
- 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
- 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
- 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
- data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -9
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
- 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
- data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
- 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
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +12 -12
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
- data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
- data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
- data/lib/open-loam.rb +2 -2
- data/lib/{loam → open_loam}/attachable.rb +4 -4
- data/lib/{loam → open_loam}/auditable.rb +23 -23
- data/lib/{loam → open_loam}/auth_throttle.rb +10 -10
- data/lib/{loam → open_loam}/base32.rb +1 -1
- data/lib/{loam → open_loam}/bulk.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
- data/lib/{loam → open_loam}/business_rules.rb +19 -19
- data/lib/{loam → open_loam}/commentable.rb +6 -6
- data/lib/{loam → open_loam}/configs.rb +19 -19
- data/lib/{loam → open_loam}/cron.rb +2 -2
- data/lib/{loam → open_loam}/csv.rb +2 -2
- data/lib/{loam → open_loam}/current.rb +5 -5
- data/lib/{loam → open_loam}/custom_field_index.rb +23 -23
- data/lib/{loam → open_loam}/custom_fields.rb +17 -17
- data/lib/{loam → open_loam}/dashboard.rb +5 -5
- data/lib/{loam → open_loam}/dictionaries.rb +12 -12
- data/lib/{loam → open_loam}/durable_events.rb +19 -19
- data/lib/{loam → open_loam}/encryptable.rb +33 -33
- data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
- data/lib/{loam → open_loam}/encryption/key_provider.rb +3 -3
- data/lib/{loam → open_loam}/encryption.rb +11 -11
- data/lib/open_loam/engine.rb +57 -0
- data/lib/{loam → open_loam}/enrichers.rb +5 -5
- data/lib/{loam → open_loam}/errors.rb +10 -10
- data/lib/{loam → open_loam}/eval.rb +2 -2
- data/lib/{loam → open_loam}/event_stream.rb +14 -14
- data/lib/{loam → open_loam}/eventful.rb +12 -12
- data/lib/{loam → open_loam}/events.rb +5 -5
- data/lib/{loam → open_loam}/export.rb +8 -8
- data/lib/{loam → open_loam}/features.rb +15 -15
- data/lib/{loam → open_loam}/generated_key.rb +6 -6
- data/lib/{loam → open_loam}/import.rb +12 -12
- data/lib/{loam → open_loam}/inbound_webhooks.rb +7 -7
- data/lib/{loam → open_loam}/lifecycle.rb +25 -25
- data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
- data/lib/{loam → open_loam}/mcp/server.rb +9 -9
- data/lib/{loam → open_loam}/mcp.rb +21 -21
- data/lib/{loam → open_loam}/notifications.rb +5 -5
- data/lib/{loam → open_loam}/open_api.rb +16 -16
- data/lib/{loam → open_loam}/overrides.rb +10 -10
- data/lib/{loam → open_loam}/pending_actions.rb +9 -9
- data/lib/{loam → open_loam}/permissions.rb +8 -8
- data/lib/{loam → open_loam}/perspectives.rb +9 -9
- data/lib/{loam → open_loam}/policy.rb +6 -6
- data/lib/{loam → open_loam}/progress.rb +6 -6
- data/lib/{loam → open_loam}/record_locks.rb +9 -9
- data/lib/{loam → open_loam}/scheduler.rb +20 -20
- data/lib/{loam → open_loam}/search/driver.rb +3 -3
- data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
- data/lib/{loam → open_loam}/search/token_driver.rb +15 -15
- data/lib/{loam → open_loam}/search.rb +5 -5
- data/lib/{loam → open_loam}/searchable.rb +22 -22
- data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
- data/lib/{loam → open_loam}/sso/claims.rb +1 -1
- data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
- data/lib/{loam → open_loam}/sso/http_client.rb +3 -3
- data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
- data/lib/{loam → open_loam}/sso.rb +17 -17
- data/lib/{loam → open_loam}/telemetry.rb +6 -6
- data/lib/{loam → open_loam}/tenant_record.rb +10 -10
- data/lib/{loam → open_loam}/test_helpers.rb +4 -4
- data/lib/{loam → open_loam}/totp.rb +3 -3
- data/lib/{loam → open_loam}/translatable.rb +24 -24
- data/lib/{loam → open_loam}/undo.rb +11 -11
- data/lib/open_loam/version.rb +3 -0
- data/lib/{loam → open_loam}/webhooks.rb +4 -4
- data/lib/{loam → open_loam}/widgets.rb +9 -9
- data/lib/{loam → open_loam}/workflow.rb +45 -45
- data/lib/open_loam.rb +122 -0
- data/lib/tasks/{loam.rake → open_loam.rake} +43 -43
- data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
- data/lib/tasks/open_loam_mcp.rake +20 -0
- metadata +242 -242
- data/app/models/loam/dictionary.rb +0 -20
- data/app/models/loam/search_token.rb +0 -9
- data/app/models/loam/tenant.rb +0 -20
- data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
- data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
- data/lib/generators/loam/install/templates/AGENTS.md +0 -341
- data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
- data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
- data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
- data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
- data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
- data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
- data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
- data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
- data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
- data/lib/loam/engine.rb +0 -57
- data/lib/loam/version.rb +0 -3
- data/lib/loam.rb +0 -122
- data/lib/tasks/loam_mcp.rake +0 -20
- /data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module Admin
|
|
2
2
|
# Email + password login for the admin, then (if the user has MFA) a TOTP
|
|
3
|
-
# challenge, then the tenant pick. A
|
|
3
|
+
# challenge, then the tenant pick. A OpenLoam user can belong to several tenants,
|
|
4
4
|
# so: authenticate → second factor → choose a tenant where they hold a
|
|
5
5
|
# membership. The second factor runs BEFORE any tenant is chosen, which is why
|
|
6
|
-
# the MFA secret is keyed to the user, not a tenant (
|
|
6
|
+
# the MFA secret is keyed to the user, not a tenant (OpenLoam::MfaCredential).
|
|
7
7
|
#
|
|
8
8
|
# Not a BaseController subclass: everything there assumes an established
|
|
9
|
-
#
|
|
9
|
+
# OpenLoam::Current, which is exactly what this controller is trying to build.
|
|
10
10
|
class SessionsController < ActionController::Base
|
|
11
11
|
layout "admin"
|
|
12
12
|
|
|
@@ -14,17 +14,17 @@ module Admin
|
|
|
14
14
|
return redirect_to mfa_challenge_admin_session_path if session[:mfa_pending]
|
|
15
15
|
|
|
16
16
|
@user = authenticated_user
|
|
17
|
-
@tenants = @user ?
|
|
17
|
+
@tenants = @user ? OpenLoam::Membership.tenants_for(@user) : OpenLoam::Tenant.none
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def create
|
|
21
21
|
email = params[:email]
|
|
22
22
|
|
|
23
|
-
# Rate-limit BEFORE touching the password (
|
|
23
|
+
# Rate-limit BEFORE touching the password (OpenLoam::AuthThrottle). Throttle by
|
|
24
24
|
# the submitted identifier whether or not the account exists, and give the
|
|
25
25
|
# SAME generic locked response either way — so a lockout can't become an
|
|
26
26
|
# account-existence oracle.
|
|
27
|
-
if
|
|
27
|
+
if OpenLoam::AuthThrottle.locked?(email)
|
|
28
28
|
@error = throttle_message(email)
|
|
29
29
|
return render :new, status: :too_many_requests
|
|
30
30
|
end
|
|
@@ -32,20 +32,20 @@ module Admin
|
|
|
32
32
|
user = User.authenticate_by(email: email, password: params[:password])
|
|
33
33
|
|
|
34
34
|
if user.nil?
|
|
35
|
-
|
|
35
|
+
OpenLoam::AuthThrottle.record_failure(email, kind: "password", ip: request.remote_ip)
|
|
36
36
|
# One message for both cases on purpose: saying which half was wrong
|
|
37
37
|
# tells an attacker which emails exist.
|
|
38
38
|
@error = "Wrong email or password."
|
|
39
39
|
return render :new, status: :unauthorized
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
OpenLoam::AuthThrottle.clear(email) # a successful login resets the counter
|
|
43
43
|
reset_session # a fresh session id at login: no fixation
|
|
44
44
|
session[:user_id] = user.id
|
|
45
45
|
|
|
46
46
|
# Password is only the first factor when MFA is active — hold the login in
|
|
47
47
|
# a "pending" state (no tenant, no access) until the code checks out.
|
|
48
|
-
if
|
|
48
|
+
if OpenLoam::MfaCredential.active_for(user)
|
|
49
49
|
session[:mfa_pending] = true
|
|
50
50
|
redirect_to mfa_challenge_admin_session_path
|
|
51
51
|
else
|
|
@@ -65,20 +65,20 @@ module Admin
|
|
|
65
65
|
|
|
66
66
|
# A 6-digit TOTP (with drift, several codes valid) is the prime brute-force
|
|
67
67
|
# target — lock it out on the user's identifier.
|
|
68
|
-
if
|
|
68
|
+
if OpenLoam::AuthThrottle.locked?(user.email)
|
|
69
69
|
@user = user
|
|
70
70
|
@error = throttle_message(user.email)
|
|
71
71
|
return render :mfa_challenge, status: :too_many_requests
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
credential =
|
|
74
|
+
credential = OpenLoam::MfaCredential.active_for(user)
|
|
75
75
|
|
|
76
76
|
if credential.verify_totp(params[:code]) || credential.consume_recovery_code(params[:code])
|
|
77
|
-
|
|
77
|
+
OpenLoam::AuthThrottle.clear(user.email)
|
|
78
78
|
session.delete(:mfa_pending)
|
|
79
79
|
complete_authentication(user)
|
|
80
80
|
else
|
|
81
|
-
|
|
81
|
+
OpenLoam::AuthThrottle.record_failure(user.email, kind: "totp", ip: request.remote_ip)
|
|
82
82
|
# One generic error — never reveal whether a code was close or a
|
|
83
83
|
# recovery code was already spent.
|
|
84
84
|
@user = user
|
|
@@ -92,7 +92,7 @@ module Admin
|
|
|
92
92
|
# back to password login. The `state` is our CSRF token for the round-trip.
|
|
93
93
|
def sso_start
|
|
94
94
|
email = params[:email].to_s
|
|
95
|
-
provider =
|
|
95
|
+
provider = OpenLoam::Sso.provider_for(email: email)
|
|
96
96
|
|
|
97
97
|
if provider.nil?
|
|
98
98
|
return redirect_to new_admin_session_path(email: email),
|
|
@@ -105,8 +105,8 @@ module Admin
|
|
|
105
105
|
session[:sso_provider_id] = provider.id
|
|
106
106
|
session[:sso_tenant_id] = provider.tenant_id
|
|
107
107
|
|
|
108
|
-
url =
|
|
109
|
-
|
|
108
|
+
url = OpenLoam.as_tenant(provider.tenant) do
|
|
109
|
+
OpenLoam::Sso.build(provider, redirect_uri: sso_callback_admin_session_url)
|
|
110
110
|
.authorization_url(state: state, login_hint: email)
|
|
111
111
|
end
|
|
112
112
|
redirect_to url, allow_other_host: true
|
|
@@ -124,15 +124,15 @@ module Admin
|
|
|
124
124
|
return redirect_to new_admin_session_path, alert: "SSO sign-in could not be verified. Please try again."
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
tenant =
|
|
128
|
-
provider = tenant &&
|
|
127
|
+
tenant = OpenLoam::Tenant.find_by(id: tenant_id)
|
|
128
|
+
provider = tenant && OpenLoam.as_tenant(tenant) { OpenLoam::SsoProvider.find_by(id: provider_id) }
|
|
129
129
|
if provider.nil? || !provider.active?
|
|
130
130
|
return redirect_to new_admin_session_path, alert: "That SSO provider is no longer available."
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
-
user =
|
|
134
|
-
claims =
|
|
135
|
-
|
|
133
|
+
user = OpenLoam.as_tenant(tenant) do
|
|
134
|
+
claims = OpenLoam::Sso.build(provider, redirect_uri: sso_callback_admin_session_url).exchange(code: params[:code])
|
|
135
|
+
OpenLoam::Sso.provision(provider, claims)
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
reset_session
|
|
@@ -141,13 +141,13 @@ module Admin
|
|
|
141
141
|
|
|
142
142
|
# SSO establishes primary auth; if the user also runs app-side MFA, still
|
|
143
143
|
# require the second factor (the safe choice — SSO does not waive it).
|
|
144
|
-
if
|
|
144
|
+
if OpenLoam::MfaCredential.active_for(user)
|
|
145
145
|
session[:mfa_pending] = true
|
|
146
146
|
redirect_to mfa_challenge_admin_session_path
|
|
147
147
|
else
|
|
148
148
|
complete_authentication(user)
|
|
149
149
|
end
|
|
150
|
-
rescue
|
|
150
|
+
rescue OpenLoam::Sso::Error
|
|
151
151
|
# An unverified email, a domain the provider does not own, or any other
|
|
152
152
|
# provisioning refusal: no session, nothing linked, one generic message.
|
|
153
153
|
reset_session
|
|
@@ -162,7 +162,7 @@ module Admin
|
|
|
162
162
|
user = authenticated_user
|
|
163
163
|
return redirect_to new_admin_session_path if user.nil?
|
|
164
164
|
|
|
165
|
-
tenant =
|
|
165
|
+
tenant = OpenLoam::Membership.tenants_for(user).find_by(id: params[:tenant_id])
|
|
166
166
|
|
|
167
167
|
# A tenant the user has no membership in is simply not in that list, so
|
|
168
168
|
# picking one is impossible rather than merely forbidden.
|
|
@@ -173,7 +173,7 @@ module Admin
|
|
|
173
173
|
|
|
174
174
|
def destroy
|
|
175
175
|
reset_session
|
|
176
|
-
|
|
176
|
+
OpenLoam::Current.reset
|
|
177
177
|
redirect_to new_admin_session_path
|
|
178
178
|
end
|
|
179
179
|
|
|
@@ -184,7 +184,7 @@ module Admin
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
def throttle_message(identifier)
|
|
187
|
-
minutes = (
|
|
187
|
+
minutes = (OpenLoam::AuthThrottle.remaining_lockout(identifier) / 60.0).ceil
|
|
188
188
|
"Too many attempts. Try again in about #{[ minutes, 1 ].max} minute(s)."
|
|
189
189
|
end
|
|
190
190
|
|
|
@@ -206,11 +206,11 @@ module Admin
|
|
|
206
206
|
# SSO knows exactly which tenant to enter (the IdP's), so honor it even for
|
|
207
207
|
# a multi-tenant user rather than showing the picker.
|
|
208
208
|
if (sso_tenant_id = session.delete(:sso_tenant_id))
|
|
209
|
-
tenant =
|
|
209
|
+
tenant = OpenLoam::Membership.tenants_for(user).find_by(id: sso_tenant_id)
|
|
210
210
|
return enter(user, tenant) if tenant
|
|
211
211
|
end
|
|
212
212
|
|
|
213
|
-
tenants =
|
|
213
|
+
tenants = OpenLoam::Membership.tenants_for(user)
|
|
214
214
|
|
|
215
215
|
if tenants.one?
|
|
216
216
|
enter(user, tenants.first)
|
|
@@ -237,11 +237,11 @@ module Admin
|
|
|
237
237
|
# resolved in that tenant). If the user's role is on the list and they have
|
|
238
238
|
# no active credential, they are sent to enrollment before they can work.
|
|
239
239
|
def mfa_enrollment_required?(user, tenant)
|
|
240
|
-
return false if
|
|
240
|
+
return false if OpenLoam::MfaCredential.active_for(user)
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
role =
|
|
244
|
-
role.present? && Array(
|
|
242
|
+
OpenLoam.as_tenant(tenant) do
|
|
243
|
+
role = OpenLoam::Membership.find_by(user_id: user.id)&.role
|
|
244
|
+
role.present? && Array(OpenLoam::Configs.get("security.mfa_required_roles", default: [])).include?(role)
|
|
245
245
|
end
|
|
246
246
|
end
|
|
247
247
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Admin
|
|
2
2
|
# Configure this tenant's SSO (OIDC) provider — manager-only, since it governs
|
|
3
|
-
# who can sign in and at what role. Tenant-scoped by
|
|
3
|
+
# who can sign in and at what role. Tenant-scoped by OpenLoam::SsoProvider's default
|
|
4
4
|
# scope, so a manager only ever sees and edits their own tenant's connection.
|
|
5
5
|
# The client_secret is WRITE-ONLY: it is encrypted at rest and never rendered
|
|
6
6
|
# back, so editing without retyping it leaves the stored secret untouched.
|
|
@@ -10,20 +10,20 @@ module Admin
|
|
|
10
10
|
|
|
11
11
|
rescue_from JSON::ParserError do |error|
|
|
12
12
|
flash.now[:alert] = "The group→role map must be valid JSON: #{error.message}"
|
|
13
|
-
@provider ||=
|
|
13
|
+
@provider ||= OpenLoam::SsoProvider.new
|
|
14
14
|
render(@provider.persisted? ? :edit : :new, status: :unprocessable_entity)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def index
|
|
18
|
-
@providers =
|
|
18
|
+
@providers = OpenLoam::SsoProvider.order(:domain)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def new
|
|
22
|
-
@provider =
|
|
22
|
+
@provider = OpenLoam::SsoProvider.new(protocol: "oidc", active: true, jit_role: "employee")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def create
|
|
26
|
-
@provider =
|
|
26
|
+
@provider = OpenLoam::SsoProvider.new(provider_params)
|
|
27
27
|
if @provider.save
|
|
28
28
|
redirect_to admin_sso_providers_path, notice: "SSO provider created."
|
|
29
29
|
else
|
|
@@ -49,7 +49,7 @@ module Admin
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
51
|
def set_provider
|
|
52
|
-
@provider =
|
|
52
|
+
@provider = OpenLoam::SsoProvider.find(params[:id])
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def provider_params
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<p style="color:#b00"><%%= @error || flash[:alert] %></p>
|
|
3
3
|
<%% end %>
|
|
4
4
|
|
|
5
|
-
<%# The model is namespaced (
|
|
6
|
-
would be
|
|
5
|
+
<%# The model is namespaced (OpenLoam::SsoProvider), so an inferred URL/param-scope
|
|
6
|
+
would be admin_open_loam_sso_provider_path / open_loam_sso_provider[...]. Pin both to
|
|
7
7
|
the un-namespaced route and the "sso_provider" key the controller permits. %>
|
|
8
8
|
<%%= form_with model: @provider, scope: :sso_provider,
|
|
9
9
|
url: (@provider.persisted? ? admin_sso_provider_path(@provider) : admin_sso_providers_path) do |f| %>
|
|
@@ -10,18 +10,18 @@ module Admin
|
|
|
10
10
|
def create
|
|
11
11
|
identifier = "#{current_actor.email}:sudo" # a distinct throttle bucket from login
|
|
12
12
|
|
|
13
|
-
if
|
|
13
|
+
if OpenLoam::AuthThrottle.locked?(identifier)
|
|
14
14
|
@error = "Too many attempts. Try again later."
|
|
15
15
|
return render :new, status: :too_many_requests
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
if reauthenticated?
|
|
19
|
-
|
|
19
|
+
OpenLoam::AuthThrottle.clear(identifier)
|
|
20
20
|
session[:sudo_at] = Time.now.to_i
|
|
21
21
|
redirect_to(session.delete(:sudo_return_to).presence || admin_root_path,
|
|
22
22
|
notice: "Re-authenticated — you can complete the action now.")
|
|
23
23
|
else
|
|
24
|
-
|
|
24
|
+
OpenLoam::AuthThrottle.record_failure(identifier, kind: "sudo", ip: request.remote_ip)
|
|
25
25
|
@error = "That did not verify. Try again."
|
|
26
26
|
render :new, status: :unauthorized
|
|
27
27
|
end
|
|
@@ -32,9 +32,9 @@ module Admin
|
|
|
32
32
|
# If the user has MFA, step-up takes a TOTP code — NOT a recovery code:
|
|
33
33
|
# recovery codes are single-use and reserved for LOGIN, so a routine sudo
|
|
34
34
|
# must never silently burn one. Otherwise fall back to the password. Either
|
|
35
|
-
# way it is the SAME person in
|
|
35
|
+
# way it is the SAME person in OpenLoam::Current re-confirming.
|
|
36
36
|
def reauthenticated?
|
|
37
|
-
credential =
|
|
37
|
+
credential = OpenLoam::MfaCredential.active_for(current_actor)
|
|
38
38
|
if credential
|
|
39
39
|
credential.verify_totp(params[:code])
|
|
40
40
|
else
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p>This action is sensitive, so please re-authenticate.</p>
|
|
8
8
|
|
|
9
9
|
<%%= form_with url: admin_sudo_path, method: :post do |f| %>
|
|
10
|
-
<%% if
|
|
10
|
+
<%% if OpenLoam::MfaCredential.active_for(current_actor) %>
|
|
11
11
|
<p>
|
|
12
12
|
<%%= label_tag :code, "Authenticator code" %><br>
|
|
13
13
|
<%%= text_field_tag :code, nil, autofocus: true, autocomplete: "one-time-code", inputmode: "numeric" %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Per-record content translations (
|
|
2
|
+
# Per-record content translations (OpenLoam::Translatable) — manager-only, since
|
|
3
3
|
# translating content is a privileged edit. Scoped by translatable_type +
|
|
4
|
-
# translatable_id; edits each translatable field across
|
|
4
|
+
# translatable_id; edits each translatable field across OpenLoam.locales. The base
|
|
5
5
|
# (default-locale) value stays on the record itself and is shown read-only for
|
|
6
6
|
# reference — translations are additive.
|
|
7
7
|
class TranslationsController < BaseController
|
|
@@ -12,9 +12,9 @@ module Admin
|
|
|
12
12
|
|
|
13
13
|
def update
|
|
14
14
|
params.require(:translations).to_unsafe_h.each do |field, by_locale|
|
|
15
|
-
next unless @record.class.
|
|
15
|
+
next unless @record.class.open_loam_translatable?(field)
|
|
16
16
|
|
|
17
|
-
by_locale.each { |locale, value| @record.set_translation(field, locale, value) if
|
|
17
|
+
by_locale.each { |locale, value| @record.set_translation(field, locale, value) if OpenLoam.locales.include?(locale) }
|
|
18
18
|
end
|
|
19
19
|
redirect_to translate_path, notice: "Translations saved."
|
|
20
20
|
end
|
|
@@ -22,11 +22,11 @@ module Admin
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
24
|
def set_record
|
|
25
|
-
model =
|
|
26
|
-
raise ActiveRecord::RecordNotFound unless model.include?(
|
|
25
|
+
model = OpenLoam::Import.allowed_model(params[:translatable_type]) # whitelist: a OpenLoam entity only
|
|
26
|
+
raise ActiveRecord::RecordNotFound unless model.include?(OpenLoam::Translatable)
|
|
27
27
|
|
|
28
28
|
@record = model.find(params[:translatable_id])
|
|
29
|
-
@fields = model.
|
|
29
|
+
@fields = model.open_loam_translatable_attributes
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def translate_path
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<h1>Translations — <%%= @record.class.model_name.human %> #<%%= @record.id %></h1>
|
|
2
2
|
|
|
3
|
-
<p>Translate content per locale. The base (<%%=
|
|
3
|
+
<p>Translate content per locale. The base (<%%= OpenLoam.default_locale.upcase %>) value
|
|
4
4
|
stays on the record; other locales overlay onto it, falling back to the base when
|
|
5
5
|
empty.</p>
|
|
6
6
|
|
|
7
7
|
<%%= form_with url: translate_path, method: :patch do %>
|
|
8
8
|
<%% @fields.each do |field| %>
|
|
9
9
|
<h3><%%= field.humanize %></h3>
|
|
10
|
-
<p><em>Base (<%%=
|
|
11
|
-
<%% (
|
|
10
|
+
<p><em>Base (<%%= OpenLoam.default_locale.upcase %>):</em> <%%= @record.read_attribute(field) %></p>
|
|
11
|
+
<%% (OpenLoam.locales - [OpenLoam.default_locale]).each do |locale| %>
|
|
12
12
|
<p>
|
|
13
13
|
<%%= label_tag "translations[#{field}][#{locale}]", locale.upcase %><br>
|
|
14
14
|
<%%= text_field_tag "translations[#{field}][#{locale}]", @record.translations_for(field)[locale] %>
|
data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb
RENAMED
|
@@ -6,15 +6,15 @@ module Admin
|
|
|
6
6
|
before_action { require_role!(:manager) }
|
|
7
7
|
|
|
8
8
|
def index
|
|
9
|
-
@records =
|
|
9
|
+
@records = OpenLoam::WebhookEndpoint.order(:event_pattern)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def new
|
|
13
|
-
@record =
|
|
13
|
+
@record = OpenLoam::WebhookEndpoint.new(active: true)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def create
|
|
17
|
-
@record =
|
|
17
|
+
@record = OpenLoam::WebhookEndpoint.new(permitted_params)
|
|
18
18
|
|
|
19
19
|
if @record.save
|
|
20
20
|
redirect_to admin_webhook_endpoints_path
|
|
@@ -24,7 +24,7 @@ module Admin
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def destroy
|
|
27
|
-
|
|
27
|
+
OpenLoam::WebhookEndpoint.find(params[:id]).destroy!
|
|
28
28
|
redirect_to admin_webhook_endpoints_path
|
|
29
29
|
end
|
|
30
30
|
|
data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb
RENAMED
|
@@ -28,5 +28,5 @@ domain ("rental."); anything else is one exact event name.</p>
|
|
|
28
28
|
</tbody>
|
|
29
29
|
</table>
|
|
30
30
|
|
|
31
|
-
<p>Each delivery is signed: <code>X-
|
|
31
|
+
<p>Each delivery is signed: <code>X-OpenLoam-Signature: sha256=HMAC-SHA256(secret, body)</code>.
|
|
32
32
|
Recompute it on your side and reject anything that does not match.</p>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* OpenLoam admin console — shared styles (extracted from the layout, L-402). */
|
|
2
2
|
body { font-family: system-ui, -apple-system, sans-serif; margin: 2rem; color: #1c1c1a; line-height: 1.5; }
|
|
3
3
|
h1 { font-size: 1.55rem; margin: 0 0 0.75rem; }
|
|
4
4
|
h2 { font-size: 1.2rem; margin: 1.25rem 0 0.5rem; }
|
|
@@ -3,9 +3,9 @@ module Api
|
|
|
3
3
|
#
|
|
4
4
|
# curl -H "Authorization: Bearer <token>" http://localhost:3000/api/equipment
|
|
5
5
|
#
|
|
6
|
-
# A token identifies one user in one tenant, and `
|
|
7
|
-
# establishes both in
|
|
8
|
-
# on this is ordinary tenant-scoped
|
|
6
|
+
# A token identifies one user in one tenant, and `OpenLoam::ApiToken.authenticate`
|
|
7
|
+
# establishes both in OpenLoam::Current before any action runs. From that point
|
|
8
|
+
# on this is ordinary tenant-scoped OpenLoam code: every query is filtered, every
|
|
9
9
|
# write is audited, every policy applies — the API is not a side door.
|
|
10
10
|
#
|
|
11
11
|
# Finding a token is the one lookup that cannot start from a tenant (the
|
|
@@ -14,7 +14,7 @@ module Api
|
|
|
14
14
|
class BaseController < ActionController::API
|
|
15
15
|
before_action :authenticate_api_token!
|
|
16
16
|
|
|
17
|
-
rescue_from
|
|
17
|
+
rescue_from OpenLoam::NotAuthorizedError do
|
|
18
18
|
render json: { error: "forbidden" }, status: :forbidden
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -22,14 +22,14 @@ module Api
|
|
|
22
22
|
render json: { error: "not_found" }, status: :not_found
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
rescue_from
|
|
25
|
+
rescue_from OpenLoam::UnknownCustomFieldError do |error|
|
|
26
26
|
render json: { error: error.message }, status: :unprocessable_entity
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
31
|
def authenticate_api_token!
|
|
32
|
-
@api_token =
|
|
32
|
+
@api_token = OpenLoam::ApiToken.authenticate(bearer_token)
|
|
33
33
|
|
|
34
34
|
render json: { error: "unauthorized" }, status: :unauthorized unless @api_token
|
|
35
35
|
end
|
|
@@ -38,21 +38,21 @@ module Api
|
|
|
38
38
|
request.headers["Authorization"].to_s[/\ABearer (.+)\z/, 1]
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def current_tenant =
|
|
42
|
-
def current_actor =
|
|
41
|
+
def current_tenant = OpenLoam::Current.tenant
|
|
42
|
+
def current_actor = OpenLoam::Current.actor
|
|
43
43
|
|
|
44
44
|
def policy_for(record)
|
|
45
|
-
|
|
45
|
+
OpenLoam::Policy.for(record)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def authorize!(policy, action)
|
|
49
|
-
raise
|
|
49
|
+
raise OpenLoam::NotAuthorizedError unless policy.public_send(action)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# The JSON shape of an entity: its columns, custom fields included (they
|
|
53
53
|
# live in the `custom_fields` column that every generated entity carries).
|
|
54
54
|
#
|
|
55
|
-
# Encrypted fields (
|
|
55
|
+
# Encrypted fields (OpenLoam::Encryptable) are returned DECRYPTED — the caller is
|
|
56
56
|
# authenticated, tenant-scoped and policy-gated, exactly like the admin show
|
|
57
57
|
# screen — and their blind-index `<field>_hash` column is dropped, so the
|
|
58
58
|
# equality-leaking hash never goes over the wire. `record.attributes` alone
|
|
@@ -60,17 +60,17 @@ module Api
|
|
|
60
60
|
def entity_json(record, enrichments: nil)
|
|
61
61
|
json = record.attributes
|
|
62
62
|
|
|
63
|
-
if record.class.respond_to?(:
|
|
64
|
-
record.class.
|
|
63
|
+
if record.class.respond_to?(:open_loam_encrypted_attributes)
|
|
64
|
+
record.class.open_loam_encrypted_attributes.each do |name|
|
|
65
65
|
json[name] = record.public_send(name)
|
|
66
66
|
json.delete("#{name}_hash")
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# Computed cross-module blocks (
|
|
70
|
+
# Computed cross-module blocks (OpenLoam::Enrichers), under a separate key so
|
|
71
71
|
# they're never confused with the record's own columns. An index passes the
|
|
72
72
|
# batched result (avoiding N+1); a single show computes it here.
|
|
73
|
-
enrichments =
|
|
73
|
+
enrichments = OpenLoam::Enrichers.enrich(record) if enrichments.nil?
|
|
74
74
|
json["enrichments"] = enrichments if enrichments.present?
|
|
75
75
|
json
|
|
76
76
|
end
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
require "test_helper"
|
|
2
2
|
|
|
3
|
-
# Structural guardrails. These are not feature tests — they enforce
|
|
3
|
+
# Structural guardrails. These are not feature tests — they enforce OpenLoam's
|
|
4
4
|
# invariants on the codebase itself. If one of these fails, an agent (or a
|
|
5
5
|
# human) stepped outside the conventions.
|
|
6
|
-
class
|
|
7
|
-
# Models that are legitimately not tenant-scoped.
|
|
6
|
+
class OpenLoamGuardrailsTest < ActiveSupport::TestCase
|
|
7
|
+
# Models that are legitimately not tenant-scoped. OpenLoam::Config holds both
|
|
8
8
|
# global (tenant_id NULL) and per-tenant override rows, so tenancy is a
|
|
9
|
-
# nullable column and resolution lives in vetted gem code (
|
|
10
|
-
#
|
|
9
|
+
# nullable column and resolution lives in vetted gem code (OpenLoam::Configs).
|
|
10
|
+
# OpenLoam::MfaCredential belongs to the person, who spans tenants — MFA is
|
|
11
11
|
# verified at login before any tenant is chosen.
|
|
12
|
-
TENANCY_ALLOWLIST = %w[ApplicationRecord User
|
|
12
|
+
TENANCY_ALLOWLIST = %w[ApplicationRecord User OpenLoam::Tenant OpenLoam::Config OpenLoam::MfaCredential OpenLoam::AuthAttempt].freeze
|
|
13
13
|
|
|
14
14
|
# Rails' own engine models (storage, rich text, jobs) are framework
|
|
15
15
|
# plumbing, not business data — they are out of scope for tenant linting.
|
|
16
16
|
FRAMEWORK_NAMESPACES = %w[ActiveStorage:: ActionText:: ActionMailbox:: SolidQueue:: SolidCache:: SolidCable::].freeze
|
|
17
17
|
|
|
18
|
-
test "every app model is tenant-scoped (inherits
|
|
18
|
+
test "every app model is tenant-scoped (inherits OpenLoam::TenantRecord)" do
|
|
19
19
|
Rails.application.eager_load!
|
|
20
20
|
|
|
21
21
|
offenders = ActiveRecord::Base.descendants.reject do |model|
|
|
@@ -23,20 +23,20 @@ class LoamGuardrailsTest < ActiveSupport::TestCase
|
|
|
23
23
|
model.name.nil? ||
|
|
24
24
|
TENANCY_ALLOWLIST.include?(model.name) ||
|
|
25
25
|
FRAMEWORK_NAMESPACES.any? { |ns| model.name.start_with?(ns) } ||
|
|
26
|
-
model <=
|
|
26
|
+
model <= OpenLoam::TenantRecord
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
assert_empty offenders,
|
|
30
30
|
"These models are NOT tenant-scoped: #{offenders.map(&:name).join(', ')}. " \
|
|
31
|
-
"Business models must inherit
|
|
31
|
+
"Business models must inherit OpenLoam::TenantRecord (use `rails g open_loam:entity`). " \
|
|
32
32
|
"If a model is intentionally global, add it to TENANCY_ALLOWLIST here — in review, on purpose."
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
test "touching a tenant-scoped model with no tenant context raises" do
|
|
36
|
-
|
|
36
|
+
OpenLoam::Current.reset
|
|
37
37
|
|
|
38
|
-
assert_raises(
|
|
39
|
-
assert_raises(
|
|
38
|
+
assert_raises(OpenLoam::MissingTenantError) { OpenLoam::AuditRecord.count }
|
|
39
|
+
assert_raises(OpenLoam::MissingTenantError) { OpenLoam::Membership.first }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
test "nobody uses .unscoped outside vetted framework code" do
|
|
@@ -64,16 +64,16 @@ class LoamGuardrailsTest < ActiveSupport::TestCase
|
|
|
64
64
|
"docs/ and link it, rather than raising the budget."
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
test "every
|
|
67
|
+
test "every OpenLoam::FieldDefinition entity_type resolves to a class that uses OpenLoam::CustomFields" do
|
|
68
68
|
Rails.application.eager_load!
|
|
69
69
|
|
|
70
|
-
offenders =
|
|
70
|
+
offenders = OpenLoam::FieldDefinition.unscoped.distinct.pluck(:entity_type).reject do |entity_type|
|
|
71
71
|
klass = entity_type.safe_constantize
|
|
72
|
-
klass && klass.include?(
|
|
72
|
+
klass && klass.include?(OpenLoam::CustomFields)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
assert_empty offenders,
|
|
76
|
-
"These
|
|
77
|
-
"`include
|
|
76
|
+
"These OpenLoam::FieldDefinition entity_type values don't resolve to a class that " \
|
|
77
|
+
"`include OpenLoam::CustomFields`: #{offenders.join(', ')}. Likely a typo when the field was created."
|
|
78
78
|
end
|
|
79
79
|
end
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Runs a CSV import in the background, advancing a
|
|
2
|
-
# watches it live (L-915). Carries tenant + actor explicitly, like every
|
|
1
|
+
# Runs a CSV import in the background, advancing a OpenLoam::ProgressJob so the admin
|
|
2
|
+
# watches it live (L-915). Carries tenant + actor explicitly, like every OpenLoam job.
|
|
3
3
|
# The per-row summary (created/updated/failed + error rows) is stored on the
|
|
4
4
|
# ProgressJob's `result` for the summary screen.
|
|
5
5
|
class ImportJob < ApplicationJob
|
|
6
6
|
queue_as :default
|
|
7
7
|
|
|
8
8
|
def perform(entity_type:, csv:, mapping:, match_key:, tenant_id:, actor_id:)
|
|
9
|
-
tenant =
|
|
9
|
+
tenant = OpenLoam::Tenant.find(tenant_id)
|
|
10
10
|
actor = User.find_by(id: actor_id)
|
|
11
|
-
model =
|
|
11
|
+
model = OpenLoam::Import.allowed_model(entity_type)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
OpenLoam.as_tenant(tenant, actor: actor) do
|
|
14
14
|
total = [ CSV.parse(csv).size - 1, 0 ].max
|
|
15
|
-
progress =
|
|
15
|
+
progress = OpenLoam::Progress.start(name: "Import #{entity_type}", total: total)
|
|
16
16
|
begin
|
|
17
|
-
result =
|
|
17
|
+
result = OpenLoam::Import.run(csv, model: model, mapping: mapping, actor: actor, match_key: match_key, progress: progress)
|
|
18
18
|
progress.update_columns(result: result.to_h)
|
|
19
19
|
progress.complete!
|
|
20
20
|
rescue StandardError => error
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# Public endpoint that receives webhooks FROM external systems at
|
|
2
2
|
# /webhooks/:token. No session, no bearer token: the HMAC signature is the auth
|
|
3
3
|
# (ActionController::API, so there is no CSRF to skip). All the verified,
|
|
4
|
-
# replay-resistant work lives in
|
|
4
|
+
# replay-resistant work lives in OpenLoam::InboundWebhooks.ingest; this controller
|
|
5
5
|
# only maps its Result to an HTTP status and never leaks tenant context to the
|
|
6
6
|
# next request on this thread.
|
|
7
7
|
class InboundWebhooksController < ActionController::API
|
|
8
8
|
def receive
|
|
9
|
-
result =
|
|
9
|
+
result = OpenLoam::InboundWebhooks.ingest(
|
|
10
10
|
token: params[:token], raw_body: request.raw_post, headers: request.headers
|
|
11
11
|
)
|
|
12
|
-
Rails.logger.info("[
|
|
12
|
+
Rails.logger.info("[open_loam inbound] #{result.status} #{result.reason}") if result.status >= 400
|
|
13
13
|
head result.status
|
|
14
14
|
ensure
|
|
15
|
-
|
|
15
|
+
OpenLoam::Current.reset
|
|
16
16
|
end
|
|
17
17
|
end
|