# SAML Single Sign-On This application now acts as a multi-tenant SAML 2.0 Service Provider (SP) using [`python3-saml`](https://github.com/onelogin/python3-saml). Each customer/tenant can upload their Identity Provider (IdP) metadata and control how SAML attributes map to local users. SAML logins reuse the same session/JWT creation flow as password-based logins. ## Components - **Tenants (`/api/saml/tenants`)** – logical customers. Tenants can allow/disallow password auth and host one or more SAML configs. - **SAML configurations (`/api/saml/configs`)** – derived from IdP metadata. Store IdP entity ID, SSO/SLO endpoints, certificates, attribute mappings, and SP overrides (ACS URL, entity ID, etc.). - **External identities** – map SAML `NameID`/email values to local users and keep a copy of the attributes returned by the IdP. > **Migrations:** After pulling this change, run `flask db migrate && flask db upgrade` (or the equivalent for your environment) to create the new tables/columns. ## Required Environment Variables | Variable | Purpose | | --- | --- | | `PUBLIC_BASE_URL` | Public HTTPS base URL that IdPs can reach (e.g., `https://attstore.example.com`). Used to calculate ACS/SLO endpoints inside the generated metadata. | | `SAML_SP_CERTIFICATE` | Optional PEM certificate if you need to sign Authn/SLO requests. Leave unset for unsigned requests. | | `SAML_SP_PRIVATE_KEY` | Private key that matches `SAML_SP_CERTIFICATE`. | The legacy `FILE_STORAGE_BASE_URL` is still used for file downloads; it is **not** used for SAML. ## Authenticating Admin API Calls The `/api/saml/*` endpoints require a JWT (same as the rest of the admin API surface). Use an API token from **Settings → API Tokens** to mint a short-lived JWT and export both values for reuse: ```bash # Base URL (should match PUBLIC_BASE_URL) export BASE_URL="https://attstore.example.com" # 1. Paste the API token generated in the UI export TOKEN="dXNlcm5hbWU6c2VjcmV0Cg==" # 2. Exchange it for a JWT (requires jq) export JWT_TOKEN=$(curl -s -X POST "$BASE_URL/v1/login" \ -H 'Content-Type: application/json' \ -d "{\"api_token\":\"$TOKEN\"}" | jq -r '.access_token') # 3. Use the JWT for all privileged calls curl -H "Authorization: Bearer $JWT_TOKEN" "$BASE_URL/api/saml/tenants" ``` This keeps API tokens long-lived while every admin request runs with the scoped JWT. ## Provisioning a Tenant & IdP Metadata 1. **Create a tenant** ```bash curl -X POST "$BASE_URL/api/saml/tenants" \ -H "Authorization: Bearer $JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Acme Corp", "slug": "acme", "allow_password_auth": false}' ``` 2. **Create a SAML config** ```bash # Convert your IdP metadata into JSON-safe form (example path: tmp/GoogleIDPMetadata.xml) export METADATA=$(jq -Rs . tmp/GoogleIDPMetadata.xml) curl -X POST "$BASE_URL/api/saml/configs" \ -H "Authorization: Bearer $JWT_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"tenant_id\": 1, \"name\": \"Acme SSO\", \"slug\": \"acme-sso\", \"metadata_xml\": $METADATA, \"attribute_mapping\": { \"email\": \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\", \"username\": \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name\" }, \"auto_provision_users\": true }" ``` The server parses the metadata, stores the IdP certificate and endpoints, and responds with derived SP URLs: ```json { "id": 4, "slug": "acme-sso", "service_urls": { "acs": "https://attstore.example.com/saml/acme-sso/acs", "entity_id": "https://attstore.example.com/saml/acme-sso/metadata", "slo": "https://attstore.example.com/saml/acme-sso/slo" } } ``` 3. **Hand the SP metadata to the IdP**\ Each SAML config exposes SP metadata at `/saml//metadata`. Provide this URL to the IdP (or download the XML and upload it manually). ## Google Workspace Example 1. **Create the custom SAML app** - Google Admin Console → *Apps → Web and mobile apps → Add custom SAML app*. - Download the IdP metadata XML provided during the wizard. 2. **Configure URLs** - **ACS URL**: `https:///saml//acs` - **Entity ID**: `https:///saml//metadata` - **Name ID**: `Email` (default) or whichever attribute you plan to map. 3. **Upload metadata to Attestation Store** - Call `POST /api/saml/configs` (above) using the downloaded metadata. - Set `attribute_mapping.email` to `email` or `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`. 4. **Assign users**\ In Google Admin, assign the SAML app to the users/groups who should access Attestation Store. 5. **Test** - Initiate login from Attestation Store: `/login` now shows "Continue with " buttons. - Or IdP-initiated flows (if `allow_idp_initiated` is `true`). - Watch the server logs for `saml_login_success` events; run through both ACS and SLO endpoints to confirm. > **Local development tip:** When testing with Google, expose your dev server via `ngrok` or run it in a public environment so Google can reach `https://.../saml//acs`. ## Attribute Mapping & Auto-Provisioning - `attribute_mapping`: JSON object mapping logical fields (`email`, `username`, `first_name`, `last_name`) to the IdP attribute names. Defaults to `email`, `username`, `firstName`, `lastName`. - `auto_provision_users`: When `true`, the server creates a local user the first time it sees a new SAML identity. The user is placed in the tenant and assigned a random password (unused). - When `auto_provision_users` is `false`, the assertion must match an existing user (by `NameID` or mapped email); otherwise the login fails. All SAML identities are stored in the `external_identities` table with the assertion attributes for auditability. ## Testing & Troubleshooting 1. **Health check** – `/healthz` ensures DB access before testing. 2. **Metadata validation** – hitting `/saml//metadata` should return valid XML. Errors will appear in `system_logs` with `saml_metadata_error`. 3. **ACS testing** – use the IdP test button or tools like [samltool.com](https://www.samltool.com/) to post assertions to `/saml//acs`. 4. **SLO** – ensure `/saml//slo` works in both SP-initiated and IdP-initiated directions. 5. **Logs** – search `system_logs` for `saml_*` events to trace configuration or login issues. ## Tenant Management ### API Endpoints The following tenant management endpoints are available: | Method | Endpoint | Description | | --- | --- | --- | | GET | `/api/saml/tenants` | List all tenants | | POST | `/api/saml/tenants` | Create a new tenant | | PATCH | `/api/saml/tenants/` | Update a tenant | | DELETE | `/api/saml/tenants/` | Delete a tenant | ### Deleting a Tenant When you delete a tenant via `DELETE /api/saml/tenants/`, the following actions occur: - ✅ Tenant record is deleted - ✅ All SAML configurations for the tenant are deleted - ✅ All external identity records are deleted - ⚠️ Users are preserved but unlinked from the tenant (their `tenant_id` is set to NULL) **Example:** ```bash # Delete tenant with ID 5 curl -X DELETE "$BASE_URL/api/saml/tenants/5" \ -H "Authorization: Bearer $JWT_TOKEN" ``` **Response:** ```json { "message": "Tenant acme-corp deleted successfully", "users_affected": 5, "saml_configs_deleted": 2 } ``` ### Shell Scripts Two convenience scripts are provided in the `scripts/` directory: #### List Tenants ```bash # Make executable first chmod +x scripts/list_tenants.sh # Usage ./scripts/list_tenants.sh [API_URL] [USERNAME] [PASSWORD] # Examples ./scripts/list_tenants.sh ./scripts/list_tenants.sh http://localhost:5555 admin mypassword ``` The script displays a formatted table with tenant details and full JSON output. #### Delete Tenant ```bash # Make executable first chmod +x scripts/delete_tenant.sh # Usage ./scripts/delete_tenant.sh [API_URL] [USERNAME] [PASSWORD] # Examples ./scripts/delete_tenant.sh acme-corp ./scripts/delete_tenant.sh "Acme Corporation" http://localhost:5555 admin mypassword ``` The script will: 1. Authenticate with the API 2. Search for the tenant by name or slug (case-insensitive) 3. Display tenant information 4. Ask for confirmation before deletion 5. Delete the tenant and display the results **Important:** Deletion is permanent and cannot be undone. Users are NOT deleted, only their tenant association is removed. ### Testing Tenant Management Run the automated test script to verify tenant management functionality: ```bash python3 scripts/test_tenant_management.py ``` This will test creating, listing, and deleting tenants, ensuring all operations work correctly. ## Security Notes - Always run the public endpoints over HTTPS. - Keep `SAML_SP_PRIVATE_KEY` secret; rotate SP certificates on a schedule. - Disable `allow_idp_initiated` if you require SP-initiated flows only. - When deactivating accounts, set `User.is_active = False`; SAML logins will honor the flag. - All tenant management operations require admin privileges and are logged for audit purposes.