Merged in feature/VBV-753-sso-logout (pull request #409)

Add logout with id_token_hint

Approved-by: Elia Bieri
This commit is contained in:
Christian Cueni 2024-10-23 13:43:50 +00:00
commit 50a0927a23
13 changed files with 53 additions and 48 deletions

View File

@ -5,14 +5,6 @@ import { directUpload } from "@/services/files";
import dayjs from "dayjs";
import { defineStore } from "pinia";
let logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT || "/";
if (import.meta.env.VITE_OAUTH_API_BASE_URL) {
logoutRedirectUrl = `${
import.meta.env.VITE_OAUTH_API_BASE_URL
}logout/?post_logout_redirect_uri=${window.location.origin}&client_id=iterativ`;
}
const AVAILABLE_LANGUAGES = ["de", "fr", "it"];
export type AvailableLanguages = "de" | "fr" | "it";
@ -150,16 +142,7 @@ export const useUserStore = defineStore({
handleLogout() {
Object.assign(this, initialUserState);
itPost("/api/core/logout/", {}).then(() => {
let redirectUrl;
if (logoutRedirectUrl !== "") {
redirectUrl = logoutRedirectUrl;
} else {
redirectUrl = "/";
}
window.location.href = redirectUrl;
});
window.location.href = `${window.location.origin}/sso/logout`;
},
async fetchUser() {
const data: any = await itGetCached("/api/core/me/");

Binary file not shown.

Binary file not shown.

View File

@ -639,6 +639,8 @@ OAUTH_SIGNIN_REDIRECT_URI = env(
"OAUTH_SIGNIN_REDIRECT_URI", default="http://localhost:8000/sso/callback"
)
OAUTH_LOGOUT_REDIRECT_URI = env("OAUTH_LOGOUT_REDIRECT_URI", default="")
OAUTH_SIGNIN_URL = env("OAUTH_SIGNIN_URL", default="")
OAUTH_SIGNIN_REALM = env("OAUTH_SIGNIN_REALM", default="vbv")
OAUTH_SIGNIN_ADMIN_CLIENT_ID = env("OAUTH_SIGNIN_ADMIN_CLIENT_ID", default="")

View File

@ -29,7 +29,6 @@ from vbv_lernwelt.core.views import (
rate_limit_exceeded_view,
vue_home,
vue_login,
vue_logout,
)
from vbv_lernwelt.course.views import (
course_page_api_view,
@ -124,7 +123,6 @@ urlpatterns = [
re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login),
name='vue_login'),
re_path(r'api/core/logout/$', vue_logout, name='vue_logout'),
# notifications
re_path(r'^notifications/', include(notifications.urls, namespace='notifications')),

View File

@ -5,7 +5,7 @@ from pathlib import Path
import requests
import structlog
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth import authenticate, login
from django.core.management import call_command
from django.http import (
HttpResponse,
@ -96,12 +96,6 @@ def vue_login(request):
)
@api_view(["POST"])
def vue_logout(request):
logout(request)
return Response({"success": True}, 200)
def permission_denied_view(request, exception):
return render(request, "403.html", status=403)

View File

@ -4,9 +4,7 @@ from rest_framework.response import Response
from vbv_lernwelt.course.models import CircleDocument
from vbv_lernwelt.course.serializers import CircleDocumentSerializer
from vbv_lernwelt.iam.permissions import (
has_course_session_document_access,
)
from vbv_lernwelt.iam.permissions import has_course_session_document_access
@api_view(["GET"])

View File

@ -30,9 +30,7 @@ from vbv_lernwelt.dashboard.graphql.types.feedback import (
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
from vbv_lernwelt.learnpath.models import Circle
from vbv_lernwelt.shop.models import CheckoutInformation, CheckoutState
from vbv_lernwelt.shop.views import (
COURSE_SESSION_ID_TO_PRODUCT_SKU,
)
from vbv_lernwelt.shop.views import COURSE_SESSION_ID_TO_PRODUCT_SKU
class StatisticsCourseSessionDataType(graphene.ObjectType):

View File

@ -24,10 +24,7 @@ from vbv_lernwelt.competence.services import (
query_competence_course_session_edoniq_tests,
)
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import (
CourseConfiguration,
CourseSessionUser,
)
from vbv_lernwelt.course.models import CourseConfiguration, CourseSessionUser
from vbv_lernwelt.course.views import logger
from vbv_lernwelt.course_session.services.export_attendance import (
ATTENDANCE_EXPORT_FILENAME,

View File

@ -7,9 +7,7 @@ from vbv_lernwelt.course.creators.test_utils import (
)
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.iam.permissions import (
has_course_session_document_access,
)
from vbv_lernwelt.iam.permissions import has_course_session_document_access
from vbv_lernwelt.learning_mentor.models import (
AgentParticipantRelation,
AgentParticipantRoleType,

View File

@ -496,6 +496,7 @@ def create_or_update_user(
contract_number: str = "",
date_of_birth: str = "",
intermediate_sso_id: str = "", # from keycloak
id_token: str = "", # used for sso logout
) -> User:
logger.debug(
"create_or_update_user",
@ -544,6 +545,9 @@ def create_or_update_user(
user.update_additional_json_data({"intermediate_sso_id": intermediate_sso_id})
init_notification_settings(user)
if id_token:
user.update_additional_json_data({"id_token": id_token})
user.set_unusable_password()
user.save()

View File

@ -12,4 +12,9 @@ urlpatterns = [
django_view_authentication_exempt(views.authorize_signin),
name="authorize",
),
path(
r"logout/",
django_view_authentication_exempt(views.logout),
name="logout",
),
]

View File

@ -5,6 +5,7 @@ import structlog as structlog
from authlib.integrations.base_client import OAuthError
from django.conf import settings
from django.contrib.auth import login as dj_login
from django.contrib.auth import logout as dj_logout
from django.shortcuts import redirect
from sentry_sdk import capture_exception
@ -97,7 +98,7 @@ def authorize_signin(request):
capture_exception(e)
return redirect("/")
id_token = decode_jwt(jwt_token["id_token"])
id_token_decoded = decode_jwt(jwt_token["id_token"])
state = json.loads(
base64.urlsafe_b64decode(request.GET.get("state").encode()).decode()
@ -108,17 +109,44 @@ def authorize_signin(request):
logger.debug(
f"SSO Authorize (course={course}, next={next_url}",
sso_authorize_id_token=id_token,
sso_authorize_id_token=id_token_decoded,
)
user = create_or_update_user(
email=id_token.get("email", ""),
sso_id=id_token.get("oid"),
first_name=id_token.get("given_name", ""),
last_name=id_token.get("family_name", ""),
intermediate_sso_id=id_token.get("sub"),
email=id_token_decoded.get("email", ""),
sso_id=id_token_decoded.get("oid"),
first_name=id_token_decoded.get("given_name", ""),
last_name=id_token_decoded.get("family_name", ""),
intermediate_sso_id=id_token_decoded.get("sub"),
id_token=jwt_token["id_token"],
)
dj_login(request, user)
return get_redirect_uri(user=user, course=course, next_url=next_url)
def logout(request):
user_data = (
request.user.additional_json_data if request.user.is_authenticated else {}
)
dj_logout(request)
redirect_uri = getattr(settings, "OAUTH_LOGOUT_REDIRECT_URI", "/")
# Handle scenarios when SSO-related data is missing, assume local login
if not user_data.get("intermediate_sso_id"):
logger.debug("SSO Logout", extra={"mode": "intermediate_sso_id_not_set"})
return redirect("/")
# Temporary solution if id_token is not set in the user data, can be removed after rollout + 2 weeks
id_token = user_data.get("id_token", "")
if not id_token:
logger.debug("SSO Logout", extra={"mode": "id_token_not_set"})
return redirect(f"{redirect_uri}&client_id=iterativ")
# Handle scenarios when SSO-related data is present or redirect_uri is not set
if not redirect_uri:
logger.debug("SSO Logout", extra={"mode": "redirect_uri_not_set"})
return redirect("/")
return redirect(f"{redirect_uri}&id_token_hint={id_token}")