fix tenant view temporary password email, display message if user tries to register...
[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=False)
159     is_staff = models.BooleanField(default=True)
160     is_readonly = models.BooleanField(default=False)
161     is_registering = models.BooleanField(default=False)
162
163     created = models.DateTimeField(auto_now_add=True)
164     updated = models.DateTimeField(auto_now=True)
165     enacted = models.DateTimeField(null=True, default=None)
166     policed = models.DateTimeField(null=True, default=None)
167     backend_status = models.CharField(max_length=140,
168                                       default="Provisioning in progress")
169     deleted = models.BooleanField(default=False)
170
171     timezone = TimeZoneField()
172
173     dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
174
175     objects = UserManager()
176     deleted_objects = DeletedUserManager()
177
178     USERNAME_FIELD = 'email'
179     REQUIRED_FIELDS = ['firstname', 'lastname']
180
181     PI_FORBIDDEN_FIELDS = ["is_admin", "site", "is_staff"]
182     USER_FORBIDDEN_FIELDS = ["is_admin", "is_active", "site", "is_staff", "is_readonly"]
183
184     def __init__(self, *args, **kwargs):
185         super(User, self).__init__(*args, **kwargs)
186         self._initial = self._dict # for DiffModelMixIn
187
188     def isReadOnlyUser(self):
189         return self.is_readonly
190
191     def get_full_name(self):
192         # The user is identified by their email address
193         return self.email
194
195     def get_short_name(self):
196         # The user is identified by their email address
197         return self.email
198
199     def delete(self, *args, **kwds):
200         # so we have something to give the observer
201         purge = kwds.get('purge',False)
202         if purge:
203             del kwds['purge']
204         try:
205             purge = purge or observer_disabled
206         except NameError:
207             pass
208             
209         if (purge):
210             super(User, self).delete(*args, **kwds)
211         else:
212             self.deleted = True
213             self.enacted=None
214             self.save(update_fields=['enacted','deleted'])
215
216     @property
217     def keyname(self):
218         return self.email[:self.email.find('@')]
219
220     def __unicode__(self):
221         return self.email
222
223     def has_perm(self, perm, obj=None):
224         "Does the user have a specific permission?"
225         # Simplest possible answer: Yes, always
226         return True
227
228     def has_module_perms(self, app_label):
229         "Does the user have permissions to view the app `app_label`?"
230         # Simplest possible answer: Yes, always
231         return True
232
233     def is_superuser(self):
234         return False
235
236     def get_dashboards(self):
237         DEFAULT_DASHBOARDS=["Tenant"]
238
239         dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
240         dashboards = [x.dashboardView for x in dashboards]
241
242         if not dashboards:
243             for dashboardName in DEFAULT_DASHBOARDS:
244                 dbv = DashboardView.objects.filter(name=dashboardName)
245                 if dbv:
246                     dashboards.append(dbv[0])
247
248         return dashboards
249
250 #    def get_roles(self):
251 #        from core.models.site import SitePrivilege
252 #        from core.models.slice import SliceMembership
253 #
254 #        site_privileges = SitePrivilege.objects.filter(user=self)
255 #        slice_memberships = SliceMembership.objects.filter(user=self)
256 #        roles = defaultdict(list)
257 #        for site_privilege in site_privileges:
258 #            roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
259 #        for slice_membership in slice_memberships:
260 #            roles[slice_membership.role.role_type].append(slice_membership.slice.name)
261 #        return roles   
262
263     def save(self, *args, **kwds):
264         if not self.id:
265             self.set_password(self.password)
266         print "XXX", self, self.is_active, self.is_registering
267         if self.is_active and self.is_registering:
268             self.send_temporary_password()\r
269             self.is_registering=False\r
270 \r
271         self.username = self.email
272         super(User, self).save(*args, **kwds)
273
274         self._initial = self._dict
275
276     def send_temporary_password(self):
277         password = User.objects.make_random_password()
278         self.set_password(password)\r
279         subject, from_email, to = 'OpenCloud Account Credentials', 'support@opencloud.us', str(self.email)\r
280         text_content = 'This is an important message.'\r
281         userUrl="http://%s/" % get_request().get_host()\r
282         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
283         msg = EmailMultiAlternatives(subject,text_content, from_email, [to])\r
284         msg.attach_alternative(html_content, "text/html")\r
285         msg.send()
286
287     def can_update(self, user):
288         from core.models import SitePrivilege
289         _cant_update_fieldName = None
290         if user.is_readonly:
291             return False
292         if user.is_admin:
293             return True
294         # site pis can update
295         site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
296         for site_priv in site_privs:
297             if site_priv.role.role == 'pi':
298                 for fieldName in self.diff.keys():
299                     if fieldName in self.PI_FORBIDDEN_FIELDS:
300                         _cant_update_fieldName = fieldName
301                         return False
302                 return True
303         if (user.id == self.id):
304             for fieldName in self.diff.keys():
305                 if fieldName in self.USER_FORBIDDEN_FIELDS:
306                     _cant_update_fieldName = fieldName
307                     return False
308             return True
309
310         return False
311
312     @staticmethod
313     def select_by_user(user):
314         if user.is_admin:
315             qs = User.objects.all()
316         else:
317             # can see all users at any site where this user has pi role
318             from core.models.site import SitePrivilege
319             site_privs = SitePrivilege.objects.filter(user=user)
320             sites = [sp.site for sp in site_privs if sp.role.role == 'pi']
321             # get site privs of users at these sites
322             site_privs = SitePrivilege.objects.filter(site__in=sites)
323             user_ids = [sp.user.id for sp in site_privs] + [user.id]
324             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
325         return qs
326
327     def save_by_user(self, user, *args, **kwds):
328         if not self.can_update(user):
329             if getattr(self, "_cant_update_fieldName", None) is not None:
330                 raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
331             else:
332                 raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
333
334         self.save(*args, **kwds)
335
336     def delete_by_user(self, user, *args, **kwds):
337         if not self.can_update(user):
338             raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
339         self.delete(*args, **kwds)
340
341 class UserDashboardView(PlCoreBase):
342      user = models.ForeignKey(User, related_name='userdashboardviews')
343      dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
344      order = models.IntegerField(default=0)