You can read the proposal nicely formatted here: https://gist.github.com/8dd9fb27127b44d4e789

GSoC 2012 Proposal - enhanced contirb.auth
==========================================

Hello, my name is Stratos Moros and I am a Computer Science student in the University of Piraeus in Greece.

I would like to participate in Google Summer of Code with Django, working on an enhanced contrib.auth.

Goals
-----

-       Long term solution

The proposed replacement doesn't aim at just improving some of the current limitations of contrib.auth (e.g. increasing the email field size), but at providing a long term solution.

-       Backwards compatibility

The new contrib.auth will be 100% backwards compatible with the current implementation. Developers updating their existing Django installation will have the same features available, without having to update their code or undergo a database migration.

-       Separation of concerns

The concepts of identity, authentication and authorization should not be tightly coupled by default. Developers will be able to mix and match schemes if they want, or use a more integrated approach otherwise.

-       Extensibility

It's impossible to cover all existing and future authentication / authorization schemes. Developers who have needs that aren't covered by Django should be able to extend contrib.auth by writing their own or using third party user models. Other apps that need to interact with users (e.g. a commenting app) will be able to interact with them without knowing the specific implementation.

-       Batteries included

Developers will not be required to implement their own authentication or authorization schemes for the common cases. Contrib.auth will have one or more built in user models ready for production.

-       Admin compatibility

Contrib.admin is one of the things that make Django great. It should be easy for developers to make their user models accessible through the admin interface.

Terminology
-----------

In order to distinguish between Django's users, a website's users and the proposed concept of a user, the terms "developer", "visitor" and "user" are used respectively.


Implementation
--------------

The new contrib.auth will serve three primary functions:

-       Assist developers in writing custom user models.
-       Provide built in user models.
-       Provide a way for developers to interact generically with user models.

The new contrib.auth will allow developers to specify multiple user models. A new setting will be introduced called `USER_MODELS` which will be a tuple of strings. Each string's format will be `'%(app).%(model)'`, similarly to how `AUTH_PROFILE_MODULE` is currently defined. This setting will look something like:

        USER_MODELS = (
                'auth.EmailUser',
                'myapp.CustomUser',
                # ...
        )

### Separation of concerns and the user contract

A user model will be a regular model that fulfills a predefined contract. Developers will be able to write their own user models in their applications and register them through the `USER_MODELS` setting. Any model that fulfills the user contract and is specified in `USER_MODELS` will be considered a valid user model. The contract will be subdivided in three subcontracts that will be concerned with identity, authentication and authorization.

#### Identity

The identity contract will answer the "who are you" question for a user model. This could be anything from a twitter handle to a biometric identifier, or anything else that can uniquely identify a user in a user model. To fulfill the identity contract the user model will have to:

-       Implement an `identity` method.

This method must return a string that uniquely represents a user in a user model.

-       Implement a `get_by_identity` method on the model's default manager.

This method must accept a string representing a user, as returned by the `identity` method, and return the either a user object, if it finds one, or `None` otherwise.

-       Implement a `__unicode__` method.

This method must return a string representation of a user that is suitable for displaying on the frontend. This will not be required to be unique across all users in a user model.

#### Authentication

The authentication contract will be concerned with verifying that the user is indeed who he claims to be. Again, this could be anything from a password to a hardware dongle. To fulfill the authentication contract the user model will have to:

-       Implement an `is_authenicated` method.

This method must accept a request. Contrary to the current implementation, the `is_authenticated` method will not always return `True`. It is up to the implementation to decide whether the user is actually authenticated.

-       Implement an `is_authenticated_by` method.

This method must accept any arguments necessary to authenticate the user and return `True` on success, or `False` otherwise. It must not store the user in the session or modify the user object's internal or external state to indicate that he is authenticated.

-       Implement an `login` method.

This method must accept a request and any arguments necessary to authenticate the user. It will return `True` on success, or `False` otherwise. This method must store the user in the session or modify the user object's internal or external state to indicate that he is authenticated.

