feature(chalice): support multi SSO redirect

This commit is contained in:
Taha Yassine Kraiem 2024-02-09 18:42:02 +01:00
parent 4e190cc00d
commit 19470cd41f
4 changed files with 37 additions and 16 deletions

View file

@ -15,16 +15,16 @@ fastapi = "==0.104.1"
gunicorn = "==21.2.0"
python-decouple = "==3.8"
apscheduler = "==3.10.4"
python3-saml = "==1.16.0"
python-multipart = "==0.0.6"
redis = "==5.0.1"
python3-saml = "==1.16.0"
azure-storage-blob = "==12.19.0"
psycopg = {extras = ["binary", "pool"], version = "==3.1.14"}
uvicorn = {extras = ["standard"], version = "==0.23.2"}
pydantic = {extras = ["email"], version = "==2.3.0"}
clickhouse-driver = {extras = ["lz4"], version = "==0.2.6"}
psycopg = {extras = ["binary", "pool"], version = "==3.1.12"}
[dev-packages]
[requires]
python_version = "3.11"
python_version = "3.12"

View file

@ -10,17 +10,18 @@ from starlette.datastructures import FormData
if config("ENABLE_SSO", cast=bool, default=True):
from onelogin.saml2.auth import OneLogin_Saml2_Auth
API_PREFIX = "/api"
SAML2 = {
"strict": config("saml_strict", cast=bool, default=True),
"debug": config("saml_debug", cast=bool, default=True),
"sp": {
"entityId": config("SITE_URL") + "/api/sso/saml2/metadata/",
"entityId": config("SITE_URL") + API_PREFIX + "/sso/saml2/metadata/",
"assertionConsumerService": {
"url": config("SITE_URL") + "/api/sso/saml2/acs/",
"url": config("SITE_URL") + API_PREFIX + "/sso/saml2/acs/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
"singleLogoutService": {
"url": config("SITE_URL") + "/api/sso/saml2/sls/",
"url": config("SITE_URL") + API_PREFIX + "/sso/saml2/sls/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
@ -110,8 +111,8 @@ async def prepare_request(request: Request):
# add / to /acs
if not path.endswith("/"):
path = path + '/'
if not path.startswith("/api"):
path = "/api" + path
if len(API_PREFIX) > 0 and not path.startswith(API_PREFIX):
path = API_PREFIX + path
return {
'https': 'on' if proto == 'https' else 'off',
@ -136,7 +137,13 @@ def get_saml2_provider():
config("idp_name", default="saml2")) > 0 else None
def get_landing_URL(jwt):
def get_landing_URL(jwt, redirect_to_link2=False):
if redirect_to_link2:
if len(config("sso_landing_override", default="")) == 0:
logging.warning("SSO trying to redirect to custom URL, but sso_landing_override env var is empty")
else:
return config("sso_landing_override") + "?jwt=%s" % jwt
return config("SITE_URL") + config("sso_landing", default="/login?jwt=%s") % jwt

View file

@ -16,12 +16,15 @@ mkdir .venv
# Installing dependencies (pipenv will detect the .venv folder and use it as a target)
pipenv install -r requirements.txt [--skip-lock]
# These commands must bu used everytime you make changes to FOSS.
# To clean the unused files before getting new ones
bash clean.sh
# To copy commun files from FOSS
bash prepare-dev.sh
# In case of an issue with python3-saml installation for MacOS,
# please follow these instructions:
https://github.com/xmlsec/python-xmlsec/issues/254#issuecomment-1726249435
```
### Building and deploying locally

View file

@ -1,9 +1,11 @@
import json
import logging
from fastapi import HTTPException, Request, Response, status
from chalicelib.utils import SAML2_helper
from chalicelib.utils.SAML2_helper import prepare_request, init_saml_auth
from routers.base import get_routers
import logging
logger = logging.getLogger(__name__)
@ -18,11 +20,11 @@ from starlette.responses import RedirectResponse
@public_app.get("/sso/saml2", tags=["saml2"])
@public_app.get("/sso/saml2/", tags=["saml2"])
async def start_sso(request: Request):
async def start_sso(request: Request, iFrame: bool = False):
request.path = ''
req = await prepare_request(request=request)
auth = init_saml_auth(req)
sso_built_url = auth.login()
sso_built_url = auth.login(return_to=json.dumps({'iFrame': iFrame}))
return RedirectResponse(url=sso_built_url)
@ -33,6 +35,8 @@ async def process_sso_assertion(request: Request):
session = req["cookie"]["session"]
auth = init_saml_auth(req)
redirect_to_link2 = json.loads(req.get("post_data", {}) \
.get('RelayState', '{}')).get("iFrame")
request_id = None
if 'AuthNRequestID' in session:
request_id = session['AuthNRequestID']
@ -111,7 +115,7 @@ async def process_sso_assertion(request: Request):
refresh_token_max_age = jwt["refreshTokenMaxAge"]
response = Response(
status_code=status.HTTP_302_FOUND,
headers={'Location': SAML2_helper.get_landing_URL(jwt["jwt"])})
headers={'Location': SAML2_helper.get_landing_URL(jwt["jwt"], redirect_to_link2=redirect_to_link2)})
response.set_cookie(key="refreshToken", value=refresh_token, path="/api/refresh",
max_age=refresh_token_max_age, secure=True, httponly=True)
return response
@ -124,6 +128,8 @@ async def process_sso_assertion_tk(tenantKey: str, request: Request):
session = req["cookie"]["session"]
auth = init_saml_auth(req)
redirect_to_link2 = json.loads(req.get("post_data", {}) \
.get('RelayState', '{}')).get("iFrame")
request_id = None
if 'AuthNRequestID' in session:
request_id = session['AuthNRequestID']
@ -194,9 +200,14 @@ async def process_sso_assertion_tk(tenantKey: str, request: Request):
jwt = users.authenticate_sso(email=email, internal_id=internal_id, exp=expiration)
if jwt is None:
return {"errors": ["null JWT"]}
return Response(
refresh_token = jwt["refreshToken"]
refresh_token_max_age = jwt["refreshTokenMaxAge"]
response = Response(
status_code=status.HTTP_302_FOUND,
headers={'Location': SAML2_helper.get_landing_URL(jwt)})
headers={'Location': SAML2_helper.get_landing_URL(jwt["jwt"], redirect_to_link2=redirect_to_link2)})
response.set_cookie(key="refreshToken", value=refresh_token, path="/api/refresh",
max_age=refresh_token_max_age, secure=True, httponly=True)
return response
@public_app.get('/sso/saml2/sls', tags=["saml2"])