Trac-usermgmt-analysis / t2456-morris-3y-back / user_API_r5898.1.diff

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
Index: trac/env.py
===================================================================
--- trac/env.py	(revision 5898)
+++ trac/env.py	(working copy)
@@ -25,6 +25,7 @@
 from trac.core import Component, ComponentManager, implements, Interface, \
                       ExtensionPoint, TracError
 from trac.db import DatabaseManager
+from trac.user import UserManager, User
 from trac.util import get_pkginfo
 from trac.versioncontrol import RepositoryManager
 from trac.web.href import Href
@@ -329,29 +330,16 @@
         self.log = logger_factory(logtype, logfile, self.log_level, self.path,
                                   format=format)
 
-    def get_known_users(self, cnx=None):
+    def get_known_users(self):
         """Generator that yields information about all known users, i.e. users
         that have logged in to this Trac environment and possibly set their name
         and email.
 
         This function generates one tuple for every user, of the form
         (username, name, email) ordered alpha-numerically by username.
-
-        @param cnx: the database connection; if ommitted, a new connection is
-                    retrieved
         """
-        if not cnx:
-            cnx = self.get_db_cnx()
-        cursor = cnx.cursor()
-        cursor.execute("SELECT DISTINCT s.sid, n.value, e.value "
-                       "FROM session AS s "
-                       " LEFT JOIN session_attribute AS n ON (n.sid=s.sid "
-                       "  and n.authenticated=1 AND n.name = 'name') "
-                       " LEFT JOIN session_attribute AS e ON (e.sid=s.sid "
-                       "  AND e.authenticated=1 AND e.name = 'email') "
-                       "WHERE s.authenticated=1 ORDER BY s.sid")
-        for username,name,email in cursor:
-            yield username, name, email
+        for user in UserManager(self).get_all_users():
+            yield user.username, user['name'], user['email']
 
     def backup(self, dest=None):
         """Simple SQLite-specific backup of the database.
Index: trac/notification.py
===================================================================
--- trac/notification.py	(revision 5898)
+++ trac/notification.py	(working copy)
@@ -22,6 +22,7 @@
 from trac import __version__
 from trac.config import BoolOption, IntOption, Option
 from trac.core import *
+from trac.user import UserManager
 from trac.util.text import CRLF
 from trac.web.chrome import Chrome
 
@@ -158,6 +159,7 @@
     smtp_port = 25
     from_email = 'trac+tickets@localhost'
     subject = ''
+    server = None
     template_name = None
     nodomaddr_re = re.compile(r'[\w\d_\.\-]+')
     addrsep_re = re.compile(r'[;\s,]+')
@@ -179,12 +181,7 @@
         self._init_pref_encoding()
         domains = self.env.config.get('notification', 'ignore_domains', '')
         self._ignore_domains = [x.strip() for x in domains.lower().split(',')]
-        # Get the email addresses of all known users
-        self.email_map = {}
-        for username, name, email in self.env.get_known_users(self.db):
-            if email:
-                self.email_map[username] = email
-                
+
     def _init_pref_encoding(self):
         from email.Charset import Charset, QP, BASE64
         self._charset = Charset()
@@ -270,8 +267,10 @@
         if not is_email(address):
             if address == 'anonymous':
                 return None
-            if self.email_map.has_key(address):
-                address = self.email_map[address]
+            
+            email = UserManager(self.env).get_user(address)['email']
+            if email and len(email) > 0:
+                address = email
             elif NotifyEmail.nodomaddr_re.match(address):
                 if self.config.getbool('notification', 'use_short_addr'):
                     return address
Index: trac/perm.py
===================================================================
--- trac/perm.py	(revision 5898)
+++ trac/perm.py	(working copy)
@@ -20,6 +20,7 @@
 
 from trac.config import ExtensionOption, OrderedExtensionsOption
 from trac.core import *
+from trac.user import UserManager
 from trac.util.compat import set
 from trac.util.translation import _
 
@@ -157,7 +158,8 @@
         db = self.env.get_db_cnx()
         cursor = db.cursor()
         result = set()
-        users = set([u[0] for u in self.env.get_known_users()])
+        users = set([username for username
+                    in UserManager(self.env).get_usernames()])
         for user in users:
             userperms = self.get_user_permissions(user)
             for group in permissions:
Index: trac/prefs/web_ui.py
===================================================================
--- trac/prefs/web_ui.py	(revision 5914)
+++ trac/prefs/web_ui.py	(working copy)
@@ -22,6 +22,7 @@
 
 from trac.core import *
 from trac.prefs.api import IPreferencePanelProvider
+from trac.user import UserManager, User
 from trac.util.datefmt import all_timezones, get_timezone
 from trac.util.translation import _
 from trac.web import HTTPNotFound, IRequestHandler
@@ -93,8 +94,16 @@
                 self._do_save(req)
             req.redirect(req.href.prefs(panel or None))
 
+        name = email = ''
+        if req.authname != 'anonymous':
+            user = UserManager(self.env).get_user(req.authname)
+            name, email = user['name'], user['email']
+        else:
+            name = req.session.get('name')
+            email = req.session.get('email')
         return 'prefs_%s.html' % (panel or 'general'), {
-            'settings': {'session': req.session, 'session_id': req.session.sid},
+            'settings': {'session': req.session, 'session_id': req.session.sid,
+                         'name': name, 'email': email},
             'timezones': all_timezones, 'timezone': get_timezone
         }
 
@@ -118,9 +127,19 @@
                 elif field == 'newsid' and val:
                     req.session.change_sid(val)
                 else:
-                    req.session[field] = val
-            elif field in req.args and field in req.session:
-                del req.session[field]
+                    if req.authname != 'anonymous' and \
+                            field in ('name', 'email'):
+                        user = UserManager(self.env).get_user(req.authname)
+                        user[field] = val
+                    else:
+                        req.session[field] = val
+            elif field in req.args:
+                if req.authname != 'anonymous' and \
+                        field in ('name', 'email'):
+                    user = UserManager(self.env).get_user(req.authname)
+                    user[field] = ''
+                elif field in req.session:
+                    del req.session[field]
 
     def _do_load(self, req):
         if req.authname == 'anonymous':
Index: trac/test.py
===================================================================
--- trac/test.py	(revision 5898)
+++ trac/test.py	(working copy)
@@ -187,7 +187,7 @@
     def get_db_cnx(self):
         return self.db
 
-    def get_known_users(self, db):
+    def get_known_users(self):
         return self.known_users
 
 
Index: trac/ticket/admin.py
===================================================================
--- trac/ticket/admin.py	(revision 5898)
+++ trac/ticket/admin.py	(working copy)
@@ -107,11 +107,7 @@
 
         if self.config.getbool('ticket', 'restrict_owner'):
             perm = PermissionSystem(self.env)
-            def valid_owner(username):
-                return perm.get_user_permissions(username).get('TICKET_MODIFY')
-            data['owners'] = [username for username, name, email
-                              in self.env.get_known_users()
-                              if valid_owner(username)]
+            data['owners'] = perm.get_users_with_permission('TICKET_MODIFY')
         else:
             data['owners'] = None
 
Index: trac/ticket/report.py
===================================================================
--- trac/ticket/report.py	(revision 5898)
+++ trac/ticket/report.py	(working copy)
@@ -27,6 +27,7 @@
 from trac.core import *
 from trac.db import get_column_names
 from trac.perm import IPermissionRequestor
+from trac.user import UserManager
 from trac.util import sorted
 from trac.util.datefmt import format_datetime, format_time
 from trac.util.text import to_unicode, unicode_urlencode
@@ -369,9 +370,7 @@
         # Get the email addresses of all known users
         email_map = {}
         if Chrome(self.env).show_email_addresses:
-            for username, name, email in self.env.get_known_users():
-                if email:
-                    email_map[username] = email
+            email_map = UserManager(self.env).get_attribute_mapper('email')
 
         data.update({'header_groups': header_groups,
                      'row_groups': row_groups,
Index: trac/timeline/web_ui.py
===================================================================
--- trac/timeline/web_ui.py	(revision 5912)
+++ trac/timeline/web_ui.py	(working copy)
@@ -30,6 +30,7 @@
 from trac.perm import IPermissionRequestor
 from trac.timeline.api import ITimelineEventProvider, TimelineEvent
 from trac.util.compat import sorted
+from trac.user import UserManager
 from trac.util.datefmt import format_date, format_datetime, parse_date, \
                               to_timestamp, utc, pretty_timedelta
 from trac.util.text import to_unicode
@@ -167,9 +168,7 @@
             # Get the email addresses of all known users
             email_map = {}
             if Chrome(self.env).show_email_addresses:
-                for username, name, email in self.env.get_known_users():
-                    if email:
-                        email_map[username] = email
+                email_map = UserManager(self.env).get_attribute_mapper('email')
             data['email_map'] = email_map
             return 'timeline.rss', data, 'application/rss+xml'
 
Index: trac/user.py
===================================================================
--- trac/user.py	(revision 0)
+++ trac/user.py	(revision 0)
@@ -0,0 +1,352 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2006 Waldemar Kornewald, wkornewald@haiku-os.org
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+#
+# Author: Waldemar Kornewald, wkornewald@haiku-os.org
+
+from trac.core import *
+from trac.config import *
+
+
+class IUserStore(Interface):
+    """
+    Extension point interface for backends that store known users.
+    """
+
+    def supports_user_operation(self, operation):
+        """
+        Returns whether the operation (a method name) is supported.
+
+        @return supported
+        """
+
+    def create_user(self, username, password):
+        """
+        Creates a new user with the given username and password.
+
+        @return success
+        """
+
+    def get_usernames(self):
+        """
+        Generator that yields an ordered list of known usernames.
+
+        @return username
+        """
+
+    def check_password(self, username, password):
+        """
+        Checks if the password is correct for the given user.
+        """
+
+    def change_password(self, username, password):
+        """
+        Changes a user's password.
+
+        @return success
+        """
+
+    def delete_user(self, username):
+        """
+        Deletes a user.
+
+        @return success
+            Returns False if the user didn't exist.
+        """
+
+
+class IUserAttributeProvider(Interface):
+    """
+    Extension point interface for backends that store user attributes.
+    """
+
+    def supports_attribute_operation(self, operation):
+        """
+        Returns whether the operation (a method name) is supported.
+
+        @return supported
+        """
+
+    def get_user_attribute(self, username, attribute):
+        """
+        Returns a user attribute.
+
+        If the attribute is not set it returs an empty string.
+        If the attribute is not supported None is returned.
+        """
+
+    def set_user_attribute(self, username, attribute, value):
+        """
+        Sets a user attribute.
+
+        @return success
+            Returns False if setting the attribute is not supported.
+        """
+
+    def delete_all_user_attributes(self, username):
+        """
+        Deletes all of the given user's attributes.
+        """
+
+class UserAttributeMapper(object):
+    """
+    Dict that paps usernames to attributes and caches those values to
+    increase efficiency.
+    """
+
+    _usernames_cache = None
+    _cache = {}
+
+    def __init__(self, attribute, manager):
+        self._attribute = attribute
+        self._manager = manager
+
+    def __getitem__(self, username):
+        if not self._usernames_cache:
+            self._usernames_cache = [username for username
+                                     in self._manager.get_usernames()]
+        if username not in self._usernames_cache:
+            return None
+        if username not in self._cache:
+            value = self._manager.get_user(username)[self._attribute]
+            if value:
+                self._cache[username] = value
+            return value
+        else:
+            return self._cache.get(username)
+
+    def __contains__(self, username):
+        value = self[username]
+        return value is not None and len(value) > 0
+
+    def __setitem__(self,key,value):
+        raise NotImplementedError, "dict is immutable"
+    def __delitem__(self,key):
+        raise NotImplementedError, "dict is immutable"
+    def clear(self):
+        raise NotImplementedError, "dict is immutable"
+    def setdefault(self,k,default=None):
+        raise NotImplementedError, "dict is immutable"
+    def popitem(self):
+        raise NotImplementedError, "dict is immutable"
+    def update(self,other):
+        raise NotImplementedError, "dict is immutable"
+
+
+class UserManager(Component):
+    """
+    Component responsible for managing users and user attributes.
+    """
+
+    store = ExtensionOption('users',
+        'store', IUserStore, 'SessionUserStore',
+        doc="""The user store that should be used for authentication
+            (''since 0.11'').""")
+    attribute_providers = OrderedExtensionsOption('users',
+        'attribute_providers', IUserAttributeProvider,
+        doc="""Ordered list of user attribute providers (''since 0.11'').""")
+
+    # public API
+
+    def supports_operation(self, operation):
+        if self.store.supports_user_operation(operation):
+            return True
+        for provider in self.attribute_providers:
+            if self.provider.supports_attribute_operation(operation):
+                return True
+        return False
+
+    # IUserStore methods
+
+    def create_user(self, username, password):
+        """
+        Creates a new user with the given username and password.
+
+        @return User object or None if the user couldn't be created
+        """
+        if not self.store.supports_user_operation('create_user'):
+            return None
+        if self.store.create_user(username, password):
+            return User(username, self.store, self.attribute_providers)
+
+    def get_user(self, username):
+        """
+        Returns a User object for the username.
+
+        @return User object or None if the user doesn't exist
+        """
+        return User(username, self.store, self.attribute_providers)
+
+    def get_all_users(self):
+        """
+        Generator for User objects.
+        """
+        if not self.store.supports_user_operation('get_usernames'):
+            return
+        for username in self.store.get_usernames():
+            yield User(username, self.store, self.attribute_providers)
+
+    def get_usernames(self):
+        """
+        Generator for usernames.
+        """
+        if not self.store.supports_user_operation('get_usernames'):
+            return
+        return self.store.get_usernames()
+
+    def get_attribute_mapper(self, attribute):
+        return UserAttributeMapper(attribute, self)
+
+
+class User(object):
+    """
+    Object representing a user.
+    """
+
+    def __init__(self, username, store, attribute_providers):
+        self._username = username
+        self.store = store
+        self.attribute_providers = attribute_providers
+
+    # public API
+
+    @property
+    def username(self):
+        return self._username
+
+    def exists(self):
+        return self.username in [username for username
+                                 in self.store.get_usernames()]
+
+    # IUserStore methods
+
+    def check_password(self, password):
+        if not self.store.supports_user_operation('check_password'):
+            return False
+        return self.store.check_password(self.username, password)
+
+    def change_password(self, password):
+        if not self.store.supports_user_operation('change_password'):
+            return False
+        return self.store.change_password(self.username, password)
+
+    def delete(self):
+        if not self.store.supports_user_operation('delete_user'):
+            return False
+        self.delete_all_attributes()
+        return self.store.delete_user(self.username)
+
+    # IUserAttributeProvider methods
+
+    def __getitem__(self, attribute):
+        for provider in self.attribute_providers:
+            if not provider.supports_attribute_operation('get_user_attribute'):
+                continue
+            value = provider.get_user_attribute(self.username, attribute)
+            if value is not None:
+                return value
+        return None
+
+    def __setitem__(self, attribute, value):
+        for provider in self.attribute_providers:
+            if provider.supports_attribute_operation('set_user_attribute') \
+                    and provider.set_user_attribute(self.username, attribute,
+                                                    value):
+                return True
+        return False
+
+    def delete_all_attributes(self):
+        for provider in self.attribute_providers:
+            if provider.supports_attribute_operation('delete_all_user_attributes'):
+                provider.delete_all_user_attributes(username)
+
+
+class SessionUserStore(Component):
+    """
+    Component for managing authenticated users stored in sessions.
+    """
+
+    implements(IUserStore)
+
+    def supports_user_operation(self, operation):
+        return hasattr(self, operation)
+
+    def get_usernames(self):
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        cursor.execute("SELECT sid FROM session "
+                       "WHERE authenticated=1 "
+                       "ORDER BY sid")
+        for row in cursor:
+            yield row[0]
+
+
+class SessionUserAttributeProvider(Component):
+    """
+    Component for providing user attributes via Trac sessions.
+    """
+
+    implements(IUserAttributeProvider)
+
+    def supports_attribute_operation(self, operation):
+        return hasattr(self, operation)
+
+    def get_user_attribute(self, username, attribute):
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        cursor.execute("SELECT value FROM session_attribute "
+                       "WHERE sid=%s AND name=%s AND authenticated=1",
+                       (username, attribute))
+        row = cursor.fetchone()
+        if row:
+            return row[0]
+
+        # if the attribute doesn't exist we return an empty string to
+        # indicate that the attribute is supported, but not set
+        return ''
+
+    def set_user_attribute(self, username, attribute, value):
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+
+        if not value or value == '':
+            cursor.execute("DELETE FROM session_attribute "
+                           "WHERE sid=%s AND name=%s AND authenticated=1",
+                          (username, attribute))
+            db.commit()
+            return True
+
+        # check if attribute exists
+        cursor.execute("SELECT value FROM session_attribute "
+                       "WHERE sid=%s AND name=%s AND authenticated=1",
+                       (username, attribute))
+        if cursor.fetchone():
+            # update the attribute
+            cursor.execute("UPDATE session_attribute SET value=%s "
+                           "WHERE sid=%s AND name=%s AND authenticated=1",
+                           (value, username, attribute))
+        else:
+            # create new attribute
+            cursor.execute("INSERT INTO session_attribute "
+                           "(sid,authenticated,name,value) "
+                           "VALUES(%s,1,%s,%s)",
+                           (username, attribute, value))
+        db.commit()
+        return True
+
+    def delete_all_user_attributes(self, username):
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        cursor.execute("DELETE FROM session_attribute "
+                       "WHERE sid=%s AND authenticated=1",
+                      (username,))
+        db.commit()
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 5898)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -23,6 +23,7 @@
 from trac.context import Context
 from trac.core import *
 from trac.perm import IPermissionRequestor
+from trac.user import UserManager
 from trac.util import Ranges
 from trac.util.datefmt import http_date
 from trac.util.html import html
@@ -193,9 +194,7 @@
         if format == 'rss':
             # Get the email addresses of all known users
             if Chrome(self.env).show_email_addresses:
-                for username,name,email in self.env.get_known_users():
-                    if email:
-                        email_map[username] = email
+                email_map = UserManager(self.env).get_attribute_mapper('email')
         elif format == 'changelog':
             for rev in revs:
                 changeset = changes[rev]
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.