# authentication.py: user authentication and management for web.py The goal of this project is to provide a ready-to-use microframework for creating and managing users, secure storage of authentication data, and permission management for your [web.py](http://www.webpy.org/) site. ## Dependencies * [web.py](http://www.webpy.org/) (obviously) * [nose](http://code.google.com/p/python-nose/) (for running tests only) * [sqlite3](http://www.sqlite.org/) (for running tests only) ## Project status This project is currently under heavy development. Most facilities either don't work, or are not even designed yet. We have scheduled the release of 0.1 with fully functional user management during October 2010. Here is the brief roadmap: * v0.1 (Oct 2010): fully functional user management * v0.2 (n/a): fully functional permissions management * v0.3 (n/a): fully functional group management Keep track of issues in the [issue tracker](http://github.com/foxbunny/authentication.py/issues) to stay on top of the changes that will end up in the releases. Each of the version will add one module to the project. User management is in the ``auth`` module, which is curretly being worked on. Permissions management will be pu in ``perm`` module, and groups will reside in ``group`` module. The project is definitely not dead. It was put on ice a year ago, but I've started developing web apps again, so this project has become relevant again. Expect updates soon. The v0.1 release is expected in the following weeks. The first release will _not_ include the example app, as I have no time to work on it at this time. Therefore, I will not integrate the example into the master or release branches. The README file, and the tests should be sufficient to cover the usage. ## Before using authentication.py The first thing you need to do is tell authentication.py which database it will be using. To do this, add ``authdb`` key to ``web.config``. Here's an example using PostgreSQL: import web somedb = web.database(dbn='postgres', db='my_app_db', user='postgres') web.config.authdb = somedb You can also have a separate database just for authentication (e.g, if you want to use a common authentication database between different apps). Just assign wahtever database you want to use to ``web.config.authdb``. If you want to take advantage of messaging facilities, you also need to define a ``web.config.authmail`` key, and assign it a dictionary of options: import web web.config.authmail = {'sender': 'default.sender@yoursite.com', 'activation_subject': 'Activate your account', 'reset_subject': 'Your password has been reset', 'delete_subject': Your account was removed', 'suspend_subject': 'Your account is suspended'} Defaults for the subject messages are: * ``activation_subject``: _Account activation_ * ``reset_subject``: _Password reset_ * ``delete_subject``: _Account removed_ * ``suspend_subject``: _Account suspended_ If you are happy with these defaults, you don't have to include them in the configuration dictionary. Sender is still required. ## User object ``User`` object is the key component of the ``auth`` module. It has both class methods and instance methods that facilitate its functionality. ### Creating a new user To create a user, you can use the ``User`` class from the ``authenticationpy.auth`` module. Before actually creating a user, you need to specify the three required properties: >>> from authenticationpy.auth import User >>> user = User(username='myuser' ... email='user@someserver.com') >>> user.password = 'clear-text password' >>> user.create() A user account is not atomatically activated after creation. The ``activate`` method must be called on the instance at some point to activate it. ### Creating a user with activation e-mail If you want to send out an activation e-mail, you can do so by specifying the activation message: >>> from authenticationpy.auth import User >>> user = User(username="myuser", ... email="user@someserver.com") >>> user.password = 'clear-text password' >>> user.create(message=""" Please activate your account at http://mysite.com/activate/$url """) The format of the activation message is arbitrary, and you can use any template you like. The template variables available for customizing the message dynamically are: * ``$url``: activation URL suffix that is generated automatically * ``$username``: username * ``$email``: the e-mail address used for creating the user * ``$password``: the clear-text password Note that the clear-text password is only stored in memory, and cannot be retrieved later. If you want to send activation e-mails more than once, do not include the password in your template. If the clear-text password is lost, the template variable will be replaced with an empty string. ### Setting activation information and obtaining the action code If you have your own system for notifying users about activation, and then requesting activation action, you can use the ``set_activation`` method to set the user account to an _activatable_ state. Here's an example: >>> from authenticationpy.auth import User >>> user = User(username='myuser', ... email='user@someserver.com') >>> user.password = 'clear-text password' >>> code = user.set_activation() >>> user.create() In the above example, the ``code`` variable is assigned an activation code that is generated by the ``set_activation`` method. This code is the same one used for the activation URL, and it's a SHA-256 hexdigest. ### Finding a user account by activation code This does not strictly apply to activation. It also applies to all cases where you may be using the action code (such as account removal confirmation, or password reset). To get a user account by action (activation) code, you can use the ``get_user_by_act_code`` class method. Here is an example: >>> from authenticationpy.auth import User >>> code = some_code # a SHA-256 hexdigest >>> user = User.get_user_by_act_code(code) # do something with the user account ### Testing the timeliness of user action Once a user clicks on the activation link (in fact, this applies to all cases where you are using the action code for confirming user action), you know the code, and you can also get the associated user account. If you don't impose any deadlines for user action, you may proceed to activate the account, or any other action that was confirmed. However, if you do impose deadlines, there is a convenience method that you can use to test if user action was timely. To test the timeliness of user actions, you can call the ``is_interaction_timely`` method on any ``User`` instance. >>> import datetime >>> from authenticationpy.auth import User >>> code = some_code # a SHA-256 hexdigest >>> user = User.get_user_by_act_code(code) >>> deadline = 172800 # this is the deadline in seconds (48 hours) >>> user.is_interaction_timely('a', deadline) True This method requires two arguments: * ``type``: must be ``'a'`` (activation), ``'d'`` (delete), or ``'r'`` (reset password) * ``deadline``: action deadline in seconds It returns a boolean value of the success status. If there are no actions to confirm (e.g., no pending activation or confirmation), ``UserAccountError`` is raised. Clearing user action data ------------------------- Once user action was confirmed, it is no longer necessary to keep interaction data in the database. To clear the data, you can use the ``clear_interaction`` instance method. >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.clear_interaction() >>> user.store() After clearing the interaction data, the account must be stored to make the changes permanent. Activating a user account ------------------------- Calling ``activate`` on User instance activates it. Although it is activated in-memory, it is not saved as activated, so you need to store changes before a user can log in. >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.activate() >>> user.store() Creating a user with activation ------------------------------- You can both create and activate a user account at the same time. To do that, you can pass the ``activated`` argument to the ``create`` method: >>> from authenticationpy.auth import User >>> user = User(username='myuser', ... email='user@someserver.com') >>> user.create(activated=True) >>> same_user = User.get_user(username='myuser') >>> same_user.active True This results in a single database transaction, which is more efficient than calling ``create`` without ``activated`` argument, and then calling ``activate`` explicitly. Getting the user record ----------------------- To get the user record, you can call the ``get_user`` class method:: >>> from authenticationpy.auth import User >>> user = User.get_user(email="user@someserver.com") You can use either the username or the e-mail address as an argument for the ``get_user`` method. ### Authenticating the user To check the user password (to authenticate it), you must call the ``authenticate`` method on the user object: >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.authenticate('clear-text password') True The method returns True or False depending on whether authentication was successful. Note that due to web.py's session handling, authentication.py will _not_ automatically assign users to a session. It is your responsibility to do so. ### Password length constraints Default password minimum length is 4 characters. If you want your users to use a longer minimum length password (or shorter), you can customize the minimum length by setting ``web.config.min_pwd_length`` configuration variable. In case the password you are trying to set is shorter than minimum length, ``ValueError`` is raised. The same exception is raised when password is 0-length. Even if you set ``web.config.min_pwd_length`` to 0, 0-length passwords are not allowed. The absolute minimum allowed password length is 1. ### Resetting the password You can reset the user password in two ways. You can simply assign a new clear-text password to the password property or you can call the ``reset_password`` method, which can optionally send out a notification or confirmation e-mail. Here is an example using the property method:: >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.password = 'new password' >>> user.store() And here is an example with the ``reset_password`` method:: >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.reset_password('new password', message='Your password is now $password') The template for the ``message`` argument can contain the following variables: * ``$url``: automatically generated confirmation url suffix * ``$username``: username * ``$email``: the user's e-mail address * ``$password``: the new cleartext password If you are not sending any confirmation e-mail, but you still use a different confirmation method, you can use the ``confirmation`` argument. If you pass this argument with the value of ``True``, ``reset_password`` will behave as if you have passed it the ``message`` argument. You can also set the new password even if you send the e-mail. For example, if you want to simply notify your user, rather than request an action, you can pass the ``confirmation`` argument with the value of ``False`` and omit the URL from URL from your message. ### Confirming password resets If you have decided to delay confirmation and send out a confirmation e-mail when using ``password_reset`` method, you need to explicitly assign the new password. The pending password (password that is still not confirmed) is stored in encrypted form in the database. In order to assign that password as the new password, you can call ``confirm_password``. >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.confirm_password() >>> user.store() As you can see, you have to call ``store`` to make the changes permanent. ### Sending e-mails to a user Arbitrary e-mail messages can be sent to users using the ``send_email`` method. Here's a simple example: >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.send_email(message="Hi!") As will all other e-mail facilities, you can add template variables to the e-mail message, but for regular e-mail, you can use any template variables you like. Just pass any variable as a keyword argument: >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.send_email(message="$greeting, $username", ... greeting="Hello,", ... username=user.username) ### Deleting a user To delete a user (i.e, permanently remove its records), you can use the ``delete`` class method: >>> from authenticationpy.auth import User >>> User.delete(username='myuser') You can also send out a notification or confirmation e-mail: >>> from authenticationpy.auth import User >>> User.delete(username='myuser', ... message=""" ... Please confirm this by clicking on this link: ... http://mysite.com/confirm/$url ... """) If you decide to send a confirmation e-mail, the user account is not removed until its removal is confirmed by ``confirm_delete`` class method. Available e-mail message template variables are:: * ``$url``: automatically generated confirmation url suffix * ``$username``: username * ``$email``: the user's e-mail address ### Confirming user account removal If you have used the confirmation e-mail functionality when deleting a user account, you need to explcitly confirm account removal. You can do that by using the ``confirm_delete`` class method:: >>> from authenticationpy.auth import User >>> User.confirm_delete(username='myuser') ### Suspending an account User account can be deleted, and it's gone forever. If you only want to disable authentication for a particular account, you can suspend it instead of deleting it. To do this, just use the ``suspend`` class method. It is an e-mail method, like some of the other methods. >>> from authenticationpy.auth import User >>> User.suspend(username='myuser', ... message=""" ... Hi, $username, ... Your account has been suspended, can you believe it? ... """) Unlike other e-mail methods, ``suspend`` doesn't require any confirmation, so the only available template variables are: * ``$username``: account username * ``$email``: user's e-mail address You can also suspend an account by simply deactivating it:: >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.active = False >>> user.store() Trying to authenticate using a suspended account will result in a ``UserAccountError`` exception. ### Updating user details Updating properties for a user is as simple as assigning new values to them. Some validation occurs behind the scene (e.g, creating the encrypted version of the password), but most of the time you don't need to worry about that. Once you've assigned new properties for the user, you have to call the ``store`` method to write changes to the database. >>> from authenticationpy.auth import User >>> user = User.get_user(username='myuser') >>> user.username = 'mynewuser' >>> user.store() You should note that even the e-mail address can be changed. It is your responsibility to prevent that if you don't want your users to change the e-mail address.