Which has been brought up quite a lot (see archives of this list and
django-users). As of the newforms-admin merge, however, the entire
admin is built around classes which encapsulate this stuff so that --
if using an alternate auth backend simply isn't enough configurability
-- you can subclass and completely replace the way they do auth. I
tried this out back when NFA was still under development, and found it
to be quite a lot less painful than I'd expected it to be.
If there are specific hurdles preventing you from being able to do
that effectively, please do bring them up.
--
"Bureaucrat Conrad, you are technically correct -- the best kind of correct."
It's not a case where there's going to be something that jumps up and
yells "change me and everything will work like you want it to"; it's
something that's going to require a bit of reading to get a feel for
how requests and responses flow through the admin. For example,
AdminSite.has_permission() (which checks whether a user has any access
at all to the admin) and the various
has_(add|change|delete)_permission() methods on ModelAdmin take an
HTTP request as argument, *not* a user, which means you can pull any
information you want out of the HTTP request to make decisions about
what to allow.
james...@gmail.com wrote:
> Within this vein the User object is the most annoying. It would be
> nice if we could define our own with certain methods that conform to a
> user spec. Groups and Permission are either completely overkill or
> done completely differently in our case and user messages and
> notification are handled with another service, not a DB table.
>
This has come up where I work as well. We have an LDAP server with about
8700 user accounts. For tools that not too many people use, and ldapauth
backend has worked out fairly well. The ldapauth backend we've used just
creates a row in Django's auth_user table. This isn't the most ideal, as
changes in the ldap aren't reflected in our Django site until they try
to log in again.
The solution that I've come up with is to inherit from
django.contrib.auth.models.User and override anything that would cause a
write to the Database. We only want read only support from our Django
sites anyway, so it seems like a decent solution. I'm no guru when it
comes to the database handling stuff in Django, so I'm not even sure if
this approach is even the least bit sane.
The biggest problem that I've seen with creating user-like objects
instead of using contrib.auth is I can no longer store references to
them in other models' ForeignKey or ManyToManyFields. If user bob opens
up a trouble ticket, and alice wants to be included in the emails that
go out when the ticket is updated, the ManyToManyField throws an
exception, and I have to figure out a hack job way of storing the
references in the database and then creating instances when needed. If I
make my custom user-like object picklable, and store them in a list, and
store that in a PickledObjectField, it kind of works like a
ManyToManyField does. It just doesn't 'feel' like it's the right way to
do things. It just seems too messy.
I haven't looked into a lot of the NFA new stuff, but it would be nice
to be able to plug in my own custom auth stuff. I'll put that in my list
of Django stuff to play with.
Jeff Anderson
Well, it needs some sort of authentication available by default so
that people don't have to go write auth systems for it, so the
sensible default is to use the auth system that's bundled with Django.
In a lot of common cases this works pretty well; in situations where
you're integrating with legacy auth systems it requires a bit more
work but is still doable. Or, to borrow some other language's mantra,
it aims to make the easy things easy and the hard things possible ;)
Because simplicity wins.
Step back from your personal use case for a moment, and consider the
general situation. There are four primary ways people do auth in
Django:
1. Using django.contrib.auth and nothing else.
2. Using django.contrib.auth with a custom backend, to migrate from a
legacy auth system into using django.contrib.auth exclusively.
3. Using django.contrib.auth with a custom backend, to maintain
parallel auth in Django and in a legacy system.
4. Not using django.contrib.auth in any way.
Three of these four situations use django.contrib.auth in some
fashion, and in my experience they represent the overwhelming majority
of people who are using Django. So making contrib.auth as painless,
seamless and out-of-the-box simple as possible to use needs to be a
very high priority; one thing we don't want to get into is a situation
common in more "modular" systems where introductory documentation ends
with "Congratulations on getting the base system installed; here's a
list of five hundred components you can use, go pick the one you want
because we refuse to take a stance on it."
> I'll give you with an example, just so you know I am not spouting off
> without cause. In the main AdminSite class within the "login" function
> (line 229) the first real line of code imports auth.models.User. This
> is only used if the sanctioned channels for authentication (the
> authenticate function 251:266) fail, but why?
I'm kind of stumped here because that method strikes me as a very good
example of how flexible the admin is these days. The admin gets there
by the following process:
1. It calls the has_permission() method of the AdminSite class in use,
passing the HTTP request as an argument and looking for a boolean
result.
2. If the result of has_permission() was False, it calls and returns
the result of the login() method of the AdminSite class in use,
passing the HTTP request as an argument.
This means that, if you want to use something other than Django's
bundled auth system, all you have to do is override those methods on
your AdminSite clas so that they do whatever you want. The argument
signatures and the way in which they're called make absolutely no
assumptions whatsoever about the auth system. As far as AdminSite is
concerned, they're a black box: HTTP requests go in, information comes
out.
If this is what you call heavy-handed, I'd hate to see your idea of modular ;)
> Would it not make more sense to include that chunk of code as another
> backend, or an alternate routine in the default authentication
> backend? This was the first stumbling block; and while I can get
> around this by copying most of the login funtion, it would be sweet
> not to need to.
Again, I don't really see what the complaint is here; all you have to
do at this point is override a pair of methods to implement whatever
authentication you want. If you want to change the form that's used,
you override display_login_form() as well, which means you get the
ability to completely yank django.contrib.auth *and* its assumptions
about credentials out of there by overriding three methods whose
argument signatures are all generic enough to permit this decoupling.
So override a couple methods, and you're done.
Again: the fact that you have to subclass and write a little code to
do something other than the default isn't really an onerous
requirement.
Please read my previous emails in this thread.
> I agree in principle, but in this case it seems like you have to
> rewrite the whole of `auth.views.login()`. Please correct me if I'm
> wrong.
Well, if what you want is to have something different from the way the
default login view works, yes.
> Again, here I'm speaking about the front-end site. It's much easier in
> the admin, since you can tweak the `admin/login.html` template. In
> fact, now that I look closer, that template doesn't even enforce a
> maxlength to the username and password inputs at all. Is that a bug?
No, because attributes of HTML elements are not and should not be
confused with validation routines.
> Hmm, I've just done some more testing and found another issue... I've
> got a custom auth backend allowing to login with your email. If login
> fails in the admin (say, you mistype the email address), you get a
> 'Usernames cannot contain the '@' character.' message. Anyway, I'll
> investigate more and possibly create a ticket about that.
You're not the first person to "discover" this. The reason for that
code is the fact that -- in the default setup -- a username is not and
cannot be an email address, and the form is designed to catch
erroneous use of email address in place of username (a common error).
Again, if you want custom behavior it is extremely easy to override.
> I agree in principle, but in this case it seems like you have to
> rewrite the whole of `auth.views.login()`. Please correct me if I'm
> wrong.
James: When your users are keeling over at the thought of writing < 30
lines of code, it's time to conclude that you are a victim of your
own success.
Luke
(apologies Julien, nothing personal :-)
--
"We may not return the affection of those who like us, but we always
respect their good judgement." -- Libbie Fudim
Luke Plant || http://lukeplant.me.uk/