Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
authentication framework for web.py
Python
Branch: master

Merge branch 'master' into rm

latest commit 464424b289
Branko Vukelic authored

README

# 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.
Something went wrong with that request. Please try again.