from django.utils.safestring import mark_safe
from django.contrib.auth.admin import UserAdmin
from django.contrib.admin.widgets import FilteredSelectMultiple
-from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
from django.contrib.auth.signals import user_logged_in
from django.utils import timezone
from django.contrib.contenttypes import generic
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse, NoReverseMatch
+# this block of stuff is needed for UserAdmin
+from django.db import transaction
+from django.utils.decorators import method_decorator
+from django.views.decorators.csrf import csrf_protect
+from django.views.decorators.debug import sensitive_post_parameters
+csrf_protect_m = method_decorator(csrf_protect)
+sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
+
import django_evolution
def backend_icon(obj): # backend_status, enacted, updated):
value = ''
return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
-class ReadOnlyAwareAdmin(admin.ModelAdmin):
+class PermissionCheckingAdmin(admin.ModelAdmin):
+ # call save_by_user and delete_by_user instead of save and delete
def has_add_permission(self, request, obj=None):
return (not self.__user_is_readonly(request))
def save_model(self, request, obj, form, change):
if self.__user_is_readonly(request):
+ # this 'if' might be redundant if save_by_user is implemented right
raise PermissionDenied
- #pass
- else:
- return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
+
+ obj.caller = request.user
+ # update openstack connection to use this site/tenant
+ obj.save_by_user(request.user)
+
+ def delete_model(self, request, obj):
+ obj.delete_by_user(request.user)
+
+ def save_formset(self, request, form, formset, change):
+ instances = formset.save(commit=False)
+ for instance in instances:
+ instance.save_by_user(request.user)
+
+ # BUG in django 1.7? Objects are not deleted by formset.save if
+ # commit is False. So let's delete them ourselves.
+ #
+ # code from forms/models.py save_existing_objects()
+ try:
+ forms_to_delete = formset.deleted_forms\r
+ except AttributeError:\r
+ forms_to_delete = []
+ if formset.initial_forms:
+ for form in formset.initial_forms:
+ obj = form.instance
+ if form in forms_to_delete:
+ if obj.pk is None:
+ continue
+ formset.deleted_objects.append(obj)
+ obj.delete()
+
+ formset.save_m2m()
def get_actions(self,request):
- actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
+ actions = super(PermissionCheckingAdmin,self).get_actions(request)
if self.__user_is_readonly(request):
if 'delete_selected' in actions:
self.inlines = self.inlines_save
try:
- return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
+ return super(PermissionCheckingAdmin, self).change_view(request, object_id, extra_context=extra_context)
except PermissionDenied:
pass
if request.method == 'POST':
raise PermissionDenied
request.readonly = True
- return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
+ return super(PermissionCheckingAdmin, self).change_view(request, object_id, extra_context=extra_context)
def __user_is_readonly(self, request):
return request.user.isReadOnlyUser()
return mark_safe(backend_icon(obj))
backend_status_icon.short_description = ""
+class ReadOnlyAwareAdmin(PermissionCheckingAdmin):
+ pass
+
+class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
+ save_on_top = False
class SingletonAdmin (ReadOnlyAwareAdmin):
def has_add_permission(self, request):
else:
return True
-
class PlStackTabularInline(admin.TabularInline):
def __init__(self, *args, **kwargs):
super(PlStackTabularInline, self).__init__(*args, **kwargs)
fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
readonly_fields = ['backend_status_icon', 'glance_image_id']
-class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
- save_on_top = False
-
- def save_model(self, request, obj, form, change):
- obj.caller = request.user
- # update openstack connection to use this site/tenant
- obj.save_by_user(request.user)
-
- def delete_model(self, request, obj):
- obj.delete_by_user(request.user)
-
- def save_formset(self, request, form, formset, change):
- instances = formset.save(commit=False)
- for instance in instances:
- instance.save_by_user(request.user)
-
- # BUG in django 1.7? Objects are not deleted by formset.save if
- # commit is False. So let's delete them ourselves.
- #
- # code from forms/models.py save_existing_objects()
- try:
- forms_to_delete = formset.deleted_forms\r
- except AttributeError:\r
- forms_to_delete = []
- if formset.initial_forms:
- for form in formset.initial_forms:
- obj = form.instance
- if form in forms_to_delete:
- if obj.pk is None:
- continue
- formset.deleted_objects.append(obj)
- obj.delete()
-
- formset.save_m2m()
-
class SliceRoleAdmin(PlanetStackBaseAdmin):
model = SliceRole
pass
suit_classes = 'suit-tab suit-tab-dashboards'
fields = ['user', 'dashboardView', 'order']
-class UserAdmin(UserAdmin):
+class UserAdmin(PlanetStackBaseAdmin):
class Meta:
app_label = "core"
+ add_form_template = 'admin/auth/user/add_form.html'
+ change_user_password_template = None
+
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
+ change_password_form = AdminPasswordChangeForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
list_filter = ('site',)
inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
- fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
+ fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
fieldListContactInfo = ['firstname','lastname','phone','timezone']
fieldsets = (
return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
- def has_add_permission(self, request, obj=None):
- return (not self.__user_is_readonly(request))
-
- def has_delete_permission(self, request, obj=None):
- return (not self.__user_is_readonly(request))
-
- def get_actions(self,request):
- actions = super(UserAdmin,self).get_actions(request)
-
- if self.__user_is_readonly(request):
- if 'delete_selected' in actions:
- del actions['delete_selected']
-
- return actions
-
- def change_view(self,request,object_id, extra_context=None):
-
- if self.__user_is_readonly(request):
- if not hasattr(self, "readonly_save"):
- # save the original readonly fields\r
- self.readonly_save = self.readonly_fields\r
- self.inlines_save = self.inlines
- if hasattr(self, "user_readonly_fields"):
- self.readonly_fields=self.user_readonly_fields
- if hasattr(self, "user_readonly_inlines"):
- self.inlines = self.user_readonly_inlines
- else:
- if hasattr(self, "readonly_save"):\r
- # restore the original readonly fields\r
- self.readonly_fields = self.readonly_save\r
- self.inlines = self.inlines_save
-
- try:
- return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
- except PermissionDenied:
- pass
- if request.method == 'POST':
- raise PermissionDenied
- request.readonly = True
- return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
-
- def __user_is_readonly(self, request):
- #groups = [x.name for x in request.user.groups.all() ]
- #return "readonly" in groups
- return request.user.isReadOnlyUser()
-
def queryset(self, request):
return User.select_by_user(request.user)
- def backend_status_text(self, obj):
- return mark_safe(backend_text(obj))
+ # ------------------------------------------------------------------------
+ # stuff copied from ModelAdmin.UserAdmin
+ # ------------------------------------------------------------------------
+ def get_fieldsets(self, request, obj=None):
+ if not obj:\r
+ return self.add_fieldsets\r
+ return super(UserAdmin, self).get_fieldsets(request, obj)
+
+ def get_form(self, request, obj=None, **kwargs):
+ """\r
+ Use special form during user creation\r
+ """\r
+ defaults = {}\r
+ if obj is None:\r
+ defaults['form'] = self.add_form\r
+ defaults.update(kwargs)\r
+ return super(UserAdmin, self).get_form(request, obj, **defaults)\r
+\r
+ def get_urls(self):\r
+ from django.conf.urls import patterns\r
+ return patterns('',\r
+ (r'^(\d+)/password/$',\r
+ self.admin_site.admin_view(self.user_change_password))\r
+ ) + super(UserAdmin, self).get_urls()\r
+\r
+ def lookup_allowed(self, lookup, value):\r
+ # See #20078: we don't want to allow any lookups involving passwords.\r
+ if lookup.startswith('password'):\r
+ return False\r
+ return super(UserAdmin, self).lookup_allowed(lookup, value)\r
+\r
+ @sensitive_post_parameters_m\r
+ @csrf_protect_m\r
+ @transaction.atomic\r
+ def add_view(self, request, form_url='', extra_context=None):\r
+ # It's an error for a user to have add permission but NOT change\r
+ # permission for users. If we allowed such users to add users, they\r
+ # could create superusers, which would mean they would essentially have\r
+ # the permission to change users. To avoid the problem entirely, we\r
+ # disallow users from adding users if they don't have change\r
+ # permission.\r
+ if not self.has_change_permission(request):\r
+ if self.has_add_permission(request) and settings.DEBUG:\r
+ # Raise Http404 in debug mode so that the user gets a helpful\r
+ # error message.\r
+ raise Http404(\r
+ 'Your user does not have the "Change user" permission. In '\r
+ 'order to add users, Django requires that your user '\r
+ 'account have both the "Add user" and "Change user" '\r
+ 'permissions set.')\r
+ raise PermissionDenied\r
+ if extra_context is None:\r
+ extra_context = {}\r
+ username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)\r
+ defaults = {\r
+ 'auto_populated_fields': (),\r
+ 'username_help_text': username_field.help_text,\r
+ }\r
+ extra_context.update(defaults)\r
+ return super(UserAdmin, self).add_view(request, form_url,\r
+ extra_context)\r
+\r
+ @sensitive_post_parameters_m\r
+ def user_change_password(self, request, id, form_url=''):\r
+ if not self.has_change_permission(request):\r
+ raise PermissionDenied\r
+ user = get_object_or_404(self.get_queryset(request), pk=id)\r
+ if request.method == 'POST':\r
+ form = self.change_password_form(user, request.POST)\r
+ if form.is_valid():\r
+ form.save()\r
+ change_message = self.construct_change_message(request, form, None)\r
+ self.log_change(request, user, change_message)\r
+ msg = ugettext('Password changed successfully.')\r
+ messages.success(request, msg)\r
+ update_session_auth_hash(request, form.user)\r
+ return HttpResponseRedirect('..')\r
+ else:\r
+ form = self.change_password_form(user)\r
+\r
+ fieldsets = [(None, {'fields': list(form.base_fields)})]\r
+ adminForm = admin.helpers.AdminForm(form, fieldsets, {})\r
+\r
+ context = {\r
+ 'title': _('Change password: %s') % escape(user.get_username()),\r
+ 'adminForm': adminForm,\r
+ 'form_url': form_url,\r
+ 'form': form,\r
+ 'is_popup': (IS_POPUP_VAR in request.POST or\r
+ IS_POPUP_VAR in request.GET),\r
+ 'add': True,\r
+ 'change': False,\r
+ 'has_delete_permission': False,\r
+ 'has_change_permission': True,\r
+ 'has_absolute_url': False,\r
+ 'opts': self.model._meta,\r
+ 'original': user,\r
+ 'save_as': False,\r
+ 'show_save': True,\r
+ }\r
+ context.update(admin.site.each_context())\r
+ return TemplateResponse(request,\r
+ self.change_user_password_template or\r
+ 'admin/auth/user/change_password.html',\r
+ context, current_app=self.admin_site.name)\r
+\r
+ def response_add(self, request, obj, post_url_continue=None):\r
+ """\r
+ Determines the HttpResponse for the add_view stage. It mostly defers to\r
+ its superclass implementation but is customized because the User model\r
+ has a slightly different workflow.\r
+ """\r
+ # We should allow further modification of the user just added i.e. the\r
+ # 'Save' button should behave like the 'Save and continue editing'\r
+ # button except in two scenarios:\r
+ # * The user has pressed the 'Save and add another' button\r
+ # * We are adding a user in a popup\r
+ if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST:\r
+ request.POST['_continue'] = 1\r
+ return super(UserAdmin, self).response_add(request, obj,\r
+ post_url_continue)
+
+ # ------------------------------------------------------------------------
+ # end stuff copied from ModelAdmin.UserAdmin
+ # ------------------------------------------------------------------------
- def backend_status_icon(self, obj):
- return mark_safe(backend_icon(obj))
- backend_status_icon.short_description = ""
class DashboardViewAdmin(PlanetStackBaseAdmin):
fieldsets = [('Dashboard View Details',
from collections import defaultdict
from django.db import models
from django.db.models import F, Q
-from core.models import PlCoreBase,Site, DashboardView
+from core.models import PlCoreBase,Site, DashboardView, DiffModelMixIn
from core.models.site import Deployment
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from timezones.fields import TimeZoneField
from django.core.mail import EmailMultiAlternatives
from core.middleware import get_request
import model_policy
+from django.core.exceptions import PermissionDenied
# Create your models here.
class UserManager(BaseUserManager):
def get_query_set(self):
return self.get_queryset()
-class User(AbstractBaseUser):
+class User(AbstractBaseUser, DiffModelMixIn):
class Meta:
app_label = "core"
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['firstname', 'lastname']
+ def __init__(self, *args, **kwargs):
+ super(User, self).__init__(*args, **kwargs)
+ self._initial = self._dict # for DiffModelMixIn
+
def isReadOnlyUser(self):
return self.is_readonly
self.username = self.email
super(User, self).save(*args, **kwds)
+ self._initial = self._dict
+
def send_temporary_password(self):
password = User.objects.make_random_password()
self.set_password(password)\r
msg.attach_alternative(html_content, "text/html")\r
msg.send()
+ def can_update_field(self, user, fieldName):
+ from core.models import SitePrivilege
+ if (user.is_admin):
+ # admin can update anything
+ return True
+
+ # fields that a site PI can update
+ if fieldName in ["is_active", "is_readonly"]:
+ site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
+ for site_priv in site_privs:
+ if site_priv.role.role == 'pi':
+ return True
+
+ # fields that a user cannot update in his/her own record
+ if fieldName in ["is_admin", "is_active", "site", "is_staff", "is_readonly"]:
+ return False
+
+ return True
+
+ def can_update(self, user):
+ from core.models import SitePrivilege
+ if user.is_readonly:
+ return False
+ if user.is_admin:
+ return True
+ if (user.id == self.id):
+ return True
+ # site pis can update
+ site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
+ for site_priv in site_privs:
+ if site_priv.role.role == 'pi':
+ return True
+
+ return False
+
@staticmethod
def select_by_user(user):
if user.is_admin:
qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
return qs
+ def save_by_user(self, user, *args, **kwds):
+ if not self.can_update(user):
+ raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
+
+ for fieldName in self.changed_fields:
+ if not self.can_update_field(user, fieldName):
+ raise PermissionDenied("You do not have permission to update field %s in object %s" % (fieldName, self.__class__.__name__))
+
+ self.save(*args, **kwds)
+
+ def delete_by_user(self, user, *args, **kwds):
+ if not self.can_update(user):
+ raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
+ self.delete(*args, **kwds)
+
class UserDashboardView(PlCoreBase):
user = models.ForeignKey(User, related_name="dashboardViews")
dashboardView = models.ForeignKey(DashboardView, related_name="dashboardViews")