source: usermanagerplugin/0.11/tracusermanager/api.py @ 13677

Last change on this file since 13677 was 13677, checked in by Ryan J Ollos, 10 years ago

Fixed indentation and trimmed whitespace using reindent.py.

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