Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/sentry/api/endpoints/auth_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
29 changes: 28 additions & 1 deletion src/sentry/auth/providers/saml2/provider.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,14 +13,15 @@
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
from sentry.models.authprovider import AuthProvider
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
Expand Down Expand Up @@ -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)