浏览代码

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.
 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
 Several mechanisms enforce permission.  First of all, the "access" permission
 is controlled by LoginAndPermissionMiddleware.  For eligible views
 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)
   dp = HuePermission.objects.get(app=pp, action=action)
   request.user.has_hue_permission(dp)
   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.
 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
 import logging
 from datetime import datetime
 from datetime import datetime
@@ -55,11 +45,13 @@ from enum import Enum
 
 
 from django.db import connection, models, transaction
 from django.db import connection, models, transaction
 from django.contrib.auth import models as auth_models
 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.core.cache import cache
 from django.utils.translation import ugettext_lazy as _t
 from django.utils.translation import ugettext_lazy as _t
 import django.utils.timezone as dtz
 import django.utils.timezone as dtz
 
 
 from desktop import appmanager
 from desktop import appmanager
+from desktop.conf import ENABLE_ORGANIZATIONS
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.idbroker.conf import is_idbroker_enabled
 from desktop.lib.idbroker.conf import is_idbroker_enabled
 from hadoop import cluster
 from hadoop import cluster
@@ -70,6 +62,9 @@ import useradmin.conf
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
 
 
+# -----------------------------------------------------------------------
+#  Organizations
+# -----------------------------------------------------------------------
 
 
 class OrganizationManager(models.Manager):
 class OrganizationManager(models.Manager):
   use_in_migrations = True
   use_in_migrations = True
@@ -77,7 +72,6 @@ class OrganizationManager(models.Manager):
   def get_by_natural_key(self, name):
   def get_by_natural_key(self, name):
     return self.get(name=name)
     return self.get(name=name)
 
 
-
 class Organization(models.Model):
 class Organization(models.Model):
   name = models.CharField(max_length=200, help_text=_t("The name of the organization"))
   name = models.CharField(max_length=200, help_text=_t("The name of the organization"))
   is_active = models.BooleanField(default=True)
   is_active = models.BooleanField(default=True)
@@ -90,7 +84,6 @@ class OrganizationGroupManager(models.Manager):
   def natural_key(self):
   def natural_key(self):
     return (self.organization, self.name,)
     return (self.organization, self.name,)
 
 
-
 class OrganizationGroup(models.Model):
 class OrganizationGroup(models.Model):
   name = models.CharField(_t('name'), max_length=80, unique=False)
   name = models.CharField(_t('name'), max_length=80, unique=False)
   organization = models.ForeignKey(Organization)
   organization = models.ForeignKey(Organization)
@@ -112,26 +105,64 @@ class OrganizationGroup(models.Model):
     return '%s %s' % (self.organization, self.name)
     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):
 class UserProfile(models.Model):
@@ -158,7 +189,7 @@ class UserProfile(models.Model):
     HUE = 1
     HUE = 1
     EXTERNAL = 2
     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)
   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)
   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'),
   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
   Groups that come from LDAP originally will have an LdapGroup
   record generated at creation time.
   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):
 class GroupPermission(models.Model):
   """
   """
   Represents the permissions a group has.
   Represents the permissions a group has.
   """
   """
-  group = models.ForeignKey(auth_models.Group)
+  group = models.ForeignKey(Group)
   hue_permission = models.ForeignKey("HuePermission")
   hue_permission = models.ForeignKey("HuePermission")
 
 
 
 
@@ -264,7 +295,7 @@ class HuePermission(models.Model):
   action = models.CharField(max_length=100)
   action = models.CharField(max_length=100)
   description = models.CharField(max_length=255)
   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)
   organization_groups = models.ManyToManyField(OrganizationGroup)
 
 
   def __str__(self):
   def __str__(self):
@@ -280,7 +311,7 @@ def get_default_user_group(**kwargs):
   if default_user_group is None:
   if default_user_group is None:
     return 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:
   if created:
     group.save()
     group.save()
 
 
@@ -379,14 +410,14 @@ def install_sample_user():
   user = None
   user = None
 
 
   try:
   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))
       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)
       LOG.info('Sample user found: %s' % user.username)
     else:
     else:
-      user, created = auth_models.User.objects.get_or_create(
+      user, created = User.objects.get_or_create(
         username=SAMPLE_USER_INSTALL,
         username=SAMPLE_USER_INSTALL,
         password='!',
         password='!',
         is_active=False,
         is_active=False,
@@ -400,7 +431,7 @@ def install_sample_user():
     if user.username != SAMPLE_USER_INSTALL:
     if user.username != SAMPLE_USER_INSTALL:
       LOG.warn('Sample user does not have username "%s", will attempt to modify the 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():
       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.username = SAMPLE_USER_INSTALL
         user.save()
         user.save()
   except Exception as ex:
   except Exception as ex:

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

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

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

@@ -212,7 +212,6 @@ INSTALLED_APPS = [
     #'django_celery_results',
     #'django_celery_results',
 ]
 ]
 
 
-
 WEBPACK_LOADER = {
 WEBPACK_LOADER = {
     'DEFAULT': {
     'DEFAULT': {
         'BUNDLE_DIR_NAME': 'desktop/js/bundles/hue/',
         '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.
 # configs.
 ############################################################
 ############################################################
 
 
+if desktop.conf.ENABLE_ORGANIZATIONS.get():
+  AUTH_USER_MODEL = 'useradmin.OrganizationUser'
+
 # Configure allowed hosts
 # Configure allowed hosts
 ALLOWED_HOSTS = desktop.conf.ALLOWED_HOSTS.get()
 ALLOWED_HOSTS = desktop.conf.ALLOWED_HOSTS.get()