| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- #!/usr/bin/env python2.5
- # 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.
- """
- 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).
- Several mechanisms enforce permission. First of all, the "access" permission
- is controlled by LoginAndPermissionMiddleware. For eligible views
- within an application, the access permission is checked. Second,
- views may use @desktop.decorators.hue_permission_required("action", "app")
- to annotate their function, and this decorator will check a permission.
- Thirdly, you may wish to do so manually, by using something akin to:
- app = desktop.lib.apputil.get_current_app() # a string
- 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
- 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.
- """
- import logging
- from django.db import models
- from django.contrib.auth import models as auth_models
- from desktop import appmanager
- from desktop.lib.django_util import PopupException
- LOG = logging.getLogger(__name__)
- class UserProfile(models.Model):
- """
- WARNING: Some of the columns in the UserProfile object have been added
- via south migration scripts. During an upgrade that modifies this model,
- the columns in the django ORM database will not match the
- actual object defined here, until the latest migration has been executed.
- The code that does the actual UserProfile population must reside in the most
- recent migration that modifies the UserProfile model.
- for user in User.objects.all():
- try:
- p = orm.UserProfile.objects.get(user=user)
- except orm.UserProfile.DoesNotExist:
- create_profile_for_user(user)
- IF ADDING A MIGRATION THAT MODIFIES THIS MODEL, MAKE SURE TO MOVE THIS CODE
- OUT OF THE CURRENT MIGRATION, AND INTO THE NEW ONE, OR UPGRADES WILL NOT WORK
- PROPERLY
- """
- user = models.ForeignKey(auth_models.User, unique=True)
- home_directory = models.CharField(editable=True, max_length=1024, null=True)
- def get_groups(self):
- return self.user.groups.all()
- def _lookup_permission(self, app, action):
- return HuePermission.objects.get(app=app, action=action)
- def has_hue_permission(self, action=None, app=None, perm=None):
- if perm is None:
- try:
- perm = self._lookup_permission(app, action)
- except HuePermission.DoesNotExist:
- LOG.exception("Permission object not available. Was syncdb run after installation?")
- return self.user.is_superuser
- if self.user.is_superuser:
- return True
- for group in self.user.groups.all():
- if group_has_permission(group, perm):
- return True
- return False
- def check_hue_permission(self, perm=None, app=None, action=None):
- """
- Raises a PopupException if permission is denied.
- Either perm or both app and action are required.
- """
- if perm is None:
- perm = self._lookup_permission(app, action)
- if self.has_hue_permission(perm):
- return
- else:
- raise PopupException("You do not have permissions to %s." % perm.description)
- def get_profile(user):
- """
- Caches the profile, to avoid DB queries at every call.
- """
- if hasattr(user, "_cached_userman_profile"):
- return user._cached_userman_profile
- else:
- profile = UserProfile.objects.get(user=user)
- user._cached_userman_profile = profile
- return profile
- def group_has_permission(group, perm):
- return GroupPermission.objects.filter(group=group, hue_permission=perm).count() > 0
- def group_permissions(group):
- return HuePermission.objects.filter(grouppermission__group=group).all()
- # Create a user profile for the given user
- def create_profile_for_user(user):
- p = UserProfile()
- p.user = user
- p.home_directory = "/user/%s" % p.user.username
- try:
- p.save()
- except:
- LOG.debug("Failed to automatically create user profile.", exc_info=True)
- def create_user_signal_handler(sender, **kwargs):
- if kwargs['created']:
- create_profile_for_user(kwargs['instance'])
- # Create a user profile every time a user gets created.
- models.signals.post_save.connect(create_user_signal_handler, sender=auth_models.User)
- class GroupPermission(models.Model):
- """
- Represents the permissions a group has.
- """
- group = models.ForeignKey(auth_models.Group)
- hue_permission = models.ForeignKey("HuePermission")
- # Permission Management
- class HuePermission(models.Model):
- """
- Set of non-object specific permissions that an app supports.
- For now, we only assign permissions to groups, though that may change.
- """
- app = models.CharField(max_length=30)
- action = models.CharField(max_length=100)
- description = models.CharField(max_length=255)
- groups = models.ManyToManyField(auth_models.Group, through=GroupPermission)
- def __str__(self):
- return "%s.%s:%s(%d)" % (self.app, self.action, self.description, self.pk)
- @classmethod
- def get_app_permission(cls, hue_app, action):
- return HuePermission.objects.get(app=hue_app, action=action)
- def update_app_permissions(**kwargs):
- """
- Inserts missing permissions into the database table.
- This is a 'syncdb' callback.
- We never delete permissions automatically, because apps might come and go.
- Note that signing up to the "syncdb" signal is not necessarily
- the best thing we can do, since some apps might not
- have models, but nonetheless, "syncdb" is typically
- run when apps are installed.
- """
- # Map app->action->HuePermission.
- current = {}
- try:
- for dp in HuePermission.objects.all():
- current.setdefault(dp.app, {})[dp.action] = dp
- except:
- return
- updated = 0
- uptodate = 0
- added = [ ]
- for app_obj in appmanager.DESKTOP_APPS:
- app = app_obj.name
- actions = set([("access", "launch this application")])
- actions.update(getattr(app_obj.settings, "PERMISSION_ACTIONS", []))
- if app not in current:
- current[app] = {}
- for action, description in actions:
- c = current[app].get(action)
- if c:
- if c.description != description:
- c.description = description
- c.save()
- updated += 1
- else:
- uptodate += 1
- else:
- new_dp = HuePermission(app=app, action=action, description=description)
- new_dp.save()
- added.append(new_dp)
- available = HuePermission.objects.count()
- LOG.info("HuePermissions: %d added, %d updated, %d up to date, %d stale" %
- (len(added),
- updated,
- uptodate,
- available - len(added) - updated - uptodate))
- models.signals.post_syncdb.connect(update_app_permissions)
|