ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: устойчивый global logout и auth fallback
This commit is contained in:
@@ -6,6 +6,7 @@ from urllib.parse import urlencode
|
||||
|
||||
import jwt
|
||||
import requests
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils import timezone
|
||||
from django.views import View
|
||||
@@ -53,6 +54,7 @@ class NodeDCOIDCInitiateEndpoint(View):
|
||||
|
||||
if request.GET.get("prompt") == "login":
|
||||
params["prompt"] = "login"
|
||||
params["max_age"] = "0"
|
||||
|
||||
return HttpResponseRedirect(f"{discovery['authorization_endpoint']}?{urlencode(params)}")
|
||||
|
||||
@@ -65,17 +67,28 @@ class NodeDCOIDCCallbackEndpoint(View):
|
||||
base_url = base_host(request=request, is_app=True)
|
||||
|
||||
if request.GET.get("error"):
|
||||
return oidc_error_redirect(base_url, next_path, "oidc_provider_error")
|
||||
request.session.pop(OIDC_SESSION_KEY, None)
|
||||
return oidc_login_redirect(base_url, next_path)
|
||||
|
||||
state = request.GET.get("state")
|
||||
code = request.GET.get("code")
|
||||
|
||||
if not state or state != oidc_session.get("state") or not code:
|
||||
return oidc_error_redirect(base_url, next_path, "oidc_state_failed")
|
||||
request.session.pop(OIDC_SESSION_KEY, None)
|
||||
return oidc_login_redirect(base_url, next_path)
|
||||
|
||||
try:
|
||||
discovery = load_discovery(config["issuer"])
|
||||
token_set = exchange_code(discovery, config, code, oidc_session.get("code_verifier"))
|
||||
claims = verify_id_token(discovery, config, token_set["id_token"], oidc_session.get("nonce"))
|
||||
except (KeyError, RuntimeError, requests.RequestException, jwt.PyJWTError):
|
||||
request.session.pop(OIDC_SESSION_KEY, None)
|
||||
return oidc_login_redirect(base_url, next_path)
|
||||
|
||||
if is_logout_guard_active(claims):
|
||||
request.session.pop(OIDC_SESSION_KEY, None)
|
||||
return oidc_login_redirect(base_url, next_path, prompt_login=True)
|
||||
|
||||
discovery = load_discovery(config["issuer"])
|
||||
token_set = exchange_code(discovery, config, code, oidc_session.get("code_verifier"))
|
||||
claims = verify_id_token(discovery, config, token_set["id_token"], oidc_session.get("nonce"))
|
||||
groups = normalize_groups(claims.get("groups"))
|
||||
|
||||
if not has_required_group(groups):
|
||||
@@ -260,5 +273,87 @@ def first_string_claim(claims, *keys):
|
||||
return None
|
||||
|
||||
|
||||
def normalize_logout_guard_value(value):
|
||||
return value.strip().lower() if isinstance(value, str) else ""
|
||||
|
||||
|
||||
def get_logout_guard_cache_key(kind, value):
|
||||
normalized_value = normalize_logout_guard_value(value)
|
||||
return f"nodedc:logout-guard:{kind}:{normalized_value}" if normalized_value else ""
|
||||
|
||||
|
||||
def get_logout_guard_cache_keys(claims):
|
||||
keys = set()
|
||||
|
||||
subject_key = get_logout_guard_cache_key("subject", claims.get("sub"))
|
||||
email_key = get_logout_guard_cache_key("email", claims.get("email"))
|
||||
|
||||
if subject_key:
|
||||
keys.add(subject_key)
|
||||
if email_key:
|
||||
keys.add(email_key)
|
||||
|
||||
return sorted(keys)
|
||||
|
||||
|
||||
def get_claim_auth_time(claims):
|
||||
auth_time = claims.get("auth_time")
|
||||
|
||||
try:
|
||||
return int(auth_time)
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_logout_guard_time(claims):
|
||||
guard_times = []
|
||||
|
||||
for cache_key in get_logout_guard_cache_keys(claims):
|
||||
value = cache.get(cache_key)
|
||||
|
||||
try:
|
||||
guard_times.append(int(value))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return max(guard_times) if guard_times else 0
|
||||
|
||||
|
||||
def clear_logout_guard(claims):
|
||||
cache_keys = get_logout_guard_cache_keys(claims)
|
||||
|
||||
if cache_keys:
|
||||
cache.delete_many(cache_keys)
|
||||
|
||||
|
||||
def is_logout_guard_active(claims):
|
||||
logout_guard_time = get_logout_guard_time(claims)
|
||||
|
||||
if not logout_guard_time:
|
||||
return False
|
||||
|
||||
auth_time = get_claim_auth_time(claims)
|
||||
|
||||
if auth_time > logout_guard_time:
|
||||
clear_logout_guard(claims)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def oidc_error_redirect(base_url, next_path, error_code):
|
||||
return HttpResponseRedirect(get_safe_redirect_url(base_url=base_url, next_path=next_path, params={"error": error_code}))
|
||||
|
||||
|
||||
def oidc_login_redirect(base_url, next_path, prompt_login=False):
|
||||
params = {}
|
||||
|
||||
if validate_next_path(next_path):
|
||||
params["next_path"] = next_path
|
||||
|
||||
if prompt_login:
|
||||
params["prompt"] = "login"
|
||||
|
||||
query_string = f"?{urlencode(params)}" if params else ""
|
||||
|
||||
return HttpResponseRedirect(f"{base_url.rstrip('/')}/auth/oidc/login/{query_string}")
|
||||
|
||||
Reference in New Issue
Block a user