Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d606ac7439 | |||
| 208c4467df | |||
| 0e82e8b771 | |||
| 798afdf2a9 | |||
| 7cf9fda7d8 | |||
| 1816d2a8de | |||
| c89b9b7ada | |||
| c0d252f390 | |||
| fa39964a64 | |||
| 44b9879a3b | |||
| 7c3a0fccc9 | |||
| c224e31a66 | |||
| 4892192b85 | |||
| 79aee346a2 | |||
| 4e84ec0f2b | |||
| 3401da37ff | |||
| 1124893498 | |||
| 1557f7ba22 | |||
| 6c344fae05 | |||
| cb8352787d | |||
| 9a33149e9e | |||
| a56ce17b34 | |||
| a8bf8f61b4 | |||
| 7b1571b0ed | |||
| 3ecbf1a5a0 | |||
| 445485ecc5 | |||
| 1bceed1373 | |||
| 850636970c | |||
| 9a1803b3e6 | |||
| c51c95e007 | |||
| 2687868465 | |||
| 38707412b3 | |||
| 8cd89621b2 | |||
| 425365d22a | |||
| 63806bde22 | |||
| 96299e1386 | |||
| bf015c9238 | |||
| 589d447f19 | |||
| ddc09cca50 | |||
| ae2d4a573b | |||
| 5be05fe43e | |||
| f90df5c2c6 | |||
| 6bbcd95069 | |||
| b839c32b50 | |||
| 9b062aa27d | |||
| 4a2ece217b | |||
| a7577ff85e | |||
| 4e81e1e750 | |||
| c27ebfe6ae | |||
| d84f2b32d5 | |||
| 9cb67bfb87 | |||
| ba1774bdbf | |||
| cb8ff5d878 | |||
| a6e01c071e | |||
| 5a0f2da202 | |||
| 3b2d50d657 | |||
| 52d7de305c | |||
| 64a0d4dd3d | |||
| ffbd161e29 | |||
| ba1c14291d | |||
| 4c4ccc88ba | |||
| c1b33453c3 |
@@ -9,9 +9,9 @@ We primarily use GitHub as an issue tracker. If however you're encountering an i
|
||||
|
||||
---
|
||||
|
||||
Please make sure you have read our [main Readme](https://github.com/numerique-gouv/oidc2fer).
|
||||
Please make sure you have read our [main Readme](https://github.com/proconnect-gouv/oidc2fer).
|
||||
|
||||
Also make sure it was not already answered in [an open or close issue](https://github.com/numerique-gouv/oidc2fer/issues).
|
||||
Also make sure it was not already answered in [an open or close issue](https://github.com/proconnect-gouv/oidc2fer/issues).
|
||||
|
||||
If your question was not covered, and you feel like it should be, fire away! We'd love to improve our docs! 👌
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
name: 🐳 Build docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=ref,event=pr
|
||||
type=sha,prefix=sha-
|
||||
# set latest tag for default branch
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
target: production
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
notify-argocd:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name != 'pull_request'
|
||||
needs:
|
||||
- build-and-push
|
||||
steps:
|
||||
- uses: numerique-gouv/action-argocd-webhook-notification@main
|
||||
id: notify
|
||||
with:
|
||||
deployment_repo_path: "${{ github.repository }}"
|
||||
argocd_webhook_secret: "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}"
|
||||
argocd_url: "${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}"
|
||||
@@ -1,4 +1,4 @@
|
||||
name: OIDC2FER Workflow
|
||||
name: 💚 CI Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
run: git log
|
||||
- name: Enforce absence of print statements in code
|
||||
run: |
|
||||
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude)**/oidc2fer.yml' | grep "print("
|
||||
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude).github/**' | grep "print("
|
||||
- name: Check absence of fixup commits
|
||||
run: |
|
||||
! git log | grep 'fixup!'
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check that the CHANGELOG has been modified in the current branch
|
||||
run: git whatchanged --name-only --pretty="" origin/${{ github.event.pull_request.base.ref }}..HEAD | grep CHANGELOG
|
||||
run: git log --name-only --pretty="" origin/${{ github.event.pull_request.base.ref }}..HEAD | grep CHANGELOG
|
||||
|
||||
lint-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -101,3 +101,37 @@ jobs:
|
||||
pip install --user .[dev]
|
||||
- name: Run tests
|
||||
run: ~/.local/bin/pytest
|
||||
|
||||
helmfile-lint:
|
||||
runs-on: ubuntu-latest
|
||||
# skip this job on forks since they won't have access to the required secrets
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
|
||||
container:
|
||||
image: ghcr.io/helmfile/helmfile:v1.2.0
|
||||
steps:
|
||||
-
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
owner: numerique-gouv
|
||||
repositories: secrets
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
-
|
||||
name: Helmfile lint
|
||||
env:
|
||||
SOPS_AGE_KEY: ${{ secrets.SOPS_PRIVATE }}
|
||||
run: |
|
||||
HELMFILE=src/helm/helmfile.yaml.gotmpl
|
||||
environments=$(< $HELMFILE awk -F'[ :]+' '/^ [^ ]/ {print $2} /^---/ { exit }')
|
||||
for env in $environments; do
|
||||
echo "################### $env lint ###################"
|
||||
helmfile -e $env -f "$HELMFILE" lint || exit 1
|
||||
echo -e "\n"
|
||||
done
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Docker Hub Workflow
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: "oidc2fer,secrets"
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: lasuite/oidc2fer
|
||||
-
|
||||
name: Load sops secrets
|
||||
uses: rouja/actions-sops@main
|
||||
with:
|
||||
secret-file: secrets/numerique-gouv/oidc2fer/secrets.enc.env
|
||||
age-key: ${{ secrets.SOPS_PRIVATE }}
|
||||
-
|
||||
name: Login to DockerHub
|
||||
run: echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USER" --password-stdin
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: production
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
notify-argocd:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name != 'pull_request'
|
||||
needs:
|
||||
- build-and-push
|
||||
steps:
|
||||
-
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: "oidc2fer,secrets"
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
-
|
||||
name: Load sops secrets
|
||||
uses: rouja/actions-sops@main
|
||||
with:
|
||||
secret-file: secrets/numerique-gouv/oidc2fer/secrets.enc.env
|
||||
age-key: ${{ secrets.SOPS_PRIVATE }}
|
||||
-
|
||||
name: Call argocd github webhook
|
||||
run: |
|
||||
data='{"ref": "'$GITHUB_REF'","repository": {"html_url":"'$GITHUB_SERVER_URL'/'$GITHUB_REPOSITORY'"}}'
|
||||
sig=$(echo -n ${data} | openssl dgst -sha1 -hmac ''${ARGOCD_WEBHOOK_SECRET}'' | awk '{print "X-Hub-Signature: sha1="$2}')
|
||||
curl -X POST -H 'X-GitHub-Event:push' -H "Content-Type: application/json" -H "${sig}" --data "${data}" $ARGOCD_WEBHOOK_URL
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "secrets"]
|
||||
path = secrets
|
||||
url = ../secrets
|
||||
url = ../../numerique-gouv/secrets
|
||||
|
||||
@@ -6,6 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0),
|
||||
and this project adheres to
|
||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.11] - 2025-11-24
|
||||
- remove setuptools, wheel and pip from production Docker image
|
||||
|
||||
## [1.0.10] - 2025-11-21
|
||||
- rename helmfile.yaml for compatibility with helmfile 1.x
|
||||
|
||||
## [1.0.9] - 2025-10-03
|
||||
- map SAML entity IDs to SIRET (#34)
|
||||
- include ACR claim in ID token only if requested (#32)
|
||||
|
||||
## [1.0.8] - 2025-09-10
|
||||
- fix secrets submodule path following move to `proconnect-gouv` organization
|
||||
- update other references to `numerique-gouv` organization
|
||||
- publish Docker images at ghcr.io/proconnect-gouv/oidc2fer
|
||||
|
||||
## [1.0.7] - 2025-05-07
|
||||
- remove unused APT dependencies
|
||||
- upgrade to gunicorn 23.0.0
|
||||
|
||||
## [1.0.6] - 2025-03-11
|
||||
- upgrade to SATOSA 8.5.1
|
||||
- allow faculty, staff, employee, researcher, teacher affiliations
|
||||
|
||||
## [1.0.5] - 2025-03-06
|
||||
- add allowlist for URL paths to nginx ingress
|
||||
- allow customizing log levels per logger
|
||||
|
||||
## [1.0.4] - 2024-09-11
|
||||
- serve static files
|
||||
|
||||
|
||||
+7
-12
@@ -1,17 +1,11 @@
|
||||
# ---- base image to inherit from ----
|
||||
FROM python:3.11-slim-bookworm as common
|
||||
FROM python:3.11.12-slim-bookworm AS common
|
||||
|
||||
# Install xmlsec1 dependencies required for xmlsec (for SAML)
|
||||
# Needs to be kept before the `pip install`
|
||||
RUN apt-get update && \
|
||||
apt-get -y upgrade && \
|
||||
apt-get install -y \
|
||||
pkg-config \
|
||||
gcc \
|
||||
xmlsec1 \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl && \
|
||||
apt-get install -qy --no-install-recommends xmlsec1 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# We want the most up-to-date stable pip release
|
||||
@@ -19,7 +13,7 @@ RUN pip install --upgrade pip
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Give the "root" group the same permissions as the "root" user on /etc/passwd
|
||||
# Give the "root" group the same permissions AS the "root" user on /etc/passwd
|
||||
# to allow a user belonging to the root group to add new users; typically the
|
||||
# docker user (see entrypoint).
|
||||
RUN chmod g=u /etc/passwd
|
||||
@@ -41,7 +35,7 @@ COPY docker/files/usr/local/etc/gunicorn/satosa.py /usr/local/etc/gunicorn/satos
|
||||
CMD ["gunicorn", "-c", "/usr/local/etc/gunicorn/satosa.py"]
|
||||
|
||||
# ---- Development image ----
|
||||
FROM common as development
|
||||
FROM common AS development
|
||||
|
||||
# Playwright browsers
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/pw-browsers
|
||||
@@ -67,14 +61,15 @@ COPY ./src/satosa /app/
|
||||
USER ${DOCKER_USER}
|
||||
|
||||
# ---- Production image (keep last so it is the default target) ----
|
||||
FROM common as production
|
||||
FROM common AS production
|
||||
|
||||
# Copy oidc2fer application (see .dockerignore)
|
||||
COPY ./src/satosa /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install .
|
||||
# Uninstall pip, setuptools and wheel after installation to reduce attack surface
|
||||
RUN pip install . && pip uninstall -y setuptools wheel pip
|
||||
|
||||
# Switch to unprivileged user
|
||||
USER ${DOCKER_USER}
|
||||
|
||||
@@ -57,12 +57,19 @@ number and date.
|
||||
## Deploying a release to production (DINUM instance)
|
||||
|
||||
1. Make sure the release you want to deploy has been built and appears on
|
||||
https://hub.docker.com/r/lasuite/oidc2fer/tags .
|
||||
https://github.com/proconnect-gouv/oidc2fer/pkgs/container/oidc2fer
|
||||
2. Edit `image/tag` at the top of
|
||||
`src/helm/env.d/outscale-production/values.oidc2fer.yaml.gotmpl`.
|
||||
3. Commit and push to `main`.
|
||||
4. Update the `production` tag and push it.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| variable | usage |
|
||||
| --- | --- |
|
||||
| `LOG_LEVEL` | Sets the log level for the root logger, i.e. the default. Defaults to `INFO`. |
|
||||
| `LOG_LEVELS` | A JSON object that can be used to set log levels for specific loggers, e.g. `{"satosa.backends.saml2": "DEBUG"}`. Defaults to `{}`. |
|
||||
|
||||
## Contributing
|
||||
|
||||
This project is intended to be community-driven, so please, do not hesitate to
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ services:
|
||||
BASE_URL: https://satosa.traefik.me
|
||||
GUNICORN_CMD_ARGS: --workers=2
|
||||
TEST_E2E: 1
|
||||
#TEST_E2E_AC: 1
|
||||
#TEST_E2E_PC: 1
|
||||
OIDC_DB_URI: redis://redis/0
|
||||
CLIENT_DB_JSON: |
|
||||
{
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
#OIDC_PROVIDER: https://oidc2fer-staging.beta.numerique.gouv.fr
|
||||
OIDC_CLIENT_ID: oidc-test-client
|
||||
OIDC_CLIENT_SECRET: oidc-test-secret
|
||||
OIDC_SCOPES: openid,uid,given_name,usual_name,email
|
||||
OIDC_SCOPES: openid,uid,given_name,usual_name,email,siret
|
||||
extra_hosts:
|
||||
- "oidc2fer.127.0.0.1.nip.io:host-gateway"
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -29,6 +30,8 @@ class RequestJSONFormatter(json_log_formatter.JSONFormatter):
|
||||
url += f"?{record.args['q']}"
|
||||
|
||||
return {
|
||||
"logger": record.name,
|
||||
"level": record.levelname,
|
||||
"remote_ip": record.args["h"],
|
||||
"method": record.args["m"],
|
||||
"path": url,
|
||||
@@ -37,7 +40,7 @@ class RequestJSONFormatter(json_log_formatter.JSONFormatter):
|
||||
"user_agent": record.args["a"],
|
||||
"referer": record.args["f"],
|
||||
"duration_in_ms": record.args["M"],
|
||||
"poppid": record.process,
|
||||
"pid": record.process,
|
||||
}
|
||||
|
||||
|
||||
@@ -55,11 +58,23 @@ class DefaultJSONFormatter(json_log_formatter.JSONFormatter):
|
||||
payload: dict[str, str | int | float] = super().json_record(
|
||||
message, extra, record
|
||||
)
|
||||
payload["logger"] = record.name
|
||||
payload["level"] = record.levelname
|
||||
payload["pid"] = record.process
|
||||
return payload
|
||||
|
||||
|
||||
class NoPingFilter(logging.Filter):
|
||||
"""
|
||||
Filters out /ping requests from the access log.
|
||||
"""
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
return not( record.args.get("m", "") == "GET"
|
||||
and record.args.get("U", "") == "/ping"
|
||||
and str(record.args.get("s", "")) == "200" )
|
||||
|
||||
|
||||
bind = ["0.0.0.0:8000"]
|
||||
name = "satosa"
|
||||
python_path = "/app"
|
||||
@@ -76,6 +91,9 @@ accesslog = "-"
|
||||
errorlog = "-"
|
||||
loglevel = os.environ.get("LOG_LEVEL", "INFO")
|
||||
|
||||
loglevels_json = os.environ.get("LOG_LEVELS", "{}")
|
||||
loglevels = json.loads(loglevels_json)
|
||||
|
||||
logconfig_dict = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": True,
|
||||
@@ -95,7 +113,24 @@ logconfig_dict = {
|
||||
"handlers": ["json_request"],
|
||||
"propagate": False,
|
||||
"qualname": "gunicorn.access",
|
||||
"filters": [NoPingFilter()],
|
||||
},
|
||||
"satosa.base": {
|
||||
"level": "INFO",
|
||||
},
|
||||
"satosa.state": {
|
||||
"level": "INFO",
|
||||
},
|
||||
"satosa.proxy_server": {
|
||||
"level": "INFO",
|
||||
},
|
||||
"satosa.routing": {
|
||||
"level": "INFO",
|
||||
},
|
||||
"satosa.frontends.ping": {
|
||||
"level": "INFO",
|
||||
},
|
||||
**{k: {"level": v.upper()} for k, v in loglevels.items()},
|
||||
},
|
||||
"formatters": {
|
||||
"json_request": {
|
||||
|
||||
@@ -2,13 +2,10 @@ from flask import Flask, jsonify, request, session
|
||||
from oic.oic import Client
|
||||
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
|
||||
from oic import rndstr
|
||||
from oic.oic.message import RegistrationResponse
|
||||
from oic.oic.message import Claims, ClaimsRequest, RegistrationResponse
|
||||
from oic.utils.http_util import Redirect
|
||||
from oic.oic.message import AuthorizationResponse
|
||||
import secrets
|
||||
import webbrowser
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
|
||||
@@ -52,6 +49,7 @@ def index():
|
||||
"nonce": session["nonce"],
|
||||
"redirect_uri": client.registration_response["redirect_uris"][0],
|
||||
"state": session["state"],
|
||||
"claims": ClaimsRequest(id_token=Claims(acr=None, amr=None)),
|
||||
}
|
||||
)
|
||||
login_url = auth_req.request(client.authorization_endpoint)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Flask==3.0.3
|
||||
oic==1.6.1
|
||||
Flask==3.1.3
|
||||
oic==1.7.0
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Principe de fonctionnement de la passerelle ProConnect vers RENATER (`OIDC2FER`)
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Ce premier schéma donne un aperçu des échanges entre :
|
||||
* un Fournisseur de Service ProConnecté (ex. https://pad.numerique.gouv.fr) ;
|
||||
* le service ProConnect (hébergé sur `https://auth.agentconnect.gouv.fr`) ;
|
||||
* la passerelle `OIDC2FER` (hébergé sur `https://renater.agentconnect.gouv.fr`) ;
|
||||
* le service de _discovery_ ou _Where Are You From (WAYF)_ RENATER qui permet à l'utilisateur de choisir son établissement de rattachement, hébergé sur https://discovery.renater.fr/agentconnect/ ;
|
||||
* le serveur d'identité SAML de l'établissement sélectionné (par exemple, https://cas.inria.fr).
|
||||
|
||||
``` mermaid
|
||||
sequenceDiagram
|
||||
participant FS as Fournisseur<br>de Service
|
||||
participant ProConnect
|
||||
box LightYellow
|
||||
participant OIDC2FER
|
||||
end
|
||||
participant WAYF as WAYF<br>RENATER
|
||||
participant IdP as IdP SAML
|
||||
|
||||
FS->>ProConnect: OIDC auth request
|
||||
ProConnect->>OIDC2FER: OIDC auth request
|
||||
OIDC2FER->>WAYF: discovery request
|
||||
WAYF->>OIDC2FER: IdP entityID
|
||||
OIDC2FER->>IdP: SAML AuthnRequest
|
||||
IdP->>OIDC2FER: SAML Assertion
|
||||
OIDC2FER->>ProConnect: OIDC tokens+userinfo
|
||||
ProConnect->>FS: OIDC tokens+userinfo
|
||||
```
|
||||
|
||||
## Détail des échanges
|
||||
|
||||
Noter que le schéma précédent ne reflète pas le détail des échanges entre l'utilisateur, son navigateur (qu'il est utile de distinguer pour illustrer que plusieurs échanges se font sans intervention humaine), et les différents services mentionnés. En voici une version exhaustive :
|
||||
|
||||
``` mermaid
|
||||
sequenceDiagram
|
||||
actor Utilisateur
|
||||
participant Navigateur
|
||||
participant FS as Fournisseur<br>de Service
|
||||
participant ProConnect
|
||||
box LightYellow
|
||||
participant OIDC2FER
|
||||
end
|
||||
participant WAYF as WAYF<br>RENATER
|
||||
participant IdP SAML
|
||||
|
||||
Utilisateur->>Navigateur: Saisie/clic URL FS
|
||||
Navigateur->>FS: Requête d'une page du FS
|
||||
opt si pas de session FS ouverte
|
||||
FS->>Utilisateur: Présentation page d'accueil avec bouton ProConnect
|
||||
Utilisateur->>FS: Clic bouton ProConnect
|
||||
FS->>Navigateur: Redirection OIDC vers ProConnect avec state, nonce
|
||||
Navigateur->>ProConnect: Requête GET avec state, nonce
|
||||
opt si pas de session ProConnect ouverte
|
||||
ProConnect->>Utilisateur: Présentation mire ProConnect
|
||||
Utilisateur->>ProConnect: Clic bouton RENATER, ou saisie "robert@univ-exemple.fr"
|
||||
ProConnect->>Navigateur: Redirection OIDC vers OIDC2FER avec state, nonce
|
||||
Navigateur->>OIDC2FER: Requête GET avec state, nonce
|
||||
OIDC2FER->>Navigateur: Redirection vers WAYF
|
||||
Navigateur->>WAYF: Requête GET
|
||||
opt si pas de préselection enregistrée dans le WAYF
|
||||
WAYF->>Utilisateur: Présentation liste d'établissements autorisés
|
||||
Utilisateur->>WAYF: Choix d'un établissement
|
||||
end
|
||||
WAYF->>Navigateur: Redirection vers OIDC2FER avec entityID IdP
|
||||
Navigateur->>OIDC2FER: Requête GET avec entityID IdP
|
||||
OIDC2FER->>Navigateur: Redirection vers IdP avec AuthnRequest SAML dans l'URL
|
||||
Navigateur->>IdP SAML: Requête GET avec AuthnRequest SAML dans l'URL
|
||||
opt si pas de session IdP ouverte
|
||||
IdP SAML->>Utilisateur: Présentation mire de connexion IdP
|
||||
Utilisateur->>IdP SAML: Saisie identifiants de connexion
|
||||
end
|
||||
IdP SAML->>Navigateur: Page avec formulaire contenant assertion SAML
|
||||
Navigateur->>OIDC2FER: Requête POST avec assertion SAML
|
||||
Note over OIDC2FER: Validation<br>eduPersonAffiliation
|
||||
Note over OIDC2FER: Conversion attributs<br>SAML vers OIDC
|
||||
OIDC2FER->>Navigateur: Redirection vers callback OIDC ProConnect avec authorization_code
|
||||
Navigateur->>ProConnect: Requête GET avec authorization_code
|
||||
ProConnect->>OIDC2FER: Demande tokens avec authorization_code
|
||||
OIDC2FER->>ProConnect: Tokens OIDC
|
||||
ProConnect->>OIDC2FER: Demande userinfo avec accessToken
|
||||
OIDC2FER->>ProConnect: userinfo
|
||||
end
|
||||
ProConnect->>Navigateur: Redirection vers callback OIDC FS avec authorization_code
|
||||
Navigateur->>FS: Requête GET avec authorization_code
|
||||
FS->>ProConnect: Demande tokens avec authorization_code
|
||||
ProConnect->>FS: Tokens OIDC
|
||||
FS->>ProConnect: Demande userinfo avec accessToken
|
||||
ProConnect->>FS: userinfo
|
||||
end
|
||||
FS->>Utilisateur: Contenu du service
|
||||
```
|
||||
+1
-1
Submodule secrets updated: 647b88689c...5773ab2a69
@@ -36,6 +36,10 @@ satosa:
|
||||
"token_endpoint_auth_method": "client_secret_post"
|
||||
}
|
||||
}
|
||||
SIRET_MAP: |-
|
||||
{
|
||||
"https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010"
|
||||
}
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
image:
|
||||
repository: lasuite/oidc2fer
|
||||
repository: ghcr.io/proconnect-gouv/oidc2fer
|
||||
pullPolicy: Always
|
||||
tag: "v1.0.3"
|
||||
tag: "v1.0.11"
|
||||
|
||||
satosa:
|
||||
replicas: 2
|
||||
@@ -9,7 +9,8 @@ satosa:
|
||||
BASE_URL: https://renater.agentconnect.gouv.fr
|
||||
GUNICORN_CMD_ARGS: --workers=3
|
||||
|
||||
LOG_LEVEL: info
|
||||
LOG_LEVEL: INFO
|
||||
LOG_LEVELS: '{ "satosa.backends.saml2": "DEBUG" }'
|
||||
|
||||
SAML2_DISCOVERY_URL: https://discovery.renater.fr/agentconnect/
|
||||
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/renater/main/main-idps-renater-metadata.xml
|
||||
@@ -23,6 +24,112 @@ satosa:
|
||||
|
||||
OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } }
|
||||
|
||||
# As provided by RENATER
|
||||
SIRET_MAP: |-
|
||||
{
|
||||
"http://idp.univ-poitiers.fr": "19860856400375",
|
||||
"https://apps.univ-lr.fr/idp/shibboleth": "19170032700015",
|
||||
"https://auth.sso.vet-alfort.fr/saml/metadata": "19940608300014",
|
||||
"https://auth.uttop.fr/saml/metadata": "19650048200019",
|
||||
"https://authentification.inrae.fr/saml/metadata/idp": "18007003901803",
|
||||
"https://federation.ens.psl.eu/idp/shibboleth": "19753459700012",
|
||||
"https://federation.umontpellier.fr/idp/shibboleth": "13002979600013",
|
||||
"https://federation.unimes.fr/idp/shibboleth": "93249157400012",
|
||||
"https://federation.upf.pf/idp/shibboleth": "19987001500013",
|
||||
"https://federation.utbm.fr/idp/shibboleth": "19900356700013",
|
||||
"https://federation3.univ-brest.fr/idp/shibboleth": "94129831700012",
|
||||
"https://idauthproxy.insa-lyon.fr/Saml2IDP/metadata": "19690192000013",
|
||||
"https://ident-shib.ensc-rennes.fr/idp/shibboleth": "19350077400016",
|
||||
"https://identification.univ-toulouse.fr": "13002132200016",
|
||||
"https://identites.ec-lyon.fr/idp/shibboleth": "19690187000010",
|
||||
"https://identites.ensea.fr/idp/shibboleth": "19951376300011",
|
||||
"https://identities.univ-jfc.fr/idp/prod": "19811201300018",
|
||||
"https://idp-genci.renater.fr/idp/shibboleth": "49468697500041",
|
||||
"https://idp-ng.univ-st-etienne.fr/idp/shibboleth": "93850168100010",
|
||||
"https://idp-septa.onisep.fr/idp/shibboleth": "18004302800653",
|
||||
"https://idp-v4.univ-montp3.fr/idp/shibboleth": "93249089900014",
|
||||
"https://idp.bnu.fr/idp/shibboleth": "18004406700015",
|
||||
"https://idp.cirad.fr/idp/shibboleth": "33159627000016",
|
||||
"https://idp.crous-reims.fr/idp/shibboleth": "18510200100327",
|
||||
"https://idp.ec-nantes.fr/idp/shibboleth": "19440100600011",
|
||||
"https://idp.ehesp.fr/idp/shibboleth": "13000362700010",
|
||||
"https://idp.ens-lyon.fr/idp/shibboleth": "13000812100019",
|
||||
"https://idp.ensam.eu/idp/shibboleth": "19753472000010",
|
||||
"https://idp.ensfea.fr/idp/shibboleth": "19310143300012",
|
||||
"https://idp.ensma.fr/idp/shibboleth": "19860073600021",
|
||||
"https://idp.ent.dauphine.fr/idp/shibboleth": "19754692200018",
|
||||
"https://idp.imt-atlantique.fr/idp/shibboleth": "18009202500121",
|
||||
"https://idp.inha.fr/idp/shibboleth": "19754688000018",
|
||||
"https://idp.inp-toulouse.fr/idp/shibboleth": "19311381800127",
|
||||
"https://idp.insa-rennes.fr/idp/shibboleth": "19350097200016",
|
||||
"https://idp.institut-agro.fr/idp": "13002622200013",
|
||||
"https://idp.isae.fr/idp/shibboleth": "13000427800011",
|
||||
"https://idp.lyon.archi.fr/idp/shibboleth": "19690184700018",
|
||||
"https://idp.renater.fr/idp/shibboleth": "18008947600055",
|
||||
"https://idp.rennes.archi.fr/idp/shibboleth": "19350089900029",
|
||||
"https://idp.sciencespo-lyon.fr/idp/shibboleth": "19690173000024",
|
||||
"https://idp.sciencespobordeaux.fr/idp/shibboleth": "19330192600039",
|
||||
"https://idp.uca.fr/idp/shibboleth": "13002806100013",
|
||||
"https://idp.uha.fr/idp/shibboleth": "19681166500013",
|
||||
"https://idp.unistra.fr/idp/shibboleth": "13000545700010",
|
||||
"https://idp.univ-avignon.fr/idp/shibboleth": "19840685200204",
|
||||
"https://idp.univ-cotedazur.fr/idp/shibboleth": "13002566100013",
|
||||
"https://idp.univ-eiffel.fr/idp/shibboleth": "13002612300013",
|
||||
"https://idp.univ-lemans.fr/idp/shibboleth": "19720916600010",
|
||||
"https://idp.univ-lille.fr/idp/shibboleth": "13002975400012",
|
||||
"https://idp.univ-lyon2.fr/idp/shibboleth": "19691775100014",
|
||||
"https://idp.univ-lyon3.fr/idp/shibboleth": "19692437700282",
|
||||
"https://idp.univ-orleans.fr/idp/shibboleth": "19450855200016",
|
||||
"https://idp.univ-perp.fr/idp/shibboleth": "19660437500010",
|
||||
"https://idp.univ-tln.fr/idp/shibboleth": "19830766200017",
|
||||
"https://idp.univ-tlse3.fr/idp/shibboleth": "93827139200012",
|
||||
"https://idp.univ-tours.fr/idp/shibboleth": "19370800500478",
|
||||
"https://idp.universite-paris-saclay.fr/idp": "13002602400054",
|
||||
"https://idp1.agroparistech.fr/idp/shibboleth": "13000285000134",
|
||||
"https://idp1.ens-paris-saclay.fr/idp/shibboleth": "19940607500036",
|
||||
"https://idp1.univ-fcomte.fr/idp/shibboleth": "93810656400017",
|
||||
"https://idp2.amue.fr/idp/shibboleth": "18004312700091",
|
||||
"https://idp2.brgm.fr/idp/shibboleth": "58205614900120",
|
||||
"https://idp2.emse.fr/idp/shibboleth": "18009202500105",
|
||||
"https://idp2.univ-paris8.fr/idp/shibboleth": "19931827000014",
|
||||
"https://idp3.univ-lorraine.fr/idp/shibboleth": "13001550600012",
|
||||
"https://idp3.ut-capitole.fr/idp/shibboleth": "13003061200019",
|
||||
"https://idp4.unicaen.fr/idp/shibboleth": "19141408500016",
|
||||
"https://idpv3.univ-amu.fr/idp/shibboleth": "13001533200013",
|
||||
"https://janus.cnrs.fr/idp": "18008901303720",
|
||||
"https://multipass.imt-nord-europe.fr/idp/shibboleth": "18009202500139",
|
||||
"https://papangue.vetagro-sup.fr/idp/shibboleth": "13000858400018",
|
||||
"https://prod-idp.inria.fr": "18008904700013",
|
||||
"https://ruhnu.univ-tlse2.fr/idp/shibboleth": "19311383400017",
|
||||
"https://shib.mines-albi.fr/idp/shibboleth": "18009202500097",
|
||||
"https://shib.univ-lehavre.fr/idp/shibboleth": "19762762300097",
|
||||
"https://identite.univ-reims.fr/idp/shibboleth": "19511296600799",
|
||||
"https://shib2.unc.nc/idp/shibboleth": "13000322100012",
|
||||
"https://shibboleth.ens2m.fr/idp/shibboleth": "19250082500026",
|
||||
"https://shibboleth.grenoble-inp.fr/idp/shibboleth": "19381912500017",
|
||||
"https://shibboleth.insa-rouen.fr/idp/shibboleth": "19760165100023",
|
||||
"https://shibboleth.ube.fr/idp/shibboleth": "93823061200013",
|
||||
"https://shibboleth.univ-corse.fr/idp/shibboleth": "19202664900264",
|
||||
"https://shibboleth.univ-evry.fr/idp/shibboleth": "19911975100014",
|
||||
"https://shibboleth.univ-grenoble-alpes.fr/idp/shibboleth": "13002608100013",
|
||||
"https://shibboleth.univ-savoie.fr/idp/shibboleth": "19730858800015",
|
||||
"https://shibboleth03.chartes.psl.eu/idp/shibboleth": "19753478700043",
|
||||
"https://shibboleth3.utt.fr/idp/shibboleth": "19101060200032",
|
||||
"https://srv-fii.insa-toulouse.fr/idp/shibboleth": "19310152400018",
|
||||
"https://sso-ciation-saclay.centralesupelec.fr/idp/shibboleth": "13002076100016",
|
||||
"https://sso.bordeaux-inp.fr/idp/shibboleth": "13000635600013",
|
||||
"https://sso.ird.fr/idp/shibboleth": "18000602500159",
|
||||
"https://upnidp2.parisnanterre.fr/idp/shibboleth": "19921204400010",
|
||||
"https://vip.espci.fr/saml2/idp/metadata.php": "20000068500012",
|
||||
"urn:mace:cru.fr:federation:uhb.fr": "19350937900015",
|
||||
"urn:mace:cru.fr:federation:unilim.fr": "19870669900321",
|
||||
"urn:mace:cru.fr:federation:univ-nantes.fr": "13002974700016",
|
||||
"urn:mace:cru.fr:federation:univ-paris1.fr": "19751717000019",
|
||||
"urn:mace:cru.fr:federation:univ-rennes1.fr": "13003051300019",
|
||||
"urn:mace:cru.fr:federation:univ-rouen.fr": "19761904200017",
|
||||
"urn:mace:cru.fr:federation:univ-ubs.fr": "19561718800600"
|
||||
}
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: renater.agentconnect.gouv.fr
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
image:
|
||||
repository: lasuite/oidc2fer
|
||||
repository: ghcr.io/proconnect-gouv/oidc2fer
|
||||
pullPolicy: Always
|
||||
tag: "main"
|
||||
tag: "latest"
|
||||
|
||||
satosa:
|
||||
replicas: 2
|
||||
@@ -9,7 +9,8 @@ satosa:
|
||||
BASE_URL: https://oidc2fer-staging.beta.numerique.gouv.fr
|
||||
GUNICORN_CMD_ARGS: --workers=3
|
||||
|
||||
LOG_LEVEL: debug
|
||||
LOG_LEVEL: DEBUG
|
||||
LOG_LEVELS: '{ "satosa.backends.saml2": "DEBUG" }'
|
||||
|
||||
SAML2_DISCOVERY_URL: https://discovery.renater.fr/test/
|
||||
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/test/preview/preview-idps-test-metadata.xml
|
||||
@@ -23,10 +24,15 @@ satosa:
|
||||
|
||||
OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } }
|
||||
|
||||
SIRET_MAP: |-
|
||||
{
|
||||
"https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010"
|
||||
}
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: oidc2fer-staging.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
staging:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
outscale-production:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
---
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: registry-1.docker.io/bitnamicharts
|
||||
@@ -29,21 +46,3 @@ releases:
|
||||
- env.d/{{ .Environment.Name }}/values.oidc2fer.yaml.gotmpl
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
staging:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
outscale-production:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "oidc2fer.fullname" . -}}
|
||||
{{- $port := .Values.satosa.service.port -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -33,13 +34,15 @@ spec:
|
||||
- host: {{ .Values.ingress.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
{{- range .Values.ingress.paths }}
|
||||
- path: {{ .path | quote }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "oidc2fer.satosa.fullname" . }}
|
||||
name: {{ $fullName }}-satosa
|
||||
port:
|
||||
number: {{ .Values.satosa.service.port }}
|
||||
number: {{ $port }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.customBackends }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
|
||||
@@ -82,3 +82,16 @@ spec:
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
{{ if .Values.satosa.pdb.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "oidc2fer.common.selectorLabels" (list . $component) | nindent 6 }}
|
||||
{{ end }}
|
||||
|
||||
@@ -33,7 +33,9 @@ ingress:
|
||||
enabled: false
|
||||
className: null
|
||||
host: oidc2fer.example.com
|
||||
path: /
|
||||
paths:
|
||||
- path: '/$|^/\.well-known/openid-configuration$|^/images/|^/Saml2/|^/OIDC/|^/ping$'
|
||||
pathType: ImplementationSpecific # enables regex matching
|
||||
## @param ingress.hosts Additional host to configure for the Ingress
|
||||
hosts: []
|
||||
# - chart-example.local
|
||||
@@ -48,10 +50,17 @@ ingress:
|
||||
## @param ingress.customBackends Add custom backends to ingress
|
||||
customBackends: []
|
||||
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||
|
||||
## @section satosa
|
||||
|
||||
satosa:
|
||||
|
||||
## @param satosa.pdb.enabled Enable pdb on backend
|
||||
pdb:
|
||||
enabled: true
|
||||
|
||||
## @param satosa.command Override the satosa container command
|
||||
command: []
|
||||
|
||||
|
||||
@@ -31,3 +31,6 @@ attributes:
|
||||
uid:
|
||||
openid:
|
||||
- uid
|
||||
siret:
|
||||
openid:
|
||||
- siret
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .entity_id_to_siret_mapper import EntityIdToSiretMapper
|
||||
@@ -0,0 +1,23 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from satosa.micro_services.base import ResponseMicroService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EntityIdToSiretMapper(ResponseMicroService):
|
||||
def __init__(self, config, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.attribute = config.get("attribute", "siret")
|
||||
self.mapping = json.loads(config.get("mapping_json", "{}"))
|
||||
|
||||
def process(self, context, data):
|
||||
entity_id = data.auth_info.issuer
|
||||
if entity_id in self.mapping:
|
||||
siret = self.mapping[entity_id]
|
||||
logger.info("Mapping entity ID %s to SIRET %s", entity_id, siret)
|
||||
data.attributes[self.attribute] = siret
|
||||
else:
|
||||
logger.warning("No SIRET mapping found for entity ID %s", entity_id)
|
||||
return super().process(context, data)
|
||||
@@ -3,11 +3,11 @@ name: Saml2
|
||||
config:
|
||||
disco_srv: !ENV SAML2_DISCOVERY_URL
|
||||
entityid_endpoint: true
|
||||
mirror_force_authn: 'no'
|
||||
memorize_idp: 'no'
|
||||
use_memorized_idp_when_force_authn: 'no'
|
||||
send_requester_id: 'no'
|
||||
enable_metadata_reload: 'no'
|
||||
mirror_force_authn: false
|
||||
memorize_idp: false
|
||||
use_memorized_idp_when_force_authn: false
|
||||
send_requester_id: false
|
||||
enable_metadata_reload: false
|
||||
acs_selection_strategy: prefer_matching_host
|
||||
sp_config:
|
||||
name: Passerelle OIDC vers FER
|
||||
|
||||
@@ -43,6 +43,7 @@ config:
|
||||
- uid
|
||||
- given_name
|
||||
- usual_name
|
||||
- siret
|
||||
|
||||
# Set code/token lifetimes as short as possible
|
||||
authorization_code_lifetime: 60
|
||||
@@ -56,8 +57,5 @@ config:
|
||||
- given_name
|
||||
usual_name:
|
||||
- usual_name
|
||||
extra_id_token_claims:
|
||||
oidc-test-client:
|
||||
- acr
|
||||
agent-connect:
|
||||
- acr
|
||||
siret:
|
||||
- siret
|
||||
|
||||
@@ -13,4 +13,8 @@ config:
|
||||
# later in the PrimaryIdentifier processor.
|
||||
- ".+"
|
||||
eduPersonAffiliation:
|
||||
- "^faculty$"
|
||||
- "^staff$"
|
||||
- "^employee$"
|
||||
- "^researcher$"
|
||||
- "^teacher$"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module: oidc2fer.attribute_generators.EntityIdToSiretMapper
|
||||
name: EntityIdToSiretMapper
|
||||
config:
|
||||
attribute: siret
|
||||
mapping_json: !ENV SIRET_MAP
|
||||
@@ -17,6 +17,7 @@ MICRO_SERVICES:
|
||||
- plugins/microservices/primary_identifier.yaml
|
||||
- plugins/microservices/static_attributes.yaml
|
||||
- plugins/microservices/attribute_processor.yaml
|
||||
- plugins/microservices/siret_mapping.yaml
|
||||
LOGGING:
|
||||
# All the logging configuration is done in the Gunicorn config, this is just
|
||||
# here to avoid overwriting it with the default SATOSA config
|
||||
|
||||
@@ -23,18 +23,18 @@ license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"SATOSA==8.4.0",
|
||||
"gunicorn==22.0.0",
|
||||
"SATOSA==8.5.1",
|
||||
"gunicorn==23.0.0",
|
||||
"redis==5.0.4",
|
||||
"JSON-log-formatter==1.0",
|
||||
"WhiteNoise==6.7.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Bug Tracker" = "https://github.com/numerique-gouv/oidc2fer/issues/new"
|
||||
"Changelog" = "https://github.com/numerique-gouv/oidc2fer/blob/main/CHANGELOG.md"
|
||||
"Homepage" = "https://github.com/numerique-gouv/oidc2fer"
|
||||
"Repository" = "https://github.com/numerique-gouv/oidc2fer"
|
||||
"Bug Tracker" = "https://github.com/proconnect-gouv/oidc2fer/issues/new"
|
||||
"Changelog" = "https://github.com/proconnect-gouv/oidc2fer/blob/main/CHANGELOG.md"
|
||||
"Homepage" = "https://github.com/proconnect-gouv/oidc2fer"
|
||||
"Repository" = "https://github.com/proconnect-gouv/oidc2fer"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import json
|
||||
|
||||
from satosa.context import Context
|
||||
from satosa.internal import AuthenticationInformation, InternalData
|
||||
|
||||
from oidc2fer.attribute_generators import EntityIdToSiretMapper
|
||||
|
||||
|
||||
class TestEntityIdToSiretMapper:
|
||||
def create_mapper(self):
|
||||
mapper = EntityIdToSiretMapper(
|
||||
config={
|
||||
"attribute": "siret",
|
||||
"mapping_json": json.dumps(
|
||||
{
|
||||
"https://idp.example.fr": "12345678200010",
|
||||
}
|
||||
),
|
||||
},
|
||||
name="siret_mapper",
|
||||
base_url="https://satosa.example.com",
|
||||
)
|
||||
mapper.next = lambda ctx, data: data
|
||||
return mapper
|
||||
|
||||
def test_sets_siret_for_known_entityid(self):
|
||||
mapper = self.create_mapper()
|
||||
resp = InternalData(
|
||||
auth_info=AuthenticationInformation(issuer="https://idp.example.fr")
|
||||
)
|
||||
ctx = Context()
|
||||
mapper.process(ctx, resp)
|
||||
assert resp.attributes["siret"] == "12345678200010"
|
||||
|
||||
def test_does_not_set_siret_for_unknown_entityid(self):
|
||||
mapper = self.create_mapper()
|
||||
resp = InternalData(
|
||||
auth_info=AuthenticationInformation(issuer="https://unknown.example.fr")
|
||||
)
|
||||
ctx = Context()
|
||||
mapper.process(ctx, resp)
|
||||
assert "siret" not in resp.attributes
|
||||
@@ -20,6 +20,7 @@ def renater_test_idp(page, login):
|
||||
|
||||
def renater_wayf(page):
|
||||
page.get_by_text("Veuillez sélectionner").click()
|
||||
page.get_by_role("searchbox").fill("GIP RENATER - IdP de test")
|
||||
page.get_by_role("option", name="GIP RENATER - IdP de test", exact=True).click()
|
||||
page.get_by_role("button", name="Sélection").click()
|
||||
|
||||
@@ -48,6 +49,7 @@ def oidc_to_renater(
|
||||
"email": expected_email,
|
||||
"given_name": expected_given_name,
|
||||
"usual_name": expected_usual_name,
|
||||
"siret": "12345678200010",
|
||||
}.items() <= userinfo.items()
|
||||
return id_token
|
||||
|
||||
@@ -75,57 +77,58 @@ def test_oidc_to_renater_student_not_allowed(page: Page):
|
||||
expect(page.locator("pre")).to_contain_text('"error":"access_denied"')
|
||||
|
||||
|
||||
def agent_connect_login(page: Page, email):
|
||||
def pro_connect_login(page: Page, email):
|
||||
page.goto("https://fsa1v2.integ01.dev-agentconnect.fr/")
|
||||
page.get_by_label("Connexion à AgentConnect").click()
|
||||
page.get_by_role("button", name="S’identifier avec ProConnect").click()
|
||||
page.get_by_label("Email professionnel").fill(email)
|
||||
page.get_by_test_id("interaction-connection-button").click()
|
||||
|
||||
|
||||
def agent_connect_to_renater(
|
||||
def pro_connect_to_renater(
|
||||
page: Page,
|
||||
login="enseignant1",
|
||||
expected_email="georges.grospieds@formation.renater.fr",
|
||||
expected_given_name="Georges",
|
||||
expected_usual_name="Grospieds",
|
||||
):
|
||||
agent_connect_login(page, email=expected_email)
|
||||
pro_connect_login(page, email=expected_email)
|
||||
renater_wayf(page)
|
||||
renater_test_idp(page, login=login)
|
||||
|
||||
expect(page.locator("body")).to_contain_text(expected_email)
|
||||
text = page.inner_text("#json")
|
||||
text = page.inner_text("#userinfo")
|
||||
result = json.loads(text)
|
||||
assert {
|
||||
"uid": f"{login}@test-renater.fr",
|
||||
"email": expected_email,
|
||||
"given_name": expected_given_name,
|
||||
"usual_name": expected_usual_name,
|
||||
"siret": "12345678200010",
|
||||
}.items() <= result.items()
|
||||
return result
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"TEST_E2E_AC" not in os.environ, reason="Depends on staging deployment"
|
||||
"TEST_E2E_PC" not in os.environ, reason="Depends on staging deployment"
|
||||
)
|
||||
def test_agent_connect_to_renater_keeps_sub(browser: Browser):
|
||||
def test_pro_connect_to_renater_keeps_sub(browser: Browser):
|
||||
with browser.new_context().new_page() as page:
|
||||
result1 = agent_connect_to_renater(page)
|
||||
result1 = pro_connect_to_renater(page)
|
||||
with browser.new_context().new_page() as page:
|
||||
result2 = agent_connect_to_renater(page)
|
||||
result2 = pro_connect_to_renater(page)
|
||||
|
||||
assert result1["sub"] == result2["sub"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"TEST_E2E_AC" not in os.environ, reason="Depends on staging deployment"
|
||||
"TEST_E2E_PC" not in os.environ, reason="Depends on staging deployment"
|
||||
)
|
||||
def test_agent_connect_to_renater_student_not_allowed(page: Page):
|
||||
agent_connect_login(page, email="jean.dupont@formation.renater.fr")
|
||||
def test_pro_connect_to_renater_student_not_allowed(page: Page):
|
||||
pro_connect_login(page, email="jean.dupont@formation.renater.fr")
|
||||
renater_wayf(page)
|
||||
renater_test_idp(page, login="etudiant1")
|
||||
|
||||
expect(page.locator("body")).to_contain_text("Une erreur technique est survenue.")
|
||||
expect(page.locator("body")).to_contain_text("access_denied")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
|
||||
Reference in New Issue
Block a user