blob: 144e0c942d3a6a68b6fb8364de9c82e033c0755c [file] [log] [blame]
Siobhan Tully53437282013-04-26 19:30:27 -04001import os
2import datetime
Scott Baker7ca05ab2014-12-19 13:02:31 -08003import sys
Tony Mack2f5be422015-01-03 14:26:15 -05004import hashlib
Tony Mackc14de8f2013-05-09 21:44:17 -04005from collections import defaultdict
Scott Baker301eee32014-12-19 12:22:51 -08006from django.forms.models import model_to_dict
Siobhan Tully53437282013-04-26 19:30:27 -04007from django.db import models
Tony Mack5b061472014-02-04 07:57:10 -05008from django.db.models import F, Q
Scott Bakerdaca8162015-02-02 14:28:35 -08009from django.utils import timezone
Scott Baker12113342015-02-10 15:44:30 -080010from core.models import PlCoreBase,Site, DashboardView, PlModelMixIn
Siobhan Tully30fd4292013-05-10 08:59:56 -040011from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
Scott Baker9266e6b2013-05-19 15:54:48 -070012from timezones.fields import TimeZoneField
Scott Baker2c3cb642014-05-19 17:55:56 -070013from operator import itemgetter, attrgetter
Scott Bakera36d77e2014-08-29 11:43:23 -070014from django.core.mail import EmailMultiAlternatives
15from core.middleware import get_request
Sapan Bhatiabad67742014-09-04 00:39:19 -040016import model_policy
Scott Bakercbfb6002014-10-03 00:32:37 -070017from django.core.exceptions import PermissionDenied
Siobhan Tully53437282013-04-26 19:30:27 -040018
Scott Bakerfd8c7c42014-10-09 16:16:02 -070019# ------ from plcorebase.py ------
20try:
21 # This is a no-op if observer_disabled is set to 1 in the config file
22 from observer import *
23except:
24 print >> sys.stderr, "import of observer failed! printing traceback and disabling observer:"
25 import traceback
26 traceback.print_exc()
27
28 # guard against something failing
29 def notify_observer(*args, **kwargs):
30 pass
31# ------ ------
32
Siobhan Tully53437282013-04-26 19:30:27 -040033# Create your models here.
Siobhan Tully30fd4292013-05-10 08:59:56 -040034class UserManager(BaseUserManager):
Siobhan Tully53437282013-04-26 19:30:27 -040035 def create_user(self, email, firstname, lastname, password=None):
36 """
37 Creates and saves a User with the given email, date of
38 birth and password.
39 """
40 if not email:
41 raise ValueError('Users must have an email address')
42
43 user = self.model(
Siobhan Tully30fd4292013-05-10 08:59:56 -040044 email=UserManager.normalize_email(email),
Siobhan Tully53437282013-04-26 19:30:27 -040045 firstname=firstname,
Siobhan Tully30fd4292013-05-10 08:59:56 -040046 lastname=lastname,
47 password=password
Siobhan Tully53437282013-04-26 19:30:27 -040048 )
Siobhan Tully30fd4292013-05-10 08:59:56 -040049 #user.set_password(password)
Siobhan Tully53437282013-04-26 19:30:27 -040050 user.is_admin = True
51 user.save(using=self._db)
52 return user
53
54 def create_superuser(self, email, firstname, lastname, password):
55 """
56 Creates and saves a superuser with the given email, date of
57 birth and password.
58 """
59 user = self.create_user(email,
60 password=password,
61 firstname=firstname,
62 lastname=lastname
63 )
64 user.is_admin = True
65 user.save(using=self._db)
66 return user
67
Scott Baker6c684842014-10-17 18:45:00 -070068 def get_queryset(self):
69 parent=super(UserManager, self)
70 if hasattr(parent, "get_queryset"):
71 return parent.get_queryset().filter(deleted=False)
72 else:
73 return parent.get_query_set().filter(deleted=False)
74
75 # deprecated in django 1.7 in favor of get_queryset().
76 def get_query_set(self):
77 return self.get_queryset()
78
Sapan Bhatia5d605ff2014-07-21 20:08:04 -040079class DeletedUserManager(UserManager):
Scott Bakerb08d6562014-09-12 12:57:27 -070080 def get_queryset(self):
Sapan Bhatia5d605ff2014-07-21 20:08:04 -040081 return super(UserManager, self).get_query_set().filter(deleted=True)
Siobhan Tully53437282013-04-26 19:30:27 -040082
Scott Bakerb08d6562014-09-12 12:57:27 -070083 # deprecated in django 1.7 in favor of get_queryset()
84 def get_query_set(self):
85 return self.get_queryset()
86
Scott Baker12113342015-02-10 15:44:30 -080087class User(AbstractBaseUser, PlModelMixIn):
Tony Mack2f5be422015-01-03 14:26:15 -050088 @property
89 def remote_password(self):
90 return hashlib.md5(self.password).hexdigest()[:12]
91
Siobhan Tully53437282013-04-26 19:30:27 -040092 class Meta:
93 app_label = "core"
94
95 email = models.EmailField(
96 verbose_name='email address',
97 max_length=255,
98 unique=True,
99 db_index=True,
100 )
Siobhan Tullyfece0d52013-09-06 12:57:05 -0400101
102 username = models.CharField(max_length=255, default="Something" )
103
Siobhan Tully53437282013-04-26 19:30:27 -0400104 firstname = models.CharField(help_text="person's given name", max_length=200)
105 lastname = models.CharField(help_text="person's surname", max_length=200)
106
107 phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100)
108 user_url = models.URLField(null=True, blank=True)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400109 site = models.ForeignKey(Site, related_name='users', help_text="Site this user will be homed too", null=True)
Tony Mack5cbadf82013-06-10 13:56:07 -0400110 public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
Siobhan Tully53437282013-04-26 19:30:27 -0400111
112 is_active = models.BooleanField(default=True)
Tony Mack4bfcdc82015-01-28 12:03:39 -0500113 is_admin = models.BooleanField(default=False)
Siobhan Tully53437282013-04-26 19:30:27 -0400114 is_staff = models.BooleanField(default=True)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500115 is_readonly = models.BooleanField(default=False)
Scott Baker28e2e3a2015-01-28 16:03:40 -0800116 is_registering = models.BooleanField(default=False)
Siobhan Tully53437282013-04-26 19:30:27 -0400117
Tony Mack0553f282013-06-10 22:54:50 -0400118 created = models.DateTimeField(auto_now_add=True)
119 updated = models.DateTimeField(auto_now=True)
120 enacted = models.DateTimeField(null=True, default=None)
Sapan Bhatia103465d2015-01-23 16:02:09 +0000121 policed = models.DateTimeField(null=True, default=None)
Scott Baker85b98e72015-02-06 00:11:10 -0800122 backend_status = models.CharField(max_length=1024,
Sapan Bhatiad507f432014-04-29 00:41:39 -0400123 default="Provisioning in progress")
Sapan Bhatiabcc18992014-04-29 10:32:14 -0400124 deleted = models.BooleanField(default=False)
Tony Mack0553f282013-06-10 22:54:50 -0400125
Scott Baker9266e6b2013-05-19 15:54:48 -0700126 timezone = TimeZoneField()
127
Scott Baker2c3cb642014-05-19 17:55:56 -0700128 dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
129
Siobhan Tully30fd4292013-05-10 08:59:56 -0400130 objects = UserManager()
Sapan Bhatia5d605ff2014-07-21 20:08:04 -0400131 deleted_objects = DeletedUserManager()
Siobhan Tully53437282013-04-26 19:30:27 -0400132
133 USERNAME_FIELD = 'email'
134 REQUIRED_FIELDS = ['firstname', 'lastname']
135
Scott Baker0119c152014-10-06 22:58:48 -0700136 PI_FORBIDDEN_FIELDS = ["is_admin", "site", "is_staff"]
137 USER_FORBIDDEN_FIELDS = ["is_admin", "is_active", "site", "is_staff", "is_readonly"]
138
Scott Bakercbfb6002014-10-03 00:32:37 -0700139 def __init__(self, *args, **kwargs):
140 super(User, self).__init__(*args, **kwargs)
Scott Baker12113342015-02-10 15:44:30 -0800141 self._initial = self._dict # for PlModelMixIn
Scott Bakercbfb6002014-10-03 00:32:37 -0700142
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500143 def isReadOnlyUser(self):
144 return self.is_readonly
145
Siobhan Tully53437282013-04-26 19:30:27 -0400146 def get_full_name(self):
147 # The user is identified by their email address
148 return self.email
149
150 def get_short_name(self):
151 # The user is identified by their email address
152 return self.email
153
Sapan Bhatia5d605ff2014-07-21 20:08:04 -0400154 def delete(self, *args, **kwds):
155 # so we have something to give the observer
156 purge = kwds.get('purge',False)
Scott Bakerc5b50602014-10-09 16:22:00 -0700157 if purge:
158 del kwds['purge']
Sapan Bhatia5d605ff2014-07-21 20:08:04 -0400159 try:
160 purge = purge or observer_disabled
161 except NameError:
162 pass
163
164 if (purge):
165 super(User, self).delete(*args, **kwds)
166 else:
167 self.deleted = True
168 self.enacted=None
169 self.save(update_fields=['enacted','deleted'])
170
Tony Mackb0d97422013-06-10 09:57:45 -0400171 @property
172 def keyname(self):
173 return self.email[:self.email.find('@')]
174
Siobhan Tully53437282013-04-26 19:30:27 -0400175 def __unicode__(self):
176 return self.email
177
178 def has_perm(self, perm, obj=None):
179 "Does the user have a specific permission?"
180 # Simplest possible answer: Yes, always
181 return True
182
183 def has_module_perms(self, app_label):
184 "Does the user have permissions to view the app `app_label`?"
185 # Simplest possible answer: Yes, always
186 return True
187
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400188 def is_superuser(self):
189 return False
Siobhan Tully53437282013-04-26 19:30:27 -0400190
Scott Baker2c3cb642014-05-19 17:55:56 -0700191 def get_dashboards(self):
192 DEFAULT_DASHBOARDS=["Tenant"]
193
Sapan Bhatia13d2db92014-11-11 21:47:45 -0500194 dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
Scott Baker2c3cb642014-05-19 17:55:56 -0700195 dashboards = [x.dashboardView for x in dashboards]
196
197 if not dashboards:
198 for dashboardName in DEFAULT_DASHBOARDS:
199 dbv = DashboardView.objects.filter(name=dashboardName)
200 if dbv:
201 dashboards.append(dbv[0])
202
203 return dashboards
204
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400205# def get_roles(self):
206# from core.models.site import SitePrivilege
207# from core.models.slice import SliceMembership
208#
209# site_privileges = SitePrivilege.objects.filter(user=self)
210# slice_memberships = SliceMembership.objects.filter(user=self)
211# roles = defaultdict(list)
212# for site_privilege in site_privileges:
213# roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
214# for slice_membership in slice_memberships:
215# roles[slice_membership.role.role_type].append(slice_membership.slice.name)
216# return roles
Siobhan Tully53437282013-04-26 19:30:27 -0400217
Tony Mack53106f32013-04-27 16:43:01 -0400218 def save(self, *args, **kwds):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400219 if not self.id:
Scott Bakera36d77e2014-08-29 11:43:23 -0700220 self.set_password(self.password)
Scott Baker28e2e3a2015-01-28 16:03:40 -0800221 print "XXX", self, self.is_active, self.is_registering
222 if self.is_active and self.is_registering:
223 self.send_temporary_password()
224 self.is_registering=False
Scott Bakera36d77e2014-08-29 11:43:23 -0700225
Siobhan Tullyfece0d52013-09-06 12:57:05 -0400226 self.username = self.email
Scott Bakera36d77e2014-08-29 11:43:23 -0700227 super(User, self).save(*args, **kwds)
228
Scott Bakercbfb6002014-10-03 00:32:37 -0700229 self._initial = self._dict
230
Scott Bakera36d77e2014-08-29 11:43:23 -0700231 def send_temporary_password(self):
232 password = User.objects.make_random_password()
233 self.set_password(password)
234 subject, from_email, to = 'OpenCloud Account Credentials', 'support@opencloud.us', str(self.email)
235 text_content = 'This is an important message.'
Scott Baker51e7d402014-08-29 12:32:46 -0700236 userUrl="http://%s/" % get_request().get_host()
Scott Bakera36d77e2014-08-29 11:43:23 -0700237 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>"""
238 msg = EmailMultiAlternatives(subject,text_content, from_email, [to])
239 msg.attach_alternative(html_content, "text/html")
240 msg.send()
Tony Mack5b061472014-02-04 07:57:10 -0500241
Scott Bakercbfb6002014-10-03 00:32:37 -0700242 def can_update(self, user):
243 from core.models import SitePrivilege
Scott Baker0119c152014-10-06 22:58:48 -0700244 _cant_update_fieldName = None
Tony Mack2a56ce52015-02-09 12:16:03 -0500245 if user.can_update_root():
Scott Bakercbfb6002014-10-03 00:32:37 -0700246 return True
Tony Mack2a56ce52015-02-09 12:16:03 -0500247
Scott Bakercbfb6002014-10-03 00:32:37 -0700248 # site pis can update
249 site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
250 for site_priv in site_privs:
Tony Mack2a56ce52015-02-09 12:16:03 -0500251 if site_priv.role.role == 'admin':
252 return True
Scott Bakercbfb6002014-10-03 00:32:37 -0700253 if site_priv.role.role == 'pi':
Scott Baker0119c152014-10-06 22:58:48 -0700254 for fieldName in self.diff.keys():
255 if fieldName in self.PI_FORBIDDEN_FIELDS:
256 _cant_update_fieldName = fieldName
257 return False
Scott Bakercbfb6002014-10-03 00:32:37 -0700258 return True
Scott Baker0119c152014-10-06 22:58:48 -0700259 if (user.id == self.id):
260 for fieldName in self.diff.keys():
261 if fieldName in self.USER_FORBIDDEN_FIELDS:
262 _cant_update_fieldName = fieldName
263 return False
264 return True
Scott Bakercbfb6002014-10-03 00:32:37 -0700265
266 return False
267
Tony Mack5ff90fc2015-02-08 21:38:41 -0500268 def can_update_root(self):
269 """
270 Return True if user has root (global) write access.
271 """
272 if self.is_readonly:
273 return False
274 if self.is_admin:
275 return True
276
277 return False
278
279 def can_update_deployment(self, deployment):
280 from core.models.site import DeploymentPrivilege
281 if self.can_update_root():
282 return True
283
284 if DeploymentPrivilege.objects.filter(
285 deployment=deployment,
286 user=self,
287 role__role__in=['admin', 'Admin']):
288 return True
289 return False
290
291 def can_update_site(self, site, allow=[]):
292 from core.models.site import SitePrivilege
293 if self.can_update_root():
294 return True
295 if SitePrivilege.objects.filter(
296 site=site, user=self, role__role__in=['admin', 'Admin']+allow):
297 return True
298 return False
299
300 def can_update_slice(self, slice):
301 from core.models.slice import SlicePrivilege
302 if self.can_update_root():
303 return True
304 if self == slice.creator:
305 return True
306 if self.can_update_site(slice.site, allow=['pi']):
307 return True
308
309 if SlicePrivilege.objects.filter(
310 slice=slice, user=self, role__role__in=['admin', 'Admin']):
311 return True
312 return False
313
Tony Mack5b061472014-02-04 07:57:10 -0500314 @staticmethod
315 def select_by_user(user):
316 if user.is_admin:
317 qs = User.objects.all()
318 else:
319 # can see all users at any site where this user has pi role
320 from core.models.site import SitePrivilege
321 site_privs = SitePrivilege.objects.filter(user=user)
Tony Mack83360bb2015-02-11 15:22:57 -0500322 sites = [sp.site for sp in site_privs if sp.role.role in ['Admin', 'admin', 'pi']]
Tony Mack5b061472014-02-04 07:57:10 -0500323 # get site privs of users at these sites
324 site_privs = SitePrivilege.objects.filter(site__in=sites)
Scott Bakera36d77e2014-08-29 11:43:23 -0700325 user_ids = [sp.user.id for sp in site_privs] + [user.id]
Tony Mack5b061472014-02-04 07:57:10 -0500326 qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
Scott Bakera36d77e2014-08-29 11:43:23 -0700327 return qs
Tony Mack5b061472014-02-04 07:57:10 -0500328
Scott Bakercbfb6002014-10-03 00:32:37 -0700329 def save_by_user(self, user, *args, **kwds):
330 if not self.can_update(user):
Scott Baker0119c152014-10-06 22:58:48 -0700331 if getattr(self, "_cant_update_fieldName", None) is not None:
332 raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
333 else:
334 raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
Scott Bakercbfb6002014-10-03 00:32:37 -0700335
336 self.save(*args, **kwds)
337
338 def delete_by_user(self, user, *args, **kwds):
339 if not self.can_update(user):
340 raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
341 self.delete(*args, **kwds)
342
Scott Baker2c3cb642014-05-19 17:55:56 -0700343class UserDashboardView(PlCoreBase):
Sapan Bhatia13d2db92014-11-11 21:47:45 -0500344 user = models.ForeignKey(User, related_name='userdashboardviews')
345 dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
Scott Baker2c3cb642014-05-19 17:55:56 -0700346 order = models.IntegerField(default=0)