-       Implement a `logout` method.

This method must accept a request. This method must remove the user from the session or modify the user object's internal or external state to indicate that he is no longer authenticated.


#### Authorization

The authorization contract will be responsible for keeping track of what a user model is allowed to do. To fulfill the Authorization contract a user model will have to:

-       Implement a `has_perm` method.

This method must accept a string representing a permission and an optional object. It will return a boolean value indicating whether the user has that permission. If an object was passed as well, it must indicate whether the user has the permission for the specified object. The string format can be anything and will be implementation specific.

-       Implement a `has_perms` method.

This method must accept a list of permissions and an optional object. It will behave similarly to the `has_perm` method, but it will return `True` only if the user has all permissions.

-       Implement an `add_perm` method.

This method must accept a permission and an optional object and it will add the permission to the user. If an object was passed as well it will add the permission only for that specific object.

-       Implement a `remove_perm` method.

This method must accept a permission and an optional object and it will remove the permission from the user. If an object was passed as well it will remove the permission only for that specific object.

Of course, the user model could have more methods or fields that are relevant to the specific implementation. For example an authorization mixin that categorizes permissions per app could also have a `has_module_perms` method.

### Anonymous users

Since the `is_authenticated` method will not always return `True`, there is no longer a need for a separate anonymous user model. Any user model can be used to represent a user that is not logged in. By default, contrib.auth will use the first specified model in the `USER_MODELS` setting. If a developer want to change the user model that represents a user that is not logged in, there will be an appropriate function to do so.

### Writing new User Models

To assist developers in writing new User Models contrib.auth will include mixins that developers can subclass to get the desired functionality. These mixins will be implemented as abstract models and will live in `contrib.auth.mixins`. Specifically, it will contain the following mixins:

-       Identity mixins

Contrib.auth will contain a `UsernameIdentityMixin` and an `EmailIdentityMixin` that will identify a user by a username and an email respectively.

-       Authentication mixins

        Contrib.auth will contain two authentication mixins.

        -       `SessionAuthenticationMixin`

`SessionAuthenticationMixin` will implement the `is_authenticated`, `login` and `logout` methods. These methods will check, store and remove the user from the session respectively. If a developer wants to simply store the user in the session to indicate he is logged in, he can inherit from this mixin and implement only the `is_authenticated_by` method.

        -       `PasswordAuthenticationMixin`

`PasswordAuthenticationMixin` will be a subclass of `SessionAuthenticationMixin` and will implement the `is_authenticated_by` method to authorize a user by checking password against a hash stored in a database.

-       Authorization mixins

Contrib.auth will contain a `StandardAuthorizationMixin` that will store each user's permissions as `'%(app).%(perm)'` strings in a database, similarly to the current contrib.auth implementation.

-       Complete Mixins

Contrib.auth will contain two mixins implementing the whole user model contract, `StandardUserMixin` and `EmailUserMixin`. They will both be subclasses of the above mixins, the first one using a username for identification and the second one an email address.

Using the above mixins, a developer will be able to use parts of the built in mixins and implement the rest of the user contract himself. For example, a developer that wants to use the built-in authorization with his own identification and authentication scheme could subclass the `StandardAuthorizationMixin` and add the methods and fields his implementation requires.

This would also make writing a user model that uses a custom profile trivial. As an example:

        from django.db import models
        from django.contrib.auth import mixins

        class CustomUser(mixins.EmailUserMixin):
                first_name = models.CharField(max_length=30)
                # ...

This will also allow the Django community to fill the needs of developers. For example, a third party app that provides functionality related to Facebook authentication could provide related mixins, user models and forms, in the same way that the current contrib.auth provides them for the `User` model.

### Built in User Models

Contrib.auth will have at least 2 built in user models.

-       `LegacyUser`

This will be the user model that will be used if the `USER_MODELS` setting isn't set. It will have the same fields and restrictions as the current user model. This ensures that developers updating their Django installations will get a user model that behaves exactly as it used to, without having to modify their code or database.

