Added timestamp for model policy scheduling to the User class
[plstackapi.git] / planetstack / core / models / user.py
1 import os
2 import datetime
3 import sys
4 import hashlib
5 from collections import defaultdict
6 from django.forms.models import model_to_dict
7 from django.db import models
8 from django.db.models import F, Q
9 from core.models import PlCoreBase,Site, DashboardView, DiffModelMixIn
10 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
11 from timezones.fields import TimeZoneField
12 from operator import itemgetter, attrgetter
13 from django.core.mail import EmailMultiAlternatives
14 from core.middleware import get_request
15 import model_policy
16 from django.core.exceptions import PermissionDenied
17
18 # ------ from plcorebase.py ------
19 try:
20     # This is a no-op if observer_disabled is set to 1 in the config file
21     from observer import *
22 except:
23     print >> sys.stderr, "import of observer failed! printing traceback and disabling observer:"
24     import traceback
25     traceback.print_exc()
26
27     # guard against something failing
28     def notify_observer(*args, **kwargs):
29         pass
30 # ------ ------
31
32 # Create your models here.
33 class UserManager(BaseUserManager):
34     def create_user(self, email, firstname, lastname, password=None):
35         """
36         Creates and saves a User with the given email, date of
37         birth and password.
38         """
39         if not email:
40             raise ValueError('Users must have an email address')
41
42         user = self.model(
43             email=UserManager.normalize_email(email),
44             firstname=firstname,
45             lastname=lastname,
46             password=password
47         )
48         #user.set_password(password)
49         user.is_admin = True
50         user.save(using=self._db)
51         return user
52
53     def create_superuser(self, email, firstname, lastname, password):
54         """
55         Creates and saves a superuser with the given email, date of
56         birth and password.
57         """
58         user = self.create_user(email,
59             password=password,
60             firstname=firstname,
61             lastname=lastname
62         )
63         user.is_admin = True
64         user.save(using=self._db)
65         return user
66
67     def get_queryset(self):
68         parent=super(UserManager, self)
69         if hasattr(parent, "get_queryset"):
70             return parent.get_queryset().filter(deleted=False)
71         else:
72             return parent.get_query_set().filter(deleted=False)
73
74     # deprecated in django 1.7 in favor of get_queryset().
75     def get_query_set(self):
76         return self.get_queryset()
77
78 class DeletedUserManager(UserManager):
79     def get_queryset(self):
80         return super(UserManager, self).get_query_set().filter(deleted=True)
81
82     # deprecated in django 1.7 in favor of get_queryset()
83     def get_query_set(self):
84         return self.get_queryset()
85
86 class User(AbstractBaseUser): #, DiffModelMixIn):
87
88     # ---- copy stuff from DiffModelMixin ----
89
90     @property
91     def _dict(self):
92         return model_to_dict(self, fields=[field.name for field in
93                              self._meta.fields])
94
95     @property
96     def diff(self):
97         d1 = self._initial
98         d2 = self._dict
99         diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
100         return dict(diffs)
101
102     @property
103     def has_changed(self):
104         return bool(self.diff)
105
106     @property
107     def changed_fields(self):
108         return self.diff.keys()
109
110     def has_field_changed(self, field_name):
111         return field_name in self.diff.keys()
112
113     def get_field_diff(self, field_name):
114         return self.diff.get(field_name, None)
115
116     #classmethod
117     def getValidators(cls):
118         """ primarily for REST API, return a dictionary of field names mapped
119             to lists of the type of validations that need to be applied to
120             those fields.
121         """
122         validators = {}
123         for field in cls._meta.fields:
124             l = []
125             if field.blank==False:
126                 l.append("notBlank")
127             if field.__class__.__name__=="URLField":
128                 l.append("url")
129             validators[field.name] = l
130         return validators
131     # ---- end copy stuff from DiffModelMixin ----
132
133     @property
134     def remote_password(self):
135         return hashlib.md5(self.password).hexdigest()[:12]
136
137     class Meta:
138         app_label = "core"
139
140     email = models.EmailField(
141         verbose_name='email address',
142         max_length=255,
143         unique=True,
144         db_index=True,
145     )
146
147     username = models.CharField(max_length=255, default="Something" )
148
149     firstname = models.CharField(help_text="person's given name", max_length=200)
150     lastname = models.CharField(help_text="person's surname", max_length=200)
151
152     phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100)
153     user_url = models.URLField(null=True, blank=True)
154     site = models.ForeignKey(Site, related_name='users', help_text="Site this user will be homed too", null=True)
155     public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
156
157     is_active = models.BooleanField(default=True)
158     is_admin = models.BooleanField(default=True)
159     is_staff = models.BooleanField(default=True)
160     is_readonly = models.BooleanField(default=False)
161
162     created = models.DateTimeField(auto_now_add=True)
163     updated = models.DateTimeField(auto_now=True)
164     enacted = models.DateTimeField(null=True, default=None)
165     policed = models.DateTimeField(null=True, default=None)
166     backend_status = models.CharField(max_length=140,
167                                       default="Provisioning in progress")
168     deleted = models.BooleanField(default=False)
169
170     timezone = TimeZoneField()
171
172     dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
173
174     objects = UserManager()
175     deleted_objects = DeletedUserManager()
176
177     USERNAME_FIELD = 'email'
178     REQUIRED_FIELDS = ['firstname', 'lastname']
179
180     PI_FORBIDDEN_FIELDS = ["is_admin", "site", "is_staff"]
181     USER_FORBIDDEN_FIELDS = ["is_admin", "is_active", "site", "is_staff", "is_readonly"]
182
183     def __init__(self, *args, **kwargs):
184         super(User, self).__init__(*args, **kwargs)
185         self._initial = self._dict # for DiffModelMixIn
186
187     def isReadOnlyUser(self):
188         return self.is_readonly
189
190     def get_full_name(self):
191         # The user is identified by their email address
192         return self.email
193
194     def get_short_name(self):
195         # The user is identified by their email address
196         return self.email
197
198     def delete(self, *args, **kwds):
199         # so we have something to give the observer
200         purge = kwds.get('purge',False)
201         if purge:
202             del kwds['purge']
203         try:
204             purge = purge or observer_disabled
205         except NameError:
206             pass
207             
208         if (purge):
209             super(User, self).delete(*args, **kwds)
210         else:
211             self.deleted = True
212             self.enacted=None
213             self.save(update_fields=['enacted','deleted'])
214
215     @property
216     def keyname(self):
217         return self.email[:self.email.find('@')]
218
219     def __unicode__(self):
220         return self.email
221
222     def has_perm(self, perm, obj=None):
223         "Does the user have a specific permission?"
224         # Simplest possible answer: Yes, always
225         return True
226
227     def has_module_perms(self, app_label):
228         "Does the user have permissions to view the app `app_label`?"
229         # Simplest possible answer: Yes, always
230         return True
231
232     def is_superuser(self):
233         return False
234
235     def get_dashboards(self):
236         DEFAULT_DASHBOARDS=["Tenant"]
237
238         dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
239         dashboards = [x.dashboardView for x in dashboards]
240
241         if not dashboards:
242             for dashboardName in DEFAULT_DASHBOARDS:
243                 dbv = DashboardView.objects.filter(name=dashboardName)
244                 if dbv:
245                     dashboards.append(dbv[0])
246
247         return dashboards
248
249 #    def get_roles(self):
250 #        from core.models.site import SitePrivilege
251 #        from core.models.slice import SliceMembership
252 #
253 #        site_privileges = SitePrivilege.objects.filter(user=self)
254 #        slice_memberships = SliceMembership.objects.filter(user=self)
255 #        roles = defaultdict(list)
256 #        for site_privilege in site_privileges:
257 #            roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
258 #        for slice_membership in slice_memberships:
259 #            roles[slice_membership.role.role_type].append(slice_membership.slice.name)
260 #        return roles   
261
262     def save(self, *args, **kwds):
263         if not self.id:
264             self.set_password(self.password)
265         if self.is_active:
266             if self.password=="!":\r
267                 self.send_temporary_password()\r
268 \r
269         self.username = self.email
270         super(User, self).save(*args, **kwds)
271
272         self._initial = self._dict
273
274     def send_temporary_password(self):
275         password = User.objects.make_random_password()
276         self.set_password(password)\r
277         subject, from_email, to = 'OpenCloud Account Credentials', 'support@opencloud.us', str(self.email)\r
278         text_content = 'This is an important message.'\r
279         userUrl="http://%s/" % get_request().get_host()\r
280         html_content = """<p>Your account has been created on OpenCloud. Please log in <a href="""+userUrl+""">here</a> to activate your account<br><br>Username: """+self.email+"""<br>Temporary Password: """+password+"""<br>Please change your password once you successully login into the site.</p>"""\r
281         msg = EmailMultiAlternatives(subject,text_content, from_email, [to])\r
282         msg.attach_alternative(html_content, "text/html")\r
283         msg.send()
284
285     def can_update(self, user):
286         from core.models import SitePrivilege
287         _cant_update_fieldName = None
288         if user.is_readonly:
289             return False
290         if user.is_admin:
291             return True
292         # site pis can update
293         site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
294         for site_priv in site_privs:
295             if site_priv.role.role == 'pi':
296                 for fieldName in self.diff.keys():
297                     if fieldName in self.PI_FORBIDDEN_FIELDS:
298                         _cant_update_fieldName = fieldName
299                         return False
300                 return True
301         if (user.id == self.id):
302             for fieldName in self.diff.keys():
303                 if fieldName in self.USER_FORBIDDEN_FIELDS:
304                     _cant_update_fieldName = fieldName
305                     return False
306             return True
307
308         return False
309
310     @staticmethod
311     def select_by_user(user):
312         if user.is_admin:
313             qs = User.objects.all()
314         else:
315             # can see all users at any site where this user has pi role
316             from core.models.site import SitePrivilege
317             site_privs = SitePrivilege.objects.filter(user=user)
318             sites = [sp.site for sp in site_privs if sp.role.role == 'pi']
319             # get site privs of users at these sites
320             site_privs = SitePrivilege.objects.filter(site__in=sites)
321             user_ids = [sp.user.id for sp in site_privs] + [user.id]
322             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
323         return qs
324
325     def save_by_user(self, user, *args, **kwds):
326         if not self.can_update(user):
327             if getattr(self, "_cant_update_fieldName", None) is not None:
328                 raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
329             else:
330                 raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
331
332         self.save(*args, **kwds)
333
334     def delete_by_user(self, user, *args, **kwds):
335         if not self.can_update(user):
336             raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
337         self.delete(*args, **kwds)
338
339 class UserDashboardView(PlCoreBase):
340      user = models.ForeignKey(User, related_name='userdashboardviews')
341      dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
342      order = models.IntegerField(default=0)