Skip to content

Commit

Permalink
Merge pull request #108 from maykinmedia/feature/42-add-keycloak-idp-…
Browse files Browse the repository at this point in the history
…hint

Add keycloak IDP hint and logout endpoint
  • Loading branch information
sergei-maertens authored May 28, 2024
2 parents efaf148 + 69c6706 commit 7848e8c
Show file tree
Hide file tree
Showing 13 changed files with 1,284 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ Utils

.. automodule:: mozilla_django_oidc_db.utils
:members:
:exclude-members: obfuscate_claim_value, extract_content_type
8 changes: 8 additions & 0 deletions mozilla_django_oidc_db/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class OpenIDConnectConfigAdmin(SingletonModelAdmin):
"oidc_op_token_endpoint",
"oidc_token_use_basic_auth",
"oidc_op_user_endpoint",
"oidc_op_logout_endpoint",
)
},
),
Expand All @@ -55,6 +56,13 @@ class OpenIDConnectConfigAdmin(SingletonModelAdmin):
)
},
),
(
_("Keycloak specific settings"),
{
"fields": ("oidc_keycloak_idp_hint",),
"classes": ["collapse in"],
},
),
(
_("Advanced settings"),
{
Expand Down
1 change: 1 addition & 0 deletions mozilla_django_oidc_db/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"oidc_op_token_endpoint": "token_endpoint",
"oidc_op_user_endpoint": "userinfo_endpoint",
"oidc_op_jwks_endpoint": "jwks_uri",
"oidc_op_logout_endpoint": "end_session_endpoint",
}

OPEN_ID_CONFIG_PATH = ".well-known/openid-configuration"
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.0.4 on 2024-05-25 19:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("mozilla_django_oidc_db", "0002_migrate_to_claim_field"),
]

operations = [
migrations.AddField(
model_name="openidconnectconfig",
name="oidc_keycloak_idp_hint",
field=models.CharField(
blank=True,
help_text="Specific for Keycloak: parameter that indicates which identity provider should be used (therefore skipping the Keycloak login screen).",
max_length=1000,
verbose_name="Keycloak Identity Provider hint",
),
),
migrations.AddField(
model_name="openidconnectconfig",
name="oidc_op_logout_endpoint",
field=models.URLField(
blank=True,
help_text="URL of your OpenID Connect provider logout endpoint",
max_length=1000,
verbose_name="Logout endpoint",
),
),
]
17 changes: 17 additions & 0 deletions mozilla_django_oidc_db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ class OpenIDConnectConfigBase(SingletonModel):
),
blank=True,
)
oidc_op_logout_endpoint = models.URLField(
_("Logout endpoint"),
max_length=1000,
help_text=_("URL of your OpenID Connect provider logout endpoint"),
blank=True,
)

# Advanced settings
oidc_use_nonce = models.BooleanField(
Expand Down Expand Up @@ -174,6 +180,17 @@ class OpenIDConnectConfigBase(SingletonModel):
),
)

# Keycloak specific config
oidc_keycloak_idp_hint = models.CharField(
_("Keycloak Identity Provider hint"),
max_length=1000,
help_text=_(
"Specific for Keycloak: parameter that indicates which identity provider "
"should be used (therefore skipping the Keycloak login screen)."
),
blank=True,
)

userinfo_claims_source = models.CharField(
verbose_name=_("user information claims extracted from"),
choices=UserInformationClaimsSources.choices,
Expand Down
36 changes: 35 additions & 1 deletion mozilla_django_oidc_db/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import logging
from collections.abc import Collection
from copy import deepcopy

import requests
from glom import Path, PathAccessError, assign, glom
from requests.utils import _parse_content_type_header # type: ignore

from .models import OpenIDConnectConfigBase
from .typing import ClaimPath, JSONObject, JSONValue

logger = logging.getLogger(__name__)


def obfuscate_claim_value(value: JSONValue) -> JSONValue:
"""
Expand All @@ -27,7 +32,7 @@ def obfuscate_claims(
claims: JSONObject, claims_to_obfuscate: Collection[ClaimPath]
) -> JSONObject:
"""
Obfuscates the specified claims in the specified claims dict
Obfuscates the specified claims in the provided claims object.
"""
copied_claims = deepcopy(claims)
for claim_bits in claims_to_obfuscate:
Expand All @@ -51,3 +56,32 @@ def extract_content_type(ct_header: str) -> str:
content_type, _ = _parse_content_type_header(ct_header)
# discard the params, we only want the content type itself
return content_type


def do_op_logout(config: OpenIDConnectConfigBase, id_token: str) -> None:
"""
Perform the logout with the OpenID Provider.
Standard: https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
.. warning:: Preferably, you should send the user to the configured logout endpoint
so they can confirm the logout and any session cookies are cleared. If that is not
possible, you can call this helper for server-to-server logout, but there are no
guarantees this works for every possible OpenID Provider implementation. It has
been tested with Keycloak, but the standard says nothing about server-to-server
calls to log out a user.
"""
logout_endpoint = config.oidc_op_logout_endpoint
if not logout_endpoint:
return

response = requests.post(logout_endpoint, data={"id_token_hint": id_token})
if not response.ok:
logger.warning(
"Failed to log out the user at the OpenID Provider. Status code: %s",
response.status_code,
extra={
"response": response,
"status_code": response.status_code,
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.0.4 on 2024-05-25 19:37

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("testapp", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="emptyconfig",
name="oidc_keycloak_idp_hint",
field=models.CharField(
blank=True,
help_text="Specific for Keycloak: parameter that indicates which identity provider should be used (therefore skipping the Keycloak login screen).",
max_length=1000,
verbose_name="Keycloak Identity Provider hint",
),
),
migrations.AddField(
model_name="emptyconfig",
name="oidc_op_logout_endpoint",
field=models.URLField(
blank=True,
help_text="URL of your OpenID Connect provider logout endpoint",
max_length=1000,
verbose_name="Logout endpoint",
),
),
]
Loading

0 comments on commit 7848e8c

Please sign in to comment.