attstore / docs /SAML_SETUP.md
dn-scribe's picture
feat: support tenant deletion
38536e2
|
Raw History Blame Contribute Delete
9.27 kB
# 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/<slug>/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://<your-domain>/saml/<slug>/acs`
- **Entity ID**: `https://<your-domain>/saml/<slug>/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 <Tenant>" 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/<slug>/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/<slug>/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/<slug>/acs`.
4. **SLO** – ensure `/saml/<slug>/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/<id>` | Update a tenant |
| DELETE | `/api/saml/tenants/<id>` | Delete a tenant |
### Deleting a Tenant
When you delete a tenant via `DELETE /api/saml/tenants/<tenant_id>`, 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 <tenant_name_or_slug> [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.