Răsfoiți Sursa

HUE-9396 [core] Implement SAML groups check and redirect them to 403 page if not permitted. (#1195)

Testing Done:
- Tested on private setup
Prakash Ranade 5 ani în urmă
părinte
comite
cd12de9cef

+ 4 - 0
desktop/core/src/desktop/auth/views.py

@@ -14,6 +14,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import json
 
 from future import standard_library
 standard_library.install_aliases()
@@ -48,6 +49,7 @@ from desktop.conf import OAUTH, ENABLE_ORGANIZATIONS
 from desktop.lib.django_util import render, login_notrequired, JsonResponse
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.log.access import access_log, access_warn, last_access_map
+from desktop.views import samlgroup_check
 from desktop.settings import LOAD_BALANCER_COOKIE
 
 
@@ -197,6 +199,8 @@ def dt_login(request, from_modal=False):
   if not from_modal:
     request.session.set_test_cookie()
 
+  request.session['samlgroup_permitted_flag'] = samlgroup_check(request)
+
   renderable_path = 'login.mako'
   if from_modal:
     renderable_path = 'login_modal.mako'

+ 27 - 0
desktop/core/src/desktop/views.py

@@ -16,6 +16,7 @@
 # limitations under the License.
 
 from future import standard_library
+
 standard_library.install_aliases()
 import json
 import logging
@@ -63,6 +64,8 @@ from desktop.lib.thread_util import dump_traceback
 from desktop.log.access import access_log_level, access_warn, AccessInfo
 from desktop.log import set_all_debug as _set_all_debug, reset_all_debug as _reset_all_debug, get_all_debug as _get_all_debug
 from desktop.models import Settings, hue_version, _get_apps, UserPreferences
+from libsaml.conf import REQUIRED_GROUPS, REQUIRED_GROUPS_ATTRIBUTE
+from useradmin.models import get_profile
 
 if sys.version_info[0] > 2:
   from io import StringIO as string_io
@@ -76,11 +79,35 @@ LOG = logging.getLogger(__name__)
 def is_alive(request):
   return HttpResponse('')
 
+def samlgroup_check(request):
+  if 'SAML2Backend' in desktop.auth.forms.get_backend_names():
+    if REQUIRED_GROUPS.get():
+      userprofile = get_profile(request.user)
+      json_data = json.loads(userprofile.json_data)
+      if not json_data:
+        LOG.debug("Empty userprofile data for %s user" % (request.user.username))
+        return False
+
+      if json_data.get('saml_attributes', False) and \
+         json_data['saml_attributes'].get(REQUIRED_GROUPS_ATTRIBUTE.get(), False):
+        success = set(REQUIRED_GROUPS.get()).issubset(
+                 set(json_data['saml_attributes'].get(REQUIRED_GROUPS_ATTRIBUTE.get())))
+        if not success:
+          LOG.debug("User %s not found in required SAML groups, %s" % (request.user.username, REQUIRED_GROUPS.get()))
+          return False
+  return True
 
 def hue(request):
   current_app, other_apps, apps_list = _get_apps(request.user, '')
   clusters = list(get_clusters(request.user).values())
 
+  user_permitted = request.session.get('samlgroup_permitted_flag')
+
+  if (not user_permitted) and (not samlgroup_check(request)):
+    return render('403.mako', request, {
+      'is_embeddable': True
+    })
+
   return render('hue.mako', request, {
     'apps': apps_list,
     'other_apps': other_apps,

+ 6 - 2
desktop/libs/libsaml/src/libsaml/backend.py

@@ -20,6 +20,7 @@ See desktop/auth/backend.py
 
 from __future__ import absolute_import
 
+import json
 import logging
 
 from django.contrib.auth import logout as auth_logout
@@ -63,8 +64,7 @@ class SAML2Backend(_Saml2Backend):
     """Hook to allow custom authorization policies based on user belonging to a list of SAML groups."""
     LOG.debug('is_authorized() attributes = %s' % attributes)
     LOG.debug('is_authorized() attribute_mapping = %s' % attribute_mapping)
-    return not conf.REQUIRED_GROUPS.get() or set(conf.REQUIRED_GROUPS.get()).issubset(set(attributes[conf.REQUIRED_GROUPS_ATTRIBUTE.get()]))
-
+    return True
 
   def get_user(self, user_id):
     if isinstance(user_id, str):
@@ -97,6 +97,10 @@ class SAML2Backend(_Saml2Backend):
       user.username = force_username_case(user.username)
       profile = get_profile(user)
       profile.creation_method = UserProfile.CreationMethod.EXTERNAL.name
+      json_data = json.loads(profile.json_data)
+      if attributes:
+        json_data['saml_attributes'] = attributes
+        profile.json_data = json.dumps(json_data)
       profile.save()
       user.is_superuser = is_super
       user = rewrite_user(user)

+ 1 - 110
desktop/libs/libsaml/src/libsaml/tests.py

@@ -18,18 +18,15 @@
 
 import sys
 
-from nose.plugins.skip import SkipTest
 from nose.tools import assert_equal, assert_true, assert_false
 
-from libsaml.conf import xmlsec, REQUIRED_GROUPS, REQUIRED_GROUPS_ATTRIBUTE
-
+from libsaml.conf import xmlsec
 
 if sys.version_info[0] > 2:
   from unittest.mock import patch, Mock
 else:
   from mock import patch, Mock
 
-
 def test_xmlsec_dynamic_default_no_which():
 
   with patch('libsaml.conf.subprocess') as subprocess:
@@ -38,109 +35,3 @@ def test_xmlsec_dynamic_default_no_which():
     )
 
     assert_equal('/usr/local/bin/xmlsec1', xmlsec())
-
-
-class TestLibSaml():
-
-  def setUp(self):
-    try:
-      from djangosaml2 import views as djangosaml2_views
-      from libsaml import views as libsaml_views
-    except ImportError:
-      raise SkipTest('djangosaml2 or libsaml modules not found')
-
-  def test_is_authorized_groups(self):
-    from libsaml.backend import SAML2Backend
-
-    # Single group
-    attributes = {'groups': ['analyst']}
-    attribute_mapping = {}
-
-    resets = [
-      REQUIRED_GROUPS.set_for_testing(['analyst']),
-      REQUIRED_GROUPS_ATTRIBUTE.set_for_testing('groups'),
-    ]
-
-    try:
-      assert_true(SAML2Backend().is_authorized(attributes, attribute_mapping))
-    finally:
-      for reset in resets:
-        reset()
-
-    attributes = {'groups': ['analyst', 'finance']}
-    attribute_mapping = {}
-
-    resets = [
-      REQUIRED_GROUPS.set_for_testing(['analyst']),
-      REQUIRED_GROUPS_ATTRIBUTE.set_for_testing('groups'),
-    ]
-
-    try:
-      assert_true(SAML2Backend().is_authorized(attributes, attribute_mapping))
-    finally:
-      for reset in resets:
-        reset()
-
-
-    # Multi groups
-    attributes = {'groups': ['analyst', 'sales', 'engineering']}
-    attribute_mapping = {}
-
-    resets = [
-      REQUIRED_GROUPS.set_for_testing(['analyst', 'sales']),
-      REQUIRED_GROUPS_ATTRIBUTE.set_for_testing('groups'),
-    ]
-
-    try:
-      assert_true(SAML2Backend().is_authorized(attributes, attribute_mapping))
-    finally:
-      for reset in resets:
-        reset()
-
-
-  def test_is_non_authorized_groups(self):
-    from libsaml.backend import SAML2Backend
-
-    # Single group
-    attributes = {'groups': ['intern']}
-    attribute_mapping = {}
-
-    resets = [
-      REQUIRED_GROUPS.set_for_testing(['sales']),
-      REQUIRED_GROUPS_ATTRIBUTE.set_for_testing('groups'),
-    ]
-
-    try:
-      assert_false(SAML2Backend().is_authorized(attributes, attribute_mapping))
-    finally:
-      for reset in resets:
-        reset()
-
-    attributes = {'groups': ['intern', 'finance']}
-    attribute_mapping = {}
-
-    resets = [
-      REQUIRED_GROUPS.set_for_testing(['sales']),
-      REQUIRED_GROUPS_ATTRIBUTE.set_for_testing('groups'),
-    ]
-
-    try:
-      assert_false(SAML2Backend().is_authorized(attributes, attribute_mapping))
-    finally:
-      for reset in resets:
-        reset()
-
-    # Multi groups
-    attributes = {'groups': ['intern', 'sales']}
-    attribute_mapping = {}
-
-    resets = [
-      REQUIRED_GROUPS.set_for_testing(['sales', 'engineering']),
-      REQUIRED_GROUPS_ATTRIBUTE.set_for_testing('groups'),
-    ]
-
-    try:
-      assert_false(SAML2Backend().is_authorized(attributes, attribute_mapping))
-    finally:
-      for reset in resets:
-        reset()