toga-ai 1.0.211 → 1.0.213
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.
- package/knowledge/1.0/apps/library/INDEX.md +1 -0
- package/knowledge/1.0/apps/library/features/app-sso-initiation.md +106 -0
- package/knowledge/1.0/apps/tools/INDEX.md +3 -3
- package/knowledge/1.0/apps/tools/architecture.md +18 -3
- package/knowledge/1.0/apps/tools/features/persona-gated-navigation.md +22 -1
- package/knowledge/1.0/apps/tools/features/saml-sso-auth.md +41 -8
- package/knowledge/1.0/apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md +11 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/true/profile.md +6 -3
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. | library/_.php, library/app/, library/browser/ |
|
|
6
|
+
| [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability | library/app/sso.php, library/sso/togahub_private_key.key |
|
|
6
7
|
| [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | `App_Model_Toga_Diagnostic::initializeDiagnosticDialog()` renders the device modal used across all TOGa service request views. | library/app/model/toga/diagnostic.php |
|
|
7
8
|
| [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. | library/app/api/toga2.php |
|
|
8
9
|
| [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. | library/app/email/template.php, library/app/email/agilant.php |
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: library
|
|
5
|
+
project: Library
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/sso.php
|
|
13
|
+
- library/sso/togahub_private_key.key
|
|
14
|
+
related:
|
|
15
|
+
- ../architecture.md
|
|
16
|
+
- ./mvc-page-pattern-and-app-skeleton.md
|
|
17
|
+
- ../../../2.0/apps/saml/features/downstream-integration-contract.md
|
|
18
|
+
- ../../apps/tools/features/saml-sso-auth.md
|
|
19
|
+
- ../../apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
`App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO
|
|
25
|
+
initiation**, packaged as a reusable, framework-level capability so **any 1.0 `App_` app** can
|
|
26
|
+
adopt SSO through `saml.togahub.com` without touching 2.0. It is the 1.0 counterpart to the 2.0
|
|
27
|
+
`_Model_Core_ClientAuthentication::singleSignOnServiceUrl()` builder described in the
|
|
28
|
+
[SAML downstream integration contract](../../../2.0/apps/saml/features/downstream-integration-contract.md).
|
|
29
|
+
|
|
30
|
+
Design intent (read before adopting): the class is **config-driven and has NO Core DB
|
|
31
|
+
dependency** — a consuming 1.0 app never queries Core. The team's standing preference is **not to
|
|
32
|
+
modify 2.0**; 1.0 SSO initiation logic lives here in `App_Sso`. This is the canonical doc to
|
|
33
|
+
follow for any future "add SSO to `<1.0 app>`" request.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
`App_Sso::initiate(array $params): string` builds a signed SAML 2.0 `AuthnRequest` and returns the
|
|
38
|
+
full IdP redirect URL (`idpSsoUrl?QUERY&Signature=...`). It uses the **HTTP-Redirect binding**:
|
|
39
|
+
|
|
40
|
+
1. Build the `AuthnRequest` XML, then encode it for the redirect binding:
|
|
41
|
+
`XML → gzdeflate → base64 → urlencode`.
|
|
42
|
+
2. Build the **RelayState** payload `{v:1, time, domain, urlParameters}` and encrypt it with
|
|
43
|
+
`App_String::encryptWithKey` (AES-256-CBC, output `base64(iv . ciphertext)`). This is
|
|
44
|
+
**byte-for-byte compatible** with the 2.0 `_String::encryptWithKey`, so the gateway `/acs` can
|
|
45
|
+
decrypt the RelayState the 1.0 app produced.
|
|
46
|
+
3. Sign the assembled query string with `openssl_sign` + `OPENSSL_ALGO_SHA256` using the bundled
|
|
47
|
+
private key, and append `&Signature=<base64>`.
|
|
48
|
+
- `SIGNATURE_ALGORITHM = http://www.w3.org/2001/04/xmldsig-more#rsa-sha256`
|
|
49
|
+
- `PRIVATE_KEY_PATH = __DIR__/../sso/togahub_private_key.key`
|
|
50
|
+
(`library/sso/togahub_private_key.key`, the togahub signing key, copied from the 2.0
|
|
51
|
+
`_underscore` `Assets/ssl`).
|
|
52
|
+
|
|
53
|
+
### Parameters
|
|
54
|
+
**Required:** `idpSingleSignOnServiceUrl`, `idpEntityIdentifier`, `assertionConsumerServiceUrl`,
|
|
55
|
+
`domainUuid`, `apiSecretAccessToken`. **Optional:** `urlParameters`.
|
|
56
|
+
Throws `InvalidArgumentException` on a missing required param; `RuntimeException` on signing failure.
|
|
57
|
+
|
|
58
|
+
### Deliberate deviation from 2.0
|
|
59
|
+
`App_Sso` emits a **valid** closing tag `</samlp:AuthnRequest>`, whereas the 2.0 builder emits a
|
|
60
|
+
malformed `<\samlp:AuthnRequest>`. `App_Sso` is the corrected version — this is intentional, not a
|
|
61
|
+
bug to "fix back."
|
|
62
|
+
|
|
63
|
+
## Adopting SSO in another 1.0 app (step-by-step)
|
|
64
|
+
|
|
65
|
+
(a) **Config — add a `[saml]` section** with:
|
|
66
|
+
`idp_single_sign_on_service_url`, `idp_entity_identifier` (= `https://saml.togahub.com/meta`),
|
|
67
|
+
`assertion_consumer_service_url` (= `https://saml.togahub.com/acs`), `domain_uuid`,
|
|
68
|
+
`api_secret_access_token` (+ `..._previous` for rotation), and the client uuid(s) the consumer
|
|
69
|
+
validates against. Document **where** these secrets live; never paste the values.
|
|
70
|
+
|
|
71
|
+
(b) **Initiation route** (e.g. `mvc/sso/initiate/get.php`): guard `isLoggedIn`, read `config[saml]`,
|
|
72
|
+
call `App_Sso::initiate([...])`, then `App_MVC::routeTo($url); exit;`.
|
|
73
|
+
|
|
74
|
+
(c) **Handoff consumer route** `mvc/sso/get.php`: base64-decode the `?saml=` payload, JSON-parse,
|
|
75
|
+
decrypt the client/user values with the shared API secret (**current then previous** key, for a
|
|
76
|
+
rotation grace window), validate the UUIDs, look up the user, and establish the session. See the
|
|
77
|
+
Tools implementation in [Tools SAML SSO Consumer & Persona-Gated Auth](../../apps/tools/features/saml-sso-auth.md).
|
|
78
|
+
|
|
79
|
+
(d) **Register a `Core.Domains` row** (`uuid, clientId, appId, environmentId, domain =
|
|
80
|
+
https://<app-host>/sso`). The `uuid` **MUST equal** the config `domain_uuid` (use a fixed uuid, not
|
|
81
|
+
`UUID()`). The gateway resolves client + environment + return URL from this row.
|
|
82
|
+
|
|
83
|
+
(e) **AWS — the app host MUST have a working HTTPS (443) listener + an ACM cert covering that exact
|
|
84
|
+
hostname**, or the gateway's return redirect to `https://<host>/sso` fails with a connection
|
|
85
|
+
timeout. See the gotcha below.
|
|
86
|
+
|
|
87
|
+
## Gotchas / known issues
|
|
88
|
+
|
|
89
|
+
- **Return-leg "connection timeout" is usually a missing HTTPS listener, not code.** When a SAML
|
|
90
|
+
SSO return leg times out in the browser, **verify the EB/ALB HTTPS (443) listener + ACM cert
|
|
91
|
+
cover the EXACT return hostname before debugging PHP/SAML.** Diagnosis signature: `curl` to the
|
|
92
|
+
public `https://` URL **from the instance itself** returns `http=000` after the full timeout,
|
|
93
|
+
while `curl http://127.0.0.1` with a `Host:` header returns `200` fast. Reusable for any TOGA app
|
|
94
|
+
behind Elastic Beanstalk; see the
|
|
95
|
+
[Tools EB deploy workflow](../../apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md).
|
|
96
|
+
- **Consumer route runs mid-render in 1.0.** The 1.0 framework runs `mvc/sso/get.php` **inside** the
|
|
97
|
+
page render (`frameworkindex → body → loadFile`) after `common/header.php` has already emitted
|
|
98
|
+
output, so headers are sent. `session_regenerate_id(true)` (and any `http_response_code` on the
|
|
99
|
+
failure path) fatals with "headers already sent." Guard `session_regenerate_id` with
|
|
100
|
+
`if (!headers_sent())`. The proper long-term fix is to route `/sso` **before any output** and
|
|
101
|
+
`exit`; until then this is a known architectural limitation any 1.0 consumer inherits.
|
|
102
|
+
|
|
103
|
+
## Change history
|
|
104
|
+
- 2026-06-26 — Built `App_Sso::initiate()`: 1.0 port of the 2.0 SP-initiated SSO builder, config-driven with no Core DB dependency, RelayState encrypted via `App_String::encryptWithKey` (2.0-compatible), rsa-sha256 query signing with the bundled togahub key, and a corrected `</samlp:AuthnRequest>` closing tag. Captured the 5-step adoption guide and the HTTPS-listener / mid-render headers-sent gotchas for any 1.0 app adopting SSO (jcardinal)
|
|
105
|
+
</content>
|
|
106
|
+
</invoke>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
-
| [Tools (1.0 Internal-Tools App) Architecture](architecture.md) | **Tools** is a standalone 1.0 (`App_`) application that houses many small internal tools behind simple interfaces, gated by Client_True staff persona. | tools/index.php, tools/_/app/framework.php, tools/_/app/frameworkindex.php, tools/_/app/auth.php, tools/_/app/nav.php, tools/common/header.php, tools/common/footer.php, tools/mvc/get.php, tools/mvc/_TEMPLATE/get.php, tools/docs/ADDING_A_TOOL.md |
|
|
5
|
+
| [Tools (1.0 Internal-Tools App) Architecture](architecture.md) | **Tools** is a standalone 1.0 (`App_`) application that houses many small internal tools behind simple interfaces, gated by Client_True staff persona. | tools/index.php, tools/_/app/framework.php, tools/_/app/frameworkindex.php, tools/assets/img/favicon/favicon.ico, tools/assets/img/favicon/favicon-32x32.png, tools/assets/img/favicon/favicon-16x16.png, tools/assets/img/favicon/apple-touch-icon.png, tools/_/app/auth.php, tools/_/app/nav.php, tools/common/header.php, tools/common/footer.php, tools/mvc/get.php, tools/mvc/_TEMPLATE/get.php, tools/docs/ADDING_A_TOOL.md |
|
|
6
6
|
| [Tools — Developers Folder (UUID & Password Generators)](features/developer-tools.md) | The first two tools shipped in the Tools app, both under the **Developers** folder and gated to personas **Development Team** / **TOGa Technology**. | tools/mvc/developers/uuid/get.php, tools/mvc/developers/password/get.php |
|
|
7
|
-
| [Tools Persona-Gated Navigation (App_Nav)](features/persona-gated-navigation.md) | `App_Nav` is the Tools app's two-level, **persona-gated** navigation. | tools/_/app/nav.php |
|
|
8
|
-
| [Tools SAML SSO Consumer & Persona-Gated Auth (App_Auth)](features/saml-sso-auth.md) | `App_Auth` is the Tools app's authentication layer: it consumes the SAML gateway `?saml=` handoff (see the 2.0 SAML downstream integration contract), establishe | tools/_/app/auth.php, tools/mvc/sso/get.php, tools/mvc/login/get.php, tools/mvc/login/post.php, tools/mvc/logout/get.php |
|
|
7
|
+
| [Tools Persona-Gated Navigation (App_Nav)](features/persona-gated-navigation.md) | `App_Nav` is the Tools app's two-level, **persona-gated** navigation. | tools/_/app/nav.php, tools/mvc/get.php |
|
|
8
|
+
| [Tools SAML SSO Consumer & Persona-Gated Auth (App_Auth)](features/saml-sso-auth.md) | `App_Auth` is the Tools app's authentication layer: it consumes the SAML gateway `?saml=` handoff (see the 2.0 SAML downstream integration contract), establishe | tools/_/app/auth.php, tools/mvc/sso/initiate/get.php, tools/mvc/sso/get.php, tools/mvc/login/get.php, tools/mvc/login/post.php, tools/mvc/logout/get.php, tools/mvc/get.php, tools/config.production.ini |
|
|
9
9
|
| [Deploying Tools to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)](workflows/deploy-to-elastic-beanstalk-al2023.md) | How the **Tools** 1.0 app boots on Elastic Beanstalk running `PHP 8.5 on 64bit Amazon Linux 2023/4.13.1 (aarch64)`. | tools/.ebextensions/004_http_to_https.config, tools/.ebextensions/006_mount-s3fs.config, tools/.ebextensions/007_setup_export_cache_folders.config, tools/.ebextensions/008_setup_ldap.config, tools/.ebextensions/009_setup_phpini.config, tools/.ebextensions/020_setup_git_libraries.config, tools/.ebextensions/050_register_instance_to_shared_application_load_balancer.config, tools/ebs/git.json |
|
|
@@ -6,12 +6,16 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-26
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- tools/index.php
|
|
13
13
|
- tools/_/app/framework.php
|
|
14
14
|
- tools/_/app/frameworkindex.php
|
|
15
|
+
- tools/assets/img/favicon/favicon.ico
|
|
16
|
+
- tools/assets/img/favicon/favicon-32x32.png
|
|
17
|
+
- tools/assets/img/favicon/favicon-16x16.png
|
|
18
|
+
- tools/assets/img/favicon/apple-touch-icon.png
|
|
15
19
|
- tools/_/app/auth.php
|
|
16
20
|
- tools/_/app/nav.php
|
|
17
21
|
- tools/common/header.php
|
|
@@ -57,14 +61,22 @@ calling the parent initialize.
|
|
|
57
61
|
|
|
58
62
|
No per-host `stylePath` subfolder — one `assets/css/style.css`. Uses TOGA Technology logos in
|
|
59
63
|
`assets/img` (`toga-brandmark-white.png` in the dark sidebar; `toga-horizontal-colored.png` on
|
|
60
|
-
login
|
|
64
|
+
login). **Favicons are app-local** under `assets/img/favicon/` (`apple-touch-icon.png` 180x180,
|
|
65
|
+
`favicon-32x32.png`, `favicon-16x16.png`, `favicon.ico`, sourced from togatech.com) and registered
|
|
66
|
+
in `_/app/frameworkindex.php` — **not** the shared `resources/images/favicon/` (Agilant) set
|
|
67
|
+
referenced via `__ASSETS__`. **Convention:** a per-app favicon/brand asset belongs under that app's
|
|
68
|
+
own `/assets`, never the shared `resources` repo, so changing one 1.0 app's favicon never affects
|
|
69
|
+
sibling apps. A fixed left two-column shell: dark left sidebar (`.app-nav`) with brand at top,
|
|
61
70
|
persona-filtered nav in the middle, pinned footer with the user's name + a ghost "Log out"
|
|
62
71
|
button; content area on the right. **No top header bar** (see Critical rules).
|
|
63
72
|
|
|
64
73
|
## Home route
|
|
65
74
|
|
|
66
75
|
`/` (`mvc/get.php`) renders a persona-filtered dashboard of available tools (tiles grouped by
|
|
67
|
-
folder) when logged in, via `App_Nav::visibleGroups()
|
|
76
|
+
folder) when logged in, via `App_Nav::visibleGroups()`. When **not** logged in, `/` immediately
|
|
77
|
+
routes to `/login` (one-step SSO sign-in — no intermediate welcome card). A persona-less but
|
|
78
|
+
authenticated user gets the dashboard's explicit empty-state (see
|
|
79
|
+
`features/persona-gated-navigation.md`).
|
|
68
80
|
|
|
69
81
|
## Adding a tool
|
|
70
82
|
|
|
@@ -79,3 +91,6 @@ Create `mvc/<folder>/<tool>/get.php` (copy `mvc/_TEMPLATE/get.php`), add one ent
|
|
|
79
91
|
**accepted this for now** (developer decision, 2026-06-25). Future remediation: git-ignore the
|
|
80
92
|
config files and rotate all exposed credentials. (Location only — no values recorded here.)
|
|
81
93
|
- **SSO initiation + replay defense are open items** — see `features/saml-sso-auth.md`.
|
|
94
|
+
|
|
95
|
+
## Change history
|
|
96
|
+
- 2026-06-26 — Documented app-local favicons under `assets/img/favicon/` (registered in `_/app/frameworkindex.php`, not the shared `resources/` set) + the per-app-asset convention; refreshed the home-route description for one-step SSO sign-in and the persona-less empty-state (jcardinal)
|
|
@@ -6,10 +6,11 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-26
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- tools/_/app/nav.php
|
|
13
|
+
- tools/mvc/get.php
|
|
13
14
|
related:
|
|
14
15
|
- ../architecture.md
|
|
15
16
|
- ./saml-sso-auth.md
|
|
@@ -40,5 +41,25 @@ An action's `route` (e.g. `/developers/uuid`) maps **1:1** to `mvc/<route>/get.p
|
|
|
40
41
|
tool means creating that file and adding one entry to `definition()` — see
|
|
41
42
|
`docs/ADDING_A_TOOL.md` and the architecture doc.
|
|
42
43
|
|
|
44
|
+
## Authorization model — authentication and authorization are separate
|
|
45
|
+
|
|
46
|
+
**Authentication (SSO) and authorization (persona-gated nav) are independent.** Any valid,
|
|
47
|
+
active Client_True user authenticates and gets a session (see
|
|
48
|
+
[saml-sso-auth](./saml-sso-auth.md)) **regardless of personas** — having zero matching personas
|
|
49
|
+
does **not** block login. Authorization is then applied entirely by `App_Nav::definition()`
|
|
50
|
+
folder/action `personas` gates.
|
|
51
|
+
|
|
52
|
+
**Persona-less user (authenticated, zero tools):** `App_Nav::render()` skips every folder/action
|
|
53
|
+
the user lacks personas for, and skips now-empty groups, so the left rail renders **no links**.
|
|
54
|
+
The home dashboard (`mvc/get.php`) detects the empty result and shows an explicit empty-state
|
|
55
|
+
rather than a blank page:
|
|
56
|
+
- subtitle: *"There are no tools available for you to see."*
|
|
57
|
+
- a card: *"Your account doesn't have access to any tools. Tool access is granted by persona —
|
|
58
|
+
if you believe you should have access, contact the development team."* + a Log out action.
|
|
59
|
+
|
|
60
|
+
This is intentional: a valid user is never shown a broken/blank dashboard, and access is granted
|
|
61
|
+
purely by assigning personas — no code change is needed to grant a user access to an existing tool.
|
|
62
|
+
|
|
43
63
|
## Change history
|
|
64
|
+
- 2026-06-26 — Documented the authorization model: authentication (SSO) and authorization (persona-gated nav) are separate — any valid user logs in regardless of personas; a persona-less user gets an empty left rail and an explicit dashboard empty-state ("no tools available", contact-dev card + Log out) instead of a blank page (jcardinal)
|
|
44
65
|
- 2026-06-25 — Built App_Nav: hard-coded two-level persona-gated navigation; render() for the left rail, visibleGroups() for the dashboard, personasForRoute() for page self-guarding; route maps 1:1 to mvc/<route>/get.php (jcardinal)
|
|
@@ -6,16 +6,20 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-26
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- tools/_/app/auth.php
|
|
13
|
+
- tools/mvc/sso/initiate/get.php
|
|
13
14
|
- tools/mvc/sso/get.php
|
|
14
15
|
- tools/mvc/login/get.php
|
|
15
16
|
- tools/mvc/login/post.php
|
|
16
17
|
- tools/mvc/logout/get.php
|
|
18
|
+
- tools/mvc/get.php
|
|
19
|
+
- tools/config.production.ini
|
|
17
20
|
related:
|
|
18
21
|
- ../architecture.md
|
|
22
|
+
- ../../library/features/app-sso-initiation.md
|
|
19
23
|
- ../../../2.0/apps/saml/features/downstream-integration-contract.md
|
|
20
24
|
- ../../../clients/true/features/users-personas-data-model.md
|
|
21
25
|
---
|
|
@@ -30,6 +34,18 @@ auto-created.
|
|
|
30
34
|
|
|
31
35
|
## How it works
|
|
32
36
|
|
|
37
|
+
### Wiring overview
|
|
38
|
+
- **`/sso/initiate`** (`mvc/sso/initiate/get.php`) — guards `isLoggedIn`, reads `config[saml]`,
|
|
39
|
+
calls `App_Sso::initiate([...])` and `App_MVC::routeTo($url); exit;`. Initiation now uses the
|
|
40
|
+
reusable 1.0 [`App_Sso`](../../library/features/app-sso-initiation.md) library class (no longer a
|
|
41
|
+
placeholder).
|
|
42
|
+
- **`/sso`** (`mvc/sso/get.php`) — the handoff consumer (below).
|
|
43
|
+
- **Core.Domains** — a fixed-uuid row registers this app's return domain so the gateway resolves
|
|
44
|
+
client/environment/return URL: `uuid 2927bc15-e347-4358-a430-fb28f9446d27`, `clientId 1` (True),
|
|
45
|
+
`appId 30`, `environmentId 1` (production), `domain https://tools.togatech.com/sso`. The uuid
|
|
46
|
+
**equals** config `domain_uuid` (created in `dbchanges2/Core/2026-06-26a - ToolsSsoDomain.sql`,
|
|
47
|
+
applied to live Core).
|
|
48
|
+
|
|
33
49
|
### Consuming the `?saml=` handoff (`mvc/sso/get.php` → `App_Auth`)
|
|
34
50
|
1. Length-guard `$_GET['saml']`, then base64-decode → `json_decode` → read `payload.client`
|
|
35
51
|
and `payload.user`.
|
|
@@ -42,7 +58,8 @@ auto-created.
|
|
|
42
58
|
4. Load the active Client_True user: `WHERE uuid = ? AND isActive = 1`. No match → fail closed.
|
|
43
59
|
|
|
44
60
|
### Session establishment (`establishSession()`)
|
|
45
|
-
- Calls `session_regenerate_id(true)
|
|
61
|
+
- Calls `session_regenerate_id(true)` **guarded by `if (!headers_sent())`** (see gotcha), then
|
|
62
|
+
caches the user and **persona names** in `$_SESSION`.
|
|
46
63
|
- Persona lookup (read-only `db_true`):
|
|
47
64
|
`SELECT p.name FROM Users u JOIN Users_Personas up ON up.userId=u.id JOIN Personas p ON p.id=up.personaId WHERE u.uuid = <escaped>`.
|
|
48
65
|
- The session cookie is set **HttpOnly + SameSite=Lax** in `App_Framework_Tools::initialize()`
|
|
@@ -51,26 +68,42 @@ auto-created.
|
|
|
51
68
|
### Page guarding
|
|
52
69
|
- `requireAuth([personas])` at the top of each page; `hasAnyPersona()` is `array_intersect`
|
|
53
70
|
against the cached session personas. App_Nav uses the same cached data to filter the nav.
|
|
71
|
+
- **Authentication ≠ authorization.** A session establishes for **any** valid/active user even
|
|
72
|
+
when they have **zero** matching personas; in that case the nav is empty and the dashboard shows
|
|
73
|
+
an explicit empty-state — see [persona-gated-navigation](./persona-gated-navigation.md).
|
|
54
74
|
|
|
55
75
|
### Dev bypass (double-gated, fails closed in prod)
|
|
56
76
|
`mvc/login/post.php` permits **email-only** login **only** when config `[internal] dev_mode`
|
|
57
77
|
is truthy **and** `App_Registry::inDevMode()` — both must hold. When used it writes a `SECURITY`
|
|
58
78
|
line to `error_log`.
|
|
59
79
|
|
|
80
|
+
### One-step sign-in
|
|
81
|
+
Home route `/` (`mvc/get.php`), when not logged in, does `App_MVC::routeTo('/login'); return;` so the
|
|
82
|
+
SSO button is the only click — the previous welcome-card "Sign in" → `/login` intermediate step was
|
|
83
|
+
removed.
|
|
84
|
+
|
|
60
85
|
## Gotchas / known issues
|
|
61
86
|
|
|
62
|
-
- **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
- **Consumer route runs mid-render → headers already sent.** The 1.0 framework runs `mvc/sso/get.php`
|
|
88
|
+
**inside** the page render (`frameworkindex → body → loadFile`) after `common/header.php` has
|
|
89
|
+
emitted output, so `session_regenerate_id(true)` fatals ("cannot be regenerated after headers
|
|
90
|
+
already sent"). Fixed by guarding with `if (!headers_sent())`. **Known architectural limitation:**
|
|
91
|
+
the failure path (`tools_ssoFail → http_response_code` at `mvc/sso/get.php:23`) *also* fatals on
|
|
92
|
+
headers-sent; the proper long-term fix is to route `/sso` **before any output** and `exit`. Any
|
|
93
|
+
1.0 app adopting this consumer pattern inherits this — see
|
|
94
|
+
[App_Sso](../../library/features/app-sso-initiation.md).
|
|
66
95
|
- **No app-side replay defense.** The handoff token carries no nonce/timestamp the app verifies.
|
|
67
96
|
Recommend the gateway embed `iat` + `jti`.
|
|
68
97
|
|
|
69
98
|
## Config keys
|
|
70
99
|
|
|
71
100
|
`[database_true]` (read-only Client_True); `[saml]` `api_secret_access_token` /
|
|
72
|
-
`api_secret_access_token_previous`, `
|
|
73
|
-
`
|
|
101
|
+
`api_secret_access_token_previous`, `client_authentication_uuid`, `domain_uuid`,
|
|
102
|
+
`true_client_uuid` (plus the IdP/ACS urls used by initiation); `[internal]` `dev_mode`. **Secret
|
|
103
|
+
location:** `config.production.ini` holds the plaintext production secrets — including the shared
|
|
104
|
+
Core `API_SECRET_ACCESS_TOKEN` — in its `[saml]` section (committed; developer explicitly accepted
|
|
105
|
+
this). Document **where** they live, never the values.
|
|
74
106
|
|
|
75
107
|
## Change history
|
|
108
|
+
- 2026-06-26 — Wired up real SSO initiation via the new 1.0 `App_Sso` library class (`/sso/initiate`), replacing the `initiation_url` placeholder; registered the fixed-uuid Core.Domains return row (dbchanges2 `2026-06-26a`); fixed the `session_regenerate_id` headers-already-sent fatal (guarded with `!headers_sent()`) and documented the mid-render failure-path limitation; collapsed home → `/login` to one-step sign-in; noted prod secrets live in `config.production.ini [saml]` (jcardinal)
|
|
76
109
|
- 2026-06-25 — Built App_Auth: SAML `?saml=` handoff consumer with dual-key decrypt, hash_equals client-uuid check, fail-closed 401, persona-cached session (HttpOnly+SameSite=Lax), and a double-gated dev bypass. Initiation + replay defense left as open items (jcardinal)
|
|
@@ -117,8 +117,19 @@ setup — the git clone (`020`) deliberately does **not** use it (see gotchas).
|
|
|
117
117
|
e.g. `GIT=$(which git || echo /usr/bin/git)`.
|
|
118
118
|
- **cfn-init `files:` runs before `container_commands`** — the correct pattern for writing a
|
|
119
119
|
script and then executing it in the same config.
|
|
120
|
+
- **A SAML SSO return-leg "connection timeout" is a missing HTTPS listener, not code. (CONFIRMED + RESOLVED.)**
|
|
121
|
+
When the gateway's return redirect to `https://<host>/sso` times out in the browser, verify the
|
|
122
|
+
**EB/ALB HTTPS (443) listener + ACM cert cover the EXACT return hostname before debugging
|
|
123
|
+
PHP/SAML.** Root cause for Tools was **confirmed**: the `tools.togatech.com` EB environment had
|
|
124
|
+
**no HTTPS (443) listener configured at all**. Adding the HTTPS:443 listener (with an ACM cert
|
|
125
|
+
covering the exact return hostname) fixed the SSO return leg completely — **no code change was
|
|
126
|
+
involved.** Diagnosis signature: `curl` to the public `https://` URL **from the instance itself**
|
|
127
|
+
returns `http=000` after the full timeout, while `curl http://127.0.0.1` with a `Host:` header
|
|
128
|
+
returns `200` fast. Reusable for any TOGA app behind Elastic Beanstalk.
|
|
120
129
|
|
|
121
130
|
## Change history
|
|
131
|
+
- 2026-06-26 — CONFIRMED + RESOLVED the SAML SSO return-leg gotcha: the `tools.togatech.com` EB environment had no HTTPS (443) listener at all; adding the HTTPS:443 listener + ACM cert covering the exact return hostname fixed the SSO return leg with no code change (jcardinal)
|
|
132
|
+
- 2026-06-26 — Added the SAML SSO return-leg gotcha: a "connection timeout" on `https://<host>/sso` was a missing EB/ALB HTTPS (443) listener for the new `togatech.com` hostname (not PHP/SAML); documented the curl `http=000` vs `127.0.0.1` 200 diagnosis (jcardinal)
|
|
122
133
|
- 2026-06-26 — Documented the AL2 → AL2023 (PHP 8.5) EB migration for Tools: package renames
|
|
123
134
|
(`libstdc++48`→`libstdc++`, `php73-ldap`→`php-ldap`, drop `libcurl`), php ini via cfn-init
|
|
124
135
|
`files:` writing `/etc/php.d/application.ini`, bash-based library clone replacing the
|
package/knowledge/INDEX.md
CHANGED
|
@@ -4,7 +4,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
4
4
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
|
-
- **library** (Library) _(framework core)_ —
|
|
7
|
+
- **library** (Library) _(framework core)_ — 10 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 10 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **togadesk** (TOGa Desk) — 8 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
10
10
|
- **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
@@ -8,7 +8,7 @@ project: _Underscore
|
|
|
8
8
|
client: true
|
|
9
9
|
type: profile
|
|
10
10
|
status: active
|
|
11
|
-
updated: 2026-06-
|
|
11
|
+
updated: 2026-06-26
|
|
12
12
|
owners: [jcardinal]
|
|
13
13
|
files: []
|
|
14
14
|
related:
|
|
@@ -26,5 +26,8 @@ by staff role (e.g. the planned Toolbox app) reads its `Users` / `Personas` mode
|
|
|
26
26
|
- **Client identifier:** `True`
|
|
27
27
|
- **SSO mapper:** `_Model_True_ClientAuthentication` (base; matches by email from NameID)
|
|
28
28
|
|
|
29
|
-
The
|
|
30
|
-
|
|
29
|
+
The **Tools** app (1.0; repo `tools`) authenticates True users via **SSO** through
|
|
30
|
+
`saml.togahub.com` and reads the `Client_True` DB **read-only** to gate its internal tooling by
|
|
31
|
+
staff persona (see `1.0/apps/tools/`). Its gateway return domain is registered in `Core.Domains`
|
|
32
|
+
(`uuid 2927bc15-e347-4358-a430-fb28f9446d27`, `clientId 1`, `appId 30`, env 1,
|
|
33
|
+
`https://tools.togatech.com/sso`).
|
package/package.json
CHANGED