-       `CompatibilityUser`

This user model will be similar to LegacyUser but it will solve most of the common complaints with the current user model. Specifically, it will have the following differences:

- The username field will be made optional, its size will be raised to 254 characters and it will be able to contain any character. - The email field will be made unique, it will have a database index and its size will be raised to 254 characters

It will be relatively easy for developers that want to go through a database migration to upgrade their project's auth model to CompatibilityUser. The auth_user table's schema will have to be altered, but not the data. Additionally, migration scripts for all the supported backends will be provided in the documentation.

For compatibility reasons, the first two user models will both be called `User` and will use the `auth_user` table. Developers will indicate that they want to use one of them by adding either `'auth.LegacyUser'` or `'auth.CompatibilityUser'` to the `USER_MODELS` setting. Specifying both will raise a configuration error.

In addition to the above models, contrib.auth will include additional user models. For example it could include a `StandardUser` and an `EmailUser` that uses the above mentioned mixins.

### Interacting with users.

Developers will be interacting with user models the same way they interact with all other models. If, for example, a visitor submits a 'login with Facebook' form, the developer will import the relevant model and interact with it. Similarly, if the developer wants to access some information about the currently logged in user he will simply grab the user instance from the request.

That said, there needs to be a way to have a generic key to a user model, without knowing which specific model is used. This will be provided by a very thin abstraction on top of Generic Foreign Keys. An abstract model will be introduced that will serve two functions.

- It will make sure that the model provided as a user is indeed a user model (specified in `USER_MODELS`) - It will take care of the boilerplate code that is needed to use a generic foreign key.

An example implementation could be something like:

        class GenericUserRelation(models.Model):
                _content_type = models.ForeignKey(ContentType)
                _object_id = models.PositiveIntegerField()
                _user = GenericForeignKey('_content_type', '_object_id')

                def _get_user(self):
                        return self._user

                def _set_user(self, user):
                        if auth.is_user_model(user):
                                self._user = user
                        else:
                                raise Exception

                user = property(_get_user, _set_user)

                class Meta:
                        abstract = True

Using the above mixin, a model that needs a foreign key to a user would be written like so:

        class Comment(GenericUserRelation):
                title = models.CharField(max_length=50)
                # ...

        comment = Comment(title="Hey!", user=some_user)
        print comment.user.email

### Helper functions

The contib.auth will also have a few helper functions.

-       `is_user_model`

This function will accept a model instance and return True if it's an instance of a model listed in `USER_MODELS`, or False otherwise.

-       `get_by_identity`

This function will accept a string representing a user's identity and return that user, or None if one is not found. It will do so by traversing the models found in the `USER_MODELS` setting, calling the `get_by_identity` method on each model's manager. It will return the user if it finds one, or `None` otherwise.

-       `is_authenticated_by`

This function will accept an identity, *args and **kwargs. It will use `get_by_identity` to find a user object and it will then call its `is_authenticated_by` function with *args and **kwargs as arguments. It will return `True` if it finds a user that can be succesfully authenticated, or `False` otherwise.

-       `get_and_authenticate`

This function will accept an identity, a request *args and **kwargs. It will use `get_by_identity` to find a user object and it will then log in the user by calling its `authenticate` method with *args and **kwargs as arguments. It will return the user if it finds one that can be succesfully authenticated, or `None` otherwise.

-       `set_anonymous_user`

This function will accept a request and a model and set the model as the user model that represents an anonymous user for this session.

### Deprecated features

The following features of the current contrib.auth will be deprecated according to Django's deprecation policy

-       The `LegacyUser` and `CompatibilityUser` models.

These two models are provided only for backwards compatibility. Developers will be expected to migrate their users to a new user model before they are removed from Django.

-       Any methods and fields of the `User` model not mentioned above.

While all of them will be provided on the `LegacyUser` and `CompatibilityUser` models and many of them may be provided on new models, they will not be part of the user contract. Developers should not expect them to be available on any user model.

