web2py basic auth

1,173 views
Skip to first unread message

Niphlod

unread,
Aug 2, 2010, 12:10:07 PM8/2/10
to web2py-users
Hi, I'm going to rewrite a REST API made with webpy into web2py.

This API is available only over https and I'd need to use basic auth.

It's an API available only to cmdline clients such as curl, wget and
so on.

I think there's an error on the documentation (http://www.web2py.com/
book/default/chapter/08#Access-control-and-Basic-authentication)
because the settings key is instead allow_basic_login. I reviewed also
the section on customizing the auth tables and I think there are some
errors in that too.

Anyway, lets start from the basics...
From what I read on the docs, to allow only basic authentication, I
need to do the following:

auth.define_tables(username=True) #to use the name as username
instead of the email

from gluon.contrib.login_methods.basic_auth import basic_auth #import
basic_auth
auth.settings.allow_basic_login = True #activate basic auth
auth.settings.login_methods = [basic_auth()] #force to use only
basic auth

and session.forget() in every function in the control.

Do I need to do anything else or is this correct ?
Right now basic auth is working for curl and wget but if I point to
the same url with a browser, I get to app/user/login?_next=etc etc etc
.
I'd like web2py to return a 401 simple error if username and password
aren't
supplied, just that.
Message has been deleted

Niphlod

unread,
Aug 2, 2010, 5:17:52 PM8/2/10
to web2py-users
ok, I took a look into tools.py to get some more clear ideas.....it
turns out that is a bit difficult to clean out the magic from Auth()

I did a little mess up here (tends to be really confusing), and
actually:

from gluon.contrib.login_methods.basic_auth import basic_auth

and

auth.settings.login_methods = [basic_auth()]

are needed if we're going to authenticate against an "external" server
with basic authentication (i.e. you have a list of users on
apache's .htaccess and you have to allow access to web2py using that
infos and not the one stored in auth tables)

So, in order to let web2py use basic authentication with the data
stored in its auth tables, all we need is:

auth.settings.allow_basic_login = True

more on, now, I don't understand if the following is needed/useful:

auth.settings.actions_disabled = [
'login',
'logout',
'register',
'verify_email',
'retrieve_username',
'retrieve_password',
'reset_password',
'request_reset_password',
'change_password',
'profile',
'groups',
'impersonate',
]

and finally:

def unauth():
head = 'Basic realm="%s"' % (request.application)
raise HTTP(401,['Unauthorized'])

So, I discovered that raise HTTP(401,'hello') return the cruft in
order to trick IE (is this needed still?), but if you put status as a
list it will return only 'hello' (nice catch) but....
1) I'd need to set this function as the default "event" of not being
authorized
(eventually controlling that authorization header is not there and
adding the www-Authenticate header)...
it would be as easy as putting WWW-Authenticate=head as argument to
HTTP), but it turns out that is reaaally difficult to put one in there
(python dict limitation??)....can anyone point me in the right
direction ?

2) I saw what auth.settings.allow_basic_login = True does (and
auth.basic()) and it "allows" the basic authentication in addition to
the default auth (also with disabled actions). Maybe the default auth
can be shut down totally?


Thanks a lot

mdipierro

unread,
Aug 2, 2010, 6:53:22 PM8/2/10
to web2py-users
Let me look into this.

Niphlod

unread,
Aug 5, 2010, 6:45:17 AM8/5/10
to web2py-users
ok, I digged more, and sadly I think that for problem 1) HTTP should
be rewritten, or at least a new class should be extended to let more
flexibility.

Don't know if I'm boring some ultra-technical pythonist, but I'll try
to explain what I managed to understand.

this is how HTTP is defined in http.py :

class HTTP(BaseException):

def __init__( self, status, body='', **headers ):
bla bla bla

basically, every extra argument passed get transformed to a dict and
passed along with the function.

eg. HTTP(401,['hello'],foo='bar', bar='foo') ---> HTTP(401,
['hello'],dict(foo='bar',bar='foo')) --> __init__ done

Now, the hard part: dict() only allow "keyword" syntax for the keys,
and that will be in our case the various headers "name", while their
values should be header content. Unfortunately, keywords accept
letters, digit and underscore.... no dash.

In fact:
**************
python
> a= dict(foo='bar',bar='foo')
> a= dict(foo_1='bar',bar='foo')
> a= dict(foo-1='bar',bar='foo')
SyntaxError: keyword can't be an expression
**************

