diff --git a/src/sentry/api/endpoints/auth_index.py b/src/sentry/api/endpoints/auth_index.py index ec3b5a2abd36e8..725d0a55feaee3 100644 --- a/src/sentry/api/endpoints/auth_index.py +++ b/src/sentry/api/endpoints/auth_index.py @@ -18,6 +18,7 @@ from sentry.api.validators import AuthVerifyValidator from sentry.api.validators.auth import MISSING_PASSWORD_OR_U2F_CODE from sentry.auth.authenticators.u2f import U2fInterface +from sentry.auth.providers.saml2.provider import handle_saml_single_logout from sentry.auth.services.auth.impl import promote_request_rpc_user from sentry.auth.superuser import SUPERUSER_ORG_ID from sentry.models.authenticator import Authenticator @@ -293,6 +294,8 @@ def delete(self, request: Request, *args, **kwargs) -> Response: Deauthenticate all active sessions for this user. """ + handle_saml_single_logout(request) + # For signals to work here, we must promote the request.user to a full user object logout(request._request) request.user = AnonymousUser() diff --git a/src/sentry/auth/providers/saml2/provider.py b/src/sentry/auth/providers/saml2/provider.py index d836ea4742c2db..41ef66f4d171fe 100644 --- a/src/sentry/auth/providers/saml2/provider.py +++ b/src/sentry/auth/providers/saml2/provider.py @@ -1,6 +1,7 @@ import abc from urllib.parse import urlparse +import sentry_sdk from django.contrib import messages from django.contrib.auth import logout from django.http import HttpResponse, HttpResponseServerError @@ -12,7 +13,7 @@ from onelogin.saml2.constants import OneLogin_Saml2_Constants from rest_framework.request import Request -from sentry import options +from sentry import features, options from sentry.auth.exceptions import IdentityNotValid from sentry.auth.provider import Provider from sentry.auth.view import AuthView @@ -20,6 +21,7 @@ from sentry.models.organization import OrganizationStatus from sentry.models.organizationmapping import OrganizationMapping from sentry.organizations.services.organization import organization_service +from sentry.users.services.user.service import user_service from sentry.utils.auth import get_login_url from sentry.utils.http import absolute_uri from sentry.web.frontend.base import BaseView, control_silo_view @@ -387,3 +389,28 @@ def build_auth(request, saml_config): } return OneLogin_Saml2_Auth(saml_request, saml_config) + + +def handle_saml_single_logout(request): + # Do not handle SLO if a user is in more than 1 organization + # Propagating it to multiple IdPs results in confusion for the user + organizations = user_service.get_organizations(user_id=request.user.id) + if not len(organizations) == 1: + return + + org = organizations[0] + if not features.has("organizations:sso-saml2-slo", org): + return + + provider = get_provider(org.slug) + if not provider or not provider.is_saml: + return + + # Try/catch is needed because IdP may not support SLO (e.g. Okta) and + # will return an error + try: + saml_config = build_saml_config(provider.config, org) + idp_auth = build_auth(request, saml_config) + idp_auth.logout() + except Exception as e: + sentry_sdk.capture_exception(e)