Creating an independent auth/permission-framework, separate the models (Was: Adding support for replacing the auth User model to fit custom needs)

23 views
Skip to first unread message

David Danier

unread,
Feb 19, 2007, 10:56:59 AM2/19/07
to Django developers
I'll pick this topic up for some changes I think Django needs.
Please read the summary between the "="-line, if you don't have the time
to read the whole mail. Perhaps it will get you interested in reading
the rest. ;-)

As far as I can see there are different approaches to improve this
(which have been posted in this discussion already):
* http://code.djangoproject.com/ticket/3011
Split auth-Module into two parts: Authentication and User-Model
-> AUTH_USER_MODULE defines which User-Model to use
* Auth-Backends, in the current SVN
-> The User-Model still is in your Database
I think the best way in solving this is to combine this two:
* The User-Model gets its own application, including:
- models.py
- backends.py
- special views or perhaps all of them (didn't read the whole code,
includes forms.py)
- create_superuser.py
* The auth-Module morphs into a authentication-framework, that can use
the User-Model, but is not bound to it (other model can be used by
changing AUTHENTICATION_BACKENDS), includes:
- __init__.py
- middleware.py
The only difference I see, is that you need to include some
"django.contrib.user"-application, if this two parts are separated in a
clean way. Big advantage would be, that the admin stays usable if you
change the User-Model, as it only needs to use the authorization-framework.

