Package qm :: Module user
[hide private]
[frames] | no frames]

Source Code for Module qm.user

  1  ######################################################################## 
  2  # 
  3  # File:   user.py 
  4  # Author: Alex Samuel 
  5  # Date:   2001-03-23 
  6  # 
  7  # Contents: 
  8  #   User management facilities. 
  9  # 
 10  # Copyright (c) 2001, 2002 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  """User management facilities. 
 17   
 18  Access to this module is primarily through two global variables. 
 19   
 20    'database' -- The user database.  The database contains objects 
 21    representing users, accessed via a *user ID*.  The user ID is a label 
 22    uniquely identifying the user in the system. 
 23   
 24    The user database also provides a notion of user groups.  Each group 
 25    is identified by a group ID, and contains zero or more user IDs.  A 
 26    user may belong to more than one group.  A group may not contain other 
 27    groups.  
 28   
 29    'authenticator' -- The authenticator object by which the application 
 30    authenticates users who attempt to access it via various channels. 
 31   
 32  Access the database object via this interface: 
 33   
 34    * Use the database object as a Python map from user IDs to 'User' 
 35      objects.  The 'keys' method returns a sequence of user IDs in the 
 36      database.  
 37   
 38    * Call 'GetGroupIds' to return a sequence of group IDs.  Use the 
 39      'GetGroup' method to retrieve a 'Group' object for a given group ID. 
 40   
 41  """ 
 42   
 43  ######################################################################## 
 44  # imports 
 45  ######################################################################## 
 46   
 47  import qm 
 48  import label 
 49  import xmlutil 
 50   
 51  ######################################################################## 
 52  # constants 
 53  ######################################################################## 
 54   
 55  xml_user_database_dtd = "-//Software Carpentry//User Database V0.1//EN" 
 56   
 57  ######################################################################## 
 58  # classes 
 59  ######################################################################## 
 60   
61 -class AuthenticationError(Exception):
62 pass
63 64 65
66 -class XmlDatabaseError(Exception):
67 pass
68 69 70
71 -class AccountDisabledError(AuthenticationError):
72 pass
73 74 75
76 -class User:
77 """A user account record. 78 79 User accounts contain three sets of properties. Each property 80 is a name-value pair. The three sets of properties are: 81 82 informational properties -- General information about the 83 user. This may include the user's real name, contact 84 information, etc. These properties are generally 85 user-visible, and should be modified only at the user's 86 request. 87 88 configuration properties -- Program-specific per-user 89 configuration. This includes user's preferences, such as 90 preferred layout and output options. These properties are 91 typically hidden as implementation details, and are often 92 changed implicitly as part of other operations. 93 94 authentication properties -- Information used to authenticate 95 the user. This may include such things as passwords, PGP 96 keys, and digital certificates. There are no accessors for 97 these properties; they should be used by authenticators only. 98 """ 99 100
101 - def __init__(self, 102 user_id, 103 role="user", 104 enabled=1, 105 information_properties={}, 106 configuration_properties={}, 107 authentication_properties={}):
108 """Create a new user account. 109 110 'user_id' -- The ID for the user. 111 112 'role' -- If "default", this is the default user account (there 113 should be only one). The default account, if provided, is used 114 for otherwise unauthenticated operations. If "admin", this is 115 an administrator account. If "user", this is an ordinary user 116 account. 117 118 'enabled' -- If true, this account is enabled; otherwise, 119 disabled. 120 121 'information_properties' -- A map contianing information 122 properties. 123 124 'configuration_properties' -- A map containing configuration 125 properties. 126 127 'authentication_properties' -- A map containing authentication 128 properties.""" 129 130 self.__id = user_id 131 self.__role = role 132 self.__is_enabled = enabled 133 # Initialize properties. 134 self.__authentication = authentication_properties.copy() 135 self.__configuration = configuration_properties.copy() 136 self.__info = information_properties.copy()
137 138
139 - def GetId(self):
140 """Return the user ID of this user.""" 141 142 return self.__id
143 144
145 - def GetRole(self):
146 """Return the role of this user.""" 147 148 return self.__role
149 150
151 - def IsEnabled(self):
152 """Return true if this account is enabled.""" 153 154 return self.__is_enabled
155 156
157 - def GetConfigurationProperty(self, name, default=None):
158 """Return a configuration property. 159 160 'name' -- The name of the property. 161 162 'default' -- The value to return if this property is not 163 specified for the user account.""" 164 165 return self.__configuration.get(name, default)
166 167
168 - def SetConfigurationProperty(self, name, value):
169 """Set the configuration property 'name' to 'value'.""" 170 171 self.__configuration[name] = value
172 173
174 - def GetInfoProperty(self, name, default=None):
175 """Return an informational property. 176 177 'name' -- The name of the property. 178 179 'default' -- The value to return if this property is not 180 specified for the user account.""" 181 182 return self.__info.get(name, default)
183 184
185 - def SetInfoProperty(self, name, value):
186 """Set the informational property 'name' to 'value'.""" 187 188 self.__info[name] = value
189 190 191
192 -class Group:
193 """A group of users. 194 195 A 'Group' object is treated as an ordinary list of user IDs (except 196 that a user ID may not appear more than once in the list).""" 197
198 - def __init__(self, group_id, user_ids=[]):
199 """Create a new group. 200 201 'group_id' -- The ID of this group. 202 203 'user_ids' -- IDs of users initially in the group.""" 204 205 self.__id = group_id 206 self.__user_ids = list(user_ids)
207 208
209 - def GetId(self):
210 """Return the group_id of this group.""" 211 212 return self.__id
213 214
215 - def __len__(self):
216 return len(self.__user_ids)
217 218
219 - def __getitem__(self, index):
220 return self.__user_ids[index]
221 222
223 - def __setitem__(self, index, user_id):
224 self.__user_ids[index] = user_id 225 # Make sure 'user_id' appears only once. 226 while self.__user_ids.count(user_id) > 1: 227 self.__user_ids.remove(user_id)
228 229
230 - def __delitem__(self, index):
231 del self.__user_ids[index]
232 233
234 - def append(self, user_id):
235 # Don't add a given user more than once. 236 if user_id not in self.__user_ids: 237 self.__user_ids.append(user_id)
238 239
240 - def remove(self, user_id):
241 self.__user_ids.remove(user_id)
242 243 244
245 -class Authenticator:
246 """Base class for authentication classes. 247 248 An 'Authenticator' object is responsible for determining the 249 authenticity of a user. The inputs to an authentication action 250 depend on the mechanism with which the user communicates with the 251 program -- for instance, a web request, command invocation, or email 252 message.""" 253 254
255 - def AuthenticateDefaultUser(self):
256 """Authenticate for the default user, if one is provided. 257 258 returns -- The user ID of the default user.""" 259 260 raise NotImplementedError
261 262
263 - def AuthenticateWebRequest(self, request):
264 """Authenticate a login web request. 265 266 'request' -- A web request containing the user's login 267 information. 268 269 returns -- The user ID of the authenticated user.""" 270 271 raise NotImplementedError
272 273 274
275 -class DefaultDatabase:
276 default_user = User("default_user", "default") 277
278 - def GetDefaultUserId(self):
279 return self.default_user.GetId()
280 281
282 - def keys(self):
283 return [self.GetDefaultUserId()]
284 285
286 - def __getitem__(self, user_id):
287 default_user_id = self.default_user.GetId() 288 if user_id == default_user_id: 289 return self.default_user 290 else: 291 raise KeyError, user_id
292 293
294 - def get(self, user_id, default=None):
295 default_user_id = self.default_user.GetId() 296 if user_id == default_user_id: 297 return self.default_user 298 else: 299 return None
300 301
302 - def GetGroupIds(self):
303 return []
304 305
306 - def GetGroup(self, group_id):
307 raise KeyError, "no such group"
308 309 310
311 -class DefaultAuthenticator(Authenticator):
312 """Authenticator for only a single user, "default_user".""" 313
314 - def AuthenticateDefaultUser(self):
316 317
318 - def AuthenticateWebRequest(self, request):
320 321 322
323 -class XmlDatabase:
324 """An XML user database. 325 326 An object of this class behaves as a read-only map from user IDs to 327 'User' objects.""" 328
329 - def __init__(self, database_path):
330 """Read in the XML user database.""" 331 332 document = xmlutil.load_xml_file(database_path) 333 self.__path = database_path 334 335 node = document.documentElement 336 assert node.tagName == "users" 337 338 self.__users = {} 339 self.__groups = {} 340 self.__default_user_id = None 341 342 # Load users. 343 for user_node in node.getElementsByTagName("user"): 344 user = get_user_from_dom(user_node) 345 # Store the account. 346 self.__users[user.GetId()] = user 347 # Make note if this is the default user. 348 if user.GetRole() == "default": 349 if self.__default_user_id is not None: 350 # More than one default user was specified. 351 raise XmlDatabaseError, "multiple default users" 352 self.__default_user_id = user.GetId() 353 354 # Load groups. 355 for group_node in node.getElementsByTagName("group"): 356 group = get_group_from_dom(group_node) 357 # Make sure all the user IDs listed for this group are IDs 358 # we know. 359 for user_id in group: 360 if not self.__users.has_key(user_id): 361 raise XmlDatabaseError, \ 362 'user "%s" in group "%s" is unknown' \ 363 % (user_id, group.GetId()) 364 # Store the group. 365 self.__groups[group.GetId()] = group
366 367 368
369 - def GetDefaultUserId(self):
370 """Return the ID of the default user, or 'None'.""" 371 372 return self.__default_user_id
373 374
375 - def GetGroupIds(self):
376 """Return the IDs of user groups.""" 377 378 return self.__groups.keys()
379 380
381 - def GetGroup(self, group_id):
382 """Return the group with ID 'group_id'.""" 383 384 return self.__groups[group_id]
385 386
387 - def Write(self):
388 """Write out the user database.""" 389 390 # Create a DOM document for the database. 391 document = xmlutil.create_dom_document( 392 public_id = "User", 393 document_element_tag="users" 394 ) 395 document_element = document.documentElement 396 # Create elements for users in the database. 397 for user in self.__users.values(): 398 user_element = create_dom_for_user(document, user) 399 document_element.appendChild(user_element) 400 # Create elements for user groups. 401 for group in self.__groups.values(): 402 group_element = create_dom_for_group(document, group) 403 document_element.appendChild(group_element) 404 # Write out the database. 405 document.writexml(open(self.__path, "w"))
406 407 408 # Methods for emulating a map object. 409
410 - def __getitem__(self, user_id):
411 return self.__users[user_id]
412 413
414 - def get(self, user_id, default=None):
415 return self.__get(user_id, default)
416 417
418 - def keys(self):
419 return self.__users.keys()
420 421 422
423 -class XmlDatabaseAuthenticator(Authenticator):
424 """An authenticator based on contents of the XML user database.""" 425
426 - def __init__(self, database):
427 """Create a new authenticator. 428 429 Authentication is performed based on information stored in 430 'XmlDatabase' instance 'database'.""" 431 432 assert isinstance(database, XmlDatabase) 433 self.__database = database
434 435
436 - def AuthenticateDefaultUser(self):
437 # Try to perform a password authentication for the default user 438 # with an empty password. 439 default_user_id = self.__database.GetDefaultUserId() 440 return self.AuthenticatePassword(default_user_id, "")
441 442
443 - def AuthenticateWebRequest(self, request):
444 # Extract the user name and password from the web request. 445 user_name = request["_login_user_name"] 446 password = request["_login_password"] 447 return self.AuthenticatePassword(user_name, password)
448 449
450 - def AuthenticatePassword(self, user_name, password):
451 try: 452 # Look up the user ID. 453 user = database[user_name] 454 expected_password = user._User__authentication.get("password", 455 None) 456 except KeyError: 457 # No user was found with this user ID. 458 expected_password = None 459 if expected_password is None \ 460 or expected_password != password: 461 # No password specified for this user, or the provided 462 # password doesn't match. 463 raise AuthenticationError, "invalid user name/password" 464 # Is the account enabled? 465 if not user.IsEnabled(): 466 # No. Prevent authentication. 467 raise AccountDisabledError, user_name 468 return user_name
469 470 471 472 ######################################################################## 473 # functions 474 ######################################################################## 475
476 -def load_xml_database(path):
477 """Load users from XML database at 'path' and set up authenticator.""" 478 479 global database 480 global authenticator 481 482 # Use a finally block to make sure either both are set, or neither. 483 try: 484 xml_database = XmlDatabase(path) 485 xml_authenticator = XmlDatabaseAuthenticator(xml_database) 486 except: 487 raise 488 else: 489 database = xml_database 490 authenticator = xml_authenticator
491 492
493 -def get_user_from_dom(user_node):
494 """Construct a 'User' object from a user DOM element. 495 496 'user_node' -- A "user" DOM element. 497 498 returns -- A 'User' object.""" 499 500 assert user_node.tagName == "user" 501 502 # The user ID is stored in the ID attribute of the user element. 503 user_id = user_node.getAttribute("id") 504 # Also the role. 505 role = user_node.getAttribute("role") 506 # Determine whether the account is disabled. 507 enabled = user_node.getAttribute("disabled") == "no" 508 # Read properties. 509 info_properties = _get_dom_properties(user_node, "info") 510 auth_properties = _get_dom_properties(user_node, "authentication") 511 conf_properties = _get_dom_properties(user_node, "configuration") 512 # Create the user object. 513 return User(user_id, role, enabled, 514 info_properties, conf_properties, auth_properties)
515 516
517 -def _get_dom_properties(node, tag):
518 """Return a dictionary of properties from a user DOM element node. 519 520 'node' -- A "user" DOM element. 521 522 'tag' -- The tag of the child element in which to look for 523 properties. 524 525 returns -- A map from property names to values.""" 526 527 # Find all child elements of 'node' with 'tag'. 528 elements = node.getElementsByTagName(tag) 529 if len(elements) == 0: 530 # The child may be omitted; that's OK. 531 return {} 532 # There element, if provided, should be unique. 533 assert len(elements) == 1 534 element = elements[0] 535 # Start a map of properties. 536 properties = {} 537 # Loop over property sub-elements. 538 for property_node in element.getElementsByTagName("property"): 539 # The property name is stored in the name attribute. 540 name = property_node.getAttribute("name") 541 # The property value is stored as a child text node. 542 value = xmlutil.get_dom_text(property_node) 543 properties[name] = value 544 return properties
545 546
547 -def create_dom_for_user(document, user):
548 """Create a DOM element node for 'user'. 549 550 'document' -- The DOM document object in which to create the 551 element. 552 553 'user' -- A 'User' instance. 554 555 returns -- A DOM element node.""" 556 557 # Create the user element. 558 element = document.createElement("user") 559 element.setAttribute("id", user.GetId()) 560 element.setAttribute("role", user.GetRole()) 561 if user.IsEnabled(): 562 disabled_attribute = "no" 563 else: 564 disabled_attribute = "yes" 565 element.setAttribute("disabled", disabled_attribute) 566 # Add informational properties. 567 info_properties_element = document.createElement("info") 568 _create_dom_properties(info_properties_element, user._User__info) 569 element.appendChild(info_properties_element) 570 # Add authentication properties. 571 auth_properties_element = document.createElement("authentication") 572 _create_dom_properties(auth_properties_element, user._User__authentication) 573 element.appendChild(auth_properties_element) 574 # Add configuration properties. 575 conf_properties_element = document.createElement("configuration") 576 _create_dom_properties(conf_properties_element, user._User__configuration) 577 element.appendChild(conf_properties_element) 578 # All done. 579 return element
580 581
582 -def _create_dom_properties(element, properties):
583 """Add user properties to a DOM element. 584 585 'element' -- A DOM element node to which properties are to be added 586 as children. 587 588 'properties' -- A map from property names to values.""" 589 590 document = element.ownerDocument 591 for name, value in properties.items(): 592 prop_element = xmlutil.create_dom_text_element( 593 document, "property", str(value)) 594 prop_element.setAttribute("name", name) 595 element.appendChild(prop_element)
596 597
598 -def get_group_from_dom(group_node):
599 """Construct a 'Group' object from a DOM element. 600 601 'group_node' -- A DOM "group" element node. 602 603 returns -- A 'Group' object.""" 604 605 assert group_node.tagName == "group" 606 607 group_id = group_node.getAttribute("id") 608 user_ids = xmlutil.get_child_texts(group_node, "user-id") 609 # Make the group. 610 return Group(group_id, user_ids)
611 612
613 -def create_dom_for_group(document, group):
614 """Create a DOM element node for 'group'. 615 616 'document' -- The DOM document object in which to create the 617 element. 618 619 'group' -- A 'Group' instance. 620 621 returns -- A DOM element node.""" 622 623 element = document.createElement("group") 624 element.setAttribute("id", group.GetId()) 625 for user_id in group: 626 user_id_element = xmlutil.create_dom_text_element( 627 document, "user-id", user_id) 628 element.appendChild(user_id_element) 629 return element
630 631 632 ######################################################################## 633 # variables 634 ######################################################################## 635 636 database = DefaultDatabase() 637 authenticator = DefaultAuthenticator() 638 639 ######################################################################## 640 # Local Variables: 641 # mode: python 642 # indent-tabs-mode: nil 643 # fill-column: 72 644 # End: 645