toga-ai 1.0.905 → 1.0.907
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.
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Per-minute external OneUptime "Incoming Request" heartbeat from every 1.0 worker box (Degraded 3 min / Offline 5 min); open for the heartbeat push, monitor prov |
|
|
17
17
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily 9:00 PM Prudential/Dell cron emailing an Excel of every device shipped today (tracking, serial, XID, FedEx status, vendor); open for the ASN join chain an |
|
|
18
18
|
| [Staples cXML Order Import (SFTP → NetSuite)](features/staples-cxml-order-import.md) | Hourly cron importing Staples cXML POs from SFTP into NetSuite Sales Orders (writes 855 ack back); open for the per-file flow, saved-search-8412 Location resolu |
|
|
19
|
+
| [UMA Student Roster Sync (SFTP → TOGaDeskSupport customers) + Ivanti Ticket Sync](features/uma-student-sync.md) | Daily SFTP full-roster file loads UMA students into TOGaDeskSupport.customers (clientId 10); open for the file format, insert/deactivate rules, how the Ivanti t |
|
|
19
20
|
| [Walmart Daily Sales Report (send_toga_walmart_report.php)](features/walmart-daily-sales-report.md) | The two Walmart sales workbooks (summary + details) built from `TOGA_Walmart` (in-store) and `TOGA_WalmartEComm` (.COM); open for the pseudo-store 1472 channel |
|
|
20
21
|
| [Diagnosing frozen 1.0 worker cron check-ins (Sentry "missed" flood)](workflows/diagnosing-frozen-cron-checkins.md) | Ordered path to diagnose frozen 1.0 worker cron timestamps + a Sentry `worker1` "missed" flood; open when check-ins stall and you suspect a wedged lock. |
|
|
21
22
|
| [isFulfillable Multi-Client Backfill (all togasupply clients)](workflows/isfulfillable-multi-client-backfill.md) | One-time backfill catching up `Items.isFulfillable` on existing items across all 17 togasupply clients; open when running/re-running it or deciding whether rema |
|
|
@@ -138,7 +138,7 @@ worker/
|
|
|
138
138
|
| `vendorinvoiceretrieval/` | 18 | Automated vendor-invoice retrieval. |
|
|
139
139
|
| `catalog/` | 17 | Catalog DB reconciliation (Office Depot inventory imports). |
|
|
140
140
|
| `maintenance/` | 9 | Hourly/daily/weekly maintenance + TOGaDesk ticket archiving. |
|
|
141
|
-
| `togadesk/` | 8 | TOGaDesk helpdesk: inactive-people cleanup, UMA customer/ticket sync, task reminders, repair-order status. |
|
|
141
|
+
| `togadesk/` | 8 | TOGaDesk helpdesk: inactive-people cleanup, UMA customer/ticket sync ([uma-student-sync](features/uma-student-sync.md)), task reminders, repair-order status. |
|
|
142
142
|
| `test_active_directory/` | 5 | AD integration test scripts. |
|
|
143
143
|
| `database/` | 4 | Backups, restores, restore scheduling. |
|
|
144
144
|
| `worker/` | 2 | `worker_heartbeat.php` (self-healing heartbeat) + helper. |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: UMA Student Roster Sync (SFTP → TOGaDeskSupport customers) + Ivanti Ticket Sync
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: uma
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-28
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/togadesk/sync_uma_customers.php
|
|
13
|
+
- worker/crons/togadesk/sync_uma_tickets.php
|
|
14
|
+
- worker/schedules/cron.worker.togadesk.json
|
|
15
|
+
- library/app/api/uma.php
|
|
16
|
+
related:
|
|
17
|
+
- 1.0/apps/worker/architecture.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Daily SFTP full-roster file loads UMA students into TOGaDeskSupport.customers (clientId 10); open for the file format, insert/deactivate rules, how the Ivanti ticket sync depends on it, health-check queries, and the PII/hardcoded-credential traps.
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
UMA students reach our system through a **daily SFTP file**, not an API. `togadesk/sync_uma_customers.php` runs `0 15 * * *` (America/Chicago) on the `togadesk` worker role (`schedules/cron.worker.togadesk.json`) and maintains `TOGaDeskSupport.customers` for **clientId 10**. `sync_uma_tickets.php` then turns UMA Ivanti incidents into TOGaDesk tickets — only for students already loaded.
|
|
25
|
+
|
|
26
|
+
## How it works
|
|
27
|
+
|
|
28
|
+
**Customer sync (`sync_uma_customers.php`):**
|
|
29
|
+
1. Log into SFTP `sftp.ultimatemedical.edu`, download every file in `/`.
|
|
30
|
+
2. Save each file to `Logs.FileLog` (link `db_logs`, job `UMA_CUSTOMERS`), then **delete it from the SFTP**.
|
|
31
|
+
3. Parse **only the newest** FileLog row. File `UMA_upload_MMDDYY.txt`, pipe-delimited, header row, 9 columns:
|
|
32
|
+
`studentId | firstName | lastName | phonePrimary | phoneSecondary | emailPersonal | emailWork | DOB (MMDDYYYY) | ssnLast4`
|
|
33
|
+
4. Write `TOGaDeskSupport.customers` (link `db_togadesk`), clientId 10, matched on `customers.reference = studentId`:
|
|
34
|
+
- new → insert, active;
|
|
35
|
+
- existing → **untouched** (name/email/phone are never updated);
|
|
36
|
+
- existing but absent from the file → `isActive = 0`. The file is the **full roster**.
|
|
37
|
+
|
|
38
|
+
**Ticket sync (`sync_uma_tickets.php`):** uses `App_Api_UMA` (`library/app/api/uma.php`, extends `App_Api_Ivanti`, endpoint `umaservicecenter.saasit.com`) to pull new student incidents and push TOGaDesk tickets. A ticket is created only when the incident email matches a student's `emailPersonal`/`emailWork`.
|
|
39
|
+
|
|
40
|
+
## Health check (read-only prod, env `legacy`)
|
|
41
|
+
|
|
42
|
+
- **Files received:** `Logs.FileLog WHERE job = 'UMA_CUSTOMERS'` (indexed on `job`). Select dates + `fileName` only. Never `LENGTH(fileData)` — timed out at 300 s.
|
|
43
|
+
- **Runs:** `Common.CronJobExecutions` (~120M rows, **no index** on `job`/`dtCheckIn`). Bound by id range (~750k rows/day, e.g. `id > MAX(id) - N`) and match `job = '/var/www/html/crons/togadesk/sync_uma_customers.php'`.
|
|
44
|
+
- `dtCheckOut` set = finished cleanly; a thrown error leaves it NULL.
|
|
45
|
+
- ~12-13 s = file processed; ~2 s = SFTP root was empty.
|
|
46
|
+
- **Normal pattern:** one file/day. Occasional misses or a late file arriving with the next day's — harmless, since only the newest (full roster) is parsed.
|
|
47
|
+
|
|
48
|
+
## Gotchas
|
|
49
|
+
|
|
50
|
+
- **Empty/truncated file mass-deactivates.** Rows without exactly 9 columns are skipped silently, then every missing student goes `isActive = 0`. No row-count guard, no alert.
|
|
51
|
+
- **Silent failure modes.** Throws only on SFTP login/download failure. An empty SFTP is a clean ~2 s run — nothing flags "no file today". Check FileLog dates.
|
|
52
|
+
- **Unloaded student = skipped tickets.** If a student is not in customers yet, their Ivanti incidents are skipped with no log.
|
|
53
|
+
- **Existing rows never refresh.** A student's changed email in UMA's file does not reach us, so their tickets stop matching.
|
|
54
|
+
- **PII.** Files hold DOB and SSN last 4. When investigating, pull only dates/file names from FileLog, never `fileData`.
|
|
55
|
+
- **Hardcoded credentials — treat as leaked, rotate.** SFTP login is hardcoded in `sync_uma_customers.php`; the Ivanti API key is hardcoded in `library/app/api/uma.php`. Do not copy them anywhere.
|
|
56
|
+
- Does not call `App_Database::closeConnections()`.
|
|
57
|
+
|
|
58
|
+
## Change history
|
|
59
|
+
- 2026-09-28 — Documented (TRUE-82352 investigation). Open incident: no UMA file since 2026-09-20; runs 9-21..9-27 clean with empty SFTP; togadesk worker instance changed that day but SFTP login still works from the new box — likely UMA stopped uploading, awaiting UMA confirmation. No wrong deactivations; new students and their tickets missing since 9-20. (mhammontree)
|
|
@@ -6,7 +6,7 @@ project: SAML SSO Gateway
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: ["rgirish", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- saml/index.php
|
|
@@ -18,6 +18,8 @@ files:
|
|
|
18
18
|
- _underscore/Cloud.php
|
|
19
19
|
related:
|
|
20
20
|
- 2.0/apps/_underscore/architecture.md
|
|
21
|
+
- features/gateway-environments-and-redirect-safety.md
|
|
22
|
+
- features/acs-assertion-trust-gaps.md
|
|
21
23
|
---
|
|
22
24
|
|
|
23
25
|
Gateway internals of the `saml` SAML 2.0 SP; open when working on `/acs`, `/meta`, cert/decryption, or deploy of the SSO gateway.
|
|
@@ -69,7 +71,7 @@ Identity mapping lives in `_underscore/Model/<ClientIdentifier>/ClientAuthentica
|
|
|
69
71
|
|
|
70
72
|
- **PHP 8.1+ in production**, on Amazon Linux with **Apache/httpd fronting PHP-FPM**. `composer.json` pins `platform.php` to **`7.2.33` for dependency resolution only** — that is not the runtime version; do not treat it as the target.
|
|
71
73
|
- **Config files are `Config/<environment>.ini`, selected by `_Environment::$name`.** Production sets `time_limit = 300`, `debug_mode = 0`. Many per-developer/per-stage configs exist (`alpha`, `beta`, `gamma`, `dev-*`, `rohan-mac`).
|
|
72
|
-
- **SP host per environment is a framework constant**, not local config: `_Model_Core_ClientAuthentication::SAML_ASSERTION_CONSUMER_SERVICE_HOST_URL_BY_ENVIRONMENT` — `dev → http://saml`, `
|
|
74
|
+
- **SP host per environment is a framework constant**, not local config: `_Model_Core_ClientAuthentication::SAML_ASSERTION_CONSUMER_SERVICE_HOST_URL_BY_ENVIRONMENT` — `dev → http://saml`, `sandbox-client → https://saml.client.sandbox.togahub.com`, `production → https://saml.togahub.com`. This map drives **both** the outbound ACS URL **and** what `/meta` publishes, so a wrong entry cross-wires two environments. `beta` currently points at production, so `saml.beta.togahub.com/meta` advertises production's entityID and ACS; the fix (`beta → https://saml.beta.togahub.com`) is on `TRUE-81993` / `_sandbox-client` and **not yet merged to `_production`**. Read the map through `samlHostUrl($slug)`, which throws — the bare array index broke 23 of the 27 `Core.Environments` rows. Three gateway hosts exist, one per AWS account — see [gateway environments & redirect safety](features/gateway-environments-and-redirect-safety.md).
|
|
73
75
|
- **Apache `.platform` configs (keep these):** `enforce_https.conf` redirects any request with `X-Forwarded-Proto != https` to HTTPS — **SAML must run over TLS only; do not weaken it**. `long_gateway_timeout.conf` sets `ProxyTimeout 1800` / `Timeout 1800` (same override as `apiproxy`). `php_include_underscore.config` adds `/var/www/html/_underscore` to PHP `include_path`.
|
|
74
76
|
|
|
75
77
|
## Deployment
|
|
@@ -93,7 +95,8 @@ Identity mapping lives in `_underscore/Model/<ClientIdentifier>/ClientAuthentica
|
|
|
93
95
|
- **SAML signature verification is commented out — repo's top security priority.** The IdP X509 cert verification block in `/acs` is disabled, so the gateway accepts any well-formed status-success `SAMLResponse` without proving it was signed by the expected IdP. The only remaining gate is the encrypted RelayState, which authenticates the *originating TOGa request* — not the *asserting IdP*. Threat: an attacker who can craft/replay a `SAMLResponse` (or a malicious/compromised IdP) can forge an identity assertion for any user. Re-enabling per-IdP X509 signature verification is the single highest-impact security fix. The per-client IdP verification certs already exist in source as base64 blobs (Compass, Office Depot, Prudential, RUMCSI, Agilant) — they are what a re-enabled verifier would load; verification is per-client and each IdP's signing cert must match.
|
|
94
96
|
- **Plaintext secrets in source — move to AWS SSM Parameter Store.** Checked-in credentials, all live and to be rotated: `saml/Config/production.ini` holds a **DB password, an AWS access key id + secret, and NetSuite credentials** in cleartext; `saml/Controller/Index.php` hardcodes the **Sentry DSN**; `.ebextensions/git.json` carries a **GitHub PAT** (same leak class as `apiproxy`). Migrate to AWS SSM Parameter Store (SecureString), load at runtime; source tree should reference parameter names only, never literal credential values.
|
|
95
97
|
- **Stale test scaffolding in `/acs` — remove it.** A large commented-out block in `saml/Controller/Index.php` hardcodes captured real `SAMLResponse` payloads and a synthetic `RelayState` for Office Depot / Compass testing. It is inert (commented out) but captured assertions living in source are a confidentiality/hygiene risk — delete it.
|
|
96
|
-
- **RelayState
|
|
98
|
+
- **RelayState is still not replay-proof, even after the TTL fix.** The check was written backwards (`$relayState->time <= time() + MAX_TIME_TO_LIVE`, true for every past timestamp) and is now `abs(time() - $relayState->time) <= MAX_TIME_TO_LIVE` on `TRUE-81993` / `_sandbox-client`. **That is hygiene, not a control:** `_String::encryptWithKey()` is AES-256-CBC with **no MAC and an attacker-supplied IV**, so the `time` field can be rewritten without the key, and there is no nonce or single-use marking. Fix direction is AES-256-GCM or an HMAC checked with `hash_equals()` before decrypt. See [/acs assertion trust gaps](features/acs-assertion-trust-gaps.md).
|
|
99
|
+
- **`/acs` must redirect only to a URL rebuilt by `safeRedirectUrl()`.** `_Page::headerLocationRedirect()` is a bare `header('Location: ')`, and the `?saml=` payload exchanges for real tokens at `POST /auth/encrypted-user-uuid` — so the destination is a credential delivery target. Redirect to the **return value**, never to the stored `Core.Domains.domain`: appending `'?saml='` to the stored string lets a stored path or query steal the handoff. A `Core.Domains` row (owned by the right client + environment) **is** the authorization — there is deliberately no host allowlist; do not add one. On `TRUE-81993` / `_sandbox-client`, not yet on `_production`.
|
|
97
100
|
- **`/meta` cert lines must be `trim()`-ed (CRLF).** The `.crt` files in `_underscore/Assets/ssl` are CRLF-encoded; cert-format code does `explode("\n", ...)` and indents each line, so without a per-line `trim()` a stray `\r` ends up on every base64 line inside `<ds:X509Certificate>`. Trim each line in both signing and encryption blocks.
|
|
98
101
|
- **SP encryption certs load from Secrets Manager at request time.** `/acs` holds no encryption key material on disk (`_underscore/Assets/ssl/` no longer carries the SP encryption key). Rotation = edit the `ENCRYPTION_SECRET_NAMES` list + the secrets themselves, not a redeploy of on-disk `.crt`/`.key` files.
|
|
99
102
|
- **Non-prod SAML depends on prod KMS/Secrets Manager; one SP key serves every env** — a non-prod leak is a prod key leak. `ENCRYPTION_SECRET_NAMES` must hold full ARNs (short names resolve only in the caller's account).
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 28 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 40 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 13 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
package/package.json
CHANGED