superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From grace...@apache.org
Subject [incubator-superset] branch master updated: DI-1113. ADDENDUM. Authentication: Enable user impersonation for Superset to HiveServer2 using hive.server2.proxy.user (a.fernandez) (#3697)
Date Mon, 06 Nov 2017 18:20:44 GMT
This is an automated email from the ASF dual-hosted git repository.

graceguo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new b059506  DI-1113. ADDENDUM. Authentication: Enable user impersonation for Superset
to HiveServer2 using hive.server2.proxy.user (a.fernandez) (#3697)
b059506 is described below

commit b059506afad33611664aa62b3ef43175828e6764
Author: Alejandro Fernandez <afernandezcmu@gmail.com>
AuthorDate: Mon Nov 6 10:20:38 2017 -0800

    DI-1113. ADDENDUM. Authentication: Enable user impersonation for Superset to HiveServer2
using hive.server2.proxy.user (a.fernandez) (#3697)
---
 superset/db_engine_specs.py | 34 ++++++++++++++++++----------------
 superset/db_engines/hive.py | 21 ---------------------
 superset/models/core.py     | 20 ++++++++++++++++----
 superset/views/core.py      | 25 +++++++++++++++++++------
 4 files changed, 53 insertions(+), 47 deletions(-)

diff --git a/superset/db_engine_specs.py b/superset/db_engine_specs.py
index c5363af..d37096a 100644
--- a/superset/db_engine_specs.py
+++ b/superset/db_engine_specs.py
@@ -198,15 +198,16 @@ class BaseEngineSpec(object):
             url.username = username
 
     @classmethod
-    def get_uri_for_impersonation(cls, uri, impersonate_user, username):
+    def get_configuration_for_impersonation(cls, uri, impersonate_user, username):
         """
-        Return a new URI string that allows for user impersonation.
+        Return a configuration dictionary that can be merged with other configs
+        that can set the correct properties for impersonating users
         :param uri: URI string
-        :param impersonate_user:  Bool indicating if impersonation is enabled
+        :param impersonate_user: Bool indicating if impersonation is enabled
         :param username: Effective username
-        :return: New URI string
+        :return: Dictionary with configs required for impersonation
         """
-        return uri
+        return {}
 
 
 class PostgresEngineSpec(BaseEngineSpec):
@@ -701,7 +702,6 @@ class HiveEngineSpec(PrestoEngineSpec):
         hive.constants = patched_constants
         hive.ttypes = patched_ttypes
         hive.Cursor.fetch_logs = patched_hive.fetch_logs
-        hive.Connection = patched_hive.ConnectionProxyUser
 
     @classmethod
     @cache_util.memoized_func(
@@ -863,27 +863,29 @@ class HiveEngineSpec(PrestoEngineSpec):
         :param impersonate_user: Bool indicating if impersonation is enabled
         :param username: Effective username
         """
-        if impersonate_user is not None and "auth" in url.query.keys() and username is not
None:
-            url.query["hive_server2_proxy_user"] = username
+        # Do nothing in the URL object since instead this should modify
+        # the configuraiton dictionary. See get_configuration_for_impersonation
+        pass
 
     @classmethod
-    def get_uri_for_impersonation(cls, uri, impersonate_user, username):
+    def get_configuration_for_impersonation(cls, uri, impersonate_user, username):
         """
-        Return a new URI string that allows for user impersonation.
+        Return a configuration dictionary that can be merged with other configs
+        that can set the correct properties for impersonating users
         :param uri: URI string
-        :param impersonate_user:  Bool indicating if impersonation is enabled
+        :param impersonate_user: Bool indicating if impersonation is enabled
         :param username: Effective username
-        :return: New URI string
+        :return: Dictionary with configs required for impersonation
         """
-        new_uri = uri
+        configuration = {}
         url = make_url(uri)
         backend_name = url.get_backend_name()
 
         # Must be Hive connection, enable impersonation, and set param auth=LDAP|KERBEROS
-        if backend_name == "hive" and "auth" in url.query.keys() and\
+        if backend_name == "hive" and "auth" in url.query.keys() and \
                         impersonate_user is True and username is not None:
-            new_uri += "&hive_server2_proxy_user={0}".format(username)
-        return new_uri
+            configuration["hive.server2.proxy.user"] = username
+        return configuration
 
 class MssqlEngineSpec(BaseEngineSpec):
     engine = 'mssql'
diff --git a/superset/db_engines/hive.py b/superset/db_engines/hive.py
index 334ae0a..bf3566f 100644
--- a/superset/db_engines/hive.py
+++ b/superset/db_engines/hive.py
@@ -3,27 +3,6 @@ from TCLIService import ttypes
 from thrift import Thrift
 
 
-old_Connection = hive.Connection
-
-# TODO
-# Monkey-patch of PyHive project's pyhive/hive.py which needed to change the constructor.
-# Submitted a pull request on October 13, 2017 and waiting for it to be merged.
-# https://github.com/dropbox/PyHive/pull/165
-class ConnectionProxyUser(hive.Connection):
-
-    def __init__(self, host=None, port=None, username=None, database='default', auth=None,
-             configuration=None, kerberos_service_name=None, password=None,
-             thrift_transport=None, hive_server2_proxy_user=None):
-        configuration = configuration or {}
-        if auth is not None and auth in ('LDAP', 'KERBEROS'):
-            if hive_server2_proxy_user is not None:
-                configuration["hive.server2.proxy.user"] = hive_server2_proxy_user
-        # restore the old connection class, otherwise, will recurse on its own __init__ method
-        hive.Connection = old_Connection
-        hive.Connection.__init__(self, host=host, port=port, username=username, database=database,
auth=auth,
-                 configuration=configuration, kerberos_service_name=kerberos_service_name,
password=password,
-                 thrift_transport=thrift_transport)
-
 
 # TODO: contribute back to pyhive.
 def fetch_logs(self, max_rows=1024,
diff --git a/superset/models/core.py b/superset/models/core.py
index aa2484f..78a72a7 100644
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -620,23 +620,35 @@ class Database(Model, AuditMixinNullable):
             effective_username = url.username
             if user_name:
                 effective_username = user_name
-            elif hasattr(g, 'user') and g.user.username:
+            elif hasattr(g, 'user') and hasattr(g.user, 'username') and g.user.username is
not None:
                 effective_username = g.user.username
         return effective_username
 
     def get_sqla_engine(self, schema=None, nullpool=False, user_name=None):
         extra = self.get_extra()
         url = make_url(self.sqlalchemy_uri_decrypted)
-        params = extra.get('engine_params', {})
-        if nullpool:
-            params['poolclass'] = NullPool
         url = self.db_engine_spec.adjust_database_uri(url, schema)
         effective_username = self.get_effective_user(url, user_name)
+        # If using MySQL or Presto for example, will set url.username
+        # If using Hive, will not do anything yet since that relies on a configuration parameter
instead.
         self.db_engine_spec.modify_url_for_impersonation(url, self.impersonate_user, effective_username)
 
         masked_url = self.get_password_masked_url(url)
         logging.info("Database.get_sqla_engine(). Masked URL: {0}".format(masked_url))
 
+        params = extra.get('engine_params', {})
+        if nullpool:
+            params['poolclass'] = NullPool
+
+        # If using Hive, this will set hive.server2.proxy.user=$effective_username
+        configuration = {}
+        configuration.update(
+            self.db_engine_spec.get_configuration_for_impersonation(str(url),
+                                                                    self.impersonate_user,
+                                                                    effective_username))
+        if configuration:
+            params["connect_args"] = {"configuration": configuration}
+        
         return create_engine(url, **params)
 
     def get_reserved_words(self):
diff --git a/superset/views/core.py b/superset/views/core.py
index bd4d4e5..9e1d6ec 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -1433,6 +1433,7 @@ class Superset(BaseSupersetView):
             uri = request.json.get('uri')
             db_name = request.json.get('name')
             impersonate_user = request.json.get('impersonate_user')
+            database = None
             if db_name:
                 database = (
                     db.session
@@ -1444,20 +1445,32 @@ class Superset(BaseSupersetView):
                     # the password-masked uri was passed
                     # use the URI associated with this database
                     uri = database.sqlalchemy_uri_decrypted
+
+            configuration = {}
+
+            if database and uri:
+                url = make_url(uri)
+                db_engine = models.Database.get_db_engine_spec_for_backend(url.get_backend_name())
+                db_engine.patch()
             
-            url = make_url(uri)
-            db_engine = models.Database.get_db_engine_spec_for_backend(url.get_backend_name())
-            db_engine.patch()
-            uri = db_engine.get_uri_for_impersonation(uri, impersonate_user, username)
-            masked_url = database.get_password_masked_url_from_uri(uri)
+                masked_url = database.get_password_masked_url_from_uri(uri)
+                logging.info("Superset.testconn(). Masked URL: {0}".format(masked_url))
 
-            logging.info("Superset.testconn(). Masked URL: {0}".format(masked_url))
+                configuration.update(
+                    db_engine.get_configuration_for_impersonation(uri,
+                                                                  impersonate_user,
+                                                                  username)
+                )
 
             connect_args = (
                 request.json
                 .get('extras', {})
                 .get('engine_params', {})
                 .get('connect_args', {}))
+
+            if configuration:
+                connect_args["configuration"] = configuration
+
             engine = create_engine(uri, connect_args=connect_args)
             engine.connect()
             return json_success(json.dumps(engine.table_names(), indent=4))

-- 
To stop receiving notification emails like this one, please contact
['"commits@superset.apache.org" <commits@superset.apache.org>'].

Mime
View raw message