| | 1 | # -*- coding: utf-8 -*- |
| | 2 | # |
| | 3 | # Copyright 2006 Waldemar Kornewald, wkornewald@haiku-os.org |
| | 4 | # All rights reserved. |
| | 5 | # |
| | 6 | # This software is licensed as described in the file COPYING, which |
| | 7 | # you should have received as part of this distribution. The terms |
| | 8 | # are also available at http://trac.edgewall.org/wiki/TracLicense. |
| | 9 | # |
| | 10 | # This software consists of voluntary contributions made by many |
| | 11 | # individuals. For the exact contribution history, see the revision |
| | 12 | # history and logs, available at http://trac.edgewall.org/log/. |
| | 13 | # |
| | 14 | # Author: Waldemar Kornewald, wkornewald@haiku-os.org |
| | 15 | |
| | 16 | from trac.core import * |
| | 17 | from trac.config import * |
| | 18 | |
| | 19 | |
| | 20 | class IUserStore(Interface): |
| | 21 | """ |
| | 22 | Extension point interface for backends that store known users. |
| | 23 | """ |
| | 24 | |
| | 25 | def supports_user_operation(self, operation): |
| | 26 | """ |
| | 27 | Returns whether the operation (a method name) is supported. |
| | 28 | |
| | 29 | @return supported |
| | 30 | """ |
| | 31 | |
| | 32 | def create_user(self, username, password): |
| | 33 | """ |
| | 34 | Creates a new user with the given username and password. |
| | 35 | |
| | 36 | @return success |
| | 37 | """ |
| | 38 | |
| | 39 | def get_usernames(self): |
| | 40 | """ |
| | 41 | Generator that yields an ordered list of known usernames. |
| | 42 | |
| | 43 | @return username |
| | 44 | """ |
| | 45 | |
| | 46 | def check_password(self, username, password): |
| | 47 | """ |
| | 48 | Checks if the password is correct for the given user. |
| | 49 | """ |
| | 50 | |
| | 51 | def change_password(self, username, password): |
| | 52 | """ |
| | 53 | Changes a user's password. |
| | 54 | |
| | 55 | @return success |
| | 56 | """ |
| | 57 | |
| | 58 | def delete_user(self, username): |
| | 59 | """ |
| | 60 | Deletes a user. |
| | 61 | |
| | 62 | @return success |
| | 63 | Returns False if the user didn't exist. |
| | 64 | """ |
| | 65 | |
| | 66 | |
| | 67 | class IUserAttributeProvider(Interface): |
| | 68 | """ |
| | 69 | Extension point interface for backends that store user attributes. |
| | 70 | """ |
| | 71 | |
| | 72 | def supports_attribute_operation(self, operation): |
| | 73 | """ |
| | 74 | Returns whether the operation (a method name) is supported. |
| | 75 | |
| | 76 | @return supported |
| | 77 | """ |
| | 78 | |
| | 79 | def get_user_attribute(self, username, attribute): |
| | 80 | """ |
| | 81 | Returns a user attribute. |
| | 82 | |
| | 83 | If the attribute is not set it returs an empty string. |
| | 84 | If the attribute is not supported None is returned. |
| | 85 | """ |
| | 86 | |
| | 87 | def set_user_attribute(self, username, attribute, value): |
| | 88 | """ |
| | 89 | Sets a user attribute. |
| | 90 | |
| | 91 | @return success |
| | 92 | Returns False if setting the attribute is not supported. |
| | 93 | """ |
| | 94 | |
| | 95 | def delete_all_user_attributes(self, username): |
| | 96 | """ |
| | 97 | Deletes all of the given user's attributes. |
| | 98 | """ |
| | 99 | |
| | 100 | class UserAttributeMapper(object): |
| | 101 | """ |
| | 102 | Dict that maps usernames to attributes and caches those values to |
| | 103 | increase efficiency. |
| | 104 | """ |
| | 105 | |
| | 106 | _usernames_cache = None |
| | 107 | _cache = {} |
| | 108 | |
| | 109 | def __init__(self, attribute, manager): |
| | 110 | self._attribute = attribute |
| | 111 | self._manager = manager |
| | 112 | |
| | 113 | def __getitem__(self, username): |
| | 114 | if not self._usernames_cache: |
| | 115 | self._usernames_cache = [username for username |
| | 116 | in self._manager.get_usernames()] |
| | 117 | if username not in self._usernames_cache: |
| | 118 | return None |
| | 119 | if username not in self._cache: |
| | 120 | value = self._manager.get_user(username)[self._attribute] |
| | 121 | if value: |
| | 122 | self._cache[username] = value |
| | 123 | return value |
| | 124 | else: |
| | 125 | return self._cache.get(username) |
| | 126 | |
| | 127 | def __contains__(self, username): |
| | 128 | value = self[username] |
| | 129 | return value is not None and len(value) > 0 |
| | 130 | |
| | 131 | def __setitem__(self,key,value): |
| | 132 | raise NotImplementedError, "dict is immutable" |
| | 133 | def __delitem__(self,key): |
| | 134 | raise NotImplementedError, "dict is immutable" |
| | 135 | def clear(self): |
| | 136 | raise NotImplementedError, "dict is immutable" |
| | 137 | def setdefault(self,k,default=None): |
| | 138 | raise NotImplementedError, "dict is immutable" |
| | 139 | def popitem(self): |
| | 140 | raise NotImplementedError, "dict is immutable" |
| | 141 | def update(self,other): |
| | 142 | raise NotImplementedError, "dict is immutable" |
| | 143 | |
| | 144 | |
| | 145 | class UserManager(Component): |
| | 146 | """ |
| | 147 | Component responsible for managing users and user attributes. |
| | 148 | """ |
| | 149 | |
| | 150 | store = ExtensionOption('users', |
| | 151 | 'store', IUserStore, 'SessionUserStore', |
| | 152 | doc="""The user store that should be used for authentication |
| | 153 | (''since 0.11'').""") |
| | 154 | attribute_providers = OrderedExtensionsOption('users', |
| | 155 | 'attribute_providers', IUserAttributeProvider, |
| | 156 | doc="""Ordered list of user attribute providers (''since 0.11'').""") |
| | 157 | |
| | 158 | # public API |
| | 159 | |
| | 160 | def supports_operation(self, operation): |
| | 161 | if self.store.supports_user_operation(operation): |
| | 162 | return True |
| | 163 | for provider in self.attribute_providers: |
| | 164 | if self.provider.supports_attribute_operation(operation): |
| | 165 | return True |
| | 166 | return False |
| | 167 | |
| | 168 | # IUserStore methods |
| | 169 | |
| | 170 | def create_user(self, username, password): |
| | 171 | """ |
| | 172 | Creates a new user with the given username and password. |
| | 173 | |
| | 174 | @return User object or None if the user couldn't be created |
| | 175 | """ |
| | 176 | if not self.store.supports_user_operation('create_user'): |
| | 177 | return None |
| | 178 | if self.store.create_user(username, password): |
| | 179 | return User(username, self.store, self.attribute_providers) |
| | 180 | |
| | 181 | def get_user(self, username): |
| | 182 | """ |
| | 183 | Returns a User object for the username. |
| | 184 | |
| | 185 | @return User object or None if the user doesn't exist |
| | 186 | """ |
| | 187 | return User(username, self.store, self.attribute_providers) |
| | 188 | |
| | 189 | def get_all_users(self): |
| | 190 | """ |
| | 191 | Generator for User objects. |
| | 192 | """ |
| | 193 | if not self.store.supports_user_operation('get_usernames'): |
| | 194 | return |
| | 195 | for username in self.store.get_usernames(): |
| | 196 | yield User(username, self.store, self.attribute_providers) |
| | 197 | |
| | 198 | def get_usernames(self): |
| | 199 | """ |
| | 200 | Generator for usernames. |
| | 201 | """ |
| | 202 | if not self.store.supports_user_operation('get_usernames'): |
| | 203 | return |
| | 204 | return self.store.get_usernames() |
| | 205 | |
| | 206 | def get_attribute_mapper(self, attribute): |
| | 207 | return UserAttributeMapper(attribute, self) |
| | 208 | |
| | 209 | |
| | 210 | class User(object): |
| | 211 | """ |
| | 212 | Object representing a user. |
| | 213 | """ |
| | 214 | |
| | 215 | def __init__(self, username, store, attribute_providers): |
| | 216 | self._username = username |
| | 217 | self.store = store |
| | 218 | self.attribute_providers = attribute_providers |
| | 219 | |
| | 220 | # public API |
| | 221 | |
| | 222 | @property |
| | 223 | def username(self): |
| | 224 | return self._username |
| | 225 | |
| | 226 | def exists(self): |
| | 227 | return self.username in [username for username |
| | 228 | in self.store.get_usernames()] |
| | 229 | |
| | 230 | # IUserStore methods |
| | 231 | |
| | 232 | def check_password(self, password): |
| | 233 | if not self.store.supports_user_operation('check_password'): |
| | 234 | return False |
| | 235 | return self.store.check_password(self.username, password) |
| | 236 | |
| | 237 | def change_password(self, password): |
| | 238 | if not self.store.supports_user_operation('change_password'): |
| | 239 | return False |
| | 240 | return self.store.change_password(self.username, password) |
| | 241 | |
| | 242 | def delete(self): |
| | 243 | if not self.store.supports_user_operation('delete_user'): |
| | 244 | return False |
| | 245 | self.delete_all_attributes() |
| | 246 | return self.store.delete_user(self.username) |
| | 247 | |
| | 248 | # IUserAttributeProvider methods |
| | 249 | |
| | 250 | def __getitem__(self, attribute): |
| | 251 | for provider in self.attribute_providers: |
| | 252 | if not provider.supports_attribute_operation('get_user_attribute'): |
| | 253 | continue |
| | 254 | value = provider.get_user_attribute(self.username, attribute) |
| | 255 | if value is not None: |
| | 256 | return value |
| | 257 | return None |
| | 258 | |
| | 259 | def __setitem__(self, attribute, value): |
| | 260 | for provider in self.attribute_providers: |
| | 261 | if provider.supports_attribute_operation('set_user_attribute') \ |
| | 262 | and provider.set_user_attribute(self.username, attribute, |
| | 263 | value): |
| | 264 | return True |
| | 265 | return False |
| | 266 | |
| | 267 | def delete_all_attributes(self): |
| | 268 | for provider in self.attribute_providers: |
| | 269 | if provider.supports_attribute_operation('delete_all_user_attributes'): |
| | 270 | provider.delete_all_user_attributes(username) |
| | 271 | |
| | 272 | |
| | 273 | class SessionUserStore(Component): |
| | 274 | """ |
| | 275 | Component for managing authenticated users stored in sessions. |
| | 276 | """ |
| | 277 | |
| | 278 | implements(IUserStore) |
| | 279 | |
| | 280 | def supports_user_operation(self, operation): |
| | 281 | return hasattr(self, operation) |
| | 282 | |
| | 283 | def get_usernames(self): |
| | 284 | db = self.env.get_db_cnx() |
| | 285 | cursor = db.cursor() |
| | 286 | cursor.execute("SELECT sid FROM session " |
| | 287 | "WHERE authenticated=1 " |
| | 288 | "ORDER BY sid") |
| | 289 | for row in cursor: |
| | 290 | yield row[0] |
| | 291 | |
| | 292 | |
| | 293 | class SessionUserAttributeProvider(Component): |
| | 294 | """ |
| | 295 | Component for providing user attributes via Trac sessions. |
| | 296 | """ |
| | 297 | |
| | 298 | implements(IUserAttributeProvider) |
| | 299 | |
| | 300 | def supports_attribute_operation(self, operation): |
| | 301 | return hasattr(self, operation) |
| | 302 | |
| | 303 | def get_user_attribute(self, username, attribute): |
| | 304 | db = self.env.get_db_cnx() |
| | 305 | cursor = db.cursor() |
| | 306 | cursor.execute("SELECT value FROM session_attribute " |
| | 307 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 308 | (username, attribute)) |
| | 309 | row = cursor.fetchone() |
| | 310 | if row: |
| | 311 | return row[0] |
| | 312 | |
| | 313 | # if the attribute doesn't exist we return an empty string to |
| | 314 | # indicate that the attribute is supported, but not set |
| | 315 | return '' |
| | 316 | |
| | 317 | def set_user_attribute(self, username, attribute, value): |
| | 318 | db = self.env.get_db_cnx() |
| | 319 | cursor = db.cursor() |
| | 320 | |
| | 321 | if not value or value == '': |
| | 322 | cursor.execute("DELETE FROM session_attribute " |
| | 323 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 324 | (username, attribute)) |
| | 325 | db.commit() |
| | 326 | return True |
| | 327 | |
| | 328 | # check if attribute exists |
| | 329 | cursor.execute("SELECT value FROM session_attribute " |
| | 330 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 331 | (username, attribute)) |
| | 332 | if cursor.fetchone(): |
| | 333 | # update the attribute |
| | 334 | cursor.execute("UPDATE session_attribute SET value=%s " |
| | 335 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 336 | (value, username, attribute)) |
| | 337 | else: |
| | 338 | # create new attribute |
| | 339 | cursor.execute("INSERT INTO session_attribute " |
| | 340 | "(sid,authenticated,name,value) " |
| | 341 | "VALUES(%s,1,%s,%s)", |
| | 342 | (username, attribute, value)) |
| | 343 | db.commit() |
| | 344 | return True |
| | 345 | |
| | 346 | def delete_all_user_attributes(self, username): |
| | 347 | db = self.env.get_db_cnx() |
| | 348 | cursor = db.cursor() |
| | 349 | cursor.execute("DELETE FROM session_attribute " |
| | 350 | "WHERE sid=%s AND authenticated=1", |
| | 351 | (username,)) |
| | 352 | db.commit() |