Additionally I would merge this idea with the django-branches that try
to improve permission-tests (generic-auth, per-object-permissions). More
precisely it would make sense to create some permission-framework es
well. I think the Ideas behind the generic-auth-branch
(http://code.djangoproject.com/wiki/GenericAuthorization) are pretty
nice, I like to have one function I pass an object, an user and some
permission and get an result.
Some additional thoughts:
* To recreate the current behavior you could use the Model itself
passed as the object (Article(=class) instead of article(=instance))
* I dislike the idea to be forced to register some permission-test. As
the Article says it should be put into the Meta/Admin- or perhaps
Permission-subclass of a Model, which would be fine or even a MUST HAVE.
If some permission-framework is created I would prefer to separate the
Permission-Model from the auth-Application (or
User-Model/user-application). It would be nice to create two
default-systems for permissions as separate applications:
* Model-Permissions like used now
* Row-Level-Permissions like proposed
The permission-framework should be able to use this two systems
concurrently. Each model (or application (as a default)) should be able
to choose which permission-system to use (or even use more than one).
Perhaps separation the permission-framework from the auth-framework
would be even nicer (you can use the auth-system, but use your own
permissions-system).
Also I don't like putting some permission-system into the models(.py).
This should only be used to define your models and not for some kind of
permission-checks

=================================================================
To summarize:
I think the User/Group/Permission-Model should be separated from the
auth-framework. You should be able to choose other models using the
backends-config or similar.
Also there should be created some generic permission-framework, close to
generic-auth. This framework should not rely on any model. Two
default-implementations should be created (model and row-level).
=================================================================

I think splitting the auth-application into multiple parts and creating
some basic frameworks that can be used to do such tasks is really
important. Without this the auth-framework will get bigger (just imagine
the generic-auth-merge), but instead of getting more flexible it will
only solve more specific problems.
And if the admin (or any other application that needs authentication)
should be usable, even if someone is not able to use the provided
authentication-system and implements his own, there needs to be some
basic framework without any drawbacks (auth-backends are there, but they
pull the whole User/Group/Permission-Models with it).

I would like to contribute creating this, if someone is interested.
Perhaps the best place to start (or even work if Joseph Kocherhans likes
my plans?) is the generic-auth-branch. I believe such a system should be
ready with Django 1.0. Otherwise the whole auth-System (and with it the
admin) is not as usable as it could be. And even better, it would
simplify managing the auth-system as a whole (only small apps, that
implement, provide or use some API).

Greetings, David Danier

P.S.: Sorry about the missing "In-Reply-To"-Header, I just joined this list.

Joseph Kocherhans

unread,
Feb 23, 2007, 1:06:13 PM2/23/07
to django-d...@googlegroups.com
On 2/19/07, David Danier <goliath.m...@gmx.de> wrote:

> I would like to contribute creating this, if someone is interested.
> Perhaps the best place to start (or even work if Joseph Kocherhans likes
> my plans?) is the generic-auth-branch. I believe such a system should be
> ready with Django 1.0. Otherwise the whole auth-System (and with it the
> admin) is not as usable as it could be. And even better, it would
> simplify managing the auth-system as a whole (only small apps, that
> implement, provide or use some API).

I'm much more of the opinion nowadays that Django doesn't necessarily
need an overreaching and generic authentication/authorization
framework. The newforms-admin branch [1] is moving towards getting rid
of the admin system's dependency on django.contrib.auth for
authorization, and I'm hoping we can eventually get there for
authentication as well. I haven't thought much about exactly *how* to
do so yet.

[1] http://code.djangoproject.com/wiki/NewformsAdminBranch
Specifically has_add_permission(), has_change_permission() and
has_delete_permission()

David Danier

unread,
Feb 27, 2007, 8:38:27 AM2/27/07
to django-d...@googlegroups.com
> I'm much more of the opinion nowadays that Django doesn't necessarily
> need an overreaching and generic authentication/authorization
> framework.

Yes and no. On the one hand it is not needed, besides for the admin
(which gets separated somehow, as you said later in your email). On the
other hand it would be nice to have such a system for better modularity.

I'm thinking of different applications needing some
authentication-system. This can be easily done using the current
auth-framework if it fits. But as this is not always the case you will
end up in having different systems in every application (in the worst
case, think of third-party-applications). So you can combine different
applications in Django, but may have problems when it comes to user
authorization.

If instead every application uses some generic calls to get the
user-data, manage the session (here this is done already) and handle
permissions this gets simple. The application does not need to know
which model is behind the user, it can count on get_absolute_url() and
__str__() to use it most of the time.

This is already done with the AUTHENTICATION_BACKENDS, but has the
drawback of forcing the user to include the Django-User-models. So I
would at least separate these two (auth-api and user-model).

As an enhancement it would be nice to get the generic-auth-branch into
the trunk (slightly changed perhaps). So not only authorization can be
done on an abstract way, but permission-checks, too.

I think Django would benefit from this in the long term (third-party
applications get more usable). As the modules are not that big, fairly
simple and should be easy to separate (this is somehow already done in
the auth-app, while keeping it together in one app) I don't see any
reason to not do this.
(Besides backwards-compability, but
http://www.djangoproject.com/documentation/api_stability/ states, that
the auth-framework will be changed anyway)

> The newforms-admin branch [1] is moving towards getting rid
> of the admin system's dependency on django.contrib.auth for
> authorization, and I'm hoping we can eventually get there for
> authentication as well. I haven't thought much about exactly *how* to
> do so yet.

A generic API for authorization would be simple and easy to implement.
The users can be changed in the admin using the normal Django-way
(Admin-subclass...).

But it's nice to see separation the admin from the auth-app is on its
way already.

Greetings, David Danier

Joseph Kocherhans

unread,
Feb 27, 2007, 12:05:05 PM2/27/07
to django-d...@googlegroups.com
On 2/27/07, David Danier <goliath.m...@gmx.de> wrote:
>
> As an enhancement it would be nice to get the generic-auth-branch into
> the trunk (slightly changed perhaps). So not only authorization can be
> done on an abstract way, but permission-checks, too.

I'll probably work on this once the newforms-admin stuff is finished.
Even if it isn't accepted into django or django.contrib, the
newforms-admin branch would allow the generic-auth stuff to be
provided as a 3rd party app.

> I think Django would benefit from this in the long term (third-party
> applications get more usable). As the modules are not that big, fairly
> simple and should be easy to separate (this is somehow already done in
> the auth-app, while keeping it together in one app) I don't see any
> reason to not do this.

I agree. I don't have the bandwidth to work on this at the moment, but
it's on my radar.

Joseph

[1] http://groups.google.com/group/django-hotclub

David Danier

unread,
Mar 5, 2007, 8:19:09 AM3/5/07
to django-d...@googlegroups.com
> I'm thinking of different applications needing some
> authentication-system. [...] you will

> end up in having different systems in every application (in the worst
> case, think of third-party-applications). [...]

> If instead every application uses some generic calls to get the
> user-data, manage the session (here this is done already) and handle
> permissions this gets simple. The application does not need to know
> which model is behind the user, it can count on get_absolute_url() and
> __str__() to use it most of the time. [...] So I

> would at least separate these two (auth-api and user-model).

After rethinking this I believe this will not work out and
AUTHENTICATION_BACKENDS will not help either, but make things even worse
(more complicated and unusable).
The real problem with own user-systems is, that you cannot use a foreign
key anymore. You could use something like it is done in the session, but
this is not really nice (save user + user-backend in the database).
You can see a workaround for this problem when reading the
LDAP-Auth-code (http://www.carthage.edu/webdev/?p=12). The
backends-framework allows the user to return any user-object, but
instead of doing so a copy is created in the database to get things
right (as the comment says: to get the permissions working).
Saving some data into the database is not what I think is wrong here
(even if it would be nice to be able to skip this, too), but using the
existing user-model only because no foreign-keys are working without it
really seems wrong to me.

So, I recommend adding some configuration-variable to set the
authentication-system. You should only be able to set one system here
(no backends). If the user wants multiple authentication-backends this
can be done using one system, that does this for him (like the backends
now). BUT: the new API should only use _one_ user-model. So you are able
to use a foreign key with it (With multiple backends you need some
translation-table).
Perhaps it is enough to be able to set the user-model inside the
settings and use UserManager-methods to do the rest (this is what I try
to do in the example below).

The backends-system really make things more complex, I don't think this
will improve development.
Note: You could of course implement some Model that does this
translation mentioned above as an enhancement to the current
backends-system. But as most people don't need multiple backends I would
prefer keeping things simple and do backends as some possible
authentication-system, that might be used, but no one is forced to. And
even when doing this with the current system, it is not ready in the
current state and really needs this enhancement, I think.

Don't get me wrong. I like the idea behind having multiple backends (and
really was excited when reading the current SVN-code), but after trying
to use it in my own application I discovered, that things get really
messy with foreign-keys and even request.user (you cannot use
request.user without checking it's type, because some backend could not
return the user-model you expect).

Examples (hint: this is all very simplified, only to show what I mean):

settings.py:
----------8<---------------------------------------------------------
AUTH_USER_MODEL = 'foo.user.models.User'
--------------------------------------------------------->8----------

django/contrib/auth/__init__.py:
----------8<---------------------------------------------------------
# this function could be put into some core-api, too (might be better)
get_authentication_model():
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
mod = __import__(module, {}, {}, [attr])
except ImportError, e:
raise ImproperlyConfigured, 'Error importing authentication backend
%s: "%s"' % (module, e)
try:
model = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured, 'Module "%s" does not define a "%s"
authentication backend' % (module, attr)
return model
--------------------------------------------------------->8----------

foo/user/models.py:
----------8<---------------------------------------------------------
class UserManager(models.Manager):
def authenticate(**credentials):
pass
def login(request, user):
pass
def logout(request):
pass

class User(models.Model):
objects = UserManager()
--------------------------------------------------------->8----------

foo/blog/models.py (some usage example):
----------8<---------------------------------------------------------
class Post(models.Manager):
user = models.ForeignKey(auth.get_authentication_model())
--------------------------------------------------------->8----------

usage:
----------8<---------------------------------------------------------
# auth
u = auth.get_authentication_model().objects.authenticate(foo=bar, bar=foo)

# login
auth.get_authentication_model().objects.login(request, u)
--------------------------------------------------------->8----------


Multiple backends (foo/user/models.py):
----------8<---------------------------------------------------------
class UserManager(models.Manager):
pass # like above

class User(models.Model):
objects = UserManager()
user_id = models.IntergerField(...)
user_backend = models.XxxField(...)
def _get_backend():
return load_backend(self.user_backend)
def _get_user():
return self._get_backend.get(pk=self.user_id)
user = property(_get_user)
--------------------------------------------------------->8----------

As you see, this makes the backends-system obsolete (sorry), but keeps
its flexibility while making things simpler for application-developers.

For the permissions-system I would do similar.
(One configuration-directive, some API thats fixed but allows model- and
row-level-permissions)

If you are interested I could try to contribute some code, too. ;-)

