root/usermanagerplugin/0.11/tracusermanager/api.py

Revision 4961, 20.1 kB (checked in by cbalan, 3 years ago)

UserManagerPlugin: - Working on Postgresql also

  • Property svn:executable set to *
Line 
1 # -*- coding: iso-8859-1 -*-
2 #
3 # Copyright 2008 Optaros, Inc.
4 #
5 try:
6     import threading
7 except ImportError:
8     import dummy_threading as threading
9 import time
10
11 import traceback
12 import time
13 from StringIO import StringIO
14
15 from trac.core import *
16 from trac.config import *
17 from trac.env import IEnvironmentSetupParticipant
18 from trac.util.translation import _
19 from trac.web.chrome import ITemplateProvider
20
21 class IUserAttributeChangeListener(Interface):
22
23     def user_attribute_changed(username, attribute, old_value, new_value):
24         """
25         Called when a user attribute changes.
26         """
27
28 class User(object):
29     """Object representing a user"""
30    
31     def __init__(self, username=None, user_manager=None, **attr):
32         self.username = username
33         self.user_manager = user_manager
34         self.default_attributes = attr
35         self.changes = {}
36         self.deleted = {}
37        
38     def exists(self):
39         """Return true if user exists."""
40         if self.store:
41             return len(self.user_manager.search_users(self.username))>0
42         return False
43
44     def __getitem__(self, attribute):
45         """Gets user attribute.
46         
47         @param name: str
48         """
49         if self.changes.has_key(attribute):
50             return self.changes[attribute]
51         if self.user_manager:
52             value = self.user_manager.get_user_attribute(self.username, attribute)
53             if value:
54                 return value
55         if self.default_attributes.has_key(attribute):
56             return self.default_attributes[attribute]     
57            
58         return None
59        
60     def __setitem__(self, attribute, value):
61         """Sets user attribute.
62         
63         @param name: str
64         @param value: str
65         """
66         self.changes[attribute] = value
67    
68     def __delitem__(self, attribute):
69         """Removes user attribute.
70         
71         @param name: str
72         """
73         self.deleted[attribute] = 1
74    
75     def save(self):
76         return self.user_manager.save_user(self)
77        
78 class IUserStore(Interface):
79    
80     def get_supported_user_operations(username):
81         """Returns supported operations
82         in form of [operation, ].
83         
84         @return: list"""
85    
86     def execute_user_operation(operation, user, operation_arguments):
87         """Executes user operation.
88         
89         @param operation: str
90         @param user: tracusermanager.api.User
91         @param operation_arguments: dict"""
92    
93     def create_user(username):
94         """Creates an user.
95         Returns True if succeeded.
96         
97         @param user: str
98         @return: bool"""
99        
100     def search_users(user_pattern):
101         """Returns a list of user names that matches user_pattern.
102         
103         @param user_pattern: str
104         @return: list"""
105    
106     def delete_user(username):
107         """Deletes an user.
108         Returns True if the delete operation succeded.
109         
110         @param user: str
111         @return: bool"""
112
113 class IAttributeProvider(Interface):
114    
115     def get_user_attribute(username, attribute):
116         """Returns user's attributes.
117         
118         @param username: str
119         @param attribute: str"""
120    
121     def set_user_attribute(username, attribute, value):
122         """Sets user's attribute value.
123                 
124         @param username: str
125         @param attribute: str
126         @param value: str
127         @return: bool
128         """
129    
130     def delete_user_attribute(username, attribute):
131         """Removes user attribute
132         
133         @param username: str
134         @param attribute: str
135         @return: bool
136         """
137    
138     def get_usernames_with_attributes(attributes_dict):
139         """Returns a list of usernames
140         that have "user[attributes_dict.keys] like attributes_dict.values".
141         
142         @param attributes_dict: str
143         @return: list"""
144
145 class UserManager(Component):
146    
147     implements(ITemplateProvider)
148    
149     user_store = ExtensionOption('user_manager', 'user_store', IUserStore,
150                             'SessionUserStore',
151         """Name of the component implementing `IUserStore`, which is used
152         for storing project's team""")
153
154     attribute_provider = ExtensionOption('user_manager', 'attribute_provider', IAttributeProvider,
155                             'SessionAttributeProvider',
156         """Name of the component implementing `IAttributeProvider`, which is used
157         for storing user attributes""")
158
159     change_listeners = ExtensionPoint(IUserAttributeChangeListener)
160    
161     # Public methods
162     def get_user(self, username):
163         return User(username, self)
164
165     def get_active_users(self):
166         """Returns a list with the current users(team)
167         in form of [tracusermanager.api.User, ]
168         
169         @return: list"""
170         return self.search_users()
171    
172     def save_user(self, user): 
173         for attribute, value in user.changes.items():
174             self.set_user_attribute(user.username, attribute, value)
175         for attribute in user.deleted.keys():
176             self.delete_user_attribute(user.username, attribute)
177         return True
178        
179     # Public methods : IUserStore
180     def get_supported_user_operations(self, username):
181         return self.user_store.get_supported_user_operations(username)
182    
183     def execute_user_operation(operation, user, operation_arguments):
184         return self.user_store.execute_user_operation(operation, user, operation_arguments)
185    
186     def create_user(self, user):
187         if user.username is None:
188             raise TracError(_("Username must be specified in order to create it"))
189         if self.user_store.create_user(user.username):
190             user_attributes = user.default_attributes
191             user_attributes.update(user.changes)
192             for attribute, value in user_attributes.items():
193                 self.set_user_attribute(user.username, attribute, value)
194             return True
195         return False
196        
197     def search_users(self, user_templates=[]):
198         """Returns a list of users matching
199         user_templates."""
200         search_result = {}
201         templates=[]
202        
203         if isinstance(user_templates, str):
204             templates = [User(user_templates)]
205         elif not isinstance(user_templates, list):
206             templates = [user_templates]
207         else:
208             templates = user_templates
209        
210         if len(templates)==0:
211             # no filters are passed so we'll return all users
212             return [ self.get_user(username)
213                         for username in self.user_store.search_users()]
214        
215         # search
216         search_candidates = []
217         for user_template in templates:
218             # by username
219             if user_template.username is not None:
220                 search_result.update([ (username,self.get_user(username))
221                                           for username in self.user_store.search_users(user_template.username)])
222             else:
223                 search_attrs = user_template.default_attributes.copy()
224                 search_attrs.update(user_template.changes.copy())
225                 search_attrs.update(enabled='1')           
226                 search_result.update([ (username, self.get_user(username))
227                                         for username in self.attribute_provider.get_usernames_with_attributes(search_attrs)])
228                
229         return search_result.values()
230        
231     def delete_user(self, username):
232         try:
233             from acct_mgr.api import AccountManager
234             if AccountManager(self.env).has_user(username):
235                 AccountManager(self.env).delete_user(username)
236         except Exception, e:
237             self.log.error("Unable to delete user's authentication details")
238         return self.user_store.delete_user(username)
239
240     # Public methods : IAttributeStore
241     def get_user_attribute(self, username, attribute):
242         return self.attribute_provider.get_user_attribute(username, attribute)
243
244     def set_user_attribute(self, username, attribute, value):
245         oldval = self.attribute_provider.get_user_attribute(username, attribute)
246         retval = self.attribute_provider.set_user_attribute(username, attribute, value)
247         for listener in self.change_listeners:
248             listener.user_attribute_changed(username, attribute, oldval, value)
249         return retval
250
251     def delete_user_attribute(self, username, attribute):
252         oldval = self.attribute_provider.get_user_attribute(username, attribute)
253         retval = self.attribute_provider.delete_user_attribute(username, attribute)
254         for listener in self.change_listeners:
255             listener.user_attribute_changed(username, attribute, oldval, None)
256         return self.attribute_provider.delete_user_attribute(username, attribute)
257    
258     def get_usernames_with_attributes(self, attribute_dict):
259         return self.attribute_provider.get_usernames_with_attributes(attribute_dict)
260    
261     # ITemplateProvider methods
262     def get_templates_dirs(self):
263         from pkg_resources import resource_filename
264         return [resource_filename('tracusermanager', 'templates')]
265
266     def get_htdocs_dirs(self):
267         from pkg_resources import resource_filename
268         return [('tracusermanager', resource_filename(__name__, 'htdocs'))]
269
270
271
272 class SessionUserStore(Component):
273    
274     implements(IUserStore)
275    
276     def get_supported_user_operations(self, username):
277         return []
278        
279     def execute_user_operation(self, operation, user, operation_arguments):
280         return True
281    
282     def create_user(self, username):
283         db = self.env.get_db_cnx()
284        
285         cursor = db.cursor()
286         try:
287             cursor.execute("INSERT INTO session (sid, last_visit, authenticated)"
288                            " VALUES(%s,%s,1)", [username, int(time.time())])
289             db.commit()
290         except Exception, e:
291             db.rollback()
292             self.log.debug("Session for %s exists, no need to re-create it."%(username))
293            
294         cursor = db.cursor()
295         try:
296             # clean up
297             cursor.execute("DELETE "
298                                "FROM session_attribute "
299                                "WHERE sid=%s and authenticated=1 and name='enabled'", [username])
300            
301             # register active user
302             cursor.execute("INSERT "
303                                "INTO session_attribute "
304                                "(sid,authenticated,name,value) "
305                                "VALUES(%s,1,'enabled','1')", [username])
306             # and .. commit
307             db.commit()
308             return True
309        
310         except Exception, e:
311             db.rollback()
312             out = StringIO()
313             traceback.print_exc(file=out)
314             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
315             raise TracError(_("Unable to create user [%s].")%(username))
316             return False
317        
318     def search_users(self, username_pattern=None):
319         db = self.env.get_db_cnx()
320         cursor = db.cursor()
321         search_result = []
322        
323         try:
324             if username_pattern is None:
325                 cursor.execute("SELECT sid FROM session_attribute "
326                                    "WHERE name='enabled' and value='1'")
327             else:
328                 cursor.execute("SELECT sid FROM session_attribute "
329                                "WHERE sid like %s "
330                                    "and name='enabled' "
331                                    "and value='1'", (username_pattern,))
332             for username, in cursor:
333                 search_result.append(username)
334            
335            
336         except Exception, e:
337             out = StringIO()
338             traceback.print_exc(file=out)
339             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
340             raise TracError(_("Unable to find username from pattern [%s].")%(username_pattern))
341        
342         return search_result
343            
344     def delete_user(self, username):
345        
346         db = self.env.get_db_cnx()
347         cursor = db.cursor()
348        
349         try:
350             cursor.execute("DELETE "
351                                "FROM session_attribute "
352                                "WHERE sid=%s and name='enabled'", (username,))
353             db.commit()
354             return True
355        
356         except Exception, e:
357             out = StringIO()
358             traceback.print_exc(file=out)
359             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
360             raise TracError(_("Unable to delete user [%s].")%(username))
361             return False
362        
363 class SessionAttributeProvider(Component):
364     implements(IAttributeProvider)
365    
366     def get_user_attribute(self, username, attribute):
367         db = self.env.get_db_cnx()
368         cursor = db.cursor()
369         try:
370             cursor.execute("SELECT value "
371                            "FROM session_attribute "
372                            "WHERE sid=%s and name=%s ", (username, attribute))
373            
374             _result = list(cursor)
375             if len(_result)>0:
376                 return _result[0][0]
377         except Exception, e:
378             out = StringIO()
379             traceback.print_exc(file=out)
380             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
381             raise TracError(_("Unable to load attribute %s for user [%s].")%(attribute, username))   
382                
383         return None
384    
385     def set_user_attribute(self, username, attribute, value):
386         """Sets user's attribute value.
387                 
388         @param username: str
389         @param attribute: str
390         @param value: str
391         @return: bool
392         """
393         db = self.env.get_db_cnx()
394         cursor = db.cursor()
395        
396         try:
397            
398             cursor.execute("DELETE FROM session_attribute "
399                                "WHERE sid=%s and name=%s", (username, attribute))
400            
401             cursor.execute("INSERT INTO session_attribute "
402                                "(sid, authenticated, name, value) VALUES (%s, 1, %s, %s)",
403                                (username, attribute, value))
404             db.commit()
405            
406             return True
407         except Exception, e:
408             out = StringIO()
409             traceback.print_exc(file=out)
410             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
411             raise TracError("Unable to set attribute %s for user [%s]."%(attribute, username))
412    
413         return False
414    
415     def delete_user_attribute(self, username, attribute):
416         """Removes user attribute.
417         
418         @param username: str
419         @param attribute: str
420         @return: bool
421         """
422         db = self.env.get_db_cnx()
423         cursor = db.cursor()
424        
425         try:
426            
427             cursor.execute("DELETE FROM session_attribute "
428                                "WHERE sid=%s and name=%s", (username, attribute))           
429             db.commit()
430            
431             return True
432         except Exception, e:
433             out = StringIO()
434             traceback.print_exc(file=out)
435             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
436             raise TracError("Unable to delete attribute %s for user [%s]."%(attribute, username))
437        
438         return False
439
440     def get_usernames_with_attributes(self, attributes_dict=None):
441         """ Returns all usernames matching attributes_dict.
442         
443         Example: self.get_usernames_with_attributes(dict(name='John%', email='%'))
444         
445         @param attributes_dict: dict
446         @return: list
447         """
448         db = self.env.get_db_cnx()
449         cursor = db.cursor()
450        
451         try:                     
452             if attributes_dict is None:
453                 cursor.execute("SELECT sid FROM session_attribute WHERE name='enabled'")
454             else:
455                 """ The following line executes a query that should look like this:
456                 
457                     #for attributes_dict = dict(name='John%', email='%@exemple.com')):
458                         SELECT  sid
459                         FROM session_attribute
460                         WHERE name='name' AND value like 'John%'
461                            OR name='email' AND value like '%@exemple.com'
462                         GROUP BY sid
463                         HAVING count(*)=2           
464                 """
465                
466                 # dict to list attr_dict = { k1:v1, k2:v2, ... } -> [k1,v1,k2,v2..., len(attr_dict)]
467                 attributes_list=[]
468                 for k, v in attributes_dict.items():
469                     attributes_list.append(k.startswith('NOT_') and k[4:] or k)
470                     attributes_list.append(v)
471                    
472                 attributes_list.append(len(attributes_dict))
473                
474                 def _get_condition(k,v):
475                     return "name=%s AND value " + (k.startswith('NOT_') and 'NOT' or '') + " LIKE %s"
476                    
477                 cursor.execute("SELECT sid"
478                                " FROM session_attribute"
479                                " WHERE " + " OR ".join([ _get_condition(k,v) for k,v in attributes_dict.items()]) +
480                                " GROUP BY sid"
481                                " HAVING count(*)=%s", attributes_list)
482             return [id for id, in cursor]
483         except Exception, e:
484             out = StringIO()
485             traceback.print_exc(file=out)
486             self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
487             return []
488
489 class CachedSessionAttributeProvider(SessionAttributeProvider):
490     CACHE_UPDATE_INTERVAL = 50
491    
492     def __init__(self):
493         self._attribute_cache = {}
494         self._attribute_cache_last_update = {}
495         self._attribute_cache_lock = threading.RLock()
496
497     def _update_cache(self, username, force=False):
498         self._attribute_cache_lock.acquire()
499         try:
500             now = time.time()
501             if now > self._attribute_cache_last_update.get(username,0) + CachedSessionAttributeProvider.CACHE_UPDATE_INTERVAL \
502                     or not self._attribute_cache.has_key(username) \
503                     or force:
504                 db = self.env.get_db_cnx()
505                 cursor = db.cursor()
506                 cursor.execute("SELECT name, value FROM session_attribute WHERE sid=%s",(username,))
507                 self._attribute_cache[username] = {}
508                 for name,value in cursor:
509                     self._attribute_cache[username][name] = value
510                 self._attribute_cache_last_update[username] = now
511                 self.log.debug("Updating SessionAttributeProvider attribute cache for user <%s>"%(username,))
512         finally:
513             self._attribute_cache_lock.release()
514    
515     def get_user_attribute(self, username, attribute):
516         self._update_cache(username)
517         if username in self._attribute_cache:
518             return self._attribute_cache[username].get(attribute)
519         return None
520    
521     def set_user_attribute(self, username, attribute, value):
522         return_value = super(CachedSessionAttributeProvider, self).set_user_attribute(username, attribute, value)
523         self._update_cache(username, force=True)
524         return return_value
525        
526     def delete_user_attribute(self, username, attribute):
527         return_value = super(CachedSessionAttributeProvider, self).delete_user_attribute(username, attribute)
528         self._update_cache(username, force=True)
529         return return_value
530
531 class EnvironmentFixKnownUsers(Component):
532     implements(IEnvironmentSetupParticipant)
533    
534     # IEnvironmentSetupParticipant methods
535     def environment_created(self):
536         pass
537    
538     def environment_needs_upgrade(self, db):
539         def inline_overwrite_get_known_users(environment = None, cnx=None):
540             users = UserManager(self.env).get_active_users()
541             if len(users)>0:
542                 for user in users:
543                     yield (user.username, user['name'], user['email'])
544             else:
545                 # No users defined, so we're returning the original list
546                 for user, name, email in  self.env.__class__.get_known_users(self.env):
547                     yield (user, name, email)
548        
549         self.env.get_known_users = inline_overwrite_get_known_users
550        
551         return False   
552
553     def upgrade_environment(self, db):
554         pass
Note: See TracBrowser for help on using the browser.