remove PLC.Debug.log, use PLC.Logger.logger instead
[plcapi.git] / PLC / Persons.py
index 7f7bd3c..8cd2856 100644 (file)
@@ -4,12 +4,8 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id$
-# $URL$
-#
 
 from types import StringTypes
 
 from types import StringTypes
-from datetime import datetime
 try:
     from hashlib import md5
 except ImportError:
 try:
     from hashlib import md5
 except ImportError:
@@ -20,7 +16,6 @@ import re
 import crypt
 
 from PLC.Faults import *
 import crypt
 
 from PLC.Faults import *
-from PLC.Debug import log
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Table import Row, Table
 from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Table import Row, Table
@@ -50,8 +45,8 @@ class Person(Row):
         'enabled': Parameter(bool, "Has been enabled"),
         'password': Parameter(str, "Account password in crypt() form", max = 254),
         'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
         'enabled': Parameter(bool, "Has been enabled"),
         'password': Parameter(str, "Account password in crypt() form", max = 254),
         'verification_key': Parameter(str, "Reset password key", max = 254, nullok = True),
-       'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = True),
-       'last_updated': Parameter(int, "Date and time of last update", ro = True),
+        'verification_expires': Parameter(int, "Date and time when verification_key expires", nullok = True),
+        'last_updated': Parameter(int, "Date and time of last update", ro = True),
         'date_created': Parameter(int, "Date and time when account was created", ro = True),
         'role_ids': Parameter([int], "List of role identifiers"),
         'roles': Parameter([str], "List of roles"),
         'date_created': Parameter(int, "Date and time when account was created", ro = True),
         'role_ids': Parameter([int], "List of role identifiers"),
         'roles': Parameter([str], "List of roles"),
@@ -63,15 +58,15 @@ class Person(Row):
         'person_tag_ids' : Parameter ([int], "List of tags attached to this person"),
         }
     related_fields = {
         'person_tag_ids' : Parameter ([int], "List of tags attached to this person"),
         }
     related_fields = {
-       'roles': [Mixed(Parameter(int, "Role identifier"),
-                       Parameter(str, "Role name"))],
-       'sites': [Mixed(Parameter(int, "Site identifier"),
-                       Parameter(str, "Site name"))],
-       'keys': [Mixed(Parameter(int, "Key identifier"),
-                      Filter(Key.fields))],
-       'slices': [Mixed(Parameter(int, "Slice identifier"),
-                        Parameter(str, "Slice name"))]
-       }       
+        'roles': [Mixed(Parameter(int, "Role identifier"),
+                        Parameter(str, "Role name"))],
+        'sites': [Mixed(Parameter(int, "Site identifier"),
+                        Parameter(str, "Site name"))],
+        'keys': [Mixed(Parameter(int, "Key identifier"),
+                       Filter(Key.fields))],
+        'slices': [Mixed(Parameter(int, "Slice identifier"),
+                         Parameter(str, "Slice name"))]
+        }
     view_tags_name = "view_person_tags"
     # tags are used by the Add/Get/Update methods to expose tags
     # this is initialized here and updated by the accessors factory
     view_tags_name = "view_person_tags"
     # tags are used by the Add/Get/Update methods to expose tags
     # this is initialized here and updated by the accessors factory
@@ -82,7 +77,7 @@ class Person(Row):
         Validate email address. Stolen from Mailman.
         """
         email = email.lower()
         Validate email address. Stolen from Mailman.
         """
         email = email.lower()
-        invalid_email = PLCInvalidArgument("Invalid e-mail address")
+        invalid_email = PLCInvalidArgument("Invalid e-mail address %s"%email)
 
         if not email:
             raise invalid_email
 
         if not email:
             raise invalid_email
@@ -91,14 +86,14 @@ class Person(Row):
         if not email_re.match(email):
             raise invalid_email
 
         if not email_re.match(email):
             raise invalid_email
 
-               # check only against users on the same peer  
+        # check only against users on the same peer
         if 'peer_id' in self:
             namespace_peer_id = self['peer_id']
         else:
             namespace_peer_id = None
         if 'peer_id' in self:
             namespace_peer_id = self['peer_id']
         else:
             namespace_peer_id = None
