|
|
|
@@ -0,0 +1,209 @@
|
|
|
|
|
# Visione CIE OIDC RP Technical Plan
|
|
|
|
|
|
|
|
|
|
## Goal
|
|
|
|
|
Implement Visione backend as SPID/CIE OIDC Federation Relying Party compatible
|
|
|
|
|
with local demo provider contained in `spid-cie-oidc-django/examples/provider`.
|
|
|
|
|
|
|
|
|
|
Scope target for first real implementation:
|
|
|
|
|
- compatibility with demo provider only
|
|
|
|
|
- CIE profile only
|
|
|
|
|
- Visione backend remains source of truth for application auth
|
|
|
|
|
- frontend drives browser redirect flow
|
|
|
|
|
|
|
|
|
|
## Local Reference Mapping
|
|
|
|
|
- Demo OP/federation behavior source:
|
|
|
|
|
- `spid-cie-oidc-django/examples/provider`
|
|
|
|
|
- `spid-cie-oidc-django/spid_cie_oidc/provider`
|
|
|
|
|
- `spid-cie-oidc-django/spid_cie_oidc/entity`
|
|
|
|
|
- Demo RP behavior to emulate in Drogon:
|
|
|
|
|
- `spid-cie-oidc-django/spid_cie_oidc/relying_party/views/rp_begin.py`
|
|
|
|
|
- `spid-cie-oidc-django/spid_cie_oidc/relying_party/views/rp_callback.py`
|
|
|
|
|
|
|
|
|
|
## Current Status
|
|
|
|
|
- `common/OidcJwt.*` now owns JWT/JWS/JWK work through `jwt-cpp`
|
|
|
|
|
- `common/OidcCrypto.*` isolates OpenSSL-only JWE compact parsing and decrypt
|
|
|
|
|
- `POST /api/v1/auth/cie/start` signs the request object with RP signing key
|
|
|
|
|
- callback exchanges code with `private_key_jwt`, verifies token JWS and `at_hash`
|
|
|
|
|
- `userinfo` GET supports plain JSON and demo-compatible nested JWE -> JWS
|
|
|
|
|
- merged OIDC claims persist through `user_external_identities`
|
|
|
|
|
- frontend polling contract is active through `GET /api/v1/auth/cie/session/{state}`
|
|
|
|
|
|
|
|
|
|
## Foundation Added In This Step
|
|
|
|
|
- config placeholders in `config.json`
|
|
|
|
|
- DB schema for OIDC auth sessions, tokens, federation cache, external identities
|
|
|
|
|
- Drogon controller skeleton in `controllers/OidcController.*`
|
|
|
|
|
- service skeleton in `services/OidcRpService.*`
|
|
|
|
|
- RP config helper in `common/OidcRpConfig.*`
|
|
|
|
|
- RP public endpoints reserved:
|
|
|
|
|
- `POST /api/v1/auth/cie/start`
|
|
|
|
|
- `GET /api/v1/auth/cie/callback`
|
|
|
|
|
- `GET /api/v1/auth/cie/session/{state}`
|
|
|
|
|
- `GET /oidc/rp/.well-known/openid-federation`
|
|
|
|
|
- `GET /oidc/rp/resolve`
|
|
|
|
|
- `GET /oidc/rp/jwks.json`
|
|
|
|
|
|
|
|
|
|
## Residual Backlog
|
|
|
|
|
### Phase 1: Dependencies and crypto plumbing
|
|
|
|
|
- Done for demo scope:
|
|
|
|
|
- `jwt-cpp` handles JWT parsing, signing and signature verification
|
|
|
|
|
- custom OpenSSL code remains only for JWE decryption in `OidcCrypto`
|
|
|
|
|
- focused unit tests cover JWT helpers and both supported JWE CBC/HMAC modes
|
|
|
|
|
|
|
|
|
|
### Phase 2: RP metadata and JWKS
|
|
|
|
|
- Done for demo flow.
|
|
|
|
|
- Residual gap:
|
|
|
|
|
- verify metadata fields against non-demo providers before reuse
|
|
|
|
|
|
|
|
|
|
### Phase 3: Federation trust resolution
|
|
|
|
|
- Current cut:
|
|
|
|
|
- fetches trust-anchor `resolve`
|
|
|
|
|
- extracts OP metadata/JWKS/endpoints
|
|
|
|
|
- Not done:
|
|
|
|
|
- full trust-chain signature validation
|
|
|
|
|
- federation cache population/reuse
|
|
|
|
|
- multi-provider or multi-anchor logic
|
|
|
|
|
|
|
|
|
|
### Phase 4: Authorization start
|
|
|
|
|
- Done.
|
|
|
|
|
- Demo scope now sent explicitly as `openid cie_demo`.
|
|
|
|
|
|
|
|
|
|
### Phase 5: Callback and token exchange
|
|
|
|
|
- Done for demo provider.
|
|
|
|
|
- Residual gap:
|
|
|
|
|
- explicit `exp` / `iat` skew checks still thin
|
|
|
|
|
- no refresh-token lifecycle use yet
|
|
|
|
|
|
|
|
|
|
### Phase 6: UserInfo and local account link
|
|
|
|
|
- Done for demo provider, including JWE decrypt + JWS verify + merged claims.
|
|
|
|
|
|
|
|
|
|
### Phase 7: Frontend completion contract
|
|
|
|
|
- Done.
|
|
|
|
|
- Session payload now exposes:
|
|
|
|
|
- public `status`
|
|
|
|
|
- internal `phase`
|
|
|
|
|
- `auth` payload only on success
|
|
|
|
|
|
|
|
|
|
### Phase 8: Tests and docs
|
|
|
|
|
- Unit tests added for:
|
|
|
|
|
- JWT parse/sign/verify
|
|
|
|
|
- `kid` lookup
|
|
|
|
|
- `at_hash` helper
|
|
|
|
|
- JWE decrypt for `A256CBC-HS512`
|
|
|
|
|
- JWE decrypt for `A128CBC-HS256`
|
|
|
|
|
- invalid JWE tag / unsupported enc
|
|
|
|
|
- merged claim precedence and fiscal code normalization
|
|
|
|
|
- Still missing:
|
|
|
|
|
- automated integration tests against live demo Django OP
|
|
|
|
|
|
|
|
|
|
## Data Model Notes
|
|
|
|
|
### `oidc_authentications`
|
|
|
|
|
Stores single login transaction state and provider snapshot.
|
|
|
|
|
|
|
|
|
|
### `oidc_authentication_tokens`
|
|
|
|
|
Stores authorization code and received tokens.
|
|
|
|
|
|
|
|
|
|
### `oidc_federation_cache`
|
|
|
|
|
Stores fetched federation docs with expiry.
|
|
|
|
|
|
|
|
|
|
### `user_external_identities`
|
|
|
|
|
Decouples local user from external subject identifiers.
|
|
|
|
|
|
|
|
|
|
## Current Decisions
|
|
|
|
|
- Keep OTP auth alive in parallel
|
|
|
|
|
- Use local `api_tokens` after OIDC success
|
|
|
|
|
- Use `fiscal_code` as strongest user reunification key
|
|
|
|
|
- Do not expose Visione token in query string
|
|
|
|
|
- Prefer external validated libs over handwritten JWT/JWK code
|
|
|
|
|
|
|
|
|
|
## Ready Criteria For Real Implementation
|
|
|
|
|
- foundation schema merged
|
|
|
|
|
- config keys finalized
|
|
|
|
|
- dependency choice finalized
|
|
|
|
|
- endpoint shapes stable enough for frontend
|
|
|
|
|
|
|
|
|
|
When these are acceptable, next step is real implementation of Phase 1-4.
|
|
|
|
|
|
|
|
|
|
## Demo CIE Runbook
|
|
|
|
|
### Prerequisites
|
|
|
|
|
- MySQL and Mailpit running locally through this repo `docker compose`
|
|
|
|
|
- RP keys available in `secrets/oidc/demo-rp-jwks.json`
|
|
|
|
|
- backend `config.json` aligned with:
|
|
|
|
|
- trust anchor `http://127.0.0.1:8000`
|
|
|
|
|
- provider issuer `http://127.0.0.1:8002/oidc/op`
|
|
|
|
|
- callback `http://127.0.0.1:5555/api/v1/auth/cie/callback`
|
|
|
|
|
|
|
|
|
|
### Bootstrap local dependencies
|
|
|
|
|
```bash
|
|
|
|
|
docker compose up -d
|
|
|
|
|
cmake -S . -B build_local -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
|
|
|
|
cmake --build build_local
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Bootstrap demo federation authority
|
|
|
|
|
```bash
|
|
|
|
|
cd spid-cie-oidc-django/examples/federation_authority
|
|
|
|
|
cp federation_authority/settingslocal.py.example federation_authority/settingslocal.py
|
|
|
|
|
python3 -m pip install --upgrade pip
|
|
|
|
|
python3 -m pip install -e ../..
|
|
|
|
|
python3 -m pip install "design-django-theme==v1.4.8"
|
|
|
|
|
./manage.py migrate
|
|
|
|
|
./manage.py loaddata dumps/example.json
|
|
|
|
|
./manage.py runserver 0.0.0.0:8000
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Bootstrap demo provider
|
|
|
|
|
```bash
|
|
|
|
|
cd spid-cie-oidc-django/examples/provider
|
|
|
|
|
cp provider/settingslocal.py.example provider/settingslocal.py
|
|
|
|
|
./manage.py migrate
|
|
|
|
|
./manage.py loaddata dumps/example.json
|
|
|
|
|
./manage.py runserver 0.0.0.0:8002
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Start Visione backend
|
|
|
|
|
```bash
|
|
|
|
|
./build_local/visione-backend
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Start login
|
|
|
|
|
```bash
|
|
|
|
|
curl -sS -X POST http://127.0.0.1:5555/api/v1/auth/cie/start \
|
|
|
|
|
-H 'Content-Type: application/json' \
|
|
|
|
|
-d '{"provider":"cie_demo","frontend_redirect_uri":"http://localhost:3000/auth/cie/callback"}'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Expected result:
|
|
|
|
|
- `authorization_url` present
|
|
|
|
|
- `state` present
|
|
|
|
|
- one row in `oidc_authentications` with phase `AUTHORIZATION_REDIRECT_READY`
|
|
|
|
|
|
|
|
|
|
### Complete browser flow
|
|
|
|
|
1. Open returned `authorization_url`.
|
|
|
|
|
2. Authenticate with the demo CIE user configured by the Django fixtures.
|
|
|
|
|
3. Accept consent if requested.
|
|
|
|
|
4. Provider redirects to backend callback.
|
|
|
|
|
5. Backend redirects frontend to `frontend_redirect_uri?state=...`.
|
|
|
|
|
|
|
|
|
|
### Poll login result
|
|
|
|
|
```bash
|
|
|
|
|
curl -sS http://127.0.0.1:5555/api/v1/auth/cie/session/<state>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Expected success payload:
|
|
|
|
|
- `status = "authenticated"`
|
|
|
|
|
- `phase = "AUTHENTICATED"`
|
|
|
|
|
- `auth.token` present
|
|
|
|
|
|
|
|
|
|
### Minimal DB verification
|
|
|
|
|
```sql
|
|
|
|
|
SELECT state,status,authenticated_user_id FROM oidc_authentications ORDER BY id DESC LIMIT 3;
|
|
|
|
|
SELECT authentication_id,token_type,scope FROM oidc_authentication_tokens ORDER BY id DESC LIMIT 3;
|
|
|
|
|
SELECT user_id,provider,issuer,subject,fiscal_code FROM user_external_identities ORDER BY id DESC LIMIT 3;
|
|
|
|
|
SELECT user_id,expires_at FROM api_tokens ORDER BY id DESC LIMIT 3;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Common failures
|
|
|
|
|
- `provider_metadata_missing`: federation authority not started or wrong trust anchor URL
|
|
|
|
|
- `token_exchange_failed`: provider callback reached but token endpoint unavailable or RP assertion invalid
|
|
|
|
|
- `invalid_userinfo_response`: JWE decryption key mismatch or unexpected provider response format
|
|
|
|
|
- `at_hash_mismatch`: provider tokens not aligned with received `access_token`
|