| | 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 | |
| | 101 | class UserManager(Component): |
| | 102 | """ |
| | 103 | Component responsible for managing users and user attributes. |
| | 104 | """ |
| | 105 | |
| | 106 | store = ExtensionOption('users', |
| | 107 | 'store', IUserStore, 'SessionUserStore', |
| | 108 | doc="""The user store that should be used for authentication |
| | 109 | (''since 0.11'').""") |
| | 110 | attribute_providers = OrderedExtensionsOption('users', |
| | 111 | 'attribute_providers', IUserAttributeProvider, |
| | 112 | doc="""Ordered list of user attribute providers (''since 0.11'').""") |
| | 113 | |
| | 114 | # public API |
| | 115 | |
| | 116 | def supports_operation(self, operation): |
| | 117 | if self.store.supports_user_operation(operation): |
| | 118 | return True |
| | 119 | for provider in self.attribute_providers: |
| | 120 | if self.provider.supports_attribute_operation(operation): |
| | 121 | return True |
| | 122 | return False |
| | 123 | |
| | 124 | # IUserStore methods |
| | 125 | |
| | 126 | def create_user(self, username, password): |
| | 127 | """ |
| | 128 | Creates a new user with the given username and password. |
| | 129 | |
| | 130 | @return User object or None if the user couldn't be created |
| | 131 | """ |
| | 132 | if not self.store.supports_user_operation('create_user'): |
| | 133 | return None |
| | 134 | if self.store.create_user(username, password): |
| | 135 | return User(username, self.store, self.attribute_providers) |
| | 136 | |
| | 137 | def get_user(self, username): |
| | 138 | """ |
| | 139 | Returns a User object for the username. |
| | 140 | |
| | 141 | @return User object or None if the user doesn't exist |
| | 142 | """ |
| | 143 | if username in self.get_usernames(): |
| | 144 | return User(username, self.store, self.attribute_providers) |
| | 145 | return None |
| | 146 | |
| | 147 | def get_all_users(self): |
| | 148 | """ |
| | 149 | Generator for User objects. |
| | 150 | """ |
| | 151 | if not self.store.supports_user_operation('get_usernames'): |
| | 152 | return |
| | 153 | for username in self.store.get_usernames(): |
| | 154 | yield User(username, self.store, self.attribute_providers) |
| | 155 | |
| | 156 | def get_usernames(self): |
| | 157 | """ |
| | 158 | Generator for usernames. |
| | 159 | """ |
| | 160 | if not self.store.supports_user_operation('get_usernames'): |
| | 161 | return |
| | 162 | return self.store.get_usernames() |
| | 163 | |
| | 164 | # often-used specialized methods |
| | 165 | |
| | 166 | def get_email_map(self): |
| | 167 | """ |
| | 168 | Returns a hash mapping usernames to email addresses. |
| | 169 | """ |
| | 170 | email_map = {} |
| | 171 | for user in self.get_all_users(): |
| | 172 | email = user['email'] |
| | 173 | if email: |
| | 174 | email_map[user.username] = email |
| | 175 | return email_map |
| | 176 | |
| | 177 | |
| | 178 | class User(object): |
| | 179 | """ |
| | 180 | Object representing a user. |
| | 181 | """ |
| | 182 | |
| | 183 | def __init__(self, username, store, attribute_providers): |
| | 184 | self._username = username |
| | 185 | self.store = store |
| | 186 | self.attribute_providers = attribute_providers |
| | 187 | |
| | 188 | # public API |
| | 189 | |
| | 190 | @property |
| | 191 | def username(self): |
| | 192 | return self._username |
| | 193 | |
| | 194 | # IUserStore methods |
| | 195 | |
| | 196 | def check_password(self, password): |
| | 197 | if not self.store.supports_user_operation('check_password'): |
| | 198 | return False |
| | 199 | return self.store.check_password(self.username, password) |
| | 200 | |
| | 201 | def change_password(self, password): |
| | 202 | if not self.store.supports_user_operation('change_password'): |
| | 203 | return False |
| | 204 | return self.store.change_password(self.username, password) |
| | 205 | |
| | 206 | def delete(self): |
| | 207 | if not self.store.supports_user_operation('delete_user'): |
| | 208 | return False |
| | 209 | self.delete_all_attributes() |
| | 210 | return self.store.delete_user(self.username) |
| | 211 | |
| | 212 | # IUserAttributeProvider methods |
| | 213 | |
| | 214 | def __getitem__(self, attribute): |
| | 215 | for provider in self.attribute_providers: |
| | 216 | if not provider.supports_attribute_operation('get_user_attribute'): |
| | 217 | continue |
| | 218 | value = provider.get_user_attribute(self.username, attribute) |
| | 219 | if value is not None: |
| | 220 | return value |
| | 221 | return None |
| | 222 | |
| | 223 | def __setitem__(self, attribute, value): |
| | 224 | for provider in self.attribute_providers: |
| | 225 | if provider.supports_attribute_operation('set_user_attribute') \ |
| | 226 | and provider.set_user_attribute(self.username, attribute, |
| | 227 | value): |
| | 228 | return True |
| | 229 | return False |
| | 230 | |
| | 231 | def delete_all_attributes(self): |
| | 232 | for provider in self.attribute_providers: |
| | 233 | if provider.supports_attribute_operation('delete_all_user_attributes'): |
| | 234 | provider.delete_all_user_attributes(username) |
| | 235 | |
| | 236 | |
| | 237 | class SessionUserStore(Component): |
| | 238 | """ |
| | 239 | Component for managing authenticated users stored in sessions. |
| | 240 | """ |
| | 241 | |
| | 242 | implements(IUserStore) |
| | 243 | |
| | 244 | def supports_user_operation(self, operation): |
| | 245 | return hasattr(self, operation) |
| | 246 | |
| | 247 | def get_usernames(self): |
| | 248 | db = self.env.get_db_cnx() |
| | 249 | cursor = db.cursor() |
| | 250 | cursor.execute("SELECT sid FROM session " |
| | 251 | "WHERE authenticated=1 " |
| | 252 | "ORDER BY sid") |
| | 253 | for row in cursor: |
| | 254 | yield row[0] |
| | 255 | |
| | 256 | |
| | 257 | class SessionUserAttributeProvider(Component): |
| | 258 | """ |
| | 259 | Component for providing user attributes via Trac sessions. |
| | 260 | """ |
| | 261 | |
| | 262 | implements(IUserAttributeProvider) |
| | 263 | |
| | 264 | def supports_attribute_operation(self, operation): |
| | 265 | return hasattr(self, operation) |
| | 266 | |
| | 267 | def get_user_attribute(self, username, attribute): |
| | 268 | db = self.env.get_db_cnx() |
| | 269 | cursor = db.cursor() |
| | 270 | cursor.execute("SELECT value FROM session_attribute " |
| | 271 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 272 | (username, attribute)) |
| | 273 | row = cursor.fetchone() |
| | 274 | if row: |
| | 275 | return row[0] |
| | 276 | |
| | 277 | # if the attribute doesn't exist we return an empty string to |
| | 278 | # indicate that the attribute is supported, but not set |
| | 279 | return '' |
| | 280 | |
| | 281 | def set_user_attribute(self, username, attribute, value): |
| | 282 | db = self.env.get_db_cnx() |
| | 283 | cursor = db.cursor() |
| | 284 | |
| | 285 | if not value or value == '': |
| | 286 | cursor.execute("DELETE FROM session_attribute " |
| | 287 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 288 | (username, attribute)) |
| | 289 | db.commit() |
| | 290 | return True |
| | 291 | |
| | 292 | # check if attribute exists |
| | 293 | cursor.execute("SELECT value FROM session_attribute " |
| | 294 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 295 | (username, attribute)) |
| | 296 | if cursor.fetchone(): |
| | 297 | # update the attribute |
| | 298 | cursor.execute("UPDATE session_attribute SET value=%s " |
| | 299 | "WHERE sid=%s AND name=%s AND authenticated=1", |
| | 300 | (value, username, attribute)) |
| | 301 | else: |
| | 302 | # create new attribute |
| | 303 | cursor.execute("INSERT INTO session_attribute " |
| | 304 | "(sid,authenticated,name,value) " |
| | 305 | "VALUES(%s,1,%s,%s)", |
| | 306 | (username, attribute, value)) |
| | 307 | db.commit() |
| | 308 | return True |
| | 309 | |
| | 310 | def delete_all_user_attributes(self, username): |
| | 311 | db = self.env.get_db_cnx() |
| | 312 | cursor = db.cursor() |
| | 313 | cursor.execute("DELETE FROM session_attribute " |
| | 314 | "WHERE sid=%s AND authenticated=1", |
| | 315 | (username,)) |
| | 316 | db.commit() |