Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
[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     # ---- end copy stuff from DiffModelMixin ----
116
117     @property
118     def remote_password(self):
119         return hashlib.md5(self.password).hexdigest()[:12]
120
121     class Meta:
122         app_label = "core"
123
124     email = models.EmailField(
125         verbose_name='email address',
126         max_length=255,
127         unique=True,
128         db_index=True,
129     )
130
131     username = models.CharField(max_length=255, default="Something" )
132
133     firstname = models.CharField(help_text="person's given name", max_length=200)
134     lastname = models.CharField(help_text="person's surname", max_length=200)
135
136     phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100)
137     user_url = models.URLField(null=True, blank=True)
138     site = models.ForeignKey(Site, related_name='users', help_text="Site this user will be homed too", null=True)
139     public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
140
141     is_active = models.BooleanField(default=True)
142     is_admin = models.BooleanField(default=True)
143     is_staff = models.BooleanField(default=True)
144     is_readonly = models.BooleanField(default=False)
145
146     created = models.DateTimeField(auto_now_add=True)
147     updated = models.DateTimeField(auto_now=True)
148     enacted = models.DateTimeField(null=True, default=None)
149     backend_status = models.CharField(max_length=140,
150                                       default="Provisioning in progress")
151     deleted = models.BooleanField(default=False)
152
153     timezone = TimeZoneField()
154
155     dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
156
157     objects = UserManager()
158     deleted_objects = DeletedUserManager()
159
160     USERNAME_FIELD = 'email'
161     REQUIRED_FIELDS = ['firstname', 'lastname']
162
163     PI_FORBIDDEN_FIELDS = ["is_admin", "site", "is_staff"]
164     USER_FORBIDDEN_FIELDS = ["is_admin", "is_active", "site", "is_staff", "is_readonly"]
165
166     def __init__(self, *args, **kwargs):
167         super(User, self).__init__(*args, **kwargs)
168         self._initial = self._dict # for DiffModelMixIn
169
170     def isReadOnlyUser(self):
171         return self.is_readonly
172
173     def get_full_name(self):
174         # The user is identified by their email address
175         return self.email
176
177     def get_short_name(self):
178         # The user is identified by their email address
179         return self.email
180
181     def delete(self, *args, **kwds):
182         # so we have something to give the observer
183         purge = kwds.get('purge',False)
184         if purge:
185             del kwds['purge']
186         try:
187             purge = purge or observer_disabled
188         except NameError:
189             pass
190             
191         if (purge):
192             super(User, self).delete(*args, **kwds)
193         else:
194             self.deleted = True
195             self.enacted=None
196             self.save(update_fields=['enacted','deleted'])
197
198     @property
199     def keyname(self):
200         return self.email[:self.email.find('@')]
201
202     def __unicode__(self):
203         return self.email
204
205     def has_perm(self, perm, obj=None):
206         "Does the user have a specific permission?"
207         # Simplest possible answer: Yes, always
208         return True
209
210     def has_module_perms(self, app_label):
211         "Does the user have permissions to view the app `app_label`?"
212         # Simplest possible answer: Yes, always
213         return True
214
215     def is_superuser(self):
216         return False
217
218     def get_dashboards(self):
219         DEFAULT_DASHBOARDS=["Tenant"]
220
221         dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
222         dashboards = [x.dashboardView for x in dashboards]
223
224         if not dashboards:
225             for dashboardName in DEFAULT_DASHBOARDS:
226                 dbv = DashboardView.objects.filter(name=dashboardName)
227                 if dbv:
228                     dashboards.append(dbv[0])
229
230         return dashboards
231
232 #    def get_roles(self):
233 #        from core.models.site import SitePrivilege
234 #        from core.models.slice import SliceMembership
235 #
236 #        site_privileges = SitePrivilege.objects.filter(user=self)
237 #        slice_memberships = SliceMembership.objects.filter(user=self)
238 #        roles = defaultdict(list)
239 #        for site_privilege in site_privileges:
240 #            roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
241 #        for slice_membership in slice_memberships:
242 #            roles[slice_membership.role.role_type].append(slice_membership.slice.name)
243 #        return roles   
244
245     def save(self, *args, **kwds):
246         if not self.id:
247             self.set_password(self.password)
248         if self.is_active:
249             if self.password=="!":\r
250                 self.send_temporary_password()\r
251 \r
252         self.username = self.email
253         super(User, self).save(*args, **kwds)
254
255         self._initial = self._dict
256
257     def send_temporary_password(self):
258         password = User.objects.make_random_password()
259         self.set_password(password)\r
260         subject, from_email, to = 'OpenCloud Account Credentials', 'support@opencloud.us', str(self.email)\r
261         text_content = 'This is an important message.'\r
262         userUrl="http://%s/" % get_request().get_host()\r
263         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
264         msg = EmailMultiAlternatives(subject,text_content, from_email, [to])\r
265         msg.attach_alternative(html_content, "text/html")\r
266         msg.send()
267
268     def can_update(self, user):
269         from core.models import SitePrivilege
270         _cant_update_fieldName = None
271         if user.is_readonly:
272             return False
273         if user.is_admin:
274             return True
275         # site pis can update
276         site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
277         for site_priv in site_privs:
278             if site_priv.role.role == 'pi':
279                 for fieldName in self.diff.keys():
280                     if fieldName in self.PI_FORBIDDEN_FIELDS:
281                         _cant_update_fieldName = fieldName
282                         return False
283                 return True
284         if (user.id == self.id):
285             for fieldName in self.diff.keys():
286                 if fieldName in self.USER_FORBIDDEN_FIELDS:
287                     _cant_update_fieldName = fieldName
288                     return False
289             return True
290
291         return False
292
293     @staticmethod
294     def select_by_user(user):
295         if user.is_admin:
296             qs = User.objects.all()
297         else:
298             # can see all users at any site where this user has pi role
299             from core.models.site import SitePrivilege
300             site_privs = SitePrivilege.objects.filter(user=user)
301             sites = [sp.site for sp in site_privs if sp.role.role == 'pi']
302             # get site privs of users at these sites
303             site_privs = SitePrivilege.objects.filter(site__in=sites)
304             user_ids = [sp.user.id for sp in site_privs] + [user.id]
305             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
306         return qs
307
308     def save_by_user(self, user, *args, **kwds):
309         if not self.can_update(user):
310             if getattr(self, "_cant_update_fieldName", None) is not None:
311                 raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
312             else:
313                 raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
314
315         self.save(*args, **kwds)
316
317     def delete_by_user(self, user, *args, **kwds):
318         if not self.can_update(user):
319             raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
320         self.delete(*args, **kwds)
321
322 class UserDashboardView(PlCoreBase):
323      user = models.ForeignKey(User, related_name='userdashboardviews')
324      dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
325      order = models.IntegerField(default=0)