-       The `AnonymousUser` model

Since any user model will be able to be used as an anonymous user, the existing model will be deprecated.

-       The `AUTH_PROFILE_MODULE` setting.

Since developers will be able to add any fields in their user model this setting will be deprecated. It will continue to be used by the `LegacyUser` and `CompatibilityUser` models.

- The `AUTHENTICATION_BACKENDS` setting and authentication backends in general.

For the same reasons as with user profiles, authentication backends will be deprecated. Developers will be able to implement a custom authentication / authorization scheme directly on the user model.

Contrib.admin compatibility
---------------------------

There are two ways in which contrib.auth must achieve compatibility with contrib.admin

-       Ability to modify users

Since user models are regular models, contrib.admin will be able to create, modify and delete users normally. If a user model has some special needs (e.g. a password reset form) it can do so through admin.py, as the current contrib.auth does.

Additionally, contrib.admin could be made aware of the `USER_MODELS` setting and have a special view that lets a visitor manage users, but I don't think this is really necessary.

-       Authentication / Authorization

There should be a way to use the contrib.admin with custom user models. This means that the user should be able to log in with admin's log in form and admin's permission should work with the user model's permissions

Authenticating with any user model should be easy with very minor modifications to admin's code, as long as that model can be authenticated with and identity - key pair. This means that both `EmailUser` and `StandardUser` could be used out of the box to log in the backend.

Making an authorization scheme admin compatible should be the responsibility of the authorization scheme developer. As long as the authorization scheme represents permissions in a way contrib.admin understands (i.e. `'%(app).%(action)_$(model)'`) admin will be able to use that custom user model.

Rationale
---------

Since contrib.auth has been the subject of intense debate on django-developers over the past few weeks, I would like to explain the reasoning behind some of the decisions I made.

### Multiple user models

There are actually a few reasons why having multiple user models at the same time makes sense.

-       It allows developers to easily deal with different types of users.

For example, imagine a scenario where we want a website's visitors to login with an email - password pair and we want to keep extensive profile information about them. On the other hand, we want the staff to login to the admin with a username and password stored on an LDAP server. While this is possible to achieve with a single user model it requires some hacks. The developer would have to store emails on a username field or make both nullable and store only one of them for each type of users, moving his validations from the model layer to the view layer.

Using different models for each user makes the above scenario easy to deal with.

-       It is easy to respond to requirement changes

Continuing the above example, lets say that we want to run a promotion and certain users will be able to log in using just a promo code. Even if we've made the user model work with the two above cases, we would need to further modify it by adding even more nullable fields, flags, special case their permissions etc. With multiple user models we can just add an extra user model.

### Permission and identity formats

Permissions are defined to be strings. A case could be made for either a more strict or a looser definition. Requiring permissions to keep their current format (`'%(app).%(perm)'`) would make permissions more standardized across different user models but some permissions don't fit well the app concept. These could be either site-wide permissions or even permissions from external sources (e.g. oAuth permissions regarding content on another site).

On the other hand it could be helpful for permissions to be more complex objects. The current permission format is already two different pieces of data separated by a period and could perhaps be better represented by a tuple. That said, allowing a permission to be any object would be a huge pain for user model developers. They would have to accept any argument as a permission, manually check its type and fail gracefully if they received a different data type than they expected.

A simple string was chosen as a middle ground. This way user models can represent permission as they wish internally, as long as they can provide a public interface that accepts a string.

The representation of a user's identity faces the same issue. It could be better represented by a more complex type, or even reuse the concept of natural keys. As with permissions, this would make it difficult to interact with a user model if you don't know which specific model was used, so a string was used as well.

Conclusion
----------

The proposal does not yet include an estimate, since I'm waiting for your feedback to adjust it accordingly.

Thanks for reading.

--
You received this message because you are subscribed to the Google Groups "Django 
developers" group.
To post to this group, send email to django-developers@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to