models.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!/usr/bin/env python2.5
  2. # Licensed to Cloudera, Inc. under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. Cloudera, Inc. licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. """
  18. The core of this module adds permissions functionality to Hue applications.
  19. A "Hue Permission" (colloquially, appname.action, but stored in the
  20. HuePermission model) is a way to specify some action whose
  21. control may be restricted. Every Hue application, by default,
  22. has an "access" action. To specify extra actions, applications
  23. can specify them in appname.settings.PERMISSION_ACTIONS, as
  24. pairs of (action_name, description).
  25. Several mechanisms enforce permission. First of all, the "access" permission
  26. is controlled by LoginAndPermissionMiddleware. For eligible views
  27. within an application, the access permission is checked. Second,
  28. views may use @desktop.decorators.hue_permission_required("action", "app")
  29. to annotate their function, and this decorator will check a permission.
  30. Thirdly, you may wish to do so manually, by using something akin to:
  31. app = desktop.lib.apputil.get_current_app() # a string
  32. dp = HuePermission.objects.get(app=pp, action=action)
  33. request.user.has_hue_permission(dp)
  34. [Design note: it is questionable that a Hue permission is
  35. a model, instead of just being a string. Could go either way.]
  36. Permissions may be granted to groups, but not, currently, to users.
  37. A user's abilities is the union of all permissions the group
  38. has access to.
  39. Note that Django itself has a notion of users, groups, and permissions.
  40. We re-use Django's notion of users and groups, but ignore its notion of
  41. permissions. The permissions notion in Django is strongly tied to
  42. what models you may or may not edit, and there are elaborations (especially
  43. in Django 1.2) to manipulate this row by row. This does not map nicely
  44. onto actions which may not relate to database models.
  45. """
  46. import logging
  47. from django.db import models
  48. from django.contrib.auth import models as auth_models
  49. from desktop import appmanager
  50. from desktop.lib.django_util import PopupException
  51. LOG = logging.getLogger(__name__)
  52. class UserProfile(models.Model):
  53. """
  54. WARNING: Some of the columns in the UserProfile object have been added
  55. via south migration scripts. During an upgrade that modifies this model,
  56. the columns in the django ORM database will not match the
  57. actual object defined here, until the latest migration has been executed.
  58. The code that does the actual UserProfile population must reside in the most
  59. recent migration that modifies the UserProfile model.
  60. for user in User.objects.all():
  61. try:
  62. p = orm.UserProfile.objects.get(user=user)
  63. except orm.UserProfile.DoesNotExist:
  64. create_profile_for_user(user)
  65. IF ADDING A MIGRATION THAT MODIFIES THIS MODEL, MAKE SURE TO MOVE THIS CODE
  66. OUT OF THE CURRENT MIGRATION, AND INTO THE NEW ONE, OR UPGRADES WILL NOT WORK
  67. PROPERLY
  68. """
  69. user = models.ForeignKey(auth_models.User, unique=True)
  70. home_directory = models.CharField(editable=True, max_length=1024, null=True)
  71. def get_groups(self):
  72. return self.user.groups.all()
  73. def _lookup_permission(self, app, action):
  74. return HuePermission.objects.get(app=app, action=action)
  75. def has_hue_permission(self, action=None, app=None, perm=None):
  76. if perm is None:
  77. try:
  78. perm = self._lookup_permission(app, action)
  79. except HuePermission.DoesNotExist:
  80. LOG.exception("Permission object not available. Was syncdb run after installation?")
  81. return self.user.is_superuser
  82. if self.user.is_superuser:
  83. return True
  84. for group in self.user.groups.all():
  85. if group_has_permission(group, perm):
  86. return True
  87. return False
  88. def check_hue_permission(self, perm=None, app=None, action=None):
  89. """
  90. Raises a PopupException if permission is denied.
  91. Either perm or both app and action are required.
  92. """
  93. if perm is None:
  94. perm = self._lookup_permission(app, action)
  95. if self.has_hue_permission(perm):
  96. return
  97. else:
  98. raise PopupException("You do not have permissions to %s." % perm.description)
  99. def get_profile(user):
  100. """
  101. Caches the profile, to avoid DB queries at every call.
  102. """
  103. if hasattr(user, "_cached_userman_profile"):
  104. return user._cached_userman_profile
  105. else:
  106. profile = UserProfile.objects.get(user=user)
  107. user._cached_userman_profile = profile
  108. return profile
  109. def group_has_permission(group, perm):
  110. return GroupPermission.objects.filter(group=group, hue_permission=perm).count() > 0
  111. def group_permissions(group):
  112. return HuePermission.objects.filter(grouppermission__group=group).all()
  113. # Create a user profile for the given user
  114. def create_profile_for_user(user):
  115. p = UserProfile()
  116. p.user = user
  117. p.home_directory = "/user/%s" % p.user.username
  118. try:
  119. p.save()
  120. except:
  121. LOG.debug("Failed to automatically create user profile.", exc_info=True)
  122. def create_user_signal_handler(sender, **kwargs):
  123. if kwargs['created']:
  124. create_profile_for_user(kwargs['instance'])
  125. # Create a user profile every time a user gets created.
  126. models.signals.post_save.connect(create_user_signal_handler, sender=auth_models.User)
  127. class GroupPermission(models.Model):
  128. """
  129. Represents the permissions a group has.
  130. """
  131. group = models.ForeignKey(auth_models.Group)
  132. hue_permission = models.ForeignKey("HuePermission")
  133. # Permission Management
  134. class HuePermission(models.Model):
  135. """
  136. Set of non-object specific permissions that an app supports.
  137. For now, we only assign permissions to groups, though that may change.
  138. """
  139. app = models.CharField(max_length=30)
  140. action = models.CharField(max_length=100)
  141. description = models.CharField(max_length=255)
  142. groups = models.ManyToManyField(auth_models.Group, through=GroupPermission)
  143. def __str__(self):
  144. return "%s.%s:%s(%d)" % (self.app, self.action, self.description, self.pk)
  145. @classmethod
  146. def get_app_permission(cls, hue_app, action):
  147. return HuePermission.objects.get(app=hue_app, action=action)
  148. def update_app_permissions(**kwargs):
  149. """
  150. Inserts missing permissions into the database table.
  151. This is a 'syncdb' callback.
  152. We never delete permissions automatically, because apps might come and go.
  153. Note that signing up to the "syncdb" signal is not necessarily
  154. the best thing we can do, since some apps might not
  155. have models, but nonetheless, "syncdb" is typically
  156. run when apps are installed.
  157. """
  158. # Map app->action->HuePermission.
  159. current = {}
  160. try:
  161. for dp in HuePermission.objects.all():
  162. current.setdefault(dp.app, {})[dp.action] = dp
  163. except:
  164. return
  165. updated = 0
  166. uptodate = 0
  167. added = [ ]
  168. for app_obj in appmanager.DESKTOP_APPS:
  169. app = app_obj.name
  170. actions = set([("access", "launch this application")])
  171. actions.update(getattr(app_obj.settings, "PERMISSION_ACTIONS", []))
  172. if app not in current:
  173. current[app] = {}
  174. for action, description in actions:
  175. c = current[app].get(action)
  176. if c:
  177. if c.description != description:
  178. c.description = description
  179. c.save()
  180. updated += 1
  181. else:
  182. uptodate += 1
  183. else:
  184. new_dp = HuePermission(app=app, action=action, description=description)
  185. new_dp.save()
  186. added.append(new_dp)
  187. available = HuePermission.objects.count()
  188. LOG.info("HuePermissions: %d added, %d updated, %d up to date, %d stale" %
  189. (len(added),
  190. updated,
  191. uptodate,
  192. available - len(added) - updated - uptodate))
  193. models.signals.post_syncdb.connect(update_app_permissions)