Переглянути джерело

HUE-878 [desktop] Add a remote user backend

This backend enable to better support running Hue behind a proxy server.

Created a simple middleware to fix a bug in Django's built-in remote user
middleware that properly sets the header name to have the HTTP_ prefix and
added a backend that authenticates or creates a user given nothing but the
header. I also had to modify the settings.py to load the new middleware class.

Adding sample configuration to the hue.ini and pseudo-distributed.ini.tmpl files
Adding a comment to describe the purpose of the new middleware
Updating the documentation on the remote_user_header configuration setting
Joey Echeverria 13 роки тому
батько
коміт
8b13b4f

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

@@ -89,10 +89,22 @@
     #     (Default. Relies on Django and user manager, after the first login)
     # - desktop.auth.backend.LdapBackend
     # - desktop.auth.backend.PamBackend
+    # - desktop.auth.backend.SpnegoDjangoBackend
+    # - desktop.auth.backend.RemoteUserDjangoBackend
     ## backend=desktop.auth.backend.AllowFirstUserDjangoBackend
 
     ## pam_service=login
 
+    # When using the desktop.auth.backend.RemoteUserDjangoBackend, this sets
+    # the normalized name of the header that contains the remote user.
+    # The HTTP header in the request is converted to a key by converting
+    # all characters to uppercase, replacing any hyphens with underscores
+    # and adding an HTTP_ prefix to the name. So, for example, if the header
+    # is called Remote-User that would be configured as HTTP_REMOTE_USER
+    #
+    # Defaults to HTTP_REMOTE_USER
+    ## remote_user_header=HTTP_REMOTE_USER
+
   # Configuration options for connecting to LDAP and Active Directory
   # -------------------------------------------------------------------
   [[ldap]]

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

@@ -98,10 +98,22 @@
     #     (Default. Relies on Django and user manager, after the first login)
     # - desktop.auth.backend.LdapBackend
     # - desktop.auth.backend.PamBackend
+    # - desktop.auth.backend.SpnegoDjangoBackend
+    # - desktop.auth.backend.RemoteUserDjangoBackend
     ## backend=desktop.auth.backend.AllowFirstUserDjangoBackend
 
     ## pam_service=login
 
+    # When using the desktop.auth.backend.RemoteUserDjangoBackend, this sets
+    # the normalized name of the header that contains the remote user.
+    # The HTTP header in the request is converted to a key by converting
+    # all characters to uppercase, replacing any hyphens with underscores
+    # and adding an HTTP_ prefix to the name. So, for example, if the header
+    # is called Remote-User that would be configured as HTTP_REMOTE_USER
+    #
+    # Defaults to HTTP_REMOTE_USER
+    ## remote_user_header=HTTP_REMOTE_USER
+
   # Configuration options for connecting to LDAP and Active Directory
   # -------------------------------------------------------------------
   [[ldap]]

+ 39 - 0
desktop/core/src/desktop/auth/backend.py

@@ -308,6 +308,8 @@ class LdapBackend(object):
   def manages_passwords_externally(cls):
     return True
 
+
+
 class SpnegoDjangoBackend(django.contrib.auth.backends.ModelBackend):
   """
   A note about configuration:
@@ -352,3 +354,40 @@ class SpnegoDjangoBackend(django.contrib.auth.backends.ModelBackend):
     user = super(SpnegoDjangoBackend, self).get_user(user_id)
     user = rewrite_user(user)
     return user
+
+
+
+class RemoteUserDjangoBackend(django.contrib.auth.backends.RemoteUserBackend):
+  """
+  Delegates to Django's RemoteUserBackend and requires HueRemoteUserMiddleware
+  """
+  def authenticate(self, remote_user=None):
+    username = self.clean_username(remote_user)
+    is_super = False
+    if User.objects.count() == 0:
+      is_super = True
+
+    try:
+      user = User.objects.get(username=username)
+    except User.DoesNotExist:
+      user = find_or_create_user(username, None)
+      if user is not None and user.is_active:
+        profile = get_profile(user)
+        profile.creation_method = UserProfile.CreationMethod.EXTERNAL
+        profile.save()
+        user.is_superuser = is_super
+
+        default_group = get_default_user_group()
+        if default_group is not None:
+          user.groups.add(default_group)
+
+        user.save()
+
+    user = rewrite_user(user)
+    return user
+
+  def get_user(self, user_id):
+    user = super(RemoteUserDjangoBackend, self).get_user(user_id)
+    user = rewrite_user(user)
+    return user
+

+ 10 - 2
desktop/core/src/desktop/conf.py

@@ -272,8 +272,16 @@ AUTH = ConfigSection(
                    help=_("Class which defines extra accessor methods for User objects.")),
     PAM_SERVICE=Config("pam_service",
                   default="login",
-                  help=_("The service to use when querying PAM."
-                         "The service usually corresponds to a single filename in /etc/pam.d"))
+                  help=_("The service to use when querying PAM. "
+                         "The service usually corresponds to a single filename in /etc/pam.d")),
+    REMOTE_USER_HEADER=Config("remote_user_header",
+                        default="HTTP_REMOTE_USER",
+                        help=_("When using the desktop.auth.backend.RemoteUserDjangoBackend, this sets "
+                               "the normalized name of the header that contains the remote user. "
+                               "The HTTP header in the request is converted to a key by converting "
+                               "all characters to uppercase, replacing any hyphens with underscores "
+                               "and adding an HTTP_ prefix to the name. So, for example, if the header "
+                               "is called Remote-User that would be configured as HTTP_REMOTE_USER"))
 ))
 
 LDAP = ConfigSection(

+ 17 - 0
desktop/core/src/desktop/middleware.py

@@ -24,6 +24,7 @@ import kerberos
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth import REDIRECT_FIELD_NAME, BACKEND_SESSION_KEY, authenticate, load_backend, login
+from django.contrib.auth.middleware import RemoteUserMiddleware
 from django.core import exceptions, urlresolvers
 import django.db
 from django.http import HttpResponseRedirect, HttpResponse
@@ -573,3 +574,19 @@ class SpnegoMiddleware(object):
     except AttributeError:
       pass
     return username
+
+class HueRemoteUserMiddleware(RemoteUserMiddleware):
+  """
+  Middleware to delegate authentication to a proxy server. The proxy server
+  will set an HTTP header (defaults to Remote-User) with the name of the
+  authenticated user. This class extends the RemoteUserMiddleware class
+  built into Django with the ability to configure the HTTP header and to
+  unload the middleware if the RemoteUserDjangoBackend is not currently
+  in use.
+  """
+  header = desktop.conf.AUTH.REMOTE_USER_HEADER.get()
+
+  def __init__(self):
+    if not 'RemoteUserDjangoBackend' in desktop.conf.AUTH.BACKEND.get():
+      LOG.info('Unloading HueRemoteUserMiddleware')
+      raise exceptions.MiddlewareNotUsed

+ 1 - 0
desktop/core/src/desktop/settings.py

@@ -103,6 +103,7 @@ MIDDLEWARE_CLASSES = [
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'desktop.middleware.SpnegoMiddleware',
+    'desktop.middleware.HueRemoteUserMiddleware',
     'django.middleware.locale.LocaleMiddleware',
     'babeldjango.middleware.LocaleMiddleware',
     'desktop.middleware.AjaxMiddleware',