Greetings, David Danier

David Danier

unread,
Mar 6, 2007, 2:17:28 PM3/6/07
to django-d...@googlegroups.com
Sorry, I didn't change the subject on the last email, should have been
this one then. If you don't know what I am talking about, please read
the last email.
(http://groups.google.com/group/django-developers/msg/50f9393fa15251b3?hl=en)

I have starting rewriting the auth-system now (""), code can be viewed
at https://svn.webmasterpro.de/django-auth-rewrite/. This branch moves
django.contrib.auth to django.contrib.oldauth and creates
django.contrib.auth which includes oldauth. django.contrib.newauth is
added with my code, but not used by default so far.

I tried to use the old function-names where possible, as you can see in
https://svn.webmasterpro.de/django-auth-rewrite/django/contrib/newauth/__init__.py.
I added some new functions:
* get_user_model: retrieve the model for foreign keys
* foo_redirect: redirect to the login-page (no configuration needed
this way)
* get_anonymous_user: get the anonymous user, which can be saved into
the database this way (so anonymous comments can include a user_id and
permissions can be set for guests)
* is_authenticated: test if a user is authenticated
* has_model_permission: test if a user may access a model (like done in
the old system)
* has_object_permission: test if a user may access an onject (like done
in generic-auth)

Most of the functions are only wrappers for functions used in some
auth-system later. The modules which are used can be set using
settings.AUTHENTICATION_MODULE and settings.AUTHORIZATION_MODULE (which
is not in the default-config as no implementation exists in this branch).
has_xxx_permissions can do anything they want. No database access is
required, but Django may ship some row-level-permission-system to set
using AUTHORIZATION_MODULE.
context_processors are included, but don't do much right now
(https://svn.webmasterpro.de/django-auth-rewrite/django/contrib/newauth/context_processors.py).
The middleware is a copy of the old middleware so far, but changes the
import-statement
(https://svn.webmasterpro.de/django-auth-rewrite/django/contrib/newauth/middleware.py).
I included some decorators here, that can be used, see __doc__-strings
for details
(https://svn.webmasterpro.de/django-auth-rewrite/django/contrib/newauth/decorators.py).
The models-file only loads the user-model into the local var "User" to
get imports fixed
(https://svn.webmasterpro.de/django-auth-rewrite/django/contrib/newauth/models.py).

Currently there exists no implementation of a user-system or
authorization-checks, besided in my project (I implemented this for a
CMS I'm writing). So the code in newauth is not able to do something
fancy, but should clarify what I ment in the last email. The models from
oldauth should be easy to convert (but I would skip the backends and put
this into a different application), but I don't have enough time today.
Of course the admin is not working, too.

Hope to whet your appetite and get some comments this time.

I know I don't follow your coding-style here, but I'm willing to change
that if I get some positive response on this.

Greetings, David Danier

Reply all
Reply to author
Forward
0 new messages