OIDC: Implement PKCE in OP and RP

This commit is contained in:
Raphael Michel
2025-02-21 13:23:17 +01:00
committed by GitHub
parent 78fbda2311
commit 67db230f72
8 changed files with 201 additions and 15 deletions

View File

@@ -148,7 +148,7 @@ def oidc_validate_and_complete_config(config):
return config
def oidc_authorize_url(provider, state, redirect_uri):
def oidc_authorize_url(provider, state, redirect_uri, pkce_code_verifier):
endpoint = provider.configuration['provider_config']['authorization_endpoint']
params = {
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
@@ -163,10 +163,14 @@ def oidc_authorize_url(provider, state, redirect_uri):
if "query_parameters" in provider.configuration and provider.configuration["query_parameters"]:
params.update(parse_qsl(provider.configuration["query_parameters"]))
if pkce_code_verifier and "S256" in provider.configuration['provider_config'].get('code_challenge_methods_supported', []):
params["code_challenge"] = base64.urlsafe_b64encode(hashlib.sha256(pkce_code_verifier.encode()).digest()).decode().rstrip("=")
params["code_challenge_method"] = "S256"
return endpoint + '?' + urlencode(params)
def oidc_validate_authorization(provider, code, redirect_uri):
def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier):
endpoint = provider.configuration['provider_config']['token_endpoint']
# Wall of shame and RFC ignorant IDPs
@@ -188,6 +192,9 @@ def oidc_validate_authorization(provider, code, redirect_uri):
'redirect_uri': redirect_uri,
}
if pkce_code_verifier and "S256" in provider.configuration['provider_config'].get('code_challenge_methods_supported', []):
params["code_verifier"] = pkce_code_verifier
if token_endpoint_auth_method == 'client_secret_post':
params['client_id'] = provider.configuration['client_id']
params['client_secret'] = provider.configuration['client_secret']

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.17 on 2025-02-07 16:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0276_item_hidden_if_item_available_mode"),
]
operations = [
migrations.AddField(
model_name="customerssoclient",
name="require_pkce",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="customerssogrant",
name="code_challenge",
field=models.TextField(null=True),
),
migrations.AddField(
model_name="customerssogrant",
name="code_challenge_method",
field=models.CharField(max_length=255, null=True),
),
]

View File

@@ -416,6 +416,10 @@ class CustomerSSOClient(LoggedModel):
authorization_grant_type = models.CharField(
max_length=32, choices=GRANT_TYPES, verbose_name=_("Grant type"), default=GRANT_AUTHORIZATION_CODE,
)
require_pkce = models.BooleanField(
verbose_name=_("Require PKCE extension"),
default=False,
)
redirect_uris = models.TextField(
blank=False,
verbose_name=_("Redirection URIs"),
@@ -481,6 +485,8 @@ class CustomerSSOGrant(models.Model):
expires = models.DateTimeField()
redirect_uri = models.TextField()
scope = models.TextField(blank=True)
code_challenge = models.TextField(blank=True, null=True)
code_challenge_method = models.CharField(max_length=255, blank=True, null=True)
class CustomerSSOAccessToken(models.Model):