feat: link local library

This commit is contained in:
gabdevele
2026-06-17 20:43:56 +02:00 Unverified
parent 9a770a2af0
commit db59882f33
327 changed files with 22274 additions and 1 deletions
@@ -0,0 +1,171 @@
import logging
from django.conf import settings
from pydantic import ValidationError
from django.urls import reverse
from django.http import HttpResponseRedirect
from spid_cie_oidc.entity.jwtse import create_jws
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from ..oidc import *
from ..oauth2 import *
from enum import Enum
from spid_cie_oidc.entity.exceptions import InvalidTrustchain
from spid_cie_oidc.entity.models import TrustChain
from spid_cie_oidc.entity.utils import get_key
from spid_cie_oidc.entity.trust_chain_operations import get_or_create_trust_chain
from spid_cie_oidc.relying_party.exceptions import ValidationException
from spid_cie_oidc.relying_party.settings import (
RP_DEFAULT_PROVIDER_PROFILES,
RP_PROVIDER_PROFILES
)
logger = logging.getLogger(__name__)
class TokenRequestType(str, Enum):
refresh = "refresh"
revocation = "revocation"
introspection = "introspection"
class SpidCieOidcRp:
"""
Baseclass with common methods for RPs
"""
def get_oidc_op(self, request) -> TrustChain:
"""
get available trust to a specific OP
"""
if not request.GET.get("provider", None):
logger.warning(
"Missing provider url. Please try '?provider=https://provider-subject/'"
)
raise InvalidTrustchain(
"Missing provider url. Please try '?provider=https://provider-subject/'"
)
trust_anchor = request.GET.get("trust_anchor", None)
if trust_anchor is not None and trust_anchor not in settings.OIDCFED_TRUST_ANCHORS:
logger.warning("Unallowed Trust Anchor")
raise InvalidTrustchain("Unallowed Trust Anchor")
if not trust_anchor:
for profile, value in settings.OIDCFED_IDENTITY_PROVIDERS.items():
if request.GET["provider"] in value:
trust_anchor = value[request.GET["provider"]]
if not trust_anchor:
trust_anchor = settings.OIDCFED_DEFAULT_TRUST_ANCHOR
tc = TrustChain.objects.filter(
sub=request.GET["provider"],
trust_anchor__sub=trust_anchor,
).first()
discover_trust = False
if not tc:
logger.info(f'Trust Chain not found for {request.GET["provider"]}')
discover_trust = True
elif not tc.is_active:
logger.warning(f"{tc} found but DISABLED at {tc.modified}")
raise InvalidTrustchain(f"{tc} found but DISABLED at {tc.modified}")
elif tc.is_expired:
logger.warning(f"{tc} found but expired at {tc.exp}")
logger.warning("Try to renew the trust chain")
discover_trust = True
if discover_trust:
tc = get_or_create_trust_chain(
subject=request.GET["provider"],
trust_anchor=trust_anchor,
# TODO - not sure that it's required for a RP that fetches OP directly from TA
# required_trust_marks = [],
force=True,
)
return tc
def validate_json_schema(self, request, schema_type, error_description):
try:
schema = RP_PROVIDER_PROFILES[RP_DEFAULT_PROVIDER_PROFILES]
schema[schema_type](**request)
except ValidationError as e:
logger.error(
f"{error_description} "
f"for {request.get('client_id', None)}: {e}"
)
raise ValidationException()
def get_token_request(self, auth_token, request, token_type):
default_logout_url = getattr(
settings, "LOGOUT_REDIRECT_URL", None
) or reverse("spid_cie_rp_landing")
global audience
authz = auth_token.authz_request
rp_conf = FederationEntityConfiguration.objects.filter(
sub=authz.client_id,
is_active=True,
).first()
token_request_data = dict(
client_id=auth_token.authz_request.client_id,
client_assertion_type="urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
)
if token_type == TokenRequestType.refresh:
token_request_data["grant_type"] = "refresh_token"
token_request_data["refresh_token"] = auth_token.refresh_token
audience = authz.provider_configuration["token_endpoint"]
elif token_type == TokenRequestType.revocation:
token_request_data["token"] = auth_token.access_token
audience = authz.provider_configuration["revocation_endpoint"]
elif token_type == TokenRequestType.introspection:
token_request_data["token"] = auth_token.access_token
audience = authz.provider_configuration["introspection_endpoint"]
if not audience:
logger.warning(
f"{authz.provider_id} doesn't expose the token ", token_type, " endpoint."
)
return HttpResponseRedirect(default_logout_url)
# private_key_jwt
client_assertion = create_jws(
{
"iss": authz.client_id,
"sub": authz.client_id,
"aud": [audience],
"iat": iat_now(),
"exp": exp_from_now(),
"jti": str(uuid.uuid4())
},
jwk_dict = get_key(rp_conf.jwks_core)
)
token_request_data["client_assertion"] = client_assertion
try:
token_request = requests.post(
audience,
data=token_request_data,
timeout=getattr(
settings, "HTTPC_TIMEOUT", 8
)
) # nosec - B113
if token_request.status_code != 200: # pragma: no cover
logger.error(
f"Something went wrong with refresh token request: {token_request.status_code}"
)
return token_request
except Exception as e: # pragma: no cover
logger.error(f"Error in token request: {e}")
@@ -0,0 +1,214 @@
import json
import logging
import uuid
from copy import deepcopy
from djagger.decorators import schema
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
from django.views import View
from spid_cie_oidc.entity.exceptions import InvalidTrustchain
from spid_cie_oidc.entity.jwtse import create_jws
from spid_cie_oidc.entity.utils import get_jwks, get_key, KeyUsage
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.relying_party.settings import OIDCFED_ACR_PROFILES, RP_PROVIDER_PROFILES, \
RP_DEFAULT_PROVIDER_PROFILES
from ..models import OidcAuthentication
from ..settings import (
RP_PKCE_CONF,
RP_REQUEST_CLAIM_BY_PROFILE,
RP_REQUEST_EXP
)
from ..utils import (
http_dict_to_redirect_uri_path,
random_string
)
from . import SpidCieOidcRp
logger = logging.getLogger(__name__)
schema_profile = RP_PROVIDER_PROFILES[RP_DEFAULT_PROVIDER_PROFILES]
@schema(
summary="OIDC Relying Party Authorization begin",
methods=['GET'],
get_response_schema= {
"302": schema_profile["authorization_request_doc"],
},
external_docs = {
"alt_text": "AgID SPID OIDC Guidelines",
"url": (
"https://www.agid.gov.it/it/agenzia/stampa-e-comunicazione/"
"notizie/2021/12/06/openid-connect-spid-adottate-linee-guida"
),
},
tags = ['Relying Party']
)
class SpidCieOidcRpBeginView(SpidCieOidcRp, View):
"""
View which processes the actual Authz request and
returns a Http Redirect
"""
error_template = "rp_error.html"
def get(self, request, *args, **kwargs):
"""
http://localhost:8001/oidc/rp/authorization?provider=http://127.0.0.1:8002/&profile=spid
"""
try:
tc = self.get_oidc_op(request)
if not tc:
context = {
"error": "request rejected",
"error_description": "Trust Chain is unavailable.",
}
return render(request, self.error_template, context, status = 404)
except InvalidTrustchain as exc:
context = {
"error": "request rejected",
"error_description": str(exc.args),
}
return render(request, self.error_template, context, status=404)
except Exception as exc:
context = {
"error": "request rejected",
"error_description": _(str(exc.args)),
}
return render(request, self.error_template, context, status=403)
provider_metadata = tc.metadata.get('openid_provider', None)
if not provider_metadata:
context = {
"error": "request rejected",
"error_description": _("provider metadata not found"),
}
return render(request, self.error_template, context, status=404)
entity_conf = FederationEntityConfiguration.objects.filter(
entity_type="openid_relying_party",
# TODO: RPs multitenancy?
# sub = request.build_absolute_uri()
).first()
if not entity_conf:
context = {
"error": "request rejected",
"error_description": _("Missing configuration."),
}
return render(request, self.error_template, context, status=404)
client_conf = entity_conf.metadata["openid_relying_party"]
if not (
provider_metadata.get("jwks_uri", None)
or
provider_metadata.get("jwks", None)
):
context = {
"error": "request rejected",
"error_description": _("Invalid provider Metadata."),
}
return render(request, self.error_template, context, status=404)
jwks_dict = get_jwks(provider_metadata, federation_jwks=tc.jwks)
# stores the resolves jwks in the provider metadata linked to this authz request
provider_metadata['jwks'] = {'keys': jwks_dict}
if not jwks_dict:
_msg = f"Failed to get jwks from {tc.sub}"
logger.error(_msg)
context = {
"error": "request rejected",
"error_description": _msg
}
return render(request, self.error_template, context)
authz_endpoint = provider_metadata["authorization_endpoint"]
redirect_uri = request.GET.get("redirect_uri", client_conf["redirect_uris"][0])
if redirect_uri not in client_conf["redirect_uris"]:
logger.warning(
f"Requested for unknown redirect uri {redirect_uri}. "
f"Reverted to default {client_conf['redirect_uris'][0]}."
)
redirect_uri = client_conf["redirect_uris"][0]
_profile = request.GET.get("profile", "spid")
_timestamp_now = int(timezone.localtime().timestamp())
authz_data = dict(
iss=client_conf["client_id"],
scope= request.GET.get("scope", None) or "openid",
redirect_uri=redirect_uri,
response_type=client_conf["response_types"][0],
nonce=random_string(32),
state=random_string(32),
client_id=client_conf["client_id"],
endpoint=authz_endpoint,
acr_values= OIDCFED_ACR_PROFILES,
iat=_timestamp_now,
exp =_timestamp_now + RP_REQUEST_EXP,
jti = str(uuid.uuid4()),
aud=[tc.sub, authz_endpoint],
claims=RP_REQUEST_CLAIM_BY_PROFILE[_profile],
)
_prompt = request.GET.get("prompt", "consent login")
# if "offline_access" in authz_data["scope"]:
# _prompt.extend(["consent login"])
authz_data["prompt"] = _prompt
# PKCE
pkce_func = import_string(RP_PKCE_CONF["function"])
pkce_values = pkce_func(**RP_PKCE_CONF["kwargs"])
authz_data.update(pkce_values)
#
authz_entry = dict(
client_id=client_conf["client_id"],
state=authz_data["state"],
endpoint=authz_endpoint,
# TODO: better have here an organization name
provider_id=tc.sub,
data=json.dumps(authz_data),
provider_configuration=provider_metadata,
)
# TODO: Prune the old or unbounded authz ...
OidcAuthentication.objects.create(**authz_entry)
authz_data.pop("code_verifier")
# add the signed request object
authz_data_obj = deepcopy(authz_data)
authz_data_obj["iss"] = client_conf["client_id"]
# sub claim MUST not be used to prevent that this jwt
# could be reused as a private_key_jwt
# authz_data_obj["sub"] = client_conf["client_id"]
jwk_core_sig = get_key(entity_conf.jwks_core, KeyUsage.signature)
request_obj = create_jws(authz_data_obj, jwk_core_sig)
authz_data["request"] = request_obj
uri_path = http_dict_to_redirect_uri_path(
{
"client_id": authz_data["client_id"],
"scope" : authz_data["scope"],
"response_type": authz_data["response_type"],
"code_challenge": authz_data["code_challenge"],
"code_challenge_method": authz_data["code_challenge_method"],
"request": authz_data["request"]
}
)
if "?" in authz_endpoint:
qstring = "&"
else:
qstring = "?"
url = qstring.join((authz_endpoint, uri_path))
logger.info(f"Starting Authz request to {url}")
return HttpResponseRedirect(url)
@@ -0,0 +1,317 @@
import json
import logging
from djagger.decorators import schema
from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views import View
from spid_cie_oidc.entity.jwtse import (
unpad_jwt_payload,
verify_jws,
verify_at_hash
)
from spid_cie_oidc.entity.models import FederationEntityConfiguration
from spid_cie_oidc.entity.settings import HTTPC_PARAMS
from spid_cie_oidc.entity.utils import get_jwks, get_jwk_from_jwt
from spid_cie_oidc.relying_party.exceptions import ValidationException
from spid_cie_oidc.relying_party.settings import RP_PROVIDER_PROFILES, RP_DEFAULT_PROVIDER_PROFILES
from ..models import OidcAuthentication, OidcAuthenticationToken
from ..oauth2 import *
from ..oidc import *
from ..settings import (
RP_ATTR_MAP,
RP_USER_CREATE,
RP_USER_LOOKUP_FIELD,
)
from ..utils import process_user_attributes
from . import SpidCieOidcRp
logger = logging.getLogger(__name__)
schema_profile = RP_PROVIDER_PROFILES[RP_DEFAULT_PROVIDER_PROFILES]
@schema(
summary="OIDC Relying Party auth code Callback",
methods=['GET'],
request_schema={
"application/x-www-form-urlencoded" : schema_profile["authn_response"],
},
external_docs = {
"alt_text": "AgID SPID OIDC Guidelines",
"url": (
"https://www.agid.gov.it/it/agenzia/stampa-e-comunicazione/"
"notizie/2021/12/06/openid-connect-spid-adottate-linee-guida"
),
},
tags = ['Relying Party']
)
class SpidCieOidcRpCallbackView(View, SpidCieOidcRp, OidcUserInfo, OAuth2AuthorizationCodeGrant):
"""
View which processes an Authorization Response
https://tools.ietf.org/html/rfc6749#section-4.1.2
eg:
/redirect_uri?code=tYkP854StRqBVcW4Kg4sQfEN5Qz&state=R9EVqaazGsj3wg5JgxIgm8e8U4BMvf7W
"""
error_template = "rp_error.html"
def user_reunification(self, user_attrs: dict):
user_model = get_user_model()
if user_attrs.get(RP_USER_LOOKUP_FIELD, None):
lookup = {
f"attributes__{RP_USER_LOOKUP_FIELD}": user_attrs.get(RP_USER_LOOKUP_FIELD)
}
else:
logger.warning("User attribute not found for reunification, try sub")
lookup = {
"username": user_attrs["username"]
}
user = user_model.objects.filter(**lookup).first()
if user:
user.attributes.update(user_attrs)
user.save()
logger.info(f"{RP_USER_LOOKUP_FIELD} matched on user {user}")
return user
elif RP_USER_CREATE:
user = user_model.objects.create(
username=user_attrs.get("username", user_attrs["sub"]),
first_name=user_attrs.get("given_name", user_attrs["sub"]),
last_name=user_attrs.get("family_name", user_attrs["sub"]),
email=user_attrs.get("email", ""),
attributes=user_attrs,
)
logger.info(f"Created new user {user}")
return user
def get(self, request, *args, **kwargs):
"""
The Authorization callback, the redirect uri where the auth code lands
"""
request_args = {k: v for k, v in request.GET.items()}
if "error" in request_args:
return render(
request,
self.error_template,
request_args,
status=401
)
authz = OidcAuthentication.objects.filter(
state=request_args.get("state"),
)
try:
self.validate_json_schema(
request.GET.dict(),
"authn_response",
"Authn response object validation failed"
)
except ValidationException:
return JsonResponse(
{
"error": "invalid_request",
"error_description": "Authn response object validation failed",
},
status = 400
)
if not authz:
context = {
"error": "unauthorized request",
"error_description": _("Authentication not found"),
}
return render(request, self.error_template, context, status=401)
else:
authz = authz.last()
code = request.GET.get("code")
# mixups attacks prevention
if request.GET.get('iss', None):
if request.GET['iss'] != authz.provider_id:
context = {
"error": "invalid request",
"error_description": _(
"authn response validation failed: mixups attack prevention."
),
}
return render(request, self.error_template, context, status=400)
authz_token = OidcAuthenticationToken.objects.create(
authz_request=authz, code=code
)
self.rp_conf = FederationEntityConfiguration.objects.filter(
sub=authz_token.authz_request.client_id
).first()
if not self.rp_conf:
context = {
"error": "invalid request",
"error_description": _("Relying party not found"),
}
return render(request, self.error_template, context, status=400)
authz_data = json.loads(authz.data)
token_response = self.access_token_request(
redirect_uri=authz_data["redirect_uri"],
state=authz.state,
code=code,
issuer_id=authz.provider_id,
client_conf=self.rp_conf,
token_endpoint_url=authz.provider_configuration["token_endpoint"],
audience=[authz.provider_id],
code_verifier=authz_data.get("code_verifier"),
)
if not token_response:
context = {
"error": "invalid token response",
"error_description": _("Token response seems not to be valid"),
}
return render(request, self.error_template, context, status=400)
else:
try:
self.validate_json_schema(
token_response,
"token_response",
"Token response object validation failed"
)
except ValidationException:
return JsonResponse(
{
"error": "invalid_request",
"error_description": "Token response object validation failed",
},
status = 400
)
jwks = get_jwks(authz.provider_configuration)
access_token = token_response["access_token"]
id_token = token_response["id_token"]
op_ac_jwk = get_jwk_from_jwt(access_token, jwks)
op_id_jwk = get_jwk_from_jwt(id_token, jwks)
if not op_ac_jwk or not op_id_jwk:
logger.warning(
"Token signature validation error, "
f"the tokens were signed with a different kid from: {jwks}."
)
context = {
"error": "invalid_token",
"error_description": _("Authentication token seems not to be valid."),
}
return render(request, self.error_template, context, status=403)
try:
verify_jws(access_token, op_ac_jwk)
except Exception as e:
logger.warning(
f"Access Token signature validation error: {e} "
)
context = {
"error": "token verification failed",
"error_description": _("Authentication token validation error."),
}
return render(request, self.error_template, context, status=403)
try:
verify_jws(id_token, op_id_jwk)
except Exception as e:
logger.warning(
f"ID Token signature validation error: {e} "
)
context = {
"error": "token verification failed",
"error_description": _("ID token validation error."),
}
return render(request, self.error_template, context, status=403)
decoded_id_token = unpad_jwt_payload(id_token)
logger.debug(decoded_id_token)
try:
verify_at_hash(decoded_id_token, access_token)
except Exception as e:
logger.warning(
f"at_hash validation error: {e} "
)
context = {
"error": "at_hash verification failed",
"error_description": _("at_hash validation error."),
}
return render(request, self.error_template, context, status=403)
decoded_access_token = unpad_jwt_payload(access_token)
logger.debug(decoded_access_token)
authz_token.access_token = access_token
authz_token.id_token = id_token
authz_token.scope = token_response.get("scope")
authz_token.token_type = token_response["token_type"]
authz_token.expires_in = token_response["expires_in"]
authz_token.save()
userinfo = self.get_userinfo(
authz.state,
authz_token.access_token,
authz.provider_configuration,
verify=HTTPC_PARAMS.get("connection", {}).get("ssl", True)
)
if not userinfo:
logger.warning(
"Userinfo request failed for state: "
f"{authz.state} to {authz.provider_id}"
)
context = {
"error": "invalid userinfo response",
"error_description": _("UserInfo response seems not to be valid"),
}
return render(request, self.error_template, context, status=400)
# here django user attr mapping
user_attrs = process_user_attributes(userinfo, RP_ATTR_MAP, authz.__dict__)
if not user_attrs:
_msg = "No user attributes have been processed"
logger.warning(f"{_msg}: {userinfo}")
# TODO: verify error message and status
context = {
"error": "missing user attributes",
"error_description": _(f"{_msg}: {userinfo}"),
}
return render(request, self.error_template, context, status=403)
user = self.user_reunification(user_attrs)
if not user:
# TODO: verify error message and status
context = {"error": _("No user found"), "error_description": _("")}
return render(request, self.error_template, context, status=403)
request.session["rt_expiration"] = 0
if token_response.get('refresh_token', None):
refresh_token = token_response["refresh_token"]
authz_token.refresh_token = refresh_token
decoded_refresh_token = unpad_jwt_payload(refresh_token)
request.session["rt_expiration"] = decoded_refresh_token['exp'] - iat_now()
request.session["rt_jti"] = decoded_refresh_token['jti']
logger.info(decoded_refresh_token)
# authenticate the user
login(request, user)
request.session["oidc_rp_user_attrs"] = user_attrs
request.session["at_expiration"] = decoded_access_token['exp'] - iat_now()
request.session["at_jti"] = decoded_access_token['jti']
authz_token.user = user
authz_token.save()
return HttpResponseRedirect(
getattr(
settings, "LOGIN_REDIRECT_URL", None
) or reverse("spid_cie_rp_echo_attributes")
)
@@ -0,0 +1,19 @@
import logging
from django.shortcuts import render
from django.views import View
logger = logging.getLogger(__name__)
class SpidCieOidcRpCallbackEchoAttributes(View):
template = "echo_attributes.html"
def get(self, request):
data = {"oidc_rp_user_attrs": request.session.get("oidc_rp_user_attrs", {}),
"at_expiration": request.session.get("at_expiration"),
"at_jti": request.session.get("at_jti"),
"rt_expiration": request.session.get("rt_expiration"),
"rt_jti": request.session.get("rt_jti")}
return render(request, self.template, data)
@@ -0,0 +1,78 @@
import logging
from djagger.decorators import schema
from django.http import HttpResponseRedirect
from django.urls import reverse
from ..models import OidcAuthenticationToken
from ..oauth2 import *
from ..oidc import *
from . import SpidCieOidcRp, TokenRequestType
from django.views import View
from spid_cie_oidc.entity.jwtse import (
unpad_jwt_payload,
)
logger = logging.getLogger(__name__)
@schema(
summary="OIDC Relying party refresh token request",
methods=['GET'],
external_docs={
"alt_text": "AgID SPID OIDC Guidelines",
"url": (
"https://www.agid.gov.it/it/agenzia/stampa-e-comunicazione/"
"notizie/2021/12/06/openid-connect-spid-adottate-linee-guida"
),
},
tags=['Relying Party']
)
class SpidCieOidcRefreshToken(SpidCieOidcRp, View):
error_template = "rp_error.html"
def get(self, request, *args, **kwargs):
"""
Call the token endpoint of the op
"""
auth_tokens = OidcAuthenticationToken.objects.filter(
user=request.user
).filter(revoked__isnull=True)
if not auth_tokens:
logger.warning(
"Token request failed: not found any authentication session"
)
auth_token = auth_tokens.last()
try:
token_response = self.get_token_request(auth_token, request, TokenRequestType.refresh) # "refresh")
if token_response.status_code == 400:
return HttpResponseRedirect(reverse("spid_cie_rp_landing"))
refresh_token_response = json.loads(token_response.content.decode())
auth_token.refresh_token = refresh_token_response["refresh_token"]
auth_token.access_token = refresh_token_response["access_token"]
auth_token.save()
decoded_access_token = unpad_jwt_payload(refresh_token_response["access_token"])
decoded_refresh_token = unpad_jwt_payload(refresh_token_response["refresh_token"])
request.session["rt_expiration"] = decoded_refresh_token['exp'] - iat_now()
request.session["rt_jti"] = decoded_refresh_token['jti']
request.session["oidc_rp_user_attrs"] = request.user.attributes
request.session["at_expiration"] = decoded_access_token['exp'] - iat_now()
request.session["at_jti"] = decoded_access_token['jti']
return HttpResponseRedirect(
getattr(
settings, "LOGIN_REDIRECT_URL", None
) or reverse("spid_cie_rp_echo_attributes")
)
except Exception as e: # pragma: no cover
logger.warning(f"Refresh Token request failed: {e}")
@@ -0,0 +1,60 @@
import logging
from djagger.decorators import schema
from django.conf import settings
from django.contrib.auth import logout
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils import timezone
from ..models import OidcAuthenticationToken
from . import SpidCieOidcRp, TokenRequestType
from django.views import View
logger = logging.getLogger(__name__)
@schema(
summary="OIDC Relying party logout",
methods=['GET'],
external_docs={
"alt_text": "AgID SPID OIDC Guidelines",
"url": (
"https://www.agid.gov.it/it/agenzia/stampa-e-comunicazione/"
"notizie/2021/12/06/openid-connect-spid-adottate-linee-guida"
),
},
tags=['Relying Party']
)
class SpidCieOidcRpLogout(SpidCieOidcRp, View):
def get(self, request):
"""
Call the token revocation endpoint of the op
"""
auth_tokens = OidcAuthenticationToken.objects.filter(
user=request.user
).filter(revoked__isnull=True)
default_logout_url = getattr(
settings, "LOGOUT_REDIRECT_URL", None
) or reverse("spid_cie_rp_landing")
if not auth_tokens:
logger.warning(
"Token revocation failed: not found any authentication session"
)
return HttpResponseRedirect(default_logout_url)
auth_token = auth_tokens.last()
logger.info(f"{request.user} logout")
logout(request)
try:
self.get_token_request(auth_token, request, TokenRequestType.revocation) # "revocation")
auth_token.logged_out = timezone.localtime()
auth_token.save()
except Exception as e: # pragma: no cover
logger.warning(f"Token revocation failed: {e}")
auth_tokens.update(revoked=timezone.localtime())
return HttpResponseRedirect(default_logout_url)
@@ -0,0 +1,56 @@
import logging
from djagger.decorators import schema
from ..models import OidcAuthenticationToken
from ..oauth2 import *
from ..oidc import *
from . import SpidCieOidcRp, TokenRequestType
from django.views import View
from django.shortcuts import render
logger = logging.getLogger(__name__)
@schema(
summary="OIDC Relying party refresh token request",
methods=['GET'],
external_docs={
"alt_text": "AgID SPID OIDC Guidelines",
"url": (
"https://www.agid.gov.it/it/agenzia/stampa-e-comunicazione/"
"notizie/2021/12/06/openid-connect-spid-adottate-linee-guida"
),
},
tags=['Relying Party']
)
class SpidCieOidcRpIntrospection(SpidCieOidcRp, View):
error_template = "rp_error.html"
template = "rp_introspection.html"
def get(self, request, *args, **kwargs):
"""
Call the token endpoint of the op
"""
auth_tokens = OidcAuthenticationToken.objects.filter(
user=request.user
).filter(revoked__isnull=True)
if not auth_tokens:
logger.warning(
"Token request failed: not found any authentication session"
)
auth_token = auth_tokens.last()
try:
token_response = self.get_token_request(auth_token, request, TokenRequestType.introspection)
introspection_token_response = json.loads(token_response.content.decode())
data = {"introspection": introspection_token_response}
return render(request, self.template, data)
except Exception as e: # pragma: no cover
logger.warning(f"Refresh Token request failed: {e}")
@@ -0,0 +1,60 @@
from copy import deepcopy
import logging
import random
from django.conf import settings
from django.shortcuts import render
from spid_cie_oidc.entity.trust_chain_operations import get_or_create_trust_chain
logger = logging.getLogger(__name__)
def oidc_rp_landing(request):
spid_providers = {
k: {"sub": v} for k, v in
settings.OIDCFED_IDENTITY_PROVIDERS.get("spid", {}).items()
}
cie_providers = {
k: {"sub": v} for k, v in
settings.OIDCFED_IDENTITY_PROVIDERS.get("cie", {}).items()
}
providers = deepcopy(spid_providers)
providers.update(cie_providers)
subs = list(spid_providers.keys()) + list(cie_providers.keys())
tcs = []
for sub in subs:
try:
tc = get_or_create_trust_chain(
subject = sub,
trust_anchor = providers[sub]["sub"]
)
tcs.append(tc)
except Exception as e:
logger.warning(
f"Failed trust chain for {sub} to {providers[sub]}: {e}"
)
for i in tcs:
if i.sub in settings.OIDCFED_IDENTITY_PROVIDERS.get("spid", {}):
target = spid_providers
elif i.sub in settings.OIDCFED_IDENTITY_PROVIDERS.get("cie", {}):
target = cie_providers
else:
continue
target[i.sub] = {
"sub": i.sub,
"logo_uri" : i.metadata.get("openid_provider", {}).get("logo_uri", ""),
"organization_name": i.metadata.get("openid_provider", {}).get("organization_name","")
}
s_spid_providers = list(spid_providers.items())
random.shuffle(s_spid_providers)
content = {
"spid_providers": dict(s_spid_providers),
"cie_providers" : cie_providers
}
return render(request, "rp_landing.html", content)