-         
-        conflicts = Persons(self.api, {'email':email,'peer_id':namespace_peer_id}) 
-       
+
+        conflicts = Persons(self.api, {'email':email,'peer_id':namespace_peer_id})
+
         for person in conflicts:
             if 'person_id' not in self or self['person_id'] != person['person_id']:
                 raise PLCInvalidArgument, "E-mail address already in use"
         for person in conflicts:
             if 'person_id' not in self or self['person_id'] != person['person_id']:
                 raise PLCInvalidArgument, "E-mail address already in use"
@@ -119,7 +114,7 @@ class Person(Row):
         else:
             # Generate a somewhat unique 8 character salt string
             salt = str(time.time()) + str(Random().random())
         else:
             # Generate a somewhat unique 8 character salt string
             salt = str(time.time()) + str(Random().random())
-            salt = md5(salt).hexdigest()[:8] 
+            salt = md5(salt).hexdigest()[:8]
             return crypt.crypt(password.encode(self.api.encoding), magic + salt + "$")
 
     validate_date_created = Row.validate_timestamp
             return crypt.crypt(password.encode(self.api.encoding), magic + salt + "$")
 
     validate_date_created = Row.validate_timestamp
@@ -147,8 +142,8 @@ class Person(Row):
 
         if 'pi' in self['roles']:
             if set(self['site_ids']).intersection(person['site_ids']):
 
         if 'pi' in self['roles']:
             if set(self['site_ids']).intersection(person['site_ids']):
-                # Can update person is neither a PI or ADMIN
-                return (not (('pi' in person['roles']) or ('admin' in person['roles'])))
+                # non-admin users cannot update a person who is neither a PI or ADMIN
+                return (not set(['pi','admin']).intersection(person['roles']))
 
         return False
 
 
         return False
 
@@ -159,7 +154,7 @@ class Person(Row):
 
         1. We are the person.
         2. We are an admin.
 
         1. We are the person.
         2. We are an admin.
-        3. We are a PI and the person is at one of our sites.
+        3. We are a PI or Tech and the person is at one of our sites.
         """
 
         assert isinstance(person, Person)
         """
 
         assert isinstance(person, Person)
@@ -167,10 +162,10 @@ class Person(Row):
         if self.can_update(person):
             return True
 
         if self.can_update(person):
             return True
 
-        if 'pi' in self['roles']:
+        # pis and techs can see all people on their site
+        if set(['pi','tech']).intersection(self['roles']):
             if set(self['site_ids']).intersection(person['site_ids']):
             if set(self['site_ids']).intersection(person['site_ids']):
-                # Can view people with equal or higher role IDs
-                return 'admin' not in person['roles']
+                return True
 
         return False
 
 
         return False
 
@@ -212,42 +207,42 @@ class Person(Row):
         """
         Update last_updated field with current time
         """
         """
         Update last_updated field with current time
         """
-       
-       assert 'person_id' in self
-       assert self.table_name
-       
-       self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
+
+        assert 'person_id' in self
+        assert self.table_name
+
+        self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
                        " where person_id = %d" % (self['person_id']) )
         self.sync(commit)
 
     def associate_roles(self, auth, field, value):
                        " where person_id = %d" % (self['person_id']) )
         self.sync(commit)
 
     def associate_roles(self, auth, field, value):
