| 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 | return hasattr(self, operation) |
|---|
| 32 | |
|---|
| 33 | def create_user(self, username, password): |
|---|
| 34 | """ |
|---|
| 35 | Creates a new user with the given username and password. |
|---|
| 36 | |
|---|
| 37 | @return success |
|---|
| 38 | """ |
|---|
| 39 | |
|---|
| 40 | def get_usernames(self): |
|---|
| 41 | """ |
|---|
| 42 | Generator that yields an ordered list of known usernames. |
|---|
| 43 | |
|---|
| 44 | @return username |
|---|
| 45 | """ |
|---|
| 46 | |
|---|
| 47 | def check_password(self, username, password): |
|---|
| 48 | """ |
|---|
| 49 | Checks if the password is correct for the given user. |
|---|
| 50 | """ |
|---|
| 51 | |
|---|
| 52 | def change_password(self, username, password): |
|---|
| 53 | """ |
|---|
| 54 | Changes a user's password. |
|---|
| 55 | |
|---|
| 56 | @return success |
|---|
| 57 | """ |
|---|
| 58 | |
|---|
| 59 | def delete_user(self, username): |
|---|
| 60 | """ |
|---|
| 61 | Deletes a user. |
|---|
| 62 | |
|---|
| 63 | @return success |
|---|
| 64 | Returns False if the user didn't exist. |
|---|
| 65 | """ |
|---|
| 66 | |
|---|
| 67 | |
|---|
| 68 | class IUserAttributeProvider(Interface): |
|---|
| 69 | """ |
|---|
| 70 | Extension point interface for backends that store user attributes. |
|---|
| 71 | """ |
|---|
| 72 | |
|---|
| 73 | def supports_attribute_operation(self, operation): |
|---|
| 74 | """ |
|---|
| 75 | Returns whether the operation (a method name) is supported. |
|---|
| 76 | |
|---|
| 77 | @return supported |
|---|
| 78 | """ |
|---|
| 79 | return hasattr(self, operation) |
|---|
| 80 | |
|---|
| 81 | def get_user_attribute(self, username, attribute): |
|---|
| 82 | """ |
|---|
| 83 | Returns a user attribute. |
|---|
| 84 | |
|---|
| 85 | If the attribute is not set it returs an empty string. |
|---|
| 86 | If the attribute is not supported None is returned. |
|---|
| 87 | """ |
|---|
| 88 | |
|---|
| 89 | def set_user_attribute(self, username, attribute, value): |
|---|
| 90 | """ |
|---|
| 91 | Sets a user attribute. |
|---|
| 92 | |
|---|
| 93 | @return success |
|---|
| 94 | Returns False if setting the attribute is not supported. |
|---|
| 95 | """ |
|---|
| 96 | |
|---|
| 97 | def delete_all_user_attributes(self, username): |
|---|
| 98 | """ |
|---|
| 99 | Deletes all of the given user's attributes. |
|---|
| 100 | |
|---|
| 101 | @return success |
|---|
| 102 | """ |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | class UserManager(Component): |
|---|
| 106 | """ |
|---|
| 107 | Component responsible for managing users and user attributes. |
|---|
| 108 | """ |
|---|
| 109 | |
|---|
| 110 | store = ExtensionOption('users', |
|---|
| 111 | 'store', IUserStore, 'SessionUserStore', |
|---|
| 112 | doc="""The user store that should be used for authentication |
|---|
| 113 | (''since 0.11'').""") |
|---|
| 114 | attribute_providers = OrderedExtensionsOption('users', |
|---|
| 115 | 'attribute_providers', IUserAttributeProvider, |
|---|
| 116 | doc="""Ordered list of user attribute providers (''since 0.11'').""") |
|---|
| 117 | |
|---|
| 118 | # public API |
|---|
| 119 | |
|---|
| 120 | def supports_operation(self, operation): |
|---|
| 121 | if self.store.supports_user_operation(operation): |
|---|
| 122 | return True |
|---|
| 123 | for provider in self.attribute_providers: |
|---|
| 124 | if self.provider.supports_attribute_operation(operation): |
|---|
| 125 | return True |
|---|
| 126 | return False |
|---|
| 127 | |
|---|
| 128 | # IUserStore methods |
|---|
| 129 | |
|---|
| 130 | def create_user(self, username, password): |
|---|
| 131 | if not self.store.supports_user_operation('create_user'): |
|---|
| 132 | return False |
|---|
| 133 | return self.store.create_user(username, password) |
|---|
| 134 | |
|---|
| 135 | def get_user(self, username): |
|---|
| 136 | """ |
|---|
| 137 | Returns a User object for the username. |
|---|
| 138 | |
|---|
| 139 | @return User object or None if the user doesn't exist |
|---|
| 140 | """ |
|---|
| 141 | if username in self.get_usernames(): |
|---|
| 142 | return User(username, self.store, self.attribute_providers) |
|---|
| 143 | return None |
|---|
| 144 | |
|---|
| 145 | def get_usernames(self): |
|---|
| 146 | if not self.store.supports_user_operation('get_usernames'): |
|---|
| 147 | return [] |
|---|
| 148 | return self.store.get_usernames() |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | |
|---|
| 152 | class User(object): |
|---|
| 153 | """ |
|---|
| 154 | Object representing a user. |
|---|
| 155 | """ |
|---|
| 156 | |
|---|
| 157 | def __init__(self, username, store, attribute_providers): |
|---|
| 158 | self._username = username |
|---|
| 159 | self.store = store |
|---|
| 160 | self.attribute_providers = attribute_providers |
|---|
| 161 | |
|---|
| 162 | # public API |
|---|
| 163 | |
|---|
| 164 | @property |
|---|
| 165 | def username(): |
|---|
| 166 | return self._username |
|---|
| 167 | |
|---|
| 168 | # IUserStore methods |
|---|
| 169 | |
|---|
| 170 | def check_password(self, password): |
|---|
| 171 | if not self.store.supports_user_operation('check_password'): |
|---|
| 172 | return False |
|---|
| 173 | return self.store.check_password(self.username, password) |
|---|
| 174 | |
|---|
| 175 | def change_password(self, password): |
|---|
| 176 | if not self.store.supports_user_operation('change_password'): |
|---|
| 177 | return False |
|---|
| 178 | return self.store.change_password(self.username, password) |
|---|
| 179 | |
|---|
| 180 | def delete(self): |
|---|
| 181 | if not self.store.supports_user_operation('delete_user') or \ |
|---|
| 182 | not self.delete_all_attributes(self.username): |
|---|
| 183 | return False |
|---|
| 184 | return self.store.delete_user(self.username) |
|---|
| 185 | |
|---|
| 186 | # IUserAttributeProvider methods |
|---|
| 187 | |
|---|
| 188 | def __getattr__(self, attribute): |
|---|
| 189 | for provider in self.attribute_providers: |
|---|
| 190 | if not provider.supports_attribute_operation('get_user_attribute'): |
|---|
| 191 | continue |
|---|
| 192 | value = provider.get_user_attribute(self.username, attribute) |
|---|
| 193 | if value is not None: |
|---|
| 194 | return value |
|---|
| 195 | return None |
|---|
| 196 | |
|---|
| 197 | def __setattr__(self, attribute, value): |
|---|
| 198 | for provider in self.attribute_providers: |
|---|
| 199 | if not provider.supports_attribute_operation('set_user_attribute'): |
|---|
| 200 | continue |
|---|
| 201 | result = provider.set_user_attribute(self.username, attribute, |
|---|
| 202 | value) |
|---|
| 203 | if result: |
|---|
| 204 | return True |
|---|
| 205 | return False |
|---|
| 206 | |
|---|
| 207 | def delete_all_attributes(self): |
|---|
| 208 | for provider in self.attribute_providers: |
|---|
| 209 | if not provider.supports_attribute_operation('delete_all_user_attributes'): |
|---|
| 210 | continue |
|---|
| 211 | result = provider.delete_all_user_attributes(username) |
|---|
| 212 | if result: |
|---|
| 213 | return True |
|---|
| 214 | return False |
|---|
| 215 | |
|---|
| 216 | |
|---|
| 217 | class SessionUserStore(Component): |
|---|
| 218 | """ |
|---|
| 219 | Component for managing authenticated users stored in sessions. |
|---|
| 220 | """ |
|---|
| 221 | |
|---|
| 222 | implements(IUserStore) |
|---|
| 223 | |
|---|
| 224 | def get_usernames(self): |
|---|
| 225 | db = self.env.get_db_cnx() |
|---|
| 226 | cursor = db.cursor() |
|---|
| 227 | cursor.execute("SELECT sid FROM session " |
|---|
| 228 | "WHERE authenticated=1 " |
|---|
| 229 | "ORDER BY sid") |
|---|
| 230 | for row in cursor: |
|---|
| 231 | yield row[0] |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | class SessionUserAttributeProvider(Component): |
|---|
| 235 | """ |
|---|
| 236 | Component for providing user attributes via Trac sessions. |
|---|
| 237 | """ |
|---|
| 238 | |
|---|
| 239 | implements(IUserAttributeProvider) |
|---|
| 240 | |
|---|
| 241 | def get_user_attribute(self, username, attribute): |
|---|
| 242 | db = self.env.get_db_cnx() |
|---|
| 243 | cursor = db.cursor() |
|---|
| 244 | cursor.execute("SELECT value FROM session_attribute " |
|---|
| 245 | "WHERE sid=%s AND name=%s AND authenticated=1", |
|---|
| 246 | (username, attribute)) |
|---|
| 247 | row = cursor.fetchone() |
|---|
| 248 | if row: |
|---|
| 249 | return row[0] |
|---|
| 250 | |
|---|
| 251 | # if the attribute isn't set we return empty string to indicate |
|---|
| 252 | # that the attribute is supported, but empty |
|---|
| 253 | return '' |
|---|
| 254 | |
|---|
| 255 | def set_user_attribute(self, username, attribute, value): |
|---|
| 256 | db = self.env.get_db_cnx() |
|---|
| 257 | cursor = db.cursor() |
|---|
| 258 | |
|---|
| 259 | # check if attribute exists |
|---|
| 260 | cursor.execute("SELECT value FROM session_attribute " |
|---|
| 261 | "WHERE sid=%s AND name=%s AND authenticated=1", |
|---|
| 262 | (username, attribute)) |
|---|
| 263 | if cursor.fetchone(): |
|---|
| 264 | # update the attribute |
|---|
| 265 | cursor.execute("UPDATE session_attribute SET value=%s " |
|---|
| 266 | "WHERE sid=%s AND name=%s AND authenticated=1", |
|---|
| 267 | (username, attribute, value)) |
|---|
| 268 | else: |
|---|
| 269 | # create new attribute |
|---|
| 270 | cursor.execute("INSERT INTO session_attribute " |
|---|
| 271 | "(sid,authenticated,name,value) " |
|---|
| 272 | "VALUES(%s,1,%s,%s)", |
|---|
| 273 | (username, attribute, value)) |
|---|
| 274 | db.commit() |
|---|
| 275 | return True |
|---|
| 276 | |
|---|
| 277 | def delete_all_user_attributes(self, username): |
|---|
| 278 | db = self.env.get_db_cnx() |
|---|
| 279 | cursor = db.cursor() |
|---|
| 280 | cursor.execute("DELETE FROM session_attribute WHERE sid=%s", |
|---|
| 281 | (username,)) |
|---|
| 282 | db.commit() |
|---|
| 283 | return True |
|---|