瀏覽代碼

HUE-8530 [organization] Add switch to organizational groups and users

Romain 6 年之前
父節點
當前提交
16ee30da14
共有 3 個文件被更改,包括 107 次插入70 次删除
  1. 77 46
      apps/useradmin/src/useradmin/models.py
  2. 27 23
      desktop/core/src/desktop/models.py
  3. 3 1
      desktop/core/src/desktop/settings.py

+ 77 - 46
apps/useradmin/src/useradmin/models.py

@@ -17,12 +17,9 @@
 """
 The core of this module adds permissions functionality to Hue applications.
 
-A "Hue Permission" (colloquially, appname.action, but stored in the
-HuePermission model) is a way to specify some action whose
-control may be restricted.  Every Hue application, by default,
-has an "access" action.  To specify extra actions, applications
-can specify them in appname.settings.PERMISSION_ACTIONS, as
-pairs of (action_name, description).
+A "Hue Permission" (colloquially, appname.action, but stored in the HuePermission model) is a way to specify some action whose
+control may be restricted.  Every Hue application, by default, has an "access" action. To specify extra actions, applications
+can specify them in appname.settings.PERMISSION_ACTIONS, as pairs of (action_name, description).
 
 Several mechanisms enforce permission.  First of all, the "access" permission
 is controlled by LoginAndPermissionMiddleware.  For eligible views
@@ -35,19 +32,12 @@ Thirdly, you may wish to do so manually, by using something akin to:
   dp = HuePermission.objects.get(app=pp, action=action)
   request.user.has_hue_permission(dp)
 
-[Design note: it is questionable that a Hue permission is
-a model, instead of just being a string.  Could go either way.]
-
-Permissions may be granted to groups, but not, currently, to users.
-A user's abilities is the union of all permissions the group
+Permissions may be granted to groups, but not, currently, to users. A user's abilities is the union of all permissions the group
 has access to.
 
-Note that Django itself has a notion of users, groups, and permissions.
-We re-use Django's notion of users and groups, but ignore its notion of
-permissions.  The permissions notion in Django is strongly tied to
-what models you may or may not edit, and there are elaborations (especially
-in Django 1.2) to manipulate this row by row.  This does not map nicely
-onto actions which may not relate to database models.
+Note that Django itself has a notion of users, groups, and permissions. We re-use Django's notion of users and groups, but ignore its notion of
+permissions.  The permissions notion in Django is strongly tied to what models you may or may not edit, and there are elaborations (especially
+in Django 1.2) to manipulate this row by row. This does not map nicely onto actions which may not relate to database models.
 """
 import logging
 from datetime import datetime
@@ -55,11 +45,13 @@ from enum import Enum
 
 from django.db import connection, models, transaction
 from django.contrib.auth import models as auth_models
+from django.contrib.auth.models import AbstractUser, BaseUserManager
 from django.core.cache import cache
 from django.utils.translation import ugettext_lazy as _t
 import django.utils.timezone as dtz
 
 from desktop import appmanager
+from desktop.conf import ENABLE_ORGANIZATIONS
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.idbroker.conf import is_idbroker_enabled
 from hadoop import cluster
@@ -70,6 +62,9 @@ import useradmin.conf
 LOG = logging.getLogger(__name__)
 
 
+# -----------------------------------------------------------------------
+#  Organizations
+# -----------------------------------------------------------------------
 
 class OrganizationManager(models.Manager):
   use_in_migrations = True
@@ -77,7 +72,6 @@ class OrganizationManager(models.Manager):
   def get_by_natural_key(self, name):
     return self.get(name=name)
 
-
 class Organization(models.Model):
   name = models.CharField(max_length=200, help_text=_t("The name of the organization"))
   is_active = models.BooleanField(default=True)
@@ -90,7 +84,6 @@ class OrganizationGroupManager(models.Manager):
   def natural_key(self):
     return (self.organization, self.name,)
 
-
 class OrganizationGroup(models.Model):
   name = models.CharField(_t('name'), max_length=80, unique=False)
   organization = models.ForeignKey(Organization)
@@ -112,26 +105,64 @@ class OrganizationGroup(models.Model):
     return '%s %s' % (self.organization, self.name)
 
 
-# class OrganizationGroupPermission(models.Model):
-#   """
-#   Represents the permissions a group has.
-#   """
-#   group = models.ForeignKey(OrganizationGroup)
-#   hue_permission = models.ForeignKey("HuePermission")
+class UserManager(BaseUserManager):
+    """Define a model manager for User model with no username field."""
 
