Browse Source

HUE-614. Add a mechanism to sync Hue DB with Unix passwd and groups

This adds a Hue command, which will iterate over the users and groups in
/etc/passwd and /etc/groups, and import any missing ones into the Hue database.
It will also update the user-group membership all users and groups. Ideally,
this can be used via a cron job, or some other periodic setup, to keep the
database updated with Unix information.
Jon Natkins 13 years ago
parent
commit
ebe7b719c7

+ 41 - 0
apps/useradmin/src/useradmin/management/commands/useradmin_sync_with_unix.py

@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+from optparse import make_option
+
+from useradmin.views import sync_unix_users_and_groups
+
+from django.core.management.base import BaseCommand
+
+class Command(BaseCommand):
+  """
+  Handler for syncing the Hue database with Unix users and groups
+  """
+
+  option_list = BaseCommand.option_list + (
+      make_option("--min-uid", help="Minimum UID to import (Inclusive).", default=500),
+      make_option("--max-uid", help="Maximum UID to import (Exclusive).", default=65334),
+      make_option("--check-shell", help="Whether or not to check that the user's shell is not /bin/false", default=True)
+  )
+
+  def handle(self, *args, **options):
+    # Typically, system users are under 500 or 1000, depending on OS, and there
+    # is usually a nobody user at the top of the ID space, so let's avoid those
+    min_uid = options['min_uid']
+    max_uid = options['max_uid']
+    check_shell = options['check_shell']
+
+    sync_unix_users_and_groups(min_uid, max_uid, check_shell)

+ 70 - 2
apps/useradmin/src/useradmin/views.py

@@ -17,16 +17,24 @@
 """
 User management application.
 """
+import os
+import pwd
+import grp
+import logging
+import threading
+import subprocess
 
 import django
-import threading
+import django.contrib.auth.forms
 from django import forms
 from django.contrib.auth.models import User
 from desktop.lib.django_util import get_username_re_rule, render, PopupException
 from django.core import urlresolvers
 
-__users_lock = threading.Lock()
+LOG = logging.getLogger(__name__)
 
+__users_lock = threading.Lock()
+__groups_lock = threading.Lock()
 
 def list_users(request):
   return render("list_users.mako", request, dict(users=User.objects.all()))
@@ -164,3 +172,63 @@ def _check_remove_last_super(user_obj):
   if num_active_su == 1:
     raise PopupException("You cannot remove the last active "
                          "superuser from the configuration.")
+
+def sync_unix_users_and_groups(min_uid, max_uid, check_shell):
+  """
+  Syncs the Hue database with the underlying Unix system, by importing users and
+  groups from 'getent passwd' and 'getent groups'. This should also pull in
+  users who are accessible via NSS.
+  """
+  global __users_lock, __groups_lock
+
+  hadoop_groups = dict((group.gr_name, group) for group in grp.getgrall() \
+      if (group.gr_gid >= min_uid and group.gr_gid < max_uid) or group.gr_name == 'hadoop')
+  user_groups = dict()
+
+  __users_lock.acquire()
+  __groups_lock.acquire()
+  # Import groups
+  for name, group in hadoop_groups.iteritems():
+    try:
+      if len(group.gr_mem) != 0:
+        hue_group = Group.objects.get(name=name)
+    except Group.DoesNotExist:
+      hue_group = Group(name=name)
+      hue_group.save()
+      LOG.info("Created group %s" % (hue_group.name,))
+
+    # Build a map of user to groups that the user is a member of
+    members = group.gr_mem
+    for member in members:
+      if member not in user_groups:
+        user_groups[member] = [ hue_group ]
+      else:
+        user_groups[member].append(hue_group)
+
+  # Now let's import the users
+  hadoop_users = dict((user.pw_name, user) for user in pwd.getpwall() \
+      if (user.pw_uid >= min_uid and user.pw_uid < max_uid) or user.pw_name in grp.getgrnam('hadoop').gr_mem)
+  for username, user in hadoop_users.iteritems():
+    try:
+      if check_shell:
+        pw_shell = user.pw_shell
+        if subprocess.call([pw_shell, "-c", "echo"], stdout=subprocess.PIPE) != 0:
+          continue
+      hue_user = User.objects.get(username=username)
+    except User.DoesNotExist:
+      hue_user = User(username=username, password='!', is_active=True, is_superuser=False)
+      hue_user.set_unusable_password()
+
+    # We have to do a save here, because the user needs to exist before we can
+    # access the associated list of groups
+    hue_user.save()
+    if username not in user_groups:
+      hue_user.groups = []
+    else:
+      # Here's where that user to group map we built comes in handy
+      hue_user.groups = user_groups[username]
+    hue_user.save()
+    LOG.info("Synced user %s from Unix" % (hue_user.username,))
+
+  __users_lock.release()
+  __groups_lock.release()