So, we can't actually pass any header to HTTP() without interfering
with dict() call that gets called on **headers if our keyword contains
'-' . That eliminates a LOT of standard headers and make HTTP quite
unuseful... I can't get the reason behind this wasn't a problem for
peoples writing APIs or services....

Another question popped: can we use response.status and
response.headers ? It seems that if my controller function return
anything (string, list or dict) the status is always 200 and no added
headers show up in firebug....

mdipierro

unread,
Aug 5, 2010, 7:40:39 AM8/5/10
to web2py-users
You can do

a= {'foo-':'bar','bar':'foo'}
HTTP(404,**a)

Niphlod

unread,
Aug 5, 2010, 8:37:23 AM8/5/10
to web2py-users
wonderful, this works, also if the workaround smells fishy ^_^

regarding response.status and response.header .... are they ignored at
all ?

mdipierro

unread,
Aug 5, 2010, 9:34:23 AM8/5/10
to web2py-users
if you raise HTTP yes.

Niphlod

unread,
Aug 5, 2010, 10:44:11 AM8/5/10
to web2py-users
uhm, I am obviously missing something....

def test():
response.headers['custom'] = 'miao'
response.status = 404
return 'hello'


GET http://127.0.0.1:8000/ciao/default/test

Status: 200 OK
Body: hello

Set-Cookie: session_id_ciao=127-0-0-1-c8d0e6d1-
fa92-4cfe-9720-9cf4dc3cddc2; Path=/
Expires: Thu, 05 Aug 2010 14:39:23 GMT
custom: miao
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-
check=0
Content-Type: text/html; charset=utf-8
Date: Thu, 05 Aug 2010 14:39:23 GMT
Server: Rocket 1.0.6 Python/2.6.1
Content-Length: 5
Connection: keep-alive

mdipierro

unread,
Aug 5, 2010, 11:47:35 AM8/5/10
to web2py-users
oops. There seems to be a bug introduced some time ago.
I fixed it in trunk, Please check it.

On Aug 5, 9:44 am, Niphlod <niph...@gmail.com> wrote:
> uhm, I am obviously missing something....
>
> def test():
>     response.headers['custom'] = 'miao'
>     response.status = 404
>     return 'hello'
>
> GEThttp://127.0.0.1:8000/ciao/default/test

Niphlod

unread,
Aug 5, 2010, 3:56:51 PM8/5/10
to web2py-users
perfectly working, thanks again.

Niphlod

unread,
Sep 22, 2010, 3:56:12 AM9/22/10
to web2py-users
I'm sorry to keep up the thread...do you think that replacing all the
"redirects" in the auth module, or overloading all decorators to raise
HTTP(404) instead of redirecting can be feasable/will work?

Right now web2py is packed with a really nice set of features(oauth,
openid, rss, csv, rtf, amf, soap, pdf generation, maybe xslx
export(just read hehe), ), and the only "part" that is not keeping up
the pace is creating apis and services...
I don't use django-piston but it seems to be really widely accepted...
regarding its features:

- Ties into Django's internal mechanisms (who cares)
- Supports OAuth out of the box (as well as Basic/Digest or custom
auth.) (web2py has it, but auth module is really tied into the "web"
world and not for the "services" one)
- Doesn't require tying to models, allowing arbitrary resources.
(web2py too)
- Speaks JSON, YAML, Python Pickle & XML (and HATEOAS.) (maybe hateoas
is missing, but, again, who cares ?)
- Ships with a convenient reusable library in Python (mmhmh...)
- Respects and encourages proper use of HTTP (status codes, ...) (this
can be implemented lately)
- Has built in (optional) form validation (via Django), throttling,
etc. (I think that web2py has yet goodies to do it)
- Supports streaming, with a small memory footprint. (web2py has it)
- Stays out of your way. (but requires django....^_^)



On 5 Ago, 21:56, Niphlod <niph...@gmail.com> wrote:
> perfectly working, thanks again.

mdipierro

unread,
Sep 22, 2010, 9:39:41 AM9/22/10
to web2py-users
I am a bit lost. What are you trying to achieve?

Niphlod

unread,
Sep 22, 2010, 3:27:33 PM9/22/10
to web2py-users
I'm really sorry....

I'm looking for an answer to this question:
2) I saw what auth.settings.allow_basic_login = True does (and
auth.basic()) and it "allows" the basic authentication in addition to
the default auth (also with disabled actions). Maybe the default auth
can be shut down totally?