+    use_in_migrations = True
+
+    def _create_user(self, email, password, **extra_fields):
+        """Create and save a User with the given email and password."""
+        if not email:
+            raise ValueError('The given email must be set')
+        email = self.normalize_email(email)
+        user = self.model(email=email, **extra_fields)
+        user.set_password(password)
+        user.save(using=self._db)
+        return user
+
+    def create_user(self, email, password=None, **extra_fields):
+        """Create and save a regular User with the given email and password."""
+        extra_fields.setdefault('is_staff', False)
+        extra_fields.setdefault('is_superuser', False)
+        return self._create_user(email, password, **extra_fields)
+
+    def create_superuser(self, email, password, **extra_fields):
+        """Create and save a SuperUser with the given email and password."""
+        extra_fields.setdefault('is_staff', True)
+        extra_fields.setdefault('is_superuser', True)
+
+        if extra_fields.get('is_staff') is not True:
+            raise ValueError('Superuser must have is_staff=True.')
+        if extra_fields.get('is_superuser') is not True:
+            raise ValueError('Superuser must have is_superuser=True.')
+
+        return self._create_user(email, password, **extra_fields)
+
+def default_organization():
+  default_organization, created = Organization.objects.get_or_create(name='default')
+  return default_organization
+
+class OrganizationUser(AbstractUser):
+    """User model."""
+
+    username = None
+    email = models.EmailField(_t('email address'), unique=True)
+    token = models.CharField(_t('token'), max_length=128, default=None, null=True)
+    customer_id = models.CharField(_t('Customer id'), max_length=128, default=None, null=True)
+    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, default=default_organization)
+
+    USERNAME_FIELD = 'email'
+    REQUIRED_FIELDS = []
+
+    objects = UserManager()
 
-# In class UserProfile
-#
-#   def get_groups(self):
-#     return self.user.groups.all()
-# to update
 
-# User
-# --> switch to email as PK
-# AUTH_USER_MODEL = 'auth.User'
+if ENABLE_ORGANIZATIONS.get():
+  from useradmin.models import OrganizationUser as User, OrganizationGroup as Group
+else:
+  from django.contrib.auth.models import User, Group
 
-# Enabled when:
-# desktop.conf.ENABLE_ORGANIZATIONS.get()
+# -----------------------------------------------------------------------
 
 
 class UserProfile(models.Model):
@@ -158,7 +189,7 @@ class UserProfile(models.Model):
     HUE = 1
     EXTERNAL = 2
 
