feat: link local library
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import logging
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import OidcAuthentication, OidcAuthenticationToken
|
||||
from .utils import html_json_preview
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OidcAuthenticationTokenInline(admin.StackedInline):
|
||||
model = OidcAuthenticationToken
|
||||
extra = 0
|
||||
max_num = 1
|
||||
readonly_fields = (
|
||||
"scope",
|
||||
"expires_in",
|
||||
"token_type",
|
||||
"code",
|
||||
"access_token",
|
||||
"id_token",
|
||||
"refresh_token",
|
||||
"access_token_preview",
|
||||
"id_token_preview",
|
||||
)
|
||||
|
||||
|
||||
@admin.register(OidcAuthentication)
|
||||
class OidcAuthenticationAdmin(admin.ModelAdmin):
|
||||
search_fields = ("endpoint", "state", "client_id", )
|
||||
list_display = ("client_id", "state", "endpoint", "created", "modified")
|
||||
list_filter = ("created", "endpoint")
|
||||
inlines = (OidcAuthenticationTokenInline,)
|
||||
readonly_fields = (
|
||||
"client_id",
|
||||
"state",
|
||||
"endpoint",
|
||||
"successful",
|
||||
"json_preview",
|
||||
"provider_configuration",
|
||||
"created",
|
||||
"modified",
|
||||
)
|
||||
exclude = ("issuer_id", "data", "provider_configuration", "provider_jwks")
|
||||
fieldsets = (
|
||||
(None, {"fields": ("provider_id", "client_id", "state", "endpoint")}),
|
||||
(
|
||||
"Status",
|
||||
{
|
||||
"fields": (
|
||||
"successful",
|
||||
"created",
|
||||
"modified",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Authorization request previews",
|
||||
{
|
||||
"fields": (
|
||||
"json_preview",
|
||||
),
|
||||
"classes": ("collapse",),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Provider Discovery result",
|
||||
{
|
||||
"fields": ("provider_configuration",),
|
||||
"classes": ("collapse",),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def json_preview(self, obj): # pragma: no cover
|
||||
return html_json_preview(obj.data)
|
||||
|
||||
json_preview.short_description = "Authentication Request data"
|
||||
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SpidCieFederationRpConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "spid_cie_oidc.relying_party"
|
||||
verbose_name = "SPID/CIE OIDC Federation Relying Party"
|
||||
label = "spid_cie_oidc_relying_party"
|
||||
@@ -0,0 +1,2 @@
|
||||
class ValidationException(Exception):
|
||||
pass
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from spid_cie_oidc.entity.settings import HTTPC_PARAMS
|
||||
from spid_cie_oidc.entity.trust_chain_operations import get_or_create_trust_chain
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Attribute release query"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.epilog = "Example: ./manage.py fetch_openid_providers"
|
||||
parser.add_argument(
|
||||
"--start",
|
||||
action="store_true",
|
||||
required=True,
|
||||
help=_(
|
||||
"Collect a trust chains for each openid_provider defined in "
|
||||
"settings.OIDCFED_IDENTITY_PROVIDERS"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
required=False,
|
||||
help=_("Don't use already cached statements and chains"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-debug", required=False, action="store_true", help="see debug message"
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not options["start"]:
|
||||
return # pragma: no cover
|
||||
|
||||
res = []
|
||||
for op_profile in settings.OIDCFED_IDENTITY_PROVIDERS.keys():
|
||||
for op_sub, ta in settings.OIDCFED_IDENTITY_PROVIDERS[op_profile].items():
|
||||
logger.info(f"Fetching Entity Configuration for {op_sub}")
|
||||
try:
|
||||
tc = get_or_create_trust_chain(
|
||||
subject=op_sub,
|
||||
trust_anchor=ta,
|
||||
httpc_params=HTTPC_PARAMS,
|
||||
required_trust_marks=getattr(
|
||||
settings, "OIDCFED_REQUIRED_TRUST_MARKS", []
|
||||
),
|
||||
force=options["force"],
|
||||
)
|
||||
if tc.is_valid:
|
||||
res.append(tc)
|
||||
|
||||
logger.info(f"Final Metadata for {tc.sub}:\n\n{tc.metadata}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to download {op_sub} due to: {e}")
|
||||
continue
|
||||
|
||||
logger.info(f"Found {res}")
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
# Generated by Django 4.0.2 on 2022-02-13 09:06
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="OidcAuthentication",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("client_id", models.CharField(max_length=255)),
|
||||
(
|
||||
"state",
|
||||
models.CharField(
|
||||
default="state-is-unique", max_length=255, unique=True
|
||||
),
|
||||
),
|
||||
("endpoint", models.URLField(blank=True, null=True)),
|
||||
("issuer", models.CharField(blank=True, max_length=255, null=True)),
|
||||
("issuer_id", models.CharField(blank=True, max_length=255, null=True)),
|
||||
("provider_jwks", models.TextField(blank=True, null=True)),
|
||||
("data", models.TextField(blank=True, null=True)),
|
||||
("successful", models.BooleanField(default=False)),
|
||||
("provider_configuration", models.TextField(blank=True, null=True)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("modified", models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "OIDC Authentication",
|
||||
"verbose_name_plural": "OIDC Authentications",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OidcAuthenticationToken",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("code", models.CharField(blank=True, max_length=255, null=True)),
|
||||
("access_token", models.TextField(blank=True, null=True)),
|
||||
("id_token", models.TextField(blank=True, null=True)),
|
||||
("scope", models.CharField(blank=True, max_length=255, null=True)),
|
||||
("token_type", models.CharField(blank=True, max_length=255, null=True)),
|
||||
("expires_in", models.IntegerField(blank=True, null=True)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("modified", models.DateTimeField(auto_now=True)),
|
||||
("logged_out", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"authz_request",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="spid_cie_oidc_relying_party.oidcauthentication",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-03 11:53
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("spid_cie_oidc_relying_party", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="oidcauthentication",
|
||||
old_name="issuer",
|
||||
new_name="provider",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="oidcauthentication",
|
||||
old_name="issuer_id",
|
||||
new_name="provider_id",
|
||||
),
|
||||
]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-07 08:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0002_rename_issuer_oidcauthentication_provider_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
old_name='logged_out',
|
||||
new_name='revoked',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
name='refresh_token',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-07 10:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0003_rename_logged_out_oidcauthenticationtoken_revoked_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
old_name='revoked',
|
||||
new_name='logged_out',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
name='refresh_token',
|
||||
),
|
||||
]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-07 11:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0004_rename_revoked_oidcauthenticationtoken_logged_out_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
old_name='logged_out',
|
||||
new_name='revoked',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
name='refresh_token',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oidcauthentication',
|
||||
name='provider_configuration',
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-07 11:27
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0005_rename_logged_out_oidcauthenticationtoken_revoked_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='oidcauthentication',
|
||||
name='provider_configuration',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oidcauthentication',
|
||||
name='state',
|
||||
field=models.CharField(default=uuid.uuid4, max_length=255, unique=True),
|
||||
),
|
||||
]
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-07 11:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0006_alter_oidcauthentication_provider_configuration_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='oidcauthentication',
|
||||
name='provider_jwks',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.0.3 on 2022-04-10 19:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0007_alter_oidcauthentication_provider_jwks'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='oidcauthentication',
|
||||
name='provider',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oidcauthentication',
|
||||
name='provider_jwks',
|
||||
),
|
||||
]
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.5 on 2026-02-02 15:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('spid_cie_oidc_relying_party', '0008_remove_oidcauthentication_provider_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='oidcauthenticationtoken',
|
||||
name='code',
|
||||
field=models.CharField(blank=True, max_length=2048, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
|
||||
from spid_cie_oidc.entity.jwtse import unpad_jwt_payload
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OidcAuthentication(models.Model):
|
||||
client_id = models.CharField(max_length=255)
|
||||
state = models.CharField(max_length=255, unique=True, default=uuid.uuid4)
|
||||
endpoint = models.URLField(blank=True, null=True)
|
||||
data = models.TextField(blank=True, null=True)
|
||||
successful = models.BooleanField(default=False)
|
||||
|
||||
provider_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
provider_configuration = models.JSONField(
|
||||
blank=True, null=True, default=dict
|
||||
)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "OIDC Authentication"
|
||||
verbose_name_plural = "OIDC Authentications"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.client_id} {self.state} to {self.endpoint}"
|
||||
|
||||
|
||||
class OidcAuthenticationToken(models.Model):
|
||||
user = models.ForeignKey(
|
||||
get_user_model(), on_delete=models.SET_NULL, blank=True, null=True
|
||||
)
|
||||
authz_request = models.ForeignKey(OidcAuthentication, on_delete=models.CASCADE)
|
||||
code = models.CharField(max_length=2048, blank=True, null=True)
|
||||
access_token = models.TextField(blank=True, null=True)
|
||||
id_token = models.TextField(blank=True, null=True)
|
||||
refresh_token = models.TextField(blank=True, null=True)
|
||||
|
||||
scope = models.CharField(max_length=255, blank=True, null=True)
|
||||
token_type = models.CharField(max_length=255, blank=True, null=True)
|
||||
expires_in = models.IntegerField(blank=True, null=True)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
revoked = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.authz_request} {self.code}"
|
||||
|
||||
def token_preview(self, token):
|
||||
if token:
|
||||
try:
|
||||
data = unpad_jwt_payload(token)
|
||||
dumps = json.dumps(data, indent=2)
|
||||
return dumps # mark_safe(dumps.replace("\n", "<br>").replace(" ", " "))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
else:
|
||||
return '--'
|
||||
|
||||
@property
|
||||
def access_token_preview(self):
|
||||
return self.token_preview(self.access_token)
|
||||
|
||||
@property
|
||||
def id_token_preview(self):
|
||||
return self.token_preview(self.id_token)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.authz_request.successful = True
|
||||
self.authz_request.save()
|
||||
@@ -0,0 +1,73 @@
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
from spid_cie_oidc.entity.models import FederationEntityConfiguration
|
||||
from spid_cie_oidc.entity.jwtse import create_jws
|
||||
from spid_cie_oidc.entity.settings import HTTPC_PARAMS, HTTPC_TIMEOUT
|
||||
from spid_cie_oidc.entity.utils import iat_now, exp_from_now, get_key, KeyUsage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OAuth2AuthorizationCodeGrant(object):
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc6749
|
||||
"""
|
||||
|
||||
def access_token_request(
|
||||
self,
|
||||
redirect_uri: str,
|
||||
state: str,
|
||||
code: str,
|
||||
issuer_id: str,
|
||||
client_conf: FederationEntityConfiguration,
|
||||
token_endpoint_url: str,
|
||||
audience: list,
|
||||
code_verifier: str = None,
|
||||
):
|
||||
"""
|
||||
Access Token Request
|
||||
https://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
"""
|
||||
grant_data = dict(
|
||||
grant_type="authorization_code",
|
||||
redirect_uri=redirect_uri,
|
||||
client_id=client_conf.sub,
|
||||
state=state,
|
||||
code=code,
|
||||
code_verifier=code_verifier,
|
||||
# here private_key_jwt
|
||||
client_assertion_type="urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion=create_jws(
|
||||
{
|
||||
"iss": client_conf.sub,
|
||||
"sub": client_conf.sub,
|
||||
"aud": [token_endpoint_url],
|
||||
"iat": iat_now(),
|
||||
"exp": exp_from_now(),
|
||||
"jti": str(uuid.uuid4()),
|
||||
},
|
||||
jwk_dict=get_key(client_conf.jwks_core, KeyUsage.signature),
|
||||
),
|
||||
)
|
||||
|
||||
logger.debug(f"Access Token Request for {state}: {grant_data} ")
|
||||
token_request = requests.post(
|
||||
token_endpoint_url,
|
||||
data=grant_data,
|
||||
verify=HTTPC_PARAMS["connection"]["ssl"],
|
||||
timeout=HTTPC_TIMEOUT,
|
||||
)
|
||||
|
||||
if token_request.status_code != 200: # pragma: no cover
|
||||
logger.error(
|
||||
f"Something went wrong with {state}: {token_request.status_code}"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
token_request = json.loads(token_request.content.decode())
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Something went wrong with {state}: {e}")
|
||||
return token_request
|
||||
@@ -0,0 +1,79 @@
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from django.conf import settings
|
||||
from spid_cie_oidc.entity.exceptions import UnknownKid
|
||||
from spid_cie_oidc.entity.jwtse import unpad_jwt_head, decrypt_jwe, verify_jws
|
||||
from spid_cie_oidc.entity.utils import get_jwks
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OidcUserInfo(object):
|
||||
"""
|
||||
https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||
"""
|
||||
|
||||
def get_jwk(self, kid, jwks):
|
||||
for jwk in jwks:
|
||||
if jwk.get("kid", None) and jwk["kid"] == kid:
|
||||
return jwk
|
||||
raise UnknownKid() # pragma: no cover
|
||||
|
||||
def get_userinfo(
|
||||
self, state: str, access_token: str, provider_conf: dict, verify: bool
|
||||
):
|
||||
"""
|
||||
User Info endpoint request with bearer access token
|
||||
"""
|
||||
# userinfo
|
||||
headers = {"Authorization": f"Bearer {access_token}"}
|
||||
authz_userinfo = requests.get(
|
||||
provider_conf["userinfo_endpoint"],
|
||||
headers=headers,
|
||||
verify=verify,
|
||||
timeout=getattr(
|
||||
settings, "HTTPC_TIMEOUT", 8
|
||||
) # nosec - B113
|
||||
)
|
||||
|
||||
if authz_userinfo.status_code != 200: # pragma: no cover
|
||||
logger.error(
|
||||
f"Something went wrong with {state}: {authz_userinfo.status_code}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
# if application/json ... let it be
|
||||
return authz_userinfo.json()
|
||||
except Exception:
|
||||
logger.debug("userinfo response is not in plain json")
|
||||
|
||||
try:
|
||||
jwe = authz_userinfo.content.decode()
|
||||
|
||||
header = unpad_jwt_head(jwe)
|
||||
# header["kid"] kid di rp
|
||||
rp_jwk = self.get_jwk(header["kid"], self.rp_conf.jwks_core)
|
||||
jws = decrypt_jwe(jwe, rp_jwk)
|
||||
|
||||
if isinstance(jws, bytes):
|
||||
jws = jws.decode()
|
||||
|
||||
header = unpad_jwt_head(jws)
|
||||
idp_jwks = get_jwks(provider_conf)
|
||||
idp_jwk = self.get_jwk(header["kid"], idp_jwks)
|
||||
|
||||
decoded_jwt = verify_jws(jws, idp_jwk)
|
||||
logger.debug(f"Userinfo endpoint result: {decoded_jwt}")
|
||||
return decoded_jwt
|
||||
|
||||
except KeyError as e: # pragma: no cover
|
||||
logger.error(f"Userinfo response error {state}: {e}")
|
||||
return False
|
||||
except UnknownKid as e:
|
||||
logger.error(f"Userinfo Unknow KID for session {state}: {e}")
|
||||
return False
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Userinfo response unknown error {state}: {e}")
|
||||
return False
|
||||
@@ -0,0 +1,4 @@
|
||||
def issuer_prefixed_sub(
|
||||
user_info: dict, client_conf: dict, data: dict = None, kwargs: dict = {}
|
||||
):
|
||||
return f"{client_conf['provider_id']}{data.get('sep', '__')}{user_info['sub']}"
|
||||
@@ -0,0 +1,124 @@
|
||||
from django.conf import settings
|
||||
from spid_cie_oidc.provider.schemas.authn_requests import (
|
||||
AcrValues,
|
||||
AuthenticationRequestCie,
|
||||
AuthenticationRequestDoc,
|
||||
AuthenticationRequestSpid,
|
||||
)
|
||||
from spid_cie_oidc.entity.schemas.rp_metadata import RPMetadataSpid, RPMetadataCie
|
||||
from spid_cie_oidc.provider.schemas.authn_response import AuthenticationResponse, AuthenticationResponseCie
|
||||
from spid_cie_oidc.provider.schemas.token_response import TokenResponse
|
||||
|
||||
|
||||
RP_PREFS = {
|
||||
"application_name": "that_fancy_rp",
|
||||
"application_type": "web",
|
||||
"contacts": ["[email protected]"],
|
||||
"response_types": ["code"],
|
||||
"scope": ["openid", "offline_access"],
|
||||
"token_endpoint_auth_method": ["private_key_jwt"],
|
||||
}
|
||||
|
||||
|
||||
OIDCFED_ACR_PROFILES = getattr(
|
||||
settings,
|
||||
"OIDCFED_ACR_PROFILES",
|
||||
AcrValues.l2.value
|
||||
)
|
||||
|
||||
RP_PROVIDER_PROFILES = getattr(
|
||||
settings,
|
||||
"RP_PROVIDER_PROFILES",
|
||||
{
|
||||
"spid": {
|
||||
"authorization_request": AuthenticationRequestSpid,
|
||||
"authorization_request_doc": AuthenticationRequestDoc,
|
||||
"rp_metadata": RPMetadataSpid,
|
||||
"authn_response": AuthenticationResponse,
|
||||
"token_response": TokenResponse
|
||||
},
|
||||
"cie": {
|
||||
"authorization_request": AuthenticationRequestCie,
|
||||
"authorization_request_doc": AuthenticationRequestDoc,
|
||||
"rp_metadata": RPMetadataCie,
|
||||
"authn_response": AuthenticationResponseCie,
|
||||
"token_response": TokenResponse
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
RP_USER_LOOKUP_FIELD = getattr(settings, "RP_USER_LOOKUP_FIELD", "fiscal_number")
|
||||
RP_USER_CREATE = getattr(settings, "RP_USER_CREATE", True)
|
||||
|
||||
RP_ATTR_MAP = getattr(
|
||||
settings,
|
||||
"RP_ATTR_MAP",
|
||||
{
|
||||
"sub": ("sub",),
|
||||
"username": (
|
||||
{
|
||||
"func": "spid_cie_oidc.relying_party.processors.issuer_prefixed_sub",
|
||||
"kwargs": {"sep": "__"},
|
||||
},
|
||||
),
|
||||
"first_name": ("given_name", "given_name"),
|
||||
"last_name": (
|
||||
"family_name",
|
||||
"last_name",
|
||||
),
|
||||
"email": (
|
||||
"email",
|
||||
"email",
|
||||
),
|
||||
"fiscal_number": ("https://attributes.eid.gov.it/fiscal_number", "fiscal_number"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
SPID_REQUESTED_CLAIMS = getattr(
|
||||
settings,
|
||||
"RP_REQUIRED_CLAIMS",
|
||||
{
|
||||
"id_token": {
|
||||
"given_name": {"essential": True},
|
||||
"email": {"essential": True},
|
||||
},
|
||||
"userinfo": {
|
||||
"given_name": None,
|
||||
"family_name": None,
|
||||
"email": None,
|
||||
"https://attributes.eid.gov.it/fiscal_number": None,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
CIE_REQUESTED_CLAIMS = getattr(
|
||||
settings,
|
||||
"RP_REQUIRED_CLAIMS",
|
||||
{
|
||||
"id_token": {"family_name": {"essential": True}, "given_name": {"essential": True}},
|
||||
"userinfo": {
|
||||
"given_name": None,
|
||||
"family_name": None,
|
||||
"email": None,
|
||||
"https://attributes.eid.gov.it/fiscal_number": None
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
RP_PKCE_CONF = getattr(
|
||||
settings,
|
||||
"RP_PKCE_CONF",
|
||||
{
|
||||
"function": "spid_cie_oidc.relying_party.utils.get_pkce",
|
||||
"kwargs": {"code_challenge_length": 64, "code_challenge_method": "S256"},
|
||||
},
|
||||
)
|
||||
|
||||
RP_REQUEST_CLAIM_BY_PROFILE = {
|
||||
"spid": SPID_REQUESTED_CLAIMS,
|
||||
"cie": CIE_REQUESTED_CLAIMS,
|
||||
}
|
||||
|
||||
RP_DEFAULT_PROVIDER_PROFILES = getattr(settings, "RP_DEFAULT_PROVIDER_PROFILES", "spid")
|
||||
RP_REQUEST_EXP = getattr(settings, "RP_REQUEST_EXP", 60)
|
||||
+413
@@ -0,0 +1,413 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Titillium+Web:400,600);
|
||||
.italia-it-button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
color: #FFF;
|
||||
font-family: "Titillium Web", HelveticaNeue, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;
|
||||
font-weight: 600;
|
||||
line-height: 1em;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.italia-it-button-icon,
|
||||
.italia-it-button-text {
|
||||
display: block;
|
||||
float: left
|
||||
}
|
||||
|
||||
.italia-it-button-icon {
|
||||
margin: 0 -.4em 0 0;
|
||||
padding: 0.6em .8em .5em;
|
||||
border-right: rgba(255, 255, 255, 0.1) 0.1em solid
|
||||
}
|
||||
|
||||
.italia-it-button-text {
|
||||
padding: .95em 1em .85em 1em;
|
||||
font-size: 1.15em;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
fill: #fff
|
||||
}
|
||||
|
||||
.italia-it-block {
|
||||
display: block
|
||||
}
|
||||
|
||||
.italia-it-button-size-s {
|
||||
font-size: 10px;
|
||||
width: 150px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.italia-it-button-size-s > span img {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
border: 0
|
||||
}
|
||||
|
||||
.italia-it-button-size-m {
|
||||
font-size: 15px;
|
||||
width: 220px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.italia-it-button-size-m > span img {
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
border: 0
|
||||
}
|
||||
|
||||
.italia-it-button-size-l {
|
||||
font-size: 20px;
|
||||
width: 280px
|
||||
}
|
||||
|
||||
.italia-it-button-size-l > span img {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border: 0
|
||||
}
|
||||
|
||||
.italia-it-button-size-xl {
|
||||
font-size: 24px;
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
.italia-it-button-size-xl > span img {
|
||||
width: 47px;
|
||||
height: 47px;
|
||||
border: 0
|
||||
}
|
||||
|
||||
.button-spid {
|
||||
background-color: #06C;
|
||||
color: #FFF
|
||||
}
|
||||
|
||||
.button-spid svg {
|
||||
fill: #FFF
|
||||
}
|
||||
|
||||
.button-spid:hover {
|
||||
background-color: #036;
|
||||
color: #FFF
|
||||
}
|
||||
|
||||
.button-spid:active {
|
||||
background-color: #83BEED;
|
||||
color: #036
|
||||
}
|
||||
|
||||
.button-cie {
|
||||
background-color: #4c5c6c;
|
||||
color: #FFF
|
||||
}
|
||||
|
||||
.button-cie svg {
|
||||
fill: #FFF
|
||||
}
|
||||
|
||||
.button-cie:hover {
|
||||
background-color: #414c58;
|
||||
color: #FFF
|
||||
}
|
||||
|
||||
.button-cie:active {
|
||||
background-color: #394046;
|
||||
color: #036
|
||||
}
|
||||
|
||||
.spid-idp-button {
|
||||
position: absolute;
|
||||
z-index: 1039;
|
||||
display: none
|
||||
}
|
||||
|
||||
.spid-idp-button .spid-idp-button-menu,
|
||||
.spid-idp-button .spid-idp-button-panel {
|
||||
list-style: none;
|
||||
background: white;
|
||||
border: solid 1px #ddd;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#spid-idp-button-small-get,
|
||||
#spid-idp-button-medium-get,
|
||||
#spid-idp-button-small-post,
|
||||
#spid-idp-button-medium-post {
|
||||
width: 230px
|
||||
}
|
||||
|
||||
#spid-idp-button-large-get,
|
||||
#spid-idp-button-large-post {
|
||||
width: 270px
|
||||
}
|
||||
|
||||
#spid-idp-button-xlarge-get,
|
||||
#spid-idp-button-xlarge-post {
|
||||
width: 330px
|
||||
}
|
||||
|
||||
.spid-idp-button .spid-idp-button-panel {
|
||||
padding: 10px
|
||||
}
|
||||
|
||||
.spid-idp-button.spid-idp-button-tip {
|
||||
margin-top: 8px
|
||||
}
|
||||
|
||||
.spid-idp-button.spid-idp-button-tip:before {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 9px;
|
||||
content: "";
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ddd;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.spid-idp-button.spid-idp-button-tip:after {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 10px;
|
||||
content: "";
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid white;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.spid-idp-button.spid-idp-button-tip.spid-idp-button-anchor-right:before {
|
||||
left: auto;
|
||||
right: 9px
|
||||
}
|
||||
|
||||
.spid-idp-button.spid-idp-button-tip.spid-idp-button-anchor-right:after {
|
||||
left: auto;
|
||||
right: 10px
|
||||
}
|
||||
|
||||
.spid-idp-button.spid-idp-button-scroll .spid-idp-button-menu,
|
||||
.spid-idp-button.spid-idp-button-scroll .spid-idp-button-panel {
|
||||
max-height: 180px;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.spid-idp-button .spid-idp-button-menu li {
|
||||
list-style: none;
|
||||
padding: 0 0;
|
||||
margin: 0;
|
||||
line-height: 18px
|
||||
}
|
||||
|
||||
.spid-idp-button .spid-idp-button-menu li > a,
|
||||
.spid-idp-button .spid-idp-button-menu label {
|
||||
display: block;
|
||||
font-family: "Titillium Web", HelveticaNeue, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: .9em;
|
||||
color: #3b61b6;
|
||||
text-decoration: underline;
|
||||
line-height: 18px;
|
||||
padding-top: 5px;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.spid-idp-button .spid-idp-button-menu li > a:hover,
|
||||
.spid-idp-button .spid-idp-button-menu label:hover {
|
||||
color: #036;
|
||||
cursor: pointer;
|
||||
background-color: #F0F0F0
|
||||
}
|
||||
|
||||
.spid-idp-button .spid-idp-button-menu li > a img {
|
||||
height: 35px;
|
||||
padding-bottom: 5px;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.idp-button-idp-logo {
|
||||
font-size: 100%;
|
||||
height: 10%;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #CCC;
|
||||
background-color: #FFF;
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.idp-button-idp-logo:hover {
|
||||
background-color: #F0F0F0
|
||||
}
|
||||
|
||||
.idp-button-idp-logo img {
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.spid-top-menu-space-10 > a img {
|
||||
margin-top: 10px
|
||||
}
|
||||
|
||||
.spid-top-menu-space-20 > a img {
|
||||
margin-top: 20px
|
||||
}
|
||||
|
||||
.spid-top-menu-line {
|
||||
border-top: 5px solid #000
|
||||
}
|
||||
|
||||
.spid-idp-support-link > a {
|
||||
padding: 5px 0 10px 10px
|
||||
}
|
||||
|
||||
.spid-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0
|
||||
}
|
||||
|
||||
.cie-idp-button {
|
||||
position: absolute;
|
||||
z-index: 1039;
|
||||
display: none
|
||||
}
|
||||
|
||||
.cie-idp-button .cie-idp-button-menu,
|
||||
.cie-idp-button .cie-idp-button-panel {
|
||||
list-style: none;
|
||||
background: white;
|
||||
border: solid 1px #ddd;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#cie-idp-button-small-get,
|
||||
#cie-idp-button-medium-get,
|
||||
#cie-idp-button-small-post,
|
||||
#cie-idp-button-medium-post {
|
||||
width: 230px
|
||||
}
|
||||
|
||||
#cie-idp-button-large-get,
|
||||
#cie-idp-button-large-post {
|
||||
width: 270px
|
||||
}
|
||||
|
||||
#cie-idp-button-xlarge-get,
|
||||
#cie-idp-button-xlarge-post {
|
||||
width: 330px
|
||||
}
|
||||
|
||||
.cie-idp-button .cie-idp-button-panel {
|
||||
padding: 10px
|
||||
}
|
||||
|
||||
.cie-idp-button.cie-idp-button-tip {
|
||||
margin-top: 8px
|
||||
}
|
||||
|
||||
.cie-idp-button.cie-idp-button-tip:before {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 9px;
|
||||
content: "";
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ddd;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.cie-idp-button.cie-idp-button-tip:after {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 10px;
|
||||
content: "";
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid white;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.cie-idp-button.cie-idp-button-tip.cie-idp-button-anchor-right:before {
|
||||
left: auto;
|
||||
right: 9px
|
||||
}
|
||||
|
||||
.cie-idp-button.cie-idp-button-tip.cie-idp-button-anchor-right:after {
|
||||
left: auto;
|
||||
right: 10px
|
||||
}
|
||||
|
||||
.cie-idp-button.cie-idp-button-scroll .cie-idp-button-menu,
|
||||
.cie-idp-button.cie-idp-button-scroll .cie-idp-button-panel {
|
||||
max-height: 180px;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.cie-idp-button .cie-idp-button-menu li {
|
||||
list-style: none;
|
||||
padding: 0 0;
|
||||
margin: 0;
|
||||
line-height: 18px
|
||||
}
|
||||
|
||||
.cie-idp-button .cie-idp-button-menu li > a,
|
||||
.cie-idp-button .cie-idp-button-menu label {
|
||||
display: block;
|
||||
font-family: "Titillium Web", HelveticaNeue, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: .9em;
|
||||
color: #3b61b6;
|
||||
text-decoration: underline;
|
||||
line-height: 18px;
|
||||
padding-top: 5px;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.cie-idp-button .cie-idp-button-menu li > a:hover,
|
||||
.cie-idp-button .cie-idp-button-menu label:hover {
|
||||
color: #036;
|
||||
cursor: pointer;
|
||||
background-color: #F0F0F0
|
||||
}
|
||||
|
||||
.cie-idp-button .cie-idp-button-menu li > a img {
|
||||
height: 35px;
|
||||
padding-bottom: 5px;
|
||||
border: 0;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
jQuery && function (t) {
|
||||
function i(i, n) {
|
||||
var d = i ? t(this) : n, o = t(d.attr("spid-idp-button")), r = d.hasClass("spid-idp-button-open")
|
||||
if (i) {
|
||||
if (t(i.target).hasClass("spid-idp-button-ignore")) return
|
||||
i.preventDefault(), i.stopPropagation()
|
||||
} else if (d !== n.target && t(n.target).hasClass("spid-idp-button-ignore")) return
|
||||
s(), r || d.hasClass("spid-idp-button-disabled") || (d.addClass("spid-idp-button-open"), o.data("spid-idp-button-trigger", d).show(), e(), o.trigger("show", { spidIDPButton: o, trigger: d }))
|
||||
}
|
||||
|
||||
function s(i) {
|
||||
var s = i ? t(i.target).parents().addBack() : null
|
||||
if (s && s.is(".spid-idp-button")) {
|
||||
if (!s.is(".spid-idp-button-menu")) return
|
||||
if (!s.is("A")) return
|
||||
}
|
||||
t(document).find(".spid-idp-button:visible").each(function () {
|
||||
var i = t(this)
|
||||
i.hide().removeData("spid-idp-button-trigger").trigger("hide", { spidIDPButton: i })
|
||||
}), t(document).find(".spid-idp-button-open").removeClass("spid-idp-button-open")
|
||||
}
|
||||
|
||||
function e() {
|
||||
var i = t(".spid-idp-button:visible").eq(0), s = i.data("spid-idp-button-trigger"), e = s ? parseInt(s.attr("data-horizontal-offset") || 0, 10) : null, n = s ? parseInt(s.attr("data-vertical-offset") || 0, 10) : null
|
||||
0 !== i.length && s && (i.hasClass("spid-idp-button-relative") ? i.css({ left: i.hasClass("spid-idp-button-anchor-right") ? s.position().left - (i.outerWidth(!0) - s.outerWidth(!0)) - parseInt(s.css("margin-right"), 10) + e : s.position().left + parseInt(s.css("margin-left"), 10) + e, top: s.position().top + s.outerHeight(!0) - parseInt(s.css("margin-top"), 10) + n }) : i.css({ left: i.hasClass("spid-idp-button-anchor-right") ? s.offset().left - (i.outerWidth() - s.outerWidth()) + e : s.offset().left + e, top: s.offset().top + s.outerHeight() + n }))
|
||||
} t.extend(t.fn, {
|
||||
spidIDPButton: function (e, n) {
|
||||
switch (e) {
|
||||
case "show": return i(null, t(this)), t(this)
|
||||
case "hide": return s(), t(this)
|
||||
case "attach": return t(this).attr("spid-idp-button", n)
|
||||
case "detach": return s(), t(this).removeAttr("spid-idp-button")
|
||||
case "disable": return t(this).addClass("spid-idp-button-disabled")
|
||||
case "enable": return s(), t(this).removeClass("spid-idp-button-disabled")
|
||||
}
|
||||
}
|
||||
}), t(document).on("click.spid-idp-button", "[spid-idp-button]", i), t(document).on("click.spid-idp-button", s), t(window).on("resize", e)
|
||||
}(jQuery)
|
||||
|
||||
|
||||
jQuery && function (t) {
|
||||
function i(i, n) {
|
||||
var d = i ? t(this) : n, o = t(d.attr("cie-idp-button")), r = d.hasClass("cie-idp-button-open")
|
||||
if (i) {
|
||||
if (t(i.target).hasClass("cie-idp-button-ignore")) return
|
||||
i.preventDefault(), i.stopPropagation()
|
||||
} else if (d !== n.target && t(n.target).hasClass("cie-idp-button-ignore")) return
|
||||
s(), r || d.hasClass("cie-idp-button-disabled") || (d.addClass("cie-idp-button-open"), o.data("cie-idp-button-trigger", d).show(), e(), o.trigger("show", { cieIDPButton: o, trigger: d }))
|
||||
}
|
||||
|
||||
function s(i) {
|
||||
var s = i ? t(i.target).parents().addBack() : null
|
||||
if (s && s.is(".cie-idp-button")) {
|
||||
if (!s.is(".cie-idp-button-menu")) return
|
||||
if (!s.is("A")) return
|
||||
}
|
||||
t(document).find(".cie-idp-button:visible").each(function () {
|
||||
var i = t(this)
|
||||
i.hide().removeData("cie-idp-button-trigger").trigger("hide", { cieIDPButton: i })
|
||||
}), t(document).find(".cie-idp-button-open").removeClass("cie-idp-button-open")
|
||||
}
|
||||
|
||||
function e() {
|
||||
var i = t(".cie-idp-button:visible").eq(0), s = i.data("cie-idp-button-trigger"), e = s ? parseInt(s.attr("data-horizontal-offset") || 0, 10) : null, n = s ? parseInt(s.attr("data-vertical-offset") || 0, 10) : null
|
||||
0 !== i.length && s && (i.hasClass("cie-idp-button-relative") ? i.css({ left: i.hasClass("cie-idp-button-anchor-right") ? s.position().left - (i.outerWidth(!0) - s.outerWidth(!0)) - parseInt(s.css("margin-right"), 10) + e : s.position().left + parseInt(s.css("margin-left"), 10) + e, top: s.position().top + s.outerHeight(!0) - parseInt(s.css("margin-top"), 10) + n }) : i.css({ left: i.hasClass("cie-idp-button-anchor-right") ? s.offset().left - (i.outerWidth() - s.outerWidth()) + e : s.offset().left + e, top: s.offset().top + s.outerHeight() + n }))
|
||||
} t.extend(t.fn, {
|
||||
cieIDPButton: function (e, n) {
|
||||
switch (e) {
|
||||
case "show": return i(null, t(this)), t(this)
|
||||
case "hide": return s(), t(this)
|
||||
case "attach": return t(this).attr("cie-idp-button", n)
|
||||
case "detach": return s(), t(this).removeAttr("cie-idp-button")
|
||||
case "disable": return t(this).addClass("cie-idp-button-disabled")
|
||||
case "enable": return s(), t(this).removeClass("cie-idp-button-disabled")
|
||||
}
|
||||
}
|
||||
}), t(document).on("click.cie-idp-button", "[cie-idp-button]", i), t(document).on("click.cie-idp-button", s), t(window).on("resize", e)
|
||||
}(jQuery)
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-small-root-get");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-medium-root-get");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#cie-idp-list-medium-root-get");
|
||||
var idpList = rootList.children(".cie-idp-button-link");
|
||||
var lnkList = rootList.children(".cie-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-large-root-get");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-xlarge-root-get");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-small-root-post");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-medium-root-post");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-large-root-post");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
$(document).ready(function(){
|
||||
var rootList = $("#spid-idp-list-xlarge-root-post");
|
||||
var idpList = rootList.children(".spid-idp-button-link");
|
||||
var lnkList = rootList.children(".spid-idp-support-link");
|
||||
while (idpList.length) {
|
||||
rootList.append(idpList.splice(Math.floor(Math.random() * idpList.length), 1)[0]);
|
||||
}
|
||||
rootList.append(lnkList);
|
||||
});
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 5.2.4</desc>
|
||||
<defs>
|
||||
</defs>
|
||||
<g transform="matrix(1 0 0 1 540 540)" id="91fda1b0-16a7-4218-be94-fe968aea9287" >
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 540 540)" id="bf67b2b1-57ff-4e67-a18b-9056d38bc3b7" >
|
||||
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 540 540)" >
|
||||
<g style="" vector-effect="non-scaling-stroke" >
|
||||
<g transform="matrix(1 0 0 1 15.3 0)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-65.81, -54.25)" d="M 30.6 15.7 L 34 14.5 L 75.5 0.2 C 76.56540168959296 -0.18066706462342558 77.75357230207264 0.04930144101780526 78.6 0.8000000000000014 C 79.45278343969967 1.5639096924539222 80.10517684036854 2.5253315460712047 80.5 3.599999999999998 C 81.7 6.699999999999999 82.9 9.7 84.1 12.9 C 85.10490287348861 15.753272139425885 85.51333420425384 18.782471175934674 85.3 21.799999999999997 C 85.2 23.1 85 24.400000000000002 85 25.7 C 84.8997487421324 26.69748742132399 84.8997487421324 27.70251257867601 85 28.7 C 85.1 30 85.2 31.3 85.4 32.6 C 85.6679127655581 35.17784232706553 86.6362237855053 37.63320241336018 88.2 39.7 C 91 43.300000000000004 93.7 47 96.5 50.7 C 97.91713033796104 52.6006439461041 99.18746959048559 54.6064427658797 100.29999999999998 56.699999999999996 C 100.72948626076172 57.46553485118747 100.96950576896607 58.32274738048874 101 59.199999999999996 C 101.07307811645249 59.956229957075166 100.93459406188998 60.71789225716893 100.6 61.400000000000006 C 99.92601972444126 62.667018833488605 99.00846791093264 63.78847104999915 97.89999999999999 64.7 C 96.65622743722774 65.80166188924085 95.27873415273983 66.74238901035454 93.8 67.5 C 92.65744570589503 68.06025733600642 91.6376786331713 68.84207875842795 90.8 69.8 C 89.9521070517764 70.80275151344352 89.75747055840081 72.20413426574774 90.3 73.39999999999999 L 91.2 75.69999999999999 C 91.8554625718165 77.04584978347667 91.65969779024242 78.65112099238415 90.7 79.79999999999998 C 90.45738714699291 80.18357117568864 90.19015996918812 80.55100854517025 89.9 80.89999999999998 C 89.42865735261731 81.64291017503078 89.28398496528875 82.54711259583415 89.5 83.39999999999998 L 89.8 84.59999999999998 C 89.94813816233079 85.54363300569577 89.6529890347512 86.50286767032942 89 87.19999999999997 C 88.4 87.79999999999997 87.9 88.49999999999997 87.4 89.09999999999998 C 86.33883817925528 90.31118837502393 85.79963941681507 91.89283807818184 85.9 93.49999999999999 C 85.9938011077572 94.45296851707025 86.19508025227655 95.39227119149392 86.5 96.29999999999998 C 86.91857745940916 97.89773589887767 87.08724214884835 99.55064985538174 87 101.19999999999997 C 86.94617594664437 103.98514037610069 85.37295020253946 106.51765011051347 82.9 107.79999999999998 C 81.6565380495359 108.39508080499027 80.26333329885159 108.60406151759291 78.9 108.39999999999998 L 73.7 107.49999999999997 C 67.2 106.39999999999998 60.900000000000006 104.79999999999997 54.5 103.39999999999998 L 47.8 102 L 47.599999999999994 102 C 58.8 94.8 66.1 84.8 68.8 71.8 C 71.69842553866987 59.30491974682914 69.11447049705681 46.166782140881125 61.699999999999996 35.7 C 54.2 24.6 43.6 18.1 30.6 15.7 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 -39.81 -13.45)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-10.7, -40.8)" d="M 12.2 57.5 C 7.3999999999999995 57.5 4.199999999999999 56.2 2.5 53.6 C 0.8000000000000007 51 0 46.7 0 40.7 C 0 34.7 0.9 30.4 2.6 27.9 C 4.3 25.4 7.5 24.099999999999998 12.2 24.099999999999998 C 15.299482037429783 24.099784037419465 18.387939251017634 24.469056095565843 21.400000000000013 25.200000000000006 L 21.4 29.5 C 18.56177516266078 28.98577132784907 15.684375758746704 28.718106267019856 12.799999999999944 28.700000000000003 C 10 28.7 8.099999999999998 29.599999999999998 7.099999999999999 31.3 C 6.1 33 5.499999999999998 36.1 5.499999999999998 40.8 C 5.499999999999998 45.49999999999999 5.999999999999998 48.599999999999994 6.999999999999998 50.3 C 7.999999999999998 52 9.899999999999999 52.8 12.7 52.8 C 15.614674853027882 52.81570359304371 18.525331068726384 52.58151286304498 21.400000000000006 52.099999999999994 L 21.4 56.49999999999999 C 18.377250101800986 57.15727407025505 15.293375109505583 57.49247787376541 12.199999999999996 57.49999999999999 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 -22.41 -13.8)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-28.1, -40.45)" d="M 25.5 29.3 L 25.5 23.9 L 30.7 23.9 L 30.7 29.299999999999997 Z M 25.5 57 L 25.5 33.3 L 30.7 33.3 L 30.7 57 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 -5.61 -9)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-44.9, -45.25)" d="M 52.9 52.7 L 54.199999999999996 52.7 L 54.199999999999996 56.5 C 51.04981609257554 57.216401107360326 47.830583889554504 57.58527146395648 44.599999999999994 57.599999999999994 C 41.099999999999994 57.6 38.599999999999994 56.7 37.099999999999994 54.7 C 35.599999999999994 52.7 34.8 49.7 34.8 45.5 C 34.8 37.1 38.199999999999996 32.9 45.099999999999994 32.9 C 51.99999999999999 32.9 54.99999999999999 36.5 54.99999999999999 43.8 L 54.699999999999996 47.4 L 40.1 47.4 C 40.01052016467688 48.97298018298103 40.470023658742875 50.528222778281304 41.4 51.8 C 42.199999999999996 52.699999999999996 43.699999999999996 53.099999999999994 46 53.099999999999994 Z M 50 43.4 C 50 41 49.7 39.4 48.9 38.5 C 48.099999999999994 37.6 46.9 37.1 45.199999999999996 37.1 C 43.75870688793964 36.98067792476598 42.33635041777776 37.491267426875375 41.3 38.5 C 40.5 39.5 40.099999999999994 41.1 40.099999999999994 43.4 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 -27.66 25.25)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-22.85, -79.5)" d="M 20.2 95.7 L 20.2 63.3 L 25.5 63.3 L 25.5 95.7 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 -7.5 25.25)" >
|
||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-43.01, -79.5)" d="M 42.2 95.7 L 30.9 95.7 L 30.9 63.3 L 42.2 63.3 C 47.1 63.3 50.6 64.5 52.400000000000006 67.1 C 54.20000000000001 69.69999999999999 55.10000000000001 73.6 55.10000000000001 79 C 55.16292964283061 81.3108982321395 54.995435555499796 83.62231663730473 54.60000000000001 85.89999999999999 C 54.25681263742343 87.72599061410322 53.61486768774912 89.48289258163291 52.7 91.10000000000001 C 50.9 94.2 47.4 95.7 42.2 95.7 Z M 49.7 79 C 49.7 75 49.2 72.2 48.2 70.5 C 47.2 68.8 45.2 67.9 42.2 67.9 L 36.2 67.9 L 36.2 91.1 L 42.2 91.1 C 45.300000000000004 91.1 47.400000000000006 89.89999999999999 48.400000000000006 87.6 C 48.94158950263057 86.43260723579563 49.27978654413 85.18127818224775 49.400000000000006 83.89999999999999 C 49.61863879768064 82.27609752559773 49.718903185302 80.63844586111547 49.699999999999996 78.99999999999999 Z" stroke-linecap="round" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 540 608.33)" style="" id="49b1b3d3-ce32-41b6-88ff-dd8774c69031" >
|
||||
<text xml:space="preserve" font-family="Lato" font-size="20" font-style="normal" font-weight="400" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,19,0); fill-rule: nonzero; opacity: 1; white-space: pre;" ><tspan x="-80.28" y="6.28" >Long Term Session</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
+1
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 101.1 108.5"><title>Risorsa 2</title><g id="Livello_2" data-name="Livello 2"><g id="Livello_1-2" data-name="Livello 1"><path d="M30.6,15.7,34,14.5,75.5.2a3.1,3.1,0,0,1,3.1.6,7,7,0,0,1,1.9,2.8c1.2,3.1,2.4,6.1,3.6,9.3a22.1,22.1,0,0,1,1.2,8.9c-.1,1.3-.3,2.6-.3,3.9a15,15,0,0,0,0,3c.1,1.3.2,2.6.4,3.9a14.2,14.2,0,0,0,2.8,7.1c2.8,3.6,5.5,7.3,8.3,11a46.7,46.7,0,0,1,3.8,6,5.5,5.5,0,0,1,.7,2.5,4.1,4.1,0,0,1-.4,2.2,10.9,10.9,0,0,1-2.7,3.3,19.8,19.8,0,0,1-4.1,2.8,9.6,9.6,0,0,0-3,2.3,3.4,3.4,0,0,0-.5,3.6l.9,2.3a3.8,3.8,0,0,1-.5,4.1,10.5,10.5,0,0,1-.8,1.1,3.2,3.2,0,0,0-.4,2.5l.3,1.2a3.1,3.1,0,0,1-.8,2.6c-.6.6-1.1,1.3-1.6,1.9a6.1,6.1,0,0,0-1.5,4.4,12.7,12.7,0,0,0,.6,2.8,16,16,0,0,1,.5,4.9,7.6,7.6,0,0,1-4.1,6.6,6.9,6.9,0,0,1-4,.6l-5.2-.9c-6.5-1.1-12.8-2.7-19.2-4.1L47.8,102h-.2c11.2-7.2,18.5-17.2,21.2-30.2a44.9,44.9,0,0,0-7.1-36.1C54.2,24.6,43.6,18.1,30.6,15.7Z" fill="#fff"/><path d="M12.2,57.5c-4.8,0-8-1.3-9.7-3.9S0,46.7,0,40.7.9,30.4,2.6,27.9s4.9-3.8,9.6-3.8a39,39,0,0,1,9.2,1.1v4.3a50,50,0,0,0-8.6-.8c-2.8,0-4.7.9-5.7,2.6s-1.6,4.8-1.6,9.5.5,7.8,1.5,9.5,2.9,2.5,5.7,2.5a51,51,0,0,0,8.7-.7v4.4A43.8,43.8,0,0,1,12.2,57.5Z" fill="#fff"/><path d="M25.5,29.3V23.9h5.2v5.4Zm0,27.7V33.3h5.2V57Z" fill="#fff"/><path d="M52.9,52.7h1.3v3.8a44.2,44.2,0,0,1-9.6,1.1c-3.5,0-6-.9-7.5-2.9s-2.3-5-2.3-9.2c0-8.4,3.4-12.6,10.3-12.6s9.9,3.6,9.9,10.9l-.3,3.6H40.1a6.8,6.8,0,0,0,1.3,4.4c.8.9,2.3,1.3,4.6,1.3ZM50,43.4c0-2.4-.3-4-1.1-4.9s-2-1.4-3.7-1.4a5,5,0,0,0-3.9,1.4c-.8,1-1.2,2.6-1.2,4.9Z" fill="#fff"/><path d="M20.2,95.7V63.3h5.3V95.7Z" fill="#fff"/><path d="M42.2,95.7H30.9V63.3H42.2c4.9,0,8.4,1.2,10.2,3.8s2.7,6.5,2.7,11.9a34.8,34.8,0,0,1-.5,6.9,16.9,16.9,0,0,1-1.9,5.2C50.9,94.2,47.4,95.7,42.2,95.7ZM49.7,79c0-4-.5-6.8-1.5-8.5s-3-2.6-6-2.6h-6V91.1h6c3.1,0,5.2-1.2,6.2-3.5a11.3,11.3,0,0,0,1-3.7A33.8,33.8,0,0,0,49.7,79Z" fill="#fff"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
+1
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 587.6 587.6"><style>.st0{fill:#FFF}.st1{fill:#06C}</style><path id="XMLID_3_" class="st0" d="M587.6 293.8c0 162.3-131.5 293.8-293.8 293.8C131.6 587.6 0 456.1 0 293.8S131.6 0 293.8 0c162.3 0 293.8 131.5 293.8 293.8"/><path id="XMLID_2_" class="st1" d="M294.6 319c-24.4 0-44.5-8.2-60.3-24.8-15.8-16.5-23.7-37-23.7-61.4 0-24.5 7.9-44.8 23.6-61 15.7-16.2 35.7-24.3 60.2-24.3 24.4 0 44.3 8.2 59.6 24.9 15.3 16.6 23 37 23 61.5 0 24.3-7.7 44.6-23 60.8-15.3 16.1-35 24.3-59.4 24.3"/><path id="XMLID_1_" class="st1" d="M210.6 439.1c0-24.5 7.9-44.8 23.5-61 15.7-16.2 35.7-24.3 60.4-24.3 24.4 0 44.3 8.2 59.5 24.9 15.3 16.7 23 37.1 23 61.5"/></svg>
|
||||
|
After Width: | Height: | Size: 703 B |
+75
@@ -0,0 +1,75 @@
|
||||
{% extends 'rp_base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block centered_container %}
|
||||
<script type="text/javascript">
|
||||
function startTimer(duration, display) {
|
||||
var timer = duration, minutes, seconds;
|
||||
setInterval(function () {
|
||||
minutes = parseInt(timer / 60, 10)
|
||||
seconds = parseInt(timer % 60, 10);
|
||||
|
||||
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||
seconds = seconds < 10 ? "0" + seconds : seconds;
|
||||
|
||||
display.text(minutes + ":" + seconds);
|
||||
|
||||
if (--timer < 0) {
|
||||
timer = duration;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
jQuery(function ($) {
|
||||
var at_display = $('#at_time'),
|
||||
rt_display = $('#rt_time');
|
||||
startTimer({{ at_expiration }}, at_display);
|
||||
startTimer({{ rt_expiration }}, rt_display);
|
||||
});
|
||||
</script>
|
||||
<div class="col-12 p-4">
|
||||
{% csrf_token %}
|
||||
<h4 class="text-left">
|
||||
{% trans "User attributes" %}
|
||||
</h4>
|
||||
|
||||
<dl class="mt-4 mb-4">
|
||||
{% for attribute, value in oidc_rp_user_attrs.items %}
|
||||
<dt>{{ attribute }}:</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
|
||||
<a href="{% url 'spid_cie_rpinitiated_logout' %}" class="btn btn-secondary" role="button">Log out</a>
|
||||
<a href="{% url 'spid_cie_rp_echo_introspection' %}" class="btn btn-secondary" role="button">Verify Access Token (Introspection)</a>
|
||||
|
||||
</div>
|
||||
{% if rt_expiration == 0 %}
|
||||
<div class="col-12 p-4">
|
||||
<h4>{% trans "Warning" %}:</h4>
|
||||
<p>{% trans 'To enable revocable long sessions you must set <em>OIDCFED_ACR_PROFILES</em> in settingslocal.py of the Relying Party' %} <em>OIDCFED_ACR_PROFILES=["https://www.spid.gov.it/SpidL2","https://www.spid.gov.it/SpidL1"]</em></p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-4 p-4">
|
||||
<h4 class="text-left">ACCESS TOKEN</h4>
|
||||
<p> {% trans "The" %} access token <em>{{ at_jti }}</em> <br />
|
||||
{% trans "expires in" %} <span id="at_time">{{ at_expiration }}</span> </p>
|
||||
</div>
|
||||
<div class="col-8 p-4">
|
||||
<h4 class="text-left">REFRESH TOKEN <a href="{% url 'spid_cie_rp_extend_session' %}" class="btn btn-primary btn-xs btn-me" role="button">{% trans "Extend Session" %}</a></h4>
|
||||
|
||||
<p> {% trans "The" %} Refresh Token <em>{{ rt_jti }}</em> <br />
|
||||
{% trans "expires in" %} <span id="rt_time">{{ rt_expiration }}</span> </p>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p style="text-align:justify;">NB:
|
||||
<ul>
|
||||
<li>{% trans "By selecting CIE PROVIDER, the Refresh Token is allowed to be used during the time frame validity of the consent given by the user and is set by the Provider with the variable" %} <em>OIDCFED_PROVIDER_MAX_CONSENT_TIMEFRAME</em> {% trans "in settingslocal.py. Upon expiration the Provider returns error HTTP/400 and the RP example redirects to the landing page" %}</li>
|
||||
<li>{% trans "By selecting SPID PROVIDER, the number of Refresh Tokens that can be issued for a single session is set in the OPs configuration" %} <em>OIDCFED_PROVIDER_MAX_REFRESH</em>. {% trans "Past this number, the Provider returns error HTTP/400 and the RP example redirects to the landing page" %} </li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<!-- Extends default Bootstrap Unical Italia template -->
|
||||
{% extends 'bootstrap-italia-base.html' %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ block.super }}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/access-button.css">
|
||||
{% endblock extra_head %}
|
||||
|
||||
{% block page_title %}
|
||||
{% trans "OIDC Relying Party Django" %}
|
||||
{% endblock page_title %}
|
||||
|
||||
<!-- URL link top left -->
|
||||
{% block header_slim_org_url %}
|
||||
https://www.spid.gov.it/
|
||||
{% endblock header_slim_org_url %}
|
||||
|
||||
<!-- Name top left -->
|
||||
{% block header_slim_org_name %}
|
||||
{% trans "OIDC Relying Party" %}
|
||||
{% endblock header_slim_org_name %}
|
||||
|
||||
<!-- Mobile slim_org_name -->
|
||||
{% block header_slim_mobile_org_name %}
|
||||
{% trans "OIDC Relying Party" %}
|
||||
{% endblock header_slim_mobile_org_name %}
|
||||
|
||||
<!-- Make empty areas -->
|
||||
{% block header_mobile_arrow %}{% endblock header_mobile_arrow %}
|
||||
{% block header_mobile_slim_menu %}{% endblock header_mobile_slim_menu %}
|
||||
|
||||
<!-- Logo in Header -->
|
||||
{% block header_center_logo %}
|
||||
{% endblock header_center_logo %}
|
||||
|
||||
|
||||
<!-- Organization name in Header -->
|
||||
{% block header_center_org_name %}
|
||||
<a href="{% url 'spid_cie_rp_landing'%}">{% trans "OIDC Relying Party" %}</a>
|
||||
{% endblock header_center_org_name %}
|
||||
|
||||
{% block main_menu %}{% endblock main_menu %}
|
||||
|
||||
<!-- Organization description in Header -->
|
||||
{% block header_center_org_subname %}
|
||||
{% endblock header_center_org_subname %}
|
||||
|
||||
{% block container %}
|
||||
<div>
|
||||
<div class="container pt-2 p-3">
|
||||
<div class="row d-lg-flex">
|
||||
<div class="col-12">
|
||||
<div class="card-wrapper card-space">
|
||||
<div class="card card-bg no-after">
|
||||
<div class="card-body">
|
||||
<div class="row h-100">
|
||||
{% block centered_container %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock container %}
|
||||
|
||||
<!-- Logo in Footer -->
|
||||
{% block footer_logo %}
|
||||
{% endblock footer_logo %}
|
||||
|
||||
<!-- Organization name in Footer -->
|
||||
{% block footer_org_name %}
|
||||
{% trans "OIDC Relying Party" %}
|
||||
{% endblock footer_org_name %}
|
||||
|
||||
<!-- Organization name in Footer -->
|
||||
{% block footer_org_subname %}
|
||||
{% trans "OIDC Relying Party" %}
|
||||
{% endblock footer_org_subname %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{% block bootstrap_italia_validation %}
|
||||
|
||||
{% endblock bootstrap_italia_validation %}
|
||||
{% endblock extra_scripts %}
|
||||
|
||||
{% block footer_menu_section %}
|
||||
{% endblock footer_menu_section %}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<!-- Extends default Bootstrap Unical Italia template -->
|
||||
{% extends 'rp_base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block centered_container %}
|
||||
|
||||
<div class="col-12 col-lg-6 pl-lg-4">
|
||||
{% csrf_token %}
|
||||
<div class="callout danger">
|
||||
<div class="callout-title">
|
||||
{% trans error%}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{% trans error_description%}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{% static 'js/spid-sp-access-button.js' %}"></script>
|
||||
<script src="{% static 'js/spid_button.js' %}"></script>
|
||||
|
||||
{% endblock %}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{% extends 'rp_base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block centered_container %}
|
||||
<div class="col-12 p-4">
|
||||
{% csrf_token %}
|
||||
<h4>{% trans 'Introspection values' %}</h4>
|
||||
|
||||
<dl class="mt-4 mb-4">
|
||||
{% for attribute, value in introspection.items %}
|
||||
<dt>{{ attribute }}:</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
|
||||
<a href="{% url 'spid_cie_rpinitiated_logout' %}" class="btn btn-secondary" role="button">Log out</a>
|
||||
<a href="javascript:history.back()" class="btn btn-secondary" role="button">Go Back</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<!-- Extends default Bootstrap Unical Italia template -->
|
||||
{% extends 'rp_base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block centered_container %}
|
||||
|
||||
<div class="col-12 pl-lg-4">
|
||||
|
||||
<div class="row p-3">
|
||||
<h3 class="text-left">
|
||||
{% trans "Welcome" %}
|
||||
</h3>
|
||||
|
||||
<p class="card-title">
|
||||
|
||||
{% blocktrans %}
|
||||
SPID is the access system that allows you to use online services with a unique digital identity.
|
||||
If you already have a digital identity, login with your Identity Provider.
|
||||
If you don’t have a digital identity yet, choose an Identity Provider where obtain your SPID Digital Identity.
|
||||
{% endblocktrans %}
|
||||
|
||||
</p>
|
||||
|
||||
<p class="card-title"></p>
|
||||
|
||||
{% blocktrans %}
|
||||
If you have the new Electronic Identity Card (CIE) you can use it to access the services
|
||||
online Public Administration. You need the PIN and PUK codes:
|
||||
the first part of the two codes is in the receipt of the CIE request, the second part is delivered
|
||||
together with the CIE. Visit the official site of CIE id to get the software, documentation and tutorial.
|
||||
{% endblocktrans %}
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<span
|
||||
class="badge square-corners mb-3 mr-2 ml-0 pr-10 p-2 mw-100">
|
||||
<a href="#" class="italia-it-button italia-it-button-size-m button-spid"
|
||||
spid-idp-button="#spid-idp-button-medium-get" aria-expanded="false">
|
||||
<span class="italia-it-button-icon"><img
|
||||
src="{% static 'svg/spid-ico-circle-bb.svg' %}"
|
||||
alt=""></span>
|
||||
<span class="italia-it-button-text">{% trans "Entra con SPID" %}</span>
|
||||
</a>
|
||||
<div id="spid-idp-button-medium-get"
|
||||
class="spid-idp-button spid-idp-button-tip spid-idp-button-relative"
|
||||
style="display: none; left: 56.5px; top: 46.05px;">
|
||||
|
||||
<ul id="spid-idp-list-medium-root-get" class="spid-idp-button-menu"
|
||||
aria-labelledby="spid-idp">
|
||||
{% for sub, attrs in spid_providers.items %}
|
||||
<li>
|
||||
<a href="{% url 'spid_cie_rp_begin' %}?provider={{sub}}&profile=spid" id="provider-spid">
|
||||
<span class="spid-sr-only">{{ attrs.organization_name }}</span>
|
||||
{% if attrs.logo_uri %}
|
||||
<img src="{{ attrs.logo_uri }}" alt="{{ attrs.organization_name }}">
|
||||
{% else %}
|
||||
<img src="{% static 'svg/no_image_available.svg' %}" alt="{{ sub }}">
|
||||
<p>{{ sub }}</p>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'spid_cie_rp_begin' %}?provider={{sub}}&profile=spid&scope=openid offline_access" id="provider-spid-refresh_token">
|
||||
<span class="spid-sr-only">{{ attrs.organization_name }}</span>
|
||||
{% if attrs.logo_uri %}
|
||||
<img src="{{ attrs.logo_uri }}-LTS.svg" alt="{{ attrs.organization_name }}">
|
||||
{% else %}
|
||||
<img src="{% static 'svg/no_image_available.svg' %}" alt="{{ sub }}">
|
||||
<p>{{ sub }}</p>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="badge square-corners mb-3 mr-2 ml-0 pr-10 p-2 mw-100"
|
||||
style="font-size: 100%">
|
||||
|
||||
<a href="#" class="italia-it-button italia-it-button-size-m button-cie"
|
||||
cie-idp-button="#cie-idp-button-medium-get">
|
||||
<span class="italia-it-button-icon"><img
|
||||
src="{% static 'svg/cie-ico-circle-bb.svg' %}"
|
||||
alt="">
|
||||
</span>
|
||||
<span class="italia-it-button-text">{% trans "Entra con CIE" %}</span>
|
||||
</a>
|
||||
<div id="cie-idp-button-medium-get"
|
||||
class="cie-idp-button cie-idp-button-tip cie-idp-button-relative"
|
||||
style="display: none; left: 56.5px; top: 46.05px;">
|
||||
|
||||
<ul id="cie-idp-list-medium-root-get" class="cie-idp-button-menu" aria-labelledby="cie-idp">
|
||||
{% for sub, attrs in cie_providers.items %}
|
||||
<li>
|
||||
<a href="{% url 'spid_cie_rp_begin' %}?provider={{sub}}&profile=cie" id="provider-cie">
|
||||
<span class="spid-sr-only">{{ attrs.organization_name }}</span>
|
||||
{% if attrs.logo_uri %}
|
||||
<img src="{{ attrs.logo_uri }}" alt="{{ attrs.organization_name }}">
|
||||
{% else %}
|
||||
<img src="{% static 'svg/no_image_available.svg' %}" alt="{{ sub }}">
|
||||
<p>{{ sub }}</p>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'spid_cie_rp_begin' %}?provider={{sub}}&profile=cie&scope=openid offline_access" id="provider-cie-refresh_token">
|
||||
<span class="spid-sr-only">{{ attrs.organization_name }} {% trans "INCLUDING REFRESH TOKEN" %}</span>
|
||||
{% if attrs.logo_uri %}
|
||||
<img src="{{ attrs.logo_uri }}" alt="{{ attrs.organization_name }}">
|
||||
<p>{% trans "INCLUDING REFRESH TOKEN" %}</p>
|
||||
{% else %}
|
||||
<img src="{% static 'svg/no_image_available.svg' %}" alt="{{ sub }}">
|
||||
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="{% static 'js/spid-sp-access-button.js' %}"></script>
|
||||
<script src="{% static 'js/spid_button.js' %}"></script>
|
||||
|
||||
{% endblock %}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
|
||||
from spid_cie_oidc.provider.tests.settings import op_conf, op_conf_priv_jwk
|
||||
from spid_cie_oidc.authority.tests.settings import rp_conf, INTERMEDIARY_JWK1
|
||||
from spid_cie_oidc.entity.jwtse import create_jws, create_jwe
|
||||
from spid_cie_oidc.entity.utils import iat_now, exp_from_now, get_key, KeyUsage
|
||||
from spid_cie_oidc.entity.utils import get_jwks
|
||||
from cryptojwt.jws.utils import left_hash
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MockedTokenEndPointResponse:
|
||||
def __init__(self):
|
||||
self.status_code = 200
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
id_token = {
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'nonce': 'ljbvL3rpscgS4ZGda7cgibXHr7vrNREW',
|
||||
'at_hash': '',
|
||||
'c_hash': 'tij0h-zL_bSrsVXy-d3qHw',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'iss': op_conf["sub"],
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
access_token = {
|
||||
'iss': op_conf["sub"],
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'client_id': rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
'scope': 'openid',
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
jwt_at = create_jws(access_token, op_conf_priv_jwk, typ="at+jwt")
|
||||
id_token['at_hash'] = left_hash(jwt_at, "HS256")
|
||||
jwt_id = create_jws(id_token, op_conf_priv_jwk)
|
||||
|
||||
self.access_token = jwt_at
|
||||
self.id_token = jwt_id
|
||||
|
||||
res = {
|
||||
"access_token": jwt_at,
|
||||
"id_token": jwt_id,
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"scope": "openid",
|
||||
}
|
||||
return json.dumps(res).encode()
|
||||
|
||||
|
||||
class MockedTokenEndPointNoCorrectResponse:
|
||||
def __init__(self):
|
||||
self.status_code = 200
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
id_token = {
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'nonce': 'ljbvL3rpscgS4ZGda7cgibXHr7vrNREW',
|
||||
'at_hash': '',
|
||||
'c_hash': 'tij0h-zL_bSrsVXy-d3qHw',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'iss': op_conf["sub"],
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
access_token = {
|
||||
'iss': op_conf["sub"],
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'client_id': rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
'scope': 'openid',
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
jwt_at = create_jws(access_token, op_conf_priv_jwk, typ="at+jwt")
|
||||
id_token['at_hash'] = left_hash(jwt_at, "HS256")
|
||||
jwt_id = create_jws(id_token, op_conf_priv_jwk)
|
||||
|
||||
self.access_token = jwt_at
|
||||
self.id_token = jwt_id
|
||||
|
||||
res = {
|
||||
"access_token": jwt_at,
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"scope": "openid",
|
||||
}
|
||||
return json.dumps(res).encode()
|
||||
|
||||
|
||||
class MockedTokenEndPointNoCorrectIdTokenResponse:
|
||||
def __init__(self):
|
||||
self.status_code = 200
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
id_token = {
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'nonce': 'ljbvL3rpscgS4ZGda7cgibXHr7vrNREW',
|
||||
'at_hash': '',
|
||||
'c_hash': 'tij0h-zL_bSrsVXy-d3qHw',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'iss': op_conf["sub"],
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
access_token = {
|
||||
'iss': op_conf["sub"],
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'client_id': rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
'scope': 'openid',
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
jwt_at = create_jws(access_token, op_conf_priv_jwk, typ="at+jwt")
|
||||
id_token['at_hash'] = left_hash(jwt_at, "HS256")
|
||||
jwt_id = create_jws(id_token, INTERMEDIARY_JWK1)
|
||||
|
||||
self.access_token = jwt_at
|
||||
self.id_token = jwt_id
|
||||
|
||||
res = {
|
||||
"access_token": jwt_at,
|
||||
"token_type": "Bearer",
|
||||
"id_token": jwt_id,
|
||||
"expires_in": 3600,
|
||||
"scope": "openid",
|
||||
}
|
||||
return json.dumps(res).encode()
|
||||
|
||||
|
||||
class MockedTokenEndPointNoCorrectAtHashResponse:
|
||||
def __init__(self):
|
||||
self.status_code = 200
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
id_token = {
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'nonce': 'ljbvL3rpscgS4ZGda7cgibXHr7vrNREW',
|
||||
'at_hash': '45e66ce53e48273dca5a446',
|
||||
'c_hash': 'tij0h-zL_bSrsVXy-d3qHw',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'iss': op_conf["sub"],
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
access_token = {
|
||||
'iss': op_conf["sub"],
|
||||
'sub': '2ed008b45e66ce53e48273dca5a4463bc8ebd036ebaa824f4582627683c2451b',
|
||||
'aud': [rp_conf["metadata"]["openid_relying_party"]["client_id"],],
|
||||
'client_id': rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
'scope': 'openid',
|
||||
'jti': '402e61bd-950c-4934-83d4-c09a05468828',
|
||||
'exp': exp_from_now(),
|
||||
'iat': iat_now()
|
||||
}
|
||||
jwt_at = create_jws(access_token, op_conf_priv_jwk, typ="at+jwt")
|
||||
jwt_id = create_jws(id_token, op_conf_priv_jwk)
|
||||
|
||||
self.access_token = jwt_at
|
||||
self.id_token = jwt_id
|
||||
|
||||
res = {
|
||||
"access_token": jwt_at,
|
||||
"id_token": jwt_id,
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"scope": "openid",
|
||||
}
|
||||
return json.dumps(res).encode()
|
||||
|
||||
|
||||
class MockedUserInfoResponse:
|
||||
def __init__(self):
|
||||
self.status_code = 200
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
jwt = {
|
||||
"sub": 'asdasdasdasasdasdas',
|
||||
"https://attributes.eid.gov.it/fiscal_number": "sdfsfs908df09s8df90s8fd0"
|
||||
}
|
||||
jws = create_jws(jwt, op_conf_priv_jwk)
|
||||
jwks = get_jwks(rp_conf["metadata"]["openid_relying_party"])
|
||||
key = get_key(jwks, KeyUsage.encryption)
|
||||
jwe = create_jwe(
|
||||
jws,
|
||||
key
|
||||
)
|
||||
return jwe.encode()
|
||||
|
||||
class MockedLogout:
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
pass
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from spid_cie_oidc.relying_party.models import OidcAuthenticationToken
|
||||
from spid_cie_oidc.relying_party.utils import html_json_preview
|
||||
|
||||
|
||||
class OidcRpTest(TestCase):
|
||||
# TODO: re enable
|
||||
def _test_stupid_logout(self):
|
||||
# a stupid logout that MUST fail
|
||||
url = f'{reverse("spid_cie_rpinitiated_logout")}'
|
||||
req = Client()
|
||||
res = req.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
# end stupid logout test
|
||||
|
||||
# TODO: re enable
|
||||
def _test_session(self):
|
||||
url = f'{reverse("spid_cie_rp_begin")}?issuer_id=op_test'
|
||||
req = Client()
|
||||
res = req.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
|
||||
authz_url = f'{reverse("op_test:spid_oidc_op_authz")}?{res.url.split("?")[1]}'
|
||||
res = req.get(authz_url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
self.assertIn("code=", res.url)
|
||||
|
||||
url = f'{reverse("spid_cie_rp_callback")}?{res.url.split("?")[1]}'
|
||||
res = req.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
|
||||
url = f'{reverse("spid_cie_rp_echo_attributes")}'
|
||||
res = req.get(url)
|
||||
self.assertIn("sando", res.content.decode())
|
||||
|
||||
# test models
|
||||
tokens = OidcAuthenticationToken.objects.first()
|
||||
|
||||
tokens.access_token_preview
|
||||
tokens.id_token_preview
|
||||
|
||||
# and now logout
|
||||
url = f'{reverse("spid_cie_rpinitiated_logout")}'
|
||||
res = req.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
|
||||
# TODO: re enable
|
||||
def _test_html_json_preview(self):
|
||||
html_json_preview('{"a" : 34, "b" : 78}')
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class OidcRPCliTests(TestCase):
|
||||
def setUp(self):
|
||||
# create a FederationEntityConfiguration SP
|
||||
pass
|
||||
|
||||
def test_fetch_openid_providers(self):
|
||||
overrides = dict(OIDCFED_IDENTITY_PROVIDERS="", OIDCFED_REQUIRED_TRUST_MARKS="")
|
||||
|
||||
with self.settings(**overrides):
|
||||
pass
|
||||
# out = self.call_command('fetch_openid_providers', '--start')
|
||||
# self.assertEqual(out.splitlines()[0], "test arbitrary command")
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
import urllib
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.test import Client, TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from spid_cie_oidc.authority.tests.settings import rp_conf
|
||||
from spid_cie_oidc.entity.models import (
|
||||
FederationEntityConfiguration,
|
||||
FetchedEntityStatement,
|
||||
TrustChain,
|
||||
)
|
||||
from spid_cie_oidc.entity.jwtse import unpad_jwt_payload
|
||||
from spid_cie_oidc.entity.tests.settings import TA_SUB
|
||||
from spid_cie_oidc.entity.utils import datetime_from_timestamp, exp_from_now, iat_now
|
||||
from spid_cie_oidc.provider.tests.settings import op_conf
|
||||
|
||||
def create_tc():
|
||||
NOW = datetime_from_timestamp(iat_now())
|
||||
EXP = datetime_from_timestamp(exp_from_now(33))
|
||||
ta_fes = FetchedEntityStatement.objects.create(
|
||||
sub=TA_SUB,
|
||||
iss=TA_SUB,
|
||||
exp=EXP,
|
||||
iat=NOW,
|
||||
)
|
||||
return TrustChain.objects.create(
|
||||
sub=op_conf["sub"],
|
||||
exp=EXP,
|
||||
jwks = [],
|
||||
status="valid",
|
||||
trust_anchor=ta_fes,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
def create_tc_metadata_no_correct():
|
||||
NOW = datetime_from_timestamp(iat_now())
|
||||
EXP = datetime_from_timestamp(exp_from_now(33))
|
||||
ta_fes = FetchedEntityStatement.objects.create(
|
||||
sub=TA_SUB,
|
||||
iss=TA_SUB,
|
||||
exp=EXP,
|
||||
iat=NOW,
|
||||
)
|
||||
local_op_conf = deepcopy(op_conf)
|
||||
metadata = local_op_conf["metadata"]
|
||||
metadata["openid_provider"]["jwks"] = {"keys": []}
|
||||
return TrustChain.objects.create(
|
||||
sub=op_conf["sub"],
|
||||
exp=EXP,
|
||||
status="valid",
|
||||
jwks = [],
|
||||
metadata=metadata,
|
||||
trust_anchor=ta_fes,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
class RPBeginTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.req = HttpRequest()
|
||||
NOW = datetime_from_timestamp(iat_now())
|
||||
EXP = datetime_from_timestamp(exp_from_now(33))
|
||||
self.rp_conf = FederationEntityConfiguration.objects.create(**rp_conf)
|
||||
self.ta_fes = FetchedEntityStatement.objects.create(
|
||||
sub=TA_SUB,
|
||||
iss=TA_SUB,
|
||||
exp=EXP,
|
||||
iat=NOW,
|
||||
)
|
||||
metadata = deepcopy(op_conf["metadata"])
|
||||
self.trust_chain = TrustChain.objects.create(
|
||||
sub=op_conf["sub"],
|
||||
exp=EXP,
|
||||
jwks = [],
|
||||
metadata=metadata,
|
||||
status="valid",
|
||||
trust_anchor=self.ta_fes,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_rp_begin(self):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
self.assertTrue(res.status_code == 302)
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_begin.SpidCieOidcRpBeginView.get_oidc_op", return_value=None)
|
||||
def test_rp_begin_no_tc(self, mocked):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
|
||||
def test_no_request(self):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"request": {}})
|
||||
self.assertTrue(res.status_code == 404)
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
self.assertTrue("Missing provider url" in res.content.decode())
|
||||
|
||||
# I changed the code to get a smarter solution
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_no_unallowed_tc(self):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": "provider"})
|
||||
self.assertTrue(res.status_code == 403)
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
# self.assertTrue("Unallowed Trust Anchor" in res.content.decode())
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_no_rp_entity_conf(self):
|
||||
FederationEntityConfiguration.objects.all().delete()
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
self.assertTrue(res.status_code == 404)
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
self.assertTrue("Missing configuration" in res.content.decode())
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_invalid_provider_metadata(self):
|
||||
metadata = deepcopy(op_conf["metadata"])
|
||||
metadata["openid_provider"].pop("jwks")
|
||||
self.trust_chain.metadata = metadata
|
||||
self.trust_chain.save()
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
self.assertTrue(res.status_code == 404)
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
self.assertTrue("Invalid provider Metadata" in res.content.decode())
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_tc_no_active(self):
|
||||
self.trust_chain.is_active = False
|
||||
self.trust_chain.save()
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
self.assertTrue(res.status_code == 404)
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
self.assertTrue("found but DISABLED at" in res.content.decode())
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_failed_to_get_jwks(self):
|
||||
metadata = deepcopy(op_conf["metadata"])
|
||||
metadata['openid_provider'].pop("jwks")
|
||||
metadata["jwks_uri"] = "jwks_uri"
|
||||
self.trust_chain.metadata = metadata
|
||||
self.trust_chain.save()
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
|
||||
self.assertTrue(res.status_code == 404)
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
|
||||
def test_rp_begin_tc_no_metadata(self):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
self.patcher = patch(
|
||||
"spid_cie_oidc.relying_party.views.rp_begin.SpidCieOidcRpBeginView.get_oidc_op",
|
||||
return_value = create_tc()
|
||||
)
|
||||
self.patcher.start()
|
||||
res = client.get(url, {"provider": op_conf["sub"], "trust_anchor": TA_SUB})
|
||||
self.assertTrue("request rejected" in res.content.decode())
|
||||
self.patcher.stop()
|
||||
|
||||
@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB, OIDCFED_TRUST_ANCHORS=[TA_SUB])
|
||||
def test_rp_begin_tc_no_redirect_uri(self):
|
||||
red_url = "http://rp-test.it/oidc/rp/callback-test/"
|
||||
FederationEntityConfiguration.objects.all().delete()
|
||||
local_rp_conf = deepcopy(rp_conf)
|
||||
local_rp_conf["metadata"]["openid_relying_party"]["redirect_uris"] = [red_url],
|
||||
self.rp_conf = FederationEntityConfiguration.objects.create(**local_rp_conf)
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_begin")
|
||||
TrustChain.objects.all()
|
||||
res = client.get(
|
||||
url,
|
||||
{
|
||||
"provider": op_conf["sub"],
|
||||
"trust_anchor": TA_SUB,
|
||||
"redirect_uri": "http://rp-test.it/oidc/rp/callback"
|
||||
}
|
||||
)
|
||||
req = urllib.parse.parse_qs(res.url)['request'][0]
|
||||
req_data = unpad_jwt_payload(req)
|
||||
self.assertTrue(red_url in req_data['redirect_uri'])
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
from copy import deepcopy
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from spid_cie_oidc.accounts.models import User
|
||||
from spid_cie_oidc.entity.models import FederationEntityConfiguration
|
||||
from spid_cie_oidc.entity.utils import get_jwks
|
||||
|
||||
from spid_cie_oidc.relying_party.models import OidcAuthentication
|
||||
from spid_cie_oidc.authority.tests.settings import rp_conf
|
||||
from spid_cie_oidc.provider.tests.settings import op_conf
|
||||
from spid_cie_oidc.provider.tests.authn_request_settings import AUTHN_REQUEST_SPID
|
||||
|
||||
from spid_cie_oidc.relying_party.tests.mocked_response import (
|
||||
MockedTokenEndPointNoCorrectIdTokenResponse,
|
||||
MockedTokenEndPointNoCorrectResponse,
|
||||
MockedTokenEndPointResponse,
|
||||
MockedTokenEndPointNoCorrectAtHashResponse,
|
||||
MockedUserInfoResponse
|
||||
)
|
||||
|
||||
|
||||
STATE = "fyZiOL9Lf2CeKuNT2JzxiLRDink0uPcd"
|
||||
CODE = "usDwMnEzJPpG5oaV8x3j&"
|
||||
|
||||
|
||||
class RpCallBack(TestCase):
|
||||
def setUp(self):
|
||||
self.rp_jwk = {
|
||||
'use': 'sig',
|
||||
'kty': 'RSA',
|
||||
'kid': '19xSsWuFOo5bFBUECA5G3V5GEhC0s7X8TTCEykdzsmo',
|
||||
'e': 'AQAB',
|
||||
'n': 'vfzFzFAv4e1IPfH6XMeB_L3f9sRZuaOtRsAs7s6ujGv6PwVMPsdELqd2NzrmKrLih3ZysJ6RoRe87rGZUZu2GbDtZhupCPb-1MuxB810svua9PwCrQK2wADM8q26colAiAOGSt912LUPC6MOYus44xd7TUDtQcsm-3VXWu4DA19NQXOaUh2TRal6GKXr7D8teod0fo736oHTObWoDZ0KdAGlwxI6IbQKpipgTD6lZ3l9L6WSmdLI9T-TyyNV9fW6rNqhnbySCEGkCM-Up6C2GQqLTq76kQNSMGasXrd3qH07KnTLBZsFy4JV4L-ws3zgaB4PLBOdfYAYe-xSp9-H_Q', 'd': 'MYc9FXduFCrko2l6yEcmhvoE8fLcJT4bRQ-CZzswW-EmWtmJt_AAwVzcv6c2K7l4vrHNUmf0NRfYJC3ed1ztiyMJsI3TckfZxSXY39za6HIZQnaUSAAmHkXXKAjMS2Gmlg69KrW4picFZhY7AOSrbuBHP6uGbpmEbxd3D3hvBqiU6cn_TAwbIhsJniTuSYYbvHd8z93QCKBfp_nDMltz6WG8YUXFiqtzRKv62Y4K2J_zjyd7-JKiWEBh5FUNuqZ2txEV3-q6bwoh3lSGA1qZtVgU61CwHqGQK3uxfEGPV68O48NVJNEe4YgNE806IfREgprTe3osGHd2S96s-t8oYQ',
|
||||
'p': '9pwMs9MCfes5vYzXb8n1BqAL3LGdPp48jg7iKD4mZ6oLpeCbwNqtB7zN24OqrOnP2nREL2ugkSU2bBrsMy1hZ6k2DnN7cZkNgv4CZ_lO35Bc9CF9j1sCTxUnXQknympL8HVZoG2TdxUL0P09TgiBYz8SVH-uxFnlCuVZBLx35Lk',
|
||||
'q': 'xTjFHi0QGPYSXGgbUN5cl9LOw7K2ifULDbEPiYSnbzd_oVneA-q6BmanMM5CLrj8qKJPy2Cuz3do-ZZPG_SN5AU_x23K9Vga3honcomo7G0cYKi9wGPBKzAMCRNRFHp3f3BAcp6HUJRn02Q_F_xDmXeC8JiPeATSBIDCXv41I2U'
|
||||
}
|
||||
data = deepcopy(AUTHN_REQUEST_SPID)
|
||||
self.rp_config = deepcopy(rp_conf)
|
||||
authz_entry = dict(
|
||||
client_id=self.rp_config["metadata"]["openid_relying_party"]["client_id"],
|
||||
provider_id=op_conf["sub"],
|
||||
data=json.dumps(data),
|
||||
state=STATE,
|
||||
provider_configuration=op_conf["metadata"]["openid_provider"],
|
||||
)
|
||||
OidcAuthentication.objects.create(**authz_entry)
|
||||
self.rp_config["sub"] = self.rp_config["metadata"]["openid_relying_party"]["client_id"]
|
||||
FederationEntityConfiguration.objects.create(**self.rp_config)
|
||||
rp_conf_saved = FederationEntityConfiguration.objects.all().first()
|
||||
get_jwks(rp_conf_saved.metadata['openid_relying_party'])[0]["kid"] = rp_conf_saved.jwks_core[0]["kid"]
|
||||
rp_conf_saved.save()
|
||||
self.op_conf = FederationEntityConfiguration.objects.create(**op_conf)
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback(self, mocked, mocked_2):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
user = get_user_model().objects.first()
|
||||
self.assertTrue(
|
||||
user.attributes['fiscal_number'] == "sdfsfs908df09s8df90s8fd0"
|
||||
)
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_mixups_attacks(self, mocked, mocked_2):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE, "iss": "WRONG"})
|
||||
self.assertTrue(res.status_code == 400)
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_callback.process_user_attributes", return_value=None)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_no_rp_attr_map(self, mocked, mocked_2, mocked_3):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue(res.status_code == 403)
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_callback.process_user_attributes", return_value=None)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_incorret_request(self, mocked, mocked_2, mocked_3):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {})
|
||||
self.assertTrue("error" in res.json())
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_callback.process_user_attributes", return_value=None)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_callback.SpidCieOidcRpCallbackView.get_userinfo", return_value=None)
|
||||
def test_rp_callback_no_userinfo(self, mocked, mocked_2, mocked_3):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue(res.status_code == 400)
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_callback.process_user_attributes", return_value=None)
|
||||
@patch("spid_cie_oidc.relying_party.views.rp_callback.SpidCieOidcRpCallbackView.access_token_request", return_value=None)
|
||||
def test_rp_callback_no_token_response(self, mocked, mocked_2):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue(res.status_code == 400)
|
||||
self.assertTrue("invalid token response" in res.content.decode())
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_no_rp_conf(self, mocked, mocked_2):
|
||||
FederationEntityConfiguration.objects.filter(
|
||||
sub = self.rp_config["sub"]
|
||||
).delete()
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue("Relying party not found" in res.content.decode())
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_no_authz(self, mocked, mocked_2):
|
||||
OidcAuthentication.objects.all().delete()
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue("Authentication not found" in res.content.decode())
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_reunification(self, mocked, mocked_2):
|
||||
user = User.objects.create(username = "username", attributes = {"fiscal_number" : "sdfsfs908df09s8df90s8fd0"}),
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
user = get_user_model().objects.first()
|
||||
self.assertTrue(
|
||||
user.attributes['fiscal_number'] == "sdfsfs908df09s8df90s8fd0"
|
||||
)
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointNoCorrectResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_no_correct_token_response(self, mocked, mocked1):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue(res.status_code == 400)
|
||||
self.assertTrue("invalid_request" in res.content.decode())
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointNoCorrectIdTokenResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_no_correct_id_token_response(self, mocked, mocked1):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue(res.status_code == 403)
|
||||
self.assertTrue("invalid_token" in res.content.decode())
|
||||
|
||||
@override_settings(HTTP_CLIENT_SYNC=True)
|
||||
@patch("requests.post", return_value=MockedTokenEndPointNoCorrectAtHashResponse())
|
||||
@patch("requests.get", return_value=MockedUserInfoResponse())
|
||||
def test_rp_callback_no_correct_at_hash_response(self, mocked, mocked1):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_callback")
|
||||
res = client.get(url, {"state": STATE, "code": CODE})
|
||||
self.assertTrue(res.status_code == 403)
|
||||
self.assertTrue("at_hash verification failed" in res.content.decode())
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from spid_cie_oidc.authority.tests.settings import rp_conf
|
||||
from spid_cie_oidc.entity.models import FederationEntityConfiguration
|
||||
from spid_cie_oidc.provider.tests.settings import op_conf
|
||||
from spid_cie_oidc.relying_party.models import (
|
||||
OidcAuthentication,
|
||||
OidcAuthenticationToken
|
||||
)
|
||||
from spid_cie_oidc.relying_party.tests.mocked_response import MockedLogout
|
||||
|
||||
|
||||
class RpIntiatedLogoutTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create(
|
||||
username="test",
|
||||
first_name="test",
|
||||
last_name="test",
|
||||
email="[email protected]",
|
||||
)
|
||||
self.user.set_password("test")
|
||||
self.user.save()
|
||||
authz_request = OidcAuthentication.objects.create(
|
||||
client_id=rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
provider_configuration=op_conf["metadata"]["openid_provider"],
|
||||
)
|
||||
self.authz_token = OidcAuthenticationToken.objects.create(
|
||||
user= self.user,
|
||||
authz_request = authz_request,
|
||||
)
|
||||
FederationEntityConfiguration.objects.create(**rp_conf)
|
||||
self.admin_user = get_user_model().objects.create_superuser(
|
||||
username="superuser", password="secret", email="[email protected]"
|
||||
)
|
||||
|
||||
|
||||
@patch("requests.post", return_value=MockedLogout())
|
||||
def test_rpinitiated_logout(self, mokced):
|
||||
c = Client()
|
||||
c.login(username="test", password="test")
|
||||
url = reverse("spid_cie_rpinitiated_logout")
|
||||
res = c.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
self.assertTrue(res.url == "/oidc/rp/landing")
|
||||
|
||||
def test_rpinitiated_logout_no_revocation_endpoint(self):
|
||||
OidcAuthentication.objects.all().delete()
|
||||
op_conf_local = deepcopy(op_conf["metadata"]["openid_provider"])
|
||||
op_conf_local.pop("revocation_endpoint")
|
||||
authz_request = OidcAuthentication.objects.create(
|
||||
client_id=rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
provider_configuration=op_conf_local,
|
||||
)
|
||||
self.authz_token = OidcAuthenticationToken.objects.create(
|
||||
user= self.user,
|
||||
authz_request = authz_request,
|
||||
)
|
||||
c = Client()
|
||||
c.login(username="test", password="test")
|
||||
url = reverse("spid_cie_rpinitiated_logout")
|
||||
res = c.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
self.assertTrue(res.url == "/oidc/rp/landing")
|
||||
|
||||
def test_rpinitiated_logout_no_auth_token(self):
|
||||
OidcAuthenticationToken.objects.all().delete()
|
||||
c = Client()
|
||||
c.login(username="test", password="test")
|
||||
url = reverse("spid_cie_rpinitiated_logout")
|
||||
res = c.get(url)
|
||||
self.assertTrue(res.status_code == 302)
|
||||
self.assertTrue(res.url == "/oidc/rp/landing")
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase, override_settings
|
||||
from spid_cie_oidc.entity.models import FetchedEntityStatement, TrustChain
|
||||
from spid_cie_oidc.entity.tests.settings import TA_SUB
|
||||
from spid_cie_oidc.entity.utils import (
|
||||
datetime_from_timestamp,
|
||||
exp_from_now,
|
||||
iat_now
|
||||
)
|
||||
from spid_cie_oidc.provider.tests.settings import op_conf
|
||||
|
||||
EXP = datetime_from_timestamp(exp_from_now(33))
|
||||
NOW = datetime_from_timestamp(iat_now())
|
||||
|
||||
|
||||
def create_tc():
|
||||
ta_fes = FetchedEntityStatement.objects.create(
|
||||
sub=TA_SUB,
|
||||
iss=TA_SUB,
|
||||
exp=EXP,
|
||||
iat=NOW
|
||||
)
|
||||
return TrustChain.objects.create(
|
||||
sub=op_conf["sub"],
|
||||
exp=EXP,
|
||||
jwks = [],
|
||||
metadata=op_conf["metadata"],
|
||||
status="valid",
|
||||
trust_anchor=ta_fes,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
|
||||
class FetchProviderTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.ta_fes = FetchedEntityStatement.objects.create(
|
||||
sub=TA_SUB,
|
||||
iss=TA_SUB,
|
||||
exp=datetime_from_timestamp(exp_from_now(33)),
|
||||
iat=datetime_from_timestamp(iat_now()),
|
||||
)
|
||||
|
||||
def exec(self, cmd_name:str, *args, **kwargs):
|
||||
call_command(
|
||||
cmd_name,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@override_settings(OIDCFED_IDENTITY_PROVIDERS = {"spid": {"http://127.0.0.1:8000/oidc/op/" :"http://testserver/"}, "cie":{}})
|
||||
def test_fetch_provider(self):
|
||||
self.patcher = patch(
|
||||
"spid_cie_oidc.entity.trust_chain_operations.get_or_create_trust_chain",
|
||||
return_value = create_tc()
|
||||
)
|
||||
self.patcher.start()
|
||||
self.exec('fetch_openid_providers', '--start')
|
||||
self.patcher.stop()
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from spid_cie_oidc.entity.models import FetchedEntityStatement, TrustChain
|
||||
from spid_cie_oidc.entity.tests.settings import TA_SUB
|
||||
from spid_cie_oidc.entity.utils import (
|
||||
datetime_from_timestamp,
|
||||
exp_from_now,
|
||||
iat_now
|
||||
)
|
||||
from spid_cie_oidc.provider.tests.settings import op_conf
|
||||
|
||||
|
||||
class RpLandingTest(TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
NOW = datetime_from_timestamp(iat_now())
|
||||
EXP = datetime_from_timestamp(exp_from_now(33))
|
||||
self.ta_fes = FetchedEntityStatement.objects.create(
|
||||
sub=TA_SUB,
|
||||
iss=TA_SUB,
|
||||
exp=EXP,
|
||||
iat=NOW,
|
||||
)
|
||||
|
||||
self.trust_chain = TrustChain.objects.create(
|
||||
sub=op_conf["sub"],
|
||||
exp=EXP,
|
||||
jwks = [],
|
||||
metadata=op_conf["metadata"],
|
||||
status="valid",
|
||||
trust_anchor=self.ta_fes,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
def test_rp_landing(self):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_landing")
|
||||
res = client.get(url)
|
||||
self.assertTrue(res.status_code == 200)
|
||||
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class CallBackEchoAttributesTest(TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
return super().setUp()
|
||||
|
||||
def test_call_back_echo_attributes(self):
|
||||
client = Client()
|
||||
url = reverse("spid_cie_rp_echo_attributes")
|
||||
session = client.session
|
||||
session.update({"oidc_rp_user_attrs": {"email":"[email protected]"}})
|
||||
session.save()
|
||||
res = client.get(url)
|
||||
self.assertTrue(res.status_code == 200)
|
||||
self.assertTrue("[email protected]" in res.content.decode())
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from spid_cie_oidc.authority.tests.settings import rp_conf
|
||||
from spid_cie_oidc.entity.jwtse import create_jws
|
||||
from spid_cie_oidc.provider.tests.settings import (
|
||||
op_conf,
|
||||
op_conf_priv_jwk
|
||||
)
|
||||
from spid_cie_oidc.relying_party.models import (
|
||||
OidcAuthentication,
|
||||
OidcAuthenticationToken
|
||||
)
|
||||
|
||||
RP_SUB = rp_conf["sub"]
|
||||
RP_CLIENT_ID = rp_conf["metadata"]["openid_relying_party"]["client_id"]
|
||||
|
||||
class RpModelTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create(
|
||||
username="test",
|
||||
first_name="test",
|
||||
last_name="test",
|
||||
email="[email protected]",
|
||||
)
|
||||
self.user.set_password("test")
|
||||
self.user.save()
|
||||
authz_request = OidcAuthentication.objects.create(
|
||||
client_id=rp_conf["metadata"]["openid_relying_party"]["client_id"],
|
||||
provider_configuration=op_conf["metadata"]["openid_provider"],
|
||||
)
|
||||
self.token = {
|
||||
"iss": op_conf["sub"],
|
||||
"sub": RP_SUB,
|
||||
"aud": [RP_CLIENT_ID],
|
||||
"client_id": RP_CLIENT_ID,
|
||||
"scope": "openid",
|
||||
}
|
||||
self.rt_jws = create_jws(self.token, op_conf_priv_jwk)
|
||||
self.authz_token = OidcAuthenticationToken.objects.create(
|
||||
user= self.user,
|
||||
authz_request = authz_request,
|
||||
code = "code",
|
||||
access_token = self.rt_jws,
|
||||
id_token = self.rt_jws,
|
||||
)
|
||||
|
||||
def test_token_preview(self):
|
||||
result = OidcAuthenticationToken.token_preview(self.authz_token, self.rt_jws)
|
||||
self.assertTrue(json.loads(result)["iss"] == "http://op-test/oidc/op/")
|
||||
result = OidcAuthenticationToken.token_preview(self.authz_token, "")
|
||||
self.assertTrue(result == "--")
|
||||
result = OidcAuthenticationToken.token_preview(self.authz_token, "token")
|
||||
self.assertTrue(result == None)
|
||||
result = OidcAuthenticationToken.__str__(self.authz_token)
|
||||
self.assertTrue("code" in result)
|
||||
result = self.authz_token.access_token_preview
|
||||
self.assertTrue(json.loads(result)["iss"] == "http://op-test/oidc/op/")
|
||||
result = self.authz_token.id_token_preview
|
||||
self.assertTrue(json.loads(result)["iss"] == "http://op-test/oidc/op/")
|
||||
@@ -0,0 +1,78 @@
|
||||
from django.urls import path
|
||||
|
||||
from spid_cie_oidc.entity.views import (
|
||||
openid_connect_jwks_uri,
|
||||
openid_connect_signed_jwks_uri
|
||||
)
|
||||
from .views.rp_begin import SpidCieOidcRpBeginView
|
||||
from .views.rp_callback import SpidCieOidcRpCallbackView
|
||||
from .views.rp_extend_session import SpidCieOidcRefreshToken
|
||||
from .views.rp_callback_echo_attributes import SpidCieOidcRpCallbackEchoAttributes
|
||||
from .views.rp_initiated_logout import SpidCieOidcRpLogout # oidc_rpinitiated_logout
|
||||
from .views.rp_introspection import SpidCieOidcRpIntrospection
|
||||
from .views.rp_landing import oidc_rp_landing
|
||||
from django.contrib.auth.decorators import login_required
|
||||
_PREF = "oidc/rp"
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += (
|
||||
path(
|
||||
f"{_PREF}/authorization",
|
||||
SpidCieOidcRpBeginView.as_view(),
|
||||
name="spid_cie_rp_begin",
|
||||
),
|
||||
)
|
||||
urlpatterns += (
|
||||
path(
|
||||
f"{_PREF}/callback",
|
||||
SpidCieOidcRpCallbackView.as_view(),
|
||||
name="spid_cie_rp_callback",
|
||||
),
|
||||
)
|
||||
urlpatterns += (
|
||||
path(
|
||||
f"{_PREF}/echo_attributes",
|
||||
SpidCieOidcRpCallbackEchoAttributes.as_view(),
|
||||
name="spid_cie_rp_echo_attributes",
|
||||
),
|
||||
)
|
||||
|
||||
urlpatterns += (
|
||||
path(
|
||||
f"{_PREF}/extend_session",
|
||||
login_required(SpidCieOidcRefreshToken.as_view()),
|
||||
name="spid_cie_rp_extend_session",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
urlpatterns += (
|
||||
path(
|
||||
f"{_PREF}/introspection",
|
||||
login_required(SpidCieOidcRpIntrospection.as_view()),
|
||||
name="spid_cie_rp_echo_introspection",
|
||||
),
|
||||
)
|
||||
|
||||
urlpatterns += (
|
||||
path(
|
||||
f"{_PREF}/logout",
|
||||
login_required(SpidCieOidcRpLogout.as_view()),
|
||||
name="spid_cie_rpinitiated_logout",
|
||||
),
|
||||
)
|
||||
|
||||
urlpatterns += (
|
||||
path(f"{_PREF}/landing", oidc_rp_landing, name="spid_cie_rp_landing"),
|
||||
# path(f"{_PREF}/logout", oidc_rpinitiated_logout, name="spid_cie_rpinitiated_logout"),
|
||||
path(
|
||||
f"{_PREF}/<str:metadata_type>/jwks.json",
|
||||
openid_connect_jwks_uri,
|
||||
name="oidc_connect_jwks_uri",
|
||||
),
|
||||
path(
|
||||
f"{_PREF}/<str:metadata_type>/jwks.jose",
|
||||
openid_connect_signed_jwks_uri,
|
||||
name="oidc_connect_signed_jwks_uri",
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
import base64
|
||||
import json
|
||||
import hashlib
|
||||
import logging
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
import urllib
|
||||
|
||||
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def http_redirect_uri_to_dict(url):
|
||||
splitted = urllib.parse.splitquery(url)
|
||||
data = dict(urllib.parse.parse_qsl(splitted[1]))
|
||||
data.update({"endpoint": splitted[0]})
|
||||
return data
|
||||
|
||||
|
||||
def http_dict_to_redirect_uri_path(data):
|
||||
return urllib.parse.urlencode(data)
|
||||
|
||||
|
||||
def random_string(n=32):
|
||||
return "".join(random.choices(string.ascii_letters + string.digits, k=n)) # nosec
|
||||
|
||||
|
||||
def get_pkce(code_challenge_method: str = "S256", code_challenge_length: int = 64):
|
||||
hashers = {"S256": hashlib.sha256}
|
||||
|
||||
# https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
||||
code_verifier_length = secrets.choice(range(43, 128 + 1))
|
||||
alpha = string.ascii_letters + string.digits + "-._~"
|
||||
code_verifier = "".join([secrets.choice(alpha) for _ in range(code_verifier_length)])
|
||||
|
||||
code_challenge = hashers.get(code_challenge_method)(
|
||||
code_verifier.encode("utf-8")
|
||||
).digest()
|
||||
code_challenge = base64.urlsafe_b64encode(code_challenge).decode("utf-8")
|
||||
code_challenge = code_challenge.replace("=", "")
|
||||
|
||||
return {
|
||||
"code_verifier": code_verifier,
|
||||
"code_challenge": code_challenge,
|
||||
"code_challenge_method": code_challenge_method,
|
||||
}
|
||||
|
||||
|
||||
def html_json_preview(value):
|
||||
msg = json.loads(value or "{}")
|
||||
dumps = json.dumps(msg, indent=2)
|
||||
return mark_safe(dumps.replace("\n", "<br>").replace(" ", " ")) # nosec
|
||||
|
||||
|
||||
def process_user_attributes(userinfo: dict, user_map: dict, authz: dict):
|
||||
data = dict()
|
||||
for k, v in user_map.items():
|
||||
for i in v:
|
||||
if isinstance(i, str):
|
||||
if i in userinfo:
|
||||
data[k] = userinfo[i]
|
||||
break
|
||||
|
||||
elif isinstance(i, dict):
|
||||
args = (userinfo, authz, i["kwargs"])
|
||||
value = import_string(i["func"])(*args)
|
||||
if value:
|
||||
data[k] = value
|
||||
break
|
||||
return data
|
||||
@@ -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)
|
||||
+317
@@ -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")
|
||||
)
|
||||
Vendored
+19
@@ -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)
|
||||
+78
@@ -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}")
|
||||
+60
@@ -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)
|
||||
+56
@@ -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)
|
||||
Reference in New Issue
Block a user