That is quite clear, I guess... I can't find a way to shut down
default auth and leave only basic auth as the default method for login

let's explain in other words the other "feature request" instead...

I don't know in deep all the auth module, but (at least for me) is the
one that is less "usable" when you create web services.
what I'm asking is the best way (i.e. the less error prone way) to
have the auth decorators to return/raise an http status instead of
raising a redirect to login page or the "user" controller.
Right now it seems that you can configure quite all, but all you can
configure is where the user will be redirected when the authorization
fails....

If you want to create an interface to a web api, maybe a REST one, you
rarely need to redirect someone to the login page if he is not a valid
user, nor you need to redirect him if he is a valid user without the
permissions to access a particular controller/resource...you just tell
him it's not authorized (the "recommended" behaviour would be to raise
a 404).

Going by hand to patch the auth module substituting all redirects to
something else or creating a new one from scratch seems a little bit a
long catch...maybe who planned and coded the auth module will figure
out a "smart" way to enable this behaviour...and I think that web2py
will be a good contender to django-piston or other frameworks of
choice when you are going to create a web [RESTful] API.

mdipierro

unread,
Sep 22, 2010, 3:38:38 PM9/22/10
to web2py-users
You want to disable the login page.

You can try
auth.settings.actions_disabled.append('login')
auth.settings.actions.login_url=URL('your_own_error_page')

Niphlod

unread,
Sep 23, 2010, 3:26:10 AM9/23/10
to web2py-users
I'll try in other words..... I/we (users building rest api) don't want
the user to be redirected to any page (my own or the default really
doesn't matter)...all the decorators seems to redirect
somewhere....instead they "should" call a function (maybe by default a
redirect, in order to don't break backword compatibility) that, for
example, I/we can modify raising a 404.
> > choice when you are going to create a web [RESTful] API.- Nascondi testo citato
>
> - Mostra testo citato -

mdipierro

unread,
Sep 23, 2010, 8:52:38 AM9/23/10
to web2py-users
You are right. That would be best. Want to send me a patch?
Message has been deleted

Niphlod

unread,
Sep 23, 2010, 9:52:18 AM9/23/10
to web2py-users
Never done a patch before, but I think in the night (here are 3PM) I
can manage to have a first draft.

I'd have to test it out, but for the beginning .. What wuold be the
patch against ? tools.py in trunk or tools.py in 1.85.3?

mdipierro

unread,
Sep 23, 2010, 10:34:35 AM9/23/10
to web2py-users
Against trunk please. It is ok if you just send me a replacement file.
I will diff and study it.

miguel

unread,
Jan 27, 2011, 7:48:26 PM1/27/11
to web...@googlegroups.com
I suppose this is mentioned in the Change Log by:
"on_failed_authorization can be a function, thanks Niphold"

How is this used? could the function be a lambda, does it take params,
is it an action (i.e. a controller function)?

Also, is it possible to allow the usual login redirection in case it
is not a service call? I'm thinking in these lines:

def theFunction():
if request.function == 'call':
# return case forservice call
else:
# redirect to login form

This would be useful for publishing the same service both for browsers
and for other apps, like it is mentioned in p://www.web2pyslices.com/main/slices/take_slice/48
(see "Authentication"). This solution would also be useful for other
non-browser-based clients. Would it work? How is the function used?

Txs,
Miguel

Niphlod

unread,
Jan 28, 2011, 7:29:50 PM1/28/11
to web2py-users
call_or_redirect() is the real "addition" here .... you can use both a
URL or a function to manage on_failed_authentication and
on_failed_authorization ... take a look in gluon/utils.py

Miguel Lopes

unread,
Jan 29, 2011, 10:01:01 AM1/29/11
to web...@googlegroups.com
gluons/tools.py

Txs,
Miguel

Niphlod

unread,
Jan 29, 2011, 3:13:44 PM1/29/11
to web2py-users
sorry, my mistake!

On Jan 29, 4:01 pm, Miguel Lopes <mig.e.lo...@gmail.com> wrote:
> gluons/tools.py
>
> Txs,
> Miguel
>

Miguel Lopes

unread,
Jan 30, 2011, 4:45:42 AM1/30/11
to web...@googlegroups.com
You got me in the right direction.
Txs.
I'll be starting a separate thread on what I'm trying to achieve, so
far without success.
Reply all
Reply to author
Forward
0 new messages