-  user = models.OneToOneField(auth_models.User, unique=True)
+  user = models.OneToOneField(User, unique=True)
   home_directory = models.CharField(editable=True, max_length=1024, null=True)
   creation_method = models.CharField(editable=True, null=False, max_length=64, default=CreationMethod.HUE.name)
   first_login = models.BooleanField(default=True, verbose_name=_t('First Login'),
@@ -243,14 +274,14 @@ class LdapGroup(models.Model):
   Groups that come from LDAP originally will have an LdapGroup
   record generated at creation time.
   """
-  group = models.ForeignKey(auth_models.Group, related_name="group")
+  group = models.ForeignKey(Group, related_name="group")
 
 
 class GroupPermission(models.Model):
   """
   Represents the permissions a group has.
   """
-  group = models.ForeignKey(auth_models.Group)
+  group = models.ForeignKey(Group)
   hue_permission = models.ForeignKey("HuePermission")
 
 
@@ -264,7 +295,7 @@ class HuePermission(models.Model):
   action = models.CharField(max_length=100)
   description = models.CharField(max_length=255)
 
-  groups = models.ManyToManyField(auth_models.Group, through=GroupPermission)
+  groups = models.ManyToManyField(Group, through=GroupPermission)
   organization_groups = models.ManyToManyField(OrganizationGroup)
 
   def __str__(self):
@@ -280,7 +311,7 @@ def get_default_user_group(**kwargs):
   if default_user_group is None:
     return None
 
-  group, created = auth_models.Group.objects.get_or_create(name=default_user_group)
+  group, created = Group.objects.get_or_create(name=default_user_group)
   if created:
     group.save()
 
@@ -379,14 +410,14 @@ def install_sample_user():
   user = None
 
   try:
-    if auth_models.User.objects.filter(id=SAMPLE_USER_ID).exists():
-      user = auth_models.User.objects.get(id=SAMPLE_USER_ID)
+    if User.objects.filter(id=SAMPLE_USER_ID).exists():
+      user = User.objects.get(id=SAMPLE_USER_ID)
       LOG.info('Sample user found with username "%s" and User ID: %s' % (user.username, user.id))
-    elif auth_models.User.objects.filter(username=SAMPLE_USER_INSTALL).exists():
-      user = auth_models.User.objects.get(username=SAMPLE_USER_INSTALL)
+    elif User.objects.filter(username=SAMPLE_USER_INSTALL).exists():
+      user = User.objects.get(username=SAMPLE_USER_INSTALL)
       LOG.info('Sample user found: %s' % user.username)
     else:
-      user, created = auth_models.User.objects.get_or_create(
+      user, created = User.objects.get_or_create(
         username=SAMPLE_USER_INSTALL,
         password='!',
         is_active=False,
@@ -400,7 +431,7 @@ def install_sample_user():
     if user.username != SAMPLE_USER_INSTALL:
       LOG.warn('Sample user does not have username "%s", will attempt to modify the username.' % SAMPLE_USER_INSTALL)
       with transaction.atomic():
-        user = auth_models.User.objects.get(id=SAMPLE_USER_ID)
+        user = User.objects.get(id=SAMPLE_USER_ID)
         user.username = SAMPLE_USER_INSTALL
         user.save()
   except Exception as ex:

+ 27 - 23
desktop/core/src/desktop/models.py

@@ -30,33 +30,30 @@ import uuid
 from collections import OrderedDict
 from itertools import chain
 
-from django.contrib.auth import models as auth_models
+from django.db import connection, models, transaction
+from django.db.models import Q
+from django.db.models.query import QuerySet
 from django.contrib.auth.validators import UnicodeUsernameValidator
 from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.staticfiles.storage import staticfiles_storage
 from django.urls import reverse, NoReverseMatch
-from django.db import connection, models, transaction
-from django.db.models import Q
-from django.db.models.query import QuerySet
 from django.utils.translation import ugettext as _, ugettext_lazy as _t
 
-from desktop.settings import HUE_DESKTOP_VERSION
-
 from dashboard.conf import get_engines, HAS_REPORT_ENABLED
+from desktop.settings import HUE_DESKTOP_VERSION
 from kafka.conf import has_kafka
 from notebook.conf import SHOW_NOTEBOOKS, get_ordered_interpreters
+from settings import HUE_DESKTOP_VERSION
 
 from desktop import appmanager
-from desktop.auth.backend import is_admin
-from desktop.conf import get_clusters, CLUSTER_ID, IS_MULTICLUSTER_ONLY, IS_K8S_ONLY
+from desktop.conf import get_clusters, CLUSTER_ID, IS_MULTICLUSTER_ONLY, IS_K8S_ONLY, ENABLE_ORGANIZATIONS
 from desktop.lib import fsmanager
 from desktop.lib.i18n import force_unicode
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.paths import get_run_root, SAFE_CHARACTERS_URI_COMPONENTS
 from desktop.redaction import global_redaction_engine
 from desktop.settings import DOCUMENT2_SEARCH_MAX_LENGTH
-from desktop.auth.backend import is_admin
 
 if sys.version_info[0] > 2:
   import urllib.request, urllib.error
@@ -64,6 +61,12 @@ if sys.version_info[0] > 2:
 else:
   from urllib import quote as urllib_quote
 
+if ENABLE_ORGANIZATIONS.get():
+  from useradmin.models import OrganizationUser as User, OrganizationGroup as Group
+else:
+  from django.contrib.auth.models import User, Group
+
+
 LOG = logging.getLogger(__name__)
 
 SAMPLE_USER_ID = 1100713
@@ -101,7 +104,8 @@ def _version_from_properties(f):
 
 PREFERENCE_IS_WELCOME_TOUR_SEEN = 'is_welcome_tour_seen'
 
-class HueUser(auth_models.User):
+
+class HueUser(User):
   class Meta(object):
     proxy = True
 
@@ -109,12 +113,12 @@ class HueUser(auth_models.User):
     self._meta.get_field(
       'username'
     ).validators[0] = UnicodeUsernameValidator()
-    super(auth_models.User, self).__init__(*args, **kwargs)
+    super(User, self).__init__(*args, **kwargs)
 
 
 class UserPreferences(models.Model):
   """Holds arbitrary key/value strings."""
-  user = models.ForeignKey(auth_models.User)
+  user = models.ForeignKey(User)
   key = models.CharField(max_length=20)
   value = models.TextField(max_length=4096)
 
@@ -165,8 +169,8 @@ class DefaultConfiguration(models.Model):
   properties = models.TextField(default='[]', help_text=_t('JSON-formatted default properties values.'))
 
   is_default = models.BooleanField(default=False, db_index=True)
-  groups = models.ManyToManyField(auth_models.Group, db_index=True, db_table='defaultconfiguration_groups')
-  user = models.ForeignKey(auth_models.User, blank=True, null=True, db_index=True)
+  groups = models.ManyToManyField(Group, db_index=True, db_table='defaultconfiguration_groups')
+  user = models.ForeignKey(User, blank=True, null=True, db_index=True)
 
   objects = DefaultConfigurationManager()
 
@@ -290,7 +294,7 @@ class DocumentTag(models.Model):
   """
   Reserved tags can't be manually removed by the user.
   """
-  owner = models.ForeignKey(auth_models.User, db_index=True)
+  owner = models.ForeignKey(User, db_index=True)
   tag = models.SlugField()
 
   DEFAULT = 'default' # Always there
@@ -618,7 +622,7 @@ class DocumentManager(models.Manager):
 
 class Document(models.Model):
 
-  owner = models.ForeignKey(auth_models.User, db_index=True, verbose_name=_t('Owner'), help_text=_t('User who can own the job.'), related_name='doc_owner')
+  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'), help_text=_t('User who can own the job.'), related_name='doc_owner')
   name = models.CharField(default='', max_length=255)
   description = models.TextField(default='')
 
@@ -781,12 +785,12 @@ class Document(models.Model):
     for name, perm in perms_dict.items():
       users = groups = None
       if perm.get('user_ids'):
-        users = auth_models.User.objects.in_bulk(perm.get('user_ids'))
+        users = User.objects.in_bulk(perm.get('user_ids'))
       else:
         users = []
 
       if perm.get('group_ids'):
-        groups = auth_models.Group.objects.in_bulk(perm.get('group_ids'))
+        groups = Group.objects.in_bulk(perm.get('group_ids'))
       else:
         groups = []
 
@@ -883,8 +887,8 @@ class DocumentPermission(models.Model):
 
   doc = models.ForeignKey(Document)
 
-  users = models.ManyToManyField(auth_models.User, db_index=True, db_table='documentpermission_users')
-  groups = models.ManyToManyField(auth_models.Group, db_index=True, db_table='documentpermission_groups')
+  users = models.ManyToManyField(User, db_index=True, db_table='documentpermission_users')
+  groups = models.ManyToManyField(Group, db_index=True, db_table='documentpermission_groups')
   perms = models.CharField(default=READ_PERM, max_length=10, choices=( # one perm
     (READ_PERM, 'read'),
     (WRITE_PERM, 'write'),
@@ -1077,7 +1081,7 @@ class Document2(models.Model):
   TRASH_DIR = '.Trash'
   EXAMPLES_DIR = 'examples'
 
-  owner = models.ForeignKey(auth_models.User, db_index=True, verbose_name=_t('Owner'), help_text=_t('Creator.'), related_name='doc2_owner')
+  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'), help_text=_t('Creator.'), related_name='doc2_owner')
   name = models.CharField(default='', max_length=255)
   description = models.TextField(default='')
   uuid = models.CharField(default=uuid_default, max_length=36, db_index=True)
@@ -1532,8 +1536,8 @@ class Document2Permission(models.Model):
 
   doc = models.ForeignKey(Document2)
 
-  users = models.ManyToManyField(auth_models.User, db_index=True, db_table='documentpermission2_users')
-  groups = models.ManyToManyField(auth_models.Group, db_index=True, db_table='documentpermission2_groups')
+  users = models.ManyToManyField(User, db_index=True, db_table='documentpermission2_users')
+  groups = models.ManyToManyField(Group, db_index=True, db_table='documentpermission2_groups')
 
   perms = models.CharField(default=READ_PERM, max_length=10, db_index=True, choices=( # one perm
     (READ_PERM, 'read'),

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

@@ -212,7 +212,6 @@ INSTALLED_APPS = [
     #'django_celery_results',
 ]
 
-
 WEBPACK_LOADER = {
     'DEFAULT': {
         'BUNDLE_DIR_NAME': 'desktop/js/bundles/hue/',
@@ -328,6 +327,9 @@ if DEBUG: # For simplification, force all DEBUG when django_debug_mode is True a
 # configs.
 ############################################################
 
+if desktop.conf.ENABLE_ORGANIZATIONS.get():
+  AUTH_USER_MODEL = 'useradmin.OrganizationUser'
+
 # Configure allowed hosts
 ALLOWED_HOSTS = desktop.conf.ALLOWED_HOSTS.get()