Pārlūkot izejas kodu

[libsaml] Implement SAML local logout

Ying Chen 3 mēneši atpakaļ
vecāks
revīzija
73ac3c53f8

+ 3 - 0
desktop/conf.dist/hue.ini

@@ -2349,6 +2349,9 @@ submit_to=True
 # after log users out of magic-sso, CDP control panel use redirect URL
 ## redirect_url=
 
+# Local logout only logout from Hue, but not IdP SAML
+## local_logout=true
+
 ###########################################################################
 # Settings to configure OAuth
 ###########################################################################

+ 3 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -2334,6 +2334,9 @@
   # after log users out of magic-sso, CDP control panel use redirect URL
   ## redirect_url=
 
+  # Local logout only logout from Hue, but not IdP SAML
+  ## local_logout=true
+
 ###########################################################################
 # Settings to configure OAuth
 ###########################################################################

+ 2 - 2
desktop/core/src/desktop/middleware.py

@@ -71,7 +71,7 @@ from desktop.lib.metrics import global_registry
 from desktop.lib.view_util import is_ajax
 from desktop.log import get_audit_logger
 from desktop.log.access import access_log, access_warn, log_page_hit
-from libsaml.conf import CDP_LOGOUT_URL
+from libsaml.conf import CDP_LOGOUT_URL, LOCAL_LOGOUT
 
 
 def nonce_exists(response):
@@ -480,7 +480,7 @@ class LoginAndPermissionMiddleware(Django4MiddlewareAdapterMixin):
         "desktop.auth.backend.SpnegoDjangoBackend",
         "desktop.auth.backend.KnoxSpnegoDjangoBackend"
     ]
-    if CDP_LOGOUT_URL.get() == "":
+    if CDP_LOGOUT_URL.get() == "" and not LOCAL_LOGOUT.get():
       no_idle_backends.append("libsaml.backend.SAML2Backend")
     if request.ajax and all(no_idle_backend not in AUTH.BACKEND.get() for no_idle_backend in no_idle_backends):
       # Send back a magic header which causes Hue.Request to interpose itself

+ 5 - 0
desktop/libs/libsaml/src/libsaml/backend.py

@@ -120,6 +120,11 @@ class SAML2Backend(_Saml2Backend):
       response = LogoutView.as_view()(request)
       auth_logout(request)
       return response
+    elif conf.LOCAL_LOGOUT.get():
+      auth_logout(request)
+      LOG.debug("SAML local logout is called ...")
+      from django.shortcuts import redirect
+      return redirect('/saml2/local_logout')
     elif conf.CDP_LOGOUT_URL.get():
       auth_logout(request)
       redirect_url = conf.get_logout_redirect_url()

+ 8 - 5
desktop/libs/libsaml/src/libsaml/conf.py

@@ -15,15 +15,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os
-import sys
 import json
 import logging
+import os
 import subprocess
 
 from django.utils.translation import gettext as _, gettext_lazy as _t
 
-from desktop.lib.conf import Config, coerce_bool, coerce_csv, coerce_password_from_script
+from desktop.lib.conf import coerce_bool, coerce_csv, coerce_password_from_script, Config
 
 LOG = logging.getLogger()
 
@@ -215,6 +214,12 @@ REDIRECT_URL = Config(
   default="",
   help=_t("After log users out of control plane, CDP control plane redirect to this URL"))
 
+LOCAL_LOGOUT = Config(
+  key="local_logout",
+  type=coerce_bool,
+  default=True,
+  help=_t("Local logout only logout from Hue, but not IdP SAML"))
+
 
 def get_key_file_password():
   password = os.environ.get('HUE_SAML_KEY_FILE_PASSWORD')
@@ -237,8 +242,6 @@ def config_validator(user):
 
 def get_logout_redirect_url():
   # This logic was derived from KNOX.
-  logout_url = CDP_LOGOUT_URL.get()
-  redirect_url = "https://sso.cloudera.com/logout"
   if REDIRECT_URL.get():
     redirect_url = REDIRECT_URL.get()
   elif any(substr in CDP_LOGOUT_URL.get() for substr in ['-dev', '-int', '-stage']):

+ 3 - 2
desktop/libs/libsaml/src/libsaml/urls.py

@@ -22,7 +22,7 @@ from django.urls import re_path
 LOG = logging.getLogger()
 
 try:
-  from libsaml.views import AssertionConsumerServiceView, EchoAttributesView, LoginView, LogoutView, MetadataView
+  from libsaml.views import AssertionConsumerServiceView, EchoAttributesView, local_logout, LoginView, LogoutView, MetadataView
 
   urlpatterns = [
     re_path(r'^logout/$', LogoutView.as_view(), name='saml2_logout'),
@@ -30,7 +30,8 @@ try:
     re_path(r'^acs/$', AssertionConsumerServiceView.as_view(), name='saml2_acs'),
     re_path(r'^login/$', LoginView.as_view(), name='saml2_login'),
     re_path(r'^metadata/$', MetadataView.as_view(), name='saml2_metadata'),
-    re_path(r'^test/$', EchoAttributesView.as_view())
+    re_path(r'^test/$', EchoAttributesView.as_view()),
+    re_path(r'^local_logout/$', local_logout, name='saml2_local_logout')
   ]
 
   try:

+ 21 - 0
desktop/libs/libsaml/src/libsaml/views.py

@@ -16,6 +16,8 @@
 # limitations under the License.
 from djangosaml2.views import AssertionConsumerServiceView, EchoAttributesView, LoginView, LogoutView, MetadataView
 
+from desktop.lib.django_util import render
+
 LoginView.dispatch.login_notrequired = True
 EchoAttributesView.dispatch.login_notrequired = True
 MetadataView.dispatch.login_notrequired = True
@@ -29,3 +31,22 @@ try:
 except ImportError:
   # We are on an older version of djangosaml2
   pass
+
+
+def local_logout(request, next_page=None):
+  """
+  Local logout: clears Django session only, not the SAML session.
+  Then presents a button to redirect to SAML login.
+  """
+  import libsaml.conf
+  base_url = libsaml.conf.BASE_URL.get()
+  logout_url = libsaml.conf.CDP_LOGOUT_URL.get()
+  authenticate_url = logout_url.replace("logout", "authenticate")
+  login_url = f"{authenticate_url}?loginRedirect={base_url}"
+
+  return render('logged_out.mako', request, {
+    'login_url': login_url,
+  })
+
+
+setattr(local_logout, 'login_notrequired', True)