-       """
-       Adds roles found in value list to this person (using AddRoleToPerson).
-       Deletes roles not found in value list from this person (using DeleteRoleFromPerson).
-       """
-       
-       assert 'role_ids' in self
-       assert 'person_id' in self
-       assert isinstance(value, list)
-       
-       (role_ids, role_names) = self.separate_types(value)[0:2]
-       
-       # Translate roles into role_ids
-       if role_names:
-           roles = Roles(self.api, role_names).dict('role_id')
-           role_ids += roles.keys()
-       
-       # Add new ids, remove stale ids
-       if self['role_ids'] != role_ids:
-           from PLC.Methods.AddRoleToPerson import AddRoleToPerson
-           from PLC.Methods.DeleteRoleFromPerson import DeleteRoleFromPerson
-           new_roles = set(role_ids).difference(self['role_ids'])
-           stale_roles = set(self['role_ids']).difference(role_ids)
-
-           for new_role in new_roles:
-               AddRoleToPerson.__call__(AddRoleToPerson(self.api), auth, new_role, self['person_id'])
-           for stale_role in stale_roles:
-               DeleteRoleFromPerson.__call__(DeleteRoleFromPerson(self.api), auth, stale_role, self['person_id'])
+        """
+        Adds roles found in value list to this person (using AddRoleToPerson).
+        Deletes roles not found in value list from this person (using DeleteRoleFromPerson).
+        """
+
+        assert 'role_ids' in self
+        assert 'person_id' in self
+        assert isinstance(value, list)
+
+        (role_ids, role_names) = self.separate_types(value)[0:2]
+
+        # Translate roles into role_ids
+        if role_names:
+            roles = Roles(self.api, role_names).dict('role_id')
+            role_ids += roles.keys()
+
+        # Add new ids, remove stale ids
+        if self['role_ids'] != role_ids:
+            from PLC.Methods.AddRoleToPerson import AddRoleToPerson
+            from PLC.Methods.DeleteRoleFromPerson import DeleteRoleFromPerson
+            new_roles = set(role_ids).difference(self['role_ids'])
+            stale_roles = set(self['role_ids']).difference(role_ids)
+
+            for new_role in new_roles:
+                AddRoleToPerson.__call__(AddRoleToPerson(self.api), auth, new_role, self['person_id'])
+            for stale_role in stale_roles:
+                DeleteRoleFromPerson.__call__(DeleteRoleFromPerson(self.api), auth, stale_role, self['person_id'])
 
 
     def associate_sites(self, auth, field, value):
 
 
     def associate_sites(self, auth, field, value):
@@ -256,7 +251,7 @@ class Person(Row):
         Deletes person from site not found in value list (using DeletePersonFromSite).
         """
 
         Deletes person from site not found in value list (using DeletePersonFromSite).
         """
 
-       from PLC.Sites import Sites
+        from PLC.Sites import Sites
 
         assert 'site_ids' in self
         assert 'person_id' in self
 
         assert 'site_ids' in self
         assert 'person_id' in self
@@ -283,44 +278,44 @@ class Person(Row):
 
 
     def associate_keys(self, auth, field, value):
 
 
     def associate_keys(self, auth, field, value):
-       """
+        """
         Deletes key_ids not found in value list (using DeleteKey).
         Adds key if key_fields w/o key_id is found (using AddPersonKey).
         Updates key if key_fields w/ key_id is found (using UpdateKey).
         """
         Deletes key_ids not found in value list (using DeleteKey).
         Adds key if key_fields w/o key_id is found (using AddPersonKey).
         Updates key if key_fields w/ key_id is found (using UpdateKey).
         """
-       assert 'key_ids' in self
-       assert 'person_id' in self
-       assert isinstance(value, list)
-       
-       (key_ids, blank, keys) = self.separate_types(value)
-       
-       if self['key_ids'] != key_ids:
-           from PLC.Methods.DeleteKey import DeleteKey
-           stale_keys = set(self['key_ids']).difference(key_ids)
-       
-           for stale_key in stale_keys:
-               DeleteKey.__call__(DeleteKey(self.api), auth, stale_key) 
-
-       if keys:
-           from PLC.Methods.AddPersonKey import AddPersonKey
-           from PLC.Methods.UpdateKey import UpdateKey         
-           updated_keys = filter(lambda key: 'key_id' in key, keys)
-           added_keys = filter(lambda key: 'key_id' not in key, keys)
-               
-           for key in added_keys:
-               AddPersonKey.__call__(AddPersonKey(self.api), auth, self['person_id'], key)
-           for key in updated_keys:
-               key_id = key.pop('key_id')
-               UpdateKey.__call__(UpdateKey(self.api), auth, key_id, key)
-                 
-       
+        assert 'key_ids' in self
+        assert 'person_id' in self
+        assert isinstance(value, list)
+
+        (key_ids, blank, keys) = self.separate_types(value)
+
+        if self['key_ids'] != key_ids:
+            from PLC.Methods.DeleteKey import DeleteKey
+            stale_keys = set(self['key_ids']).difference(key_ids)
+
+            for stale_key in stale_keys:
+                DeleteKey.__call__(DeleteKey(self.api), auth, stale_key)
+
+        if keys:
+            from PLC.Methods.AddPersonKey import AddPersonKey
+            from PLC.Methods.UpdateKey import UpdateKey
+            updated_keys = filter(lambda key: 'key_id' in key, keys)
+            added_keys = filter(lambda key: 'key_id' not in key, keys)
+
+            for key in added_keys:
+                AddPersonKey.__call__(AddPersonKey(self.api), auth, self['person_id'], key)
+            for key in updated_keys:
+                key_id = key.pop('key_id')
+                UpdateKey.__call__(UpdateKey(self.api), auth, key_id, key)
+
+
     def associate_slices(self, auth, field, value):
         """
         Adds person to slices found in value list (using AddPersonToSlice).
         Deletes person from slices found in value list (using DeletePersonFromSlice).
         """
 
     def associate_slices(self, auth, field, value):
         """
         Adds person to slices found in value list (using AddPersonToSlice).
         Deletes person from slices found in value list (using DeletePersonFromSlice).
         """
 
-       from PLC.Slices import Slices
+        from PLC.Slices import Slices
 
         assert 'slice_ids' in self
         assert 'person_id' in self
 
         assert 'slice_ids' in self
         assert 'person_id' in self
@@ -344,7 +339,7 @@ class Person(Row):
                 AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, self['person_id'], new_slice)
             for stale_slice in stale_slices:
                 DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, self['person_id'], stale_slice)
                 AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, self['person_id'], new_slice)
             for stale_slice in stale_slices:
                 DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, self['person_id'], stale_slice)
-    
+
 
     def delete(self, commit = True):
         """
 
     def delete(self, commit = True):
         """
@@ -363,7 +358,14 @@ class Person(Row):
 
         # Mark as deleted
         self['deleted'] = True
 
         # Mark as deleted
         self['deleted'] = True
-        self.sync(commit)
+
+        # delete will fail if timestamp fields aren't validated, so lets remove them
+        for field in ['verification_expires', 'date_created', 'last_updated']:
+            if field in self:
+                self.pop(field)
+
+        # don't validate, so duplicates can be consistently removed
+        self.sync(commit, validate=False)
 
 class Persons(Table):
     """
 
 class Persons(Table):
     """
@@ -378,7 +380,7 @@ class Persons(Table):
         for tagname in self.tag_columns:
             view= "%s left join %s using (%s)"%(view,Person.tagvalue_view_name(tagname),
                                                 Person.primary_key)
         for tagname in self.tag_columns:
             view= "%s left join %s using (%s)"%(view,Person.tagvalue_view_name(tagname),
                                                 Person.primary_key)
-            
+
         sql = "SELECT %s FROM %s WHERE deleted IS False" % \
             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
         sql = "SELECT %s FROM %s WHERE deleted IS False" % \
             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
 
@@ -390,13 +392,14 @@ class Persons(Table):
                 person_filter = Filter(Person.fields, {'person_id': ints, 'email': strs})
                 sql += " AND (%s) %s" % person_filter.sql(api, "OR")
             elif isinstance(person_filter, dict):
                 person_filter = Filter(Person.fields, {'person_id': ints, 'email': strs})
                 sql += " AND (%s) %s" % person_filter.sql(api, "OR")
             elif isinstance(person_filter, dict):
-                person_filter = Filter(Person.fields, person_filter)
+                allowed_fields=dict(Person.fields.items()+Person.tags.items())
+                person_filter = Filter(allowed_fields, person_filter)
                 sql += " AND (%s) %s" % person_filter.sql(api, "AND")
             elif isinstance (person_filter, StringTypes):
                 sql += " AND (%s) %s" % person_filter.sql(api, "AND")
             elif isinstance (person_filter, StringTypes):
-                person_filter = Filter(Person.fields, {'email':[person_filter]})
+                person_filter = Filter(Person.fields, {'email':person_filter})
                 sql += " AND (%s) %s" % person_filter.sql(api, "AND")
                 sql += " AND (%s) %s" % person_filter.sql(api, "AND")
-            elif isinstance (person_filter, int):
-                person_filter = Filter(Person.fields, {'person_id':[person_filter]})
+            elif isinstance (person_filter, (int, long)):
+                person_filter = Filter(Person.fields, {'person_id':person_filter})
                 sql += " AND (%s) %s" % person_filter.sql(api, "AND")
             else:
                 raise PLCInvalidArgument, "Wrong person filter %r"%person_filter
                 sql += " AND (%s) %s" % person_filter.sql(api, "AND")
             else:
                 raise PLCInvalidArgument, "Wrong person filter %r"%person_filter