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. 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:

# 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

    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

    # 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:

    {
      "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 " 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 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:

# Delete tenant with ID 5
curl -X DELETE "$BASE_URL/api/saml/tenants/5" \
  -H "Authorization: Bearer $JWT_TOKEN"

Response:

{
  "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

# 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

# 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:

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.