0001"""Objects representing users, their passwords, roles and groups
0002
0003The objects defined in this file used in conjunction with authentication,
0004authorization and permission objects form a complete user management system.
0005
0006However, there is no requirement to use this user management API at all. If you
0007define your own authentication checks by specifying your own
0008``valid_password()`` or ``digest_password()`` methods when setting up the
0009authentication middleware, and you create your own permissions objects based on
0010your own requriements then you will have no need for this implementation. It is
0011simply provided as a useful default implementation for users looking for a
0012simple, ready made solution that doesn't require any integration.
0013
0014The implementation consists of the following:
0015
0016``authkit.authenticate.valid_password()``
0017 A ``valid_password()`` implementation used by default with the
0018 ``basic`` or ``form`` authentication methods that checks usernames and
0019 passwords against those defined in the user management API object.
0020
0021``authkit.authenticate.digest_password()``
0022 A ``digest_password()`` implementation used by default with the
0023 ``digest`` authentication which produces a digest from the users set up
0024 in the user management API object.
0025
0026``authkit.permissions.HasAuthKitRole``
0027 A permission object which checks the signed in user's role from the
0028 user management API object.
0029
0030``authkit.permissions.HasAuthKitGroup``
0031 A permission object which checks the signed in user's group from the
0032 user management API object.
0033
0034``authkit.permissions.ValidAuthKitUser``
0035 A permission object which checks the signed in user is defined in the
0036 user management API object.
0037
0038Of course since the user management API is fairly generic, it is possible to
0039have different implementations. This module has two implementations both
0040derived from the base ``Users`` class. They are ``UsersFromString`` and
0041``UsersFromFile``. By default, ``authkit.authenticate.middleware`` uses
0042``UsersFromString`` and expects you to specify your users, groups and roles as
0043a string in the config file in the way described in the main AuthKit manual but
0044you can also specify you wish to use the alternative implementation to load
0045your user data from a file.
0046
0047Of course you are also free to create your own implementation derived from
0048``Users`` and as long as it keeps the same API, the existing functions and
0049permissions mentioned earlier will work without modification when using your
0050user management API object. This means that if your requirements are very
0051simple you might prefer to create a custom ``Users`` object rather than
0052integrate AuthKit into your project in the slightly lower level fashion by
0053defining the ``valid_password()`` and ``digest_password()`` functions and any
0054necessary permissions.
0055
0056If you are using the authentication middleware with users, the ``Users`` object
0057will be available in your code as ``environ[authkit.users]``.
0058"""
0059
0060import os.path
0061import md5 as _md5
0062from authkit.authenticate import AuthKitConfigError
0063
0064
0065
0066
0067
0068def md5(password, secret=''):
0069 result = _md5.md5(password)
0070 result.update(secret)
0071 return result.hexdigest()
0072
0073
0074
0075
0076
0077class AuthKitNoSuchUserError(Exception):
0078 pass
0079
0080class AuthKitNoSuchRoleError(Exception):
0081 pass
0082
0083class AuthKitNoSuchGroupError(Exception):
0084 pass
0085
0086class AuthKitNotSupportedError(Exception):
0087 pass
0088
0089class AuthKitError(Exception):
0090 pass
0091
0092
0093
0094
0095
0096class Users(object):
0097 """
0098 Base class from which all other Users classes should be derived.
0099 """
0100 def __init__(self, data, encrypt=None):
0101 self.data = data
0102 if encrypt is None:
0103 def encrypt(password):
0104 return password
0105 self.encrypt = encrypt
0106
0107
0108 def user_create(self, username, password, group=None):
0109 """
0110 Create a new user with the username, password and group name specified.
0111 """
0112 raise AuthKitNotSupportedError(
0113 "The %s implementation of the User Management API doesn't support this method"%(
0114 self.__class__.__name__
0115 )
0116 )
0117
0118 def role_create(self, role):
0119 """
0120 Add a new role to the system
0121 """
0122 raise AuthKitNotSupportedError(
0123 "The %s implementation of the User Management API doesn't support this method"%(
0124 self.__class__.__name__
0125 )
0126 )
0127
0128 def group_create(self, group):
0129 """
0130 Add a new group to the system
0131 """
0132 raise AuthKitNotSupportedError(
0133 "The %s implementation of the User Management API doesn't support this method"%(
0134 self.__class__.__name__
0135 )
0136 )
0137
0138
0139 def user_delete(self, username):
0140 """
0141 Remove the user with the specified username
0142 """
0143 raise AuthKitNotSupportedError(
0144 "The %s implementation of the User Management API doesn't support this method"%(
0145 self.__class__.__name__
0146 )
0147 )
0148
0149 def role_delete(self, role):
0150 """
0151 Remove the role specified. Rasies an exception if the role is still in use.
0152 To delete the role and remove it from all existing users use ``role_delete_cascade()``
0153 """
0154 raise AuthKitNotSupportedError(
0155 "The %s implementation of the User Management API doesn't support this method"%(
0156 self.__class__.__name__
0157 )
0158 )
0159
0160 def group_delete(self, group):
0161 """
0162 Remove the group specified. Rasies an exception if the group is still in use.
0163 To delete the group and remove it from all existing users use ``group_delete_cascade()``
0164 """
0165 raise AuthKitNotSupportedError(
0166 "The %s implementation of the User Management API doesn't support this method"%(
0167 self.__class__.__name__
0168 )
0169 )
0170
0171
0172 def role_delete_cascade(self, role):
0173 """
0174 Remove the role specified and remove the role from any users who used it
0175 """
0176 raise AuthKitNotSupportedError(
0177 "The %s implementation of the User Management API doesn't support this method"%(
0178 self.__class__.__name__
0179 )
0180 )
0181
0182 def group_delete_cascade(self, group):
0183 """
0184 Remove the group specified and remove the group from any users who used it
0185 """
0186 raise AuthKitNotSupportedError(
0187 "The %s implementation of the User Management API doesn't support this method"%(
0188 self.__class__.__name__
0189 )
0190 )
0191
0192
0193 def user_exists(self, username):
0194 """
0195 Returns ``True`` if a user exists with the given username, ``False`` otherwise. Usernames are case insensitive.
0196 """
0197 raise AuthKitNotSupportedError(
0198 "The %s implementation of the User Management API doesn't support this method"%(
0199 self.__class__.__name__
0200 )
0201 )
0202
0203 def role_exists(self, role):
0204 """
0205 Returns ``True`` if the role exists, ``False`` otherwise. Roles are case insensitive.
0206 """
0207 raise AuthKitNotSupportedError(
0208 "The %s implementation of the User Management API doesn't support this method"%(
0209 self.__class__.__name__
0210 )
0211 )
0212
0213 def group_exists(self, group):
0214 """
0215 Returns ``True`` if the group exists, ``False`` otherwise. Groups are case insensitive.
0216 """
0217 raise AuthKitNotSupportedError(
0218 "The %s implementation of the User Management API doesn't support this method"%(
0219 self.__class__.__name__
0220 )
0221 )
0222
0223
0224 def list_roles(self):
0225 """
0226 Returns a lowercase list of all role names ordered alphabetically
0227 """
0228 raise AuthKitNotSupportedError(
0229 "The %s implementation of the User Management API doesn't support this method"%(
0230 self.__class__.__name__
0231 )
0232 )
0233
0234 def list_users(self):
0235 """
0236 Returns a lowecase list of all usernames ordered alphabetically
0237 """
0238 raise AuthKitNotSupportedError(
0239 "The %s implementation of the User Management API doesn't support this method"%(
0240 self.__class__.__name__
0241 )
0242 )
0243
0244 def list_groups(self):
0245 """
0246 Returns a lowercase list of all groups ordered alphabetically
0247 """
0248 raise AuthKitNotSupportedError(
0249 "The %s implementation of the User Management API doesn't support this method"%(
0250 self.__class__.__name__
0251 )
0252 )
0253
0254
0255 def user(self, username):
0256 """
0257 Returns a dictionary in the following format:
0258
0259 .. code-block :: Python
0260
0261 {
0262 'username': username,
0263 'group': group,
0264 'password': password,
0265 'roles': [role1,role2,role3... etc]
0266 }
0267
0268 The role names are ordered alphabetically
0269 Raises an exception if the user doesn't exist.
0270 """
0271 raise AuthKitNotSupportedError(
0272 "The %s implementation of the User Management API doesn't support this method"%(
0273 self.__class__.__name__
0274 )
0275 )
0276
0277 def user_roles(self, username):
0278 """
0279 Returns a list of all the role names for the given username ordered alphabetically. Raises an exception if
0280 the username doesn't exist.
0281 """
0282 raise AuthKitNotSupportedError(
0283 "The %s implementation of the User Management API doesn't support this method"%(
0284 self.__class__.__name__
0285 )
0286 )
0287
0288 def user_group(self, username):
0289 """
0290 Returns the group associated with the user or ``None`` if no group is associated.
0291 Raises an exception is the user doesn't exist.
0292 """
0293 raise AuthKitNotSupportedError(
0294 "The %s implementation of the User Management API doesn't support this method"%(
0295 self.__class__.__name__
0296 )
0297 )
0298
0299 def user_password(self, username):
0300 """
0301 Returns the password associated with the user or ``None`` if no password exists.
0302 Raises an exception is the user doesn't exist.
0303 """
0304 raise AuthKitNotSupportedError(
0305 "The %s implementation of the User Management API doesn't support this method"%(
0306 self.__class__.__name__
0307 )
0308 )
0309
0310 def user_has_role(self, username, role):
0311 """
0312 Returns ``True`` if the user has the role specified, ``False`` otherwise. Raises an exception if the user doesn't exist.
0313 """
0314 raise AuthKitNotSupportedError(
0315 "The %s implementation of the User Management API doesn't support this method"%(
0316 self.__class__.__name__
0317 )
0318 )
0319
0320 def user_has_group(self, username, group):
0321 """
0322 Returns ``True`` if the user has the group specified, ``False`` otherwise. The value for ``group`` can be ``None`` to test that the user doesn't belong to a group. Raises an exception if the user doesn't exist.
0323 """
0324 raise AuthKitNotSupportedError(
0325 "The %s implementation of the User Management API doesn't support this method"%(
0326 self.__class__.__name__
0327 )
0328 )
0329
0330 def user_has_password(self, username, password):
0331 """
0332 Returns ``True`` if the user has the password specified, ``False`` otherwise. Passwords are case sensitive.
0333 Raises an exception if the user doesn't exist.
0334 """
0335 raise AuthKitNotSupportedError(
0336 "The %s implementation of the User Management API doesn't support this method"%(
0337 self.__class__.__name__
0338 )
0339 )
0340
0341 def user_set_username(self, username, new_username):
0342 """
0343 Sets the user's username to the lowercase of new_username.
0344 Raises an exception if the user doesn't exist or if there is already a user with the username specified by ``new_username``.
0345 """
0346 raise AuthKitNotSupportedError(
0347 "The %s implementation of the User Management API doesn't support this method"%(
0348 self.__class__.__name__
0349 )
0350 )
0351
0352 def user_set_group(self, username, group, add_if_necessary=False):
0353 """
0354 Sets the user's group to the lowercase of ``group`` or ``None``. If the group doesn't exist and ``add_if_necessary`` is ``True`` the group will also be added. Otherwise an ``AuthKitNoSuchGroupError`` will be raised.
0355 Raises an exception if the user doesn't exist.
0356 """
0357 raise AuthKitNotSupportedError(
0358 "The %s implementation of the User Management API doesn't support this method"%(
0359 self.__class__.__name__
0360 )
0361 )
0362
0363 def user_add_role(self, username, role, add_if_necessary=False):
0364 """
0365 Sets the user's role to the lowercase of ``role``. If the role doesn't exist and ``add_if_necessary`` is ``True`` the role will also be added. Otherwise an ``AuthKitNoSuchRoleError`` will be raised.
0366 Raises an exception if the user doesn't exist.
0367 """
0368 raise AuthKitNotSupportedError(
0369 "The %s implementation of the User Management API doesn't support this method"%(
0370 self.__class__.__name__
0371 )
0372 )
0373
0374 def user_remove_role(self, username, role):
0375 """
0376 Removes the role from the user specified by ``username``. Raises an exception if the user doesn't exist.
0377 """
0378 raise AuthKitNotSupportedError(
0379 "The %s implementation of the User Management API doesn't support this method"%(
0380 self.__class__.__name__
0381 )
0382 )
0383
0384 def user_remove_group(self, username):
0385 """
0386 Sets the group to ``None`` for the user specified by ``username``. Raises an exception if the user doesn't exist.
0387 """
0388 raise AuthKitNotSupportedError(
0389 "The %s implementation of the User Management API doesn't support this method"%(
0390 self.__class__.__name__
0391 )
0392 )
0393
0394class UsersReadOnly(Users):
0395 """
0396 Like the ``Users`` class except that user information is read only. All the information
0397 is obtained from the attributes self.usernames, self.passwords, self.roles, self.groups
0398 which are expected to be setup in ``__init__()``.
0399
0400 ``usernames`` should be a list of lowercase usernames
0401 ``passwords``, ``groups`` should be a dictionary where the keys are lowercase usernames
0402 and the values are the corresponding lowercase group name or password.
0403 ``roles`` is similar to ``passwords`` and ``groups`` except values are lists of lowercase role names.
0404 """
0405
0406
0407 def user_exists(self, username):
0408 """
0409 Returns ``True`` if a user exists with the given username, ``False`` otherwise. Usernames are case insensitive.
0410 """
0411 return username.lower() in self.list_users()
0412
0413 def role_exists(self, role):
0414 """
0415 Returns ``True`` if the role exists, ``False`` otherwise. Roles are case insensitive.
0416 """
0417 return role.lower() in self.list_roles()
0418
0419 def group_exists(self, group):
0420 """
0421 Returns ``True`` if the group exists, ``False`` otherwise. Groups are case insensitive.
0422 """
0423 return group.lower() in self.list_groups()
0424
0425
0426 def list_roles(self):
0427 """
0428 Returns a lowercase list of all role names ordered alphabetically
0429 """
0430 roles = []
0431 for k,v in self.roles.items():
0432 for role in v:
0433 role_ = role.lower()
0434 if role_ and role_ not in roles:
0435 roles.append(role_)
0436 roles.sort()
0437 return roles
0438
0439 def list_users(self):
0440 """
0441 Returns a lowecase list of all usernames ordered alphabetically
0442 """
0443
0444 return [u for u in self.usernames]
0445
0446 def list_groups(self):
0447 """
0448 Returns a lowercase list of all groups ordered alphabetically
0449 """
0450 groups = []
0451 for k,v in self.groups.items():
0452 if v and v not in groups:
0453 groups.append(v)
0454 groups.sort()
0455 return groups
0456
0457
0458 def user(self, username):
0459 """
0460 Returns a dictionary in the following format:
0461
0462 .. code-block :: Python
0463
0464 {
0465 'username': username,
0466 'group': group,
0467 'password': password,
0468 'roles': [role1,role2,role3... etc]
0469 }
0470
0471 The role names are ordered alphabetically
0472 Raises an exception if the user doesn't exist.
0473 """
0474 username = username.lower()
0475 if not username in self.usernames:
0476 raise AuthKitNoSuchUserError("No user named %r"%username)
0477 else:
0478 return {
0479 'username': username,
0480 'group': self.user_group(username),
0481 'password': self.user_password(username),
0482 'roles': self.user_roles(username),
0483 }
0484
0485 def user_roles(self, username):
0486 """
0487 Returns a list of all the role names for the given username ordered alphabetically. Raises an exception if
0488 the username doesn't exist.
0489 """
0490 username = username.lower()
0491 if not username in self.usernames:
0492 raise AuthKitNoSuchUserError("No user named %r"%username)
0493 return self.roles[username]
0494
0495 def user_group(self, username):
0496 """
0497 Returns the group associated with the user or ``None`` if no group is associated.
0498 Raises an exception is the user doesn't exist.
0499 """
0500 username = username.lower()
0501 if not username in self.usernames:
0502 raise AuthKitNoSuchUserError("No user named %r"%username)
0503 return self.groups[username]
0504
0505 def user_password(self, username):
0506 """
0507 Returns the password associated with the user or ``None`` if no password exists.
0508 Raises an exception is the user doesn't exist.
0509 """
0510 username = username.lower()
0511 if not username in self.usernames:
0512 raise AuthKitNoSuchUserError("No user named %r"%username)
0513 return self.passwords[username]
0514
0515 def user_has_role(self, username, role):
0516 """
0517 Returns ``True`` if the user has the role specified, ``False`` otherwise. Raises an exception if the user doesn't exist.
0518 """
0519 return role.lower() in self.user_roles(username)
0520
0521 def user_has_group(self, username, group):
0522 """
0523 Returns ``True`` if the user has the group specified, ``False`` otherwise. Raises an exception if the user doesn't exist.
0524 """
0525 return group.lower() == self.user_group(username.lower())
0526
0527 def user_has_password(self, username, password):
0528 """
0529 Passwords are case sensitive.
0530 Returns ``True`` if the user has the password specified, ``False`` otherwise.
0531 Raises an exception if the user doesn't exist.
0532 """
0533 return self.encrypt(password) == self.user_password(username.lower())
0534
0535def parse(data):
0536 """
0537 Parses the user data
0538 """
0539 passwords = {}
0540 roles = {}
0541 groups = {}
0542 counter = 1
0543 for line in data.split('\n'):
0544 line = line.strip()
0545 if not line:
0546 continue
0547 role_list = []
0548 parts = line.split(' ')
0549 if len(parts) > 1:
0550 for role in parts[1:]:
0551 if role:
0552 role_list.append(role.strip().lower())
0553 role_list.sort()
0554 group = None
0555 parts = parts[0].split(':')
0556 if len(parts) > 1:
0557 password = parts[1]
0558 if not password:
0559 'Password for %s is empty'%(
0560 username,
0561 )
0562 username = parts[0].lower()
0563 if not username:
0564 'Username on line %s is empty'%(
0565 counter,
0566 )
0567 if len(parts) == 3:
0568 group = parts[2].lower()
0569 if len(parts) <= 1 or len(parts) > 3:
0570 raise AuthKitConfigError(
0571 'Syntax error on line %s of authenticate list'%(
0572 counter,
0573 )
0574 )
0575 if passwords.has_key(username):
0576 raise AuthKitConfigError(
0577 'Username %r defined twice in authenticate list %r'%(
0578 username,
0579 passwords
0580 )
0581 )
0582 <