Closed GoogleCodeExporter closed 9 years ago
This already exists in 2.0 release candidates. See:
http://code.google.com/p/modwsgi/wiki/ChangesInVersion0200
The WSGIAuthUserScript is similar to mod_python authenhandler except that you
only need to validate password.
The WSGIAuthGroupScript is similar to mod_python authzhandler except that you
only need to indicate the
groups the user is in.
Original comment by Graham.Dumpleton@gmail.com
on 25 Dec 2007 at 10:04
Here is example Apache configuration for Dav access control.
######
Directory "/Users/grahamd/Sites/uploads/">
Dav On
AuthType Basic
AuthName "Uploads"
AuthBasicProvider wsgi
WSGIAuthUserScript /Users/grahamd/Sites/auth-uploads.wsgi
WSGIAuthGroupScript /Users/grahamd/Sites/auth-uploads.wsgi
Require valid-user
<Limit GET HEAD OPTIONS CONNECT POST>
Require group read
</Limit>
<Limit GET HEAD OPTIONS CONNECT POST PROPFIND PUT DELETE \
PROPPATCH MKCOL COPY MOVE LOCK UNLOCK>
Require group write
</Limit>
######
The auth-uploads.wsgi file is:
######
PASSWORDS = {
'user1': 'password1',
'user2': 'password2',
}
GROUPS = {
'user1': ['read'],
'user2': ['read', 'write'],
}
def check_password(environ, user, password):
print >> environ['wsgi.errors'], 'check_password ', user
print >> environ['wsgi.errors'], 'REQUEST_METHOD ', environ['REQUEST_METHOD']
print >> environ['wsgi.errors'], 'REQUEST_URI ', environ['REQUEST_URI']
if user in PASSWORDS:
return PASSWORDS[user] == password
return None
def groups_for_user(environ, user):
print >> environ['wsgi.errors'], 'groups_for_user ', user
return GROUPS.get(user, [])
######
Example shows that request method and URI are accessible in auth hook
functions. You can use this to work
out specific URLs used when dav tries to access certain resources and use a
Location directive for those URLs
in Apache configuration to further limit access base on require group directive.
Original comment by Graham.Dumpleton@gmail.com
on 26 Dec 2007 at 2:45
Thank you, I'll try this right now.
Original comment by esizi...@gmail.com
on 26 Dec 2007 at 6:10
I would like to have a possibility to return HTTP errors like HTTP 500, HTTP
FORBIDDEN, etc. Using this suggested method I can only return empty group list
for a
user with access denied. This will lead to browser's additional requests for
user
name and password without pointing the error.
Original comment by esizi...@gmail.com
on 26 Dec 2007 at 7:37
If you want to force a 500 error response, ie., indicate that an internal
server error has occurred even though one hasn't occurred, you can force it by
raising an exception and not catching it. To want
to do that seems to be the wrong way of going about things though.
As to replacing 401 with something else, I can't see that you can do that as it
will break how HTTP Basic/Digest authentication mechanisms work. Ie., browser
clients rely on being returned a 401
status with WWW-Authenticate header so as to know that authentication is
required in the first place. If you return a different status, you would never
be prompted to enter your credentials if an
interactive browser.
If you still want to investigate being able to return a different status rather
than the 401, you may be able to use the ErrorDocument directive. This
directive will need to refer to alternate page with full
URL including host:
ErrorDocument 401 http://myhost.com/subscription_info.html
At least this is what the Apache documentation says:
http://httpd.apache.org/docs/2.2/mod/core.html#errordocument
In trying it though I can't seem to get it to work though. Although one can set
ErrorDocument for 401 to use local server document, can't get it to return a
302 returning to a full URL instead. It is
quite possible that this feature was in older version of Apache but they took
it out and documentation was never updated.
Searching some more, the ability to do that has indeed been removed.
if (error_number == 401 && what == REMOTE_PATH) {
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
"cannot use a full URL in a 401 ErrorDocument "
"directive --- ignoring!");
}
So it produces in error log:
[Wed Dec 26 20:13:45 2007] [notice] cannot use a full URL in a 401
ErrorDocument directive --- ignoring!
In short, I believe you will be breaking HTTP authentication, ie., doing
something contrary to the RFC standards for this stuff, by changing the error
status.
The best one can manage as far as returning HTTP_FORBIDDEN is by denying access
even before authentication is being done. For example based on client host from
which request came from. This
can be done using script associated with WSGIAccessScript directive.
Why do you want to do what you want? Can you point to information on the
Internet that indicates that what you want to do is acceptable practice and
doesn't break RFCs as to how HTTP Basic/Digest
authentication is meant to work?
Original comment by Graham.Dumpleton@gmail.com
on 26 Dec 2007 at 9:25
Actually, the Apache documentation is correct when you read it properly.
"""In addition, if you use a remote URL in an ErrorDocument 401, the client
will not know to prompt the user for
a password since it will not receive the 401 status code. Therefore, if you use
an ErrorDocument 401 directive
then it must refer to a local document."""
This substantiates what I said that if must be a 401 status else client will
not know that credentials required.
Original comment by Graham.Dumpleton@gmail.com
on 26 Dec 2007 at 9:52
In my use case I'm not care about authentication in my Python script, as it's
handled
by other Apache module (mod_ldap).
I'm only going to implement a next-pass authorization functionality, when user
which
is already being authenticated should has been checked for access to resources
by
some ACLs. That's what I want to implement as a Python script. Is that clear?
Original comment by esizi...@gmail.com
on 26 Dec 2007 at 9:52
Even so, not returning HTTP_UNAUTHORIZED for authorization phase when checking
ACLs is going against
what is normal practice. If you return HTTP_FORBIDDEN at that point, because of
how Apache treats
authentication/authorisation as being connected you would be denying the client
the ability to reauthenticate
with different credentials if they made a mistake and used credentials without
the correct access permissions
for the resource concerned. Not returning HTTP_UNAUTHORIZED will also possibly
break Apache's ability to
check authorisation against multiple mechanisms where acceptance by any is
okay. In other words it possibly
mucks up the concept behind the Apache Satisfy directive.
If you want to do something unconventional, your only choice is probably going
to be to use mod_python
instead and implement a full authorization handler which does all its own
parsing of requires directives etc,
and which can then override what sort of error status is returned.
Original comment by Graham.Dumpleton@gmail.com
on 26 Dec 2007 at 10:14
Yes, but Authorization is not the same as Authentication. It's more than common
when
you have several authorization plugins, but for a person to have an access he
must
have been successfully authorizen by all the authorizers, not [only] one of
them.
I think that's the reason for splitting AuthenHandler (Apache's
ap_hook_check_user_id()) and AuthzHandler (Apache's ap_hook_auth_checker()) in
(both)
Apache HTTPD and mod_python.
The main idea of this issue is to discuss this concept for mod_wsgi. I do think
that
mod_wsgi should support both this concept directly, and not only the
Authentication
alone is it does for now.
P.S. There is also AccessHandler (Apache's ap_hook_access_checker()) which
supposed
to filter requests based on client's IP or something.
P.P.S. For now, how am I supposed to pass a configure-time (from Apache's config
file) parameters to WSGIAuthGroupScript and WSGIAuthUserScript?
It seems that mod_wsgi ignores SetEnv and mod_rewrite's RewriteRule , - [E=...]
options, at least I can't find them in `environ' dict.
Original comment by esizi...@gmail.com
on 27 Dec 2007 at 1:53
I am well aware of the difference between the access, authentication and
authorisation phases of Apache.
The WSGIAccessScript directive implements host based access checks as part of
the Apache access phase.
The WSGIAuthUserScript directive implements checks as part of the
authentication phase.
The WSGIAuthGroupScript directive implements checks as part of the
authorisation phase.
Documentation is at:
http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms
For all three the status returned is the same status used by all other Apache
modules included with Apache
that are implemented in those phases. No supplied Apache module which hooks
into the authorisation phase
returns HTTP_FORBIDDEN as as I have said before you break how HTTP
'Authorization' header is meant to be
used by doing so. I am therefore following what Apache itself does and which is
sufficient for practically all
cases.
As I said before, you may simply be better off using mod_python. If you go try
it and come back to me with a
working pair of authentication/authorisation handlers for mod_python that show
that returning
HTTP_FORBIDDEN actually works in practice and doesn't cause problems with users
not being able to resupply
credentials without needing to exit there client browser and start over, then
I'll look at whether being able to
supply a different status is warranted or not. At the moment this discussion
all seems to be theoretical and no
way to know if what you want to do will even work in practice.
The reason that SetEnv parameters are not passed is that they are not
associated with a request by Apache
until very late in the fixup handler phase when it is known for certain which
response handler will actually be
called for the URL. This is well after the Apache AAA phases. Technically
various things could happen in
between the AAA phases and the fixup handler which change the actual target of
the request and as a result
this can change what set of SetEnv directives could be applied. It is therefore
not safe to assume that the
target will not change and look ahead somehow and grab SetEnv directives,
something which can't be done
anyway as they are held in inaccessible data structures until the fixup handler
populates the request structure
with them.
The only reliable way of handling the issue is therefore for the script itself
to be self contained and describe
its own configuration. This could be static, or it could be dynamically
determined from what request headers
are provided in environ. If you have various URL subsets that require different
static configurations, then
factor out your core code into a proper Python module somewhere. Then create a
separate script for each
context which imports the common code and uses a wrapper around it to set the
configuration.
import commonstuff
config = { .... }
def check_password(environ, user, password):
environ.update(config)
return commonstuff.check_password(environ, user, password)
I have no desire to mirror mod_python's parallel mechanism for defining
configuration as it overly complicates
the internal code and is not necessary.
For your own interest about how Apache works internally, you may want to read
the following if you haven't
already.
http://www.fmc-modeling.org/category/projects/apache/amp/Apache_Modeling_Project.html
Original comment by Graham.Dumpleton@gmail.com
on 27 Dec 2007 at 9:41
I'm used to have an AD-based authentication for my web resources using Apaches's
mod_ldap module.
This module could be a good example of AAA modules which do returns HTTP results
other than HTTP_OK and HTTP_FORBIDDEN.
So, I would like you comment mod_ldap source file
httpd-2.2.6/modules/aaa/mod_authnz_ldap.c starting from line 410:
/* handle bind failure */
if (result != LDAP_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
"[%" APR_PID_T_FMT "] auth_ldap authenticate: "
"user %s authentication failed; URI %s [%s][%s]",
getpid(), user, r->uri, ldc->reason, ldap_err2string(result));
return (LDAP_NO_SUCH_OBJECT == result) ? AUTH_USER_NOT_FOUND
#ifdef LDAP_SECURITY_ERROR
: (LDAP_SECURITY_ERROR(result)) ? AUTH_DENIED
#else
: (LDAP_INAPPROPRIATE_AUTH == result) ? AUTH_DENIED
: (LDAP_INVALID_CREDENTIALS == result) ? AUTH_DENIED
#ifdef LDAP_INSUFFICIENT_ACCESS
: (LDAP_INSUFFICIENT_ACCESS == result) ? AUTH_DENIED
#endif
#ifdef LDAP_INSUFFICIENT_RIGHTS
: (LDAP_INSUFFICIENT_RIGHTS == result) ? AUTH_DENIED
#endif
#endif
: AUTH_GENERAL_ERROR;
}
Original comment by esizi...@gmail.com
on 28 Dec 2007 at 11:55
This is an example run of svn update without credentials supplied:
$ svn up
Authentication realm: <http://localhost:80> Authorized Area
Password for 'esizikov':
Authentication realm: <http://localhost:80> Authorized Area
Username:
Password for '':
svn: PROPFIND request failed on '/InventoryAgent'
svn: PROPFIND of '/InventoryAgent': 500 Internal Server Error (http://localhost)
This is an extract from the Apache's error_log:
[Fri Dec 28 17:43:48 2007] [warn] [client 127.0.0.1] [28580] auth_ldap
authenticate:
user esizikov authentication failed; URI /InventoryAgent [ldap_simple_bind_s()
to
check user credentials failed][Invalid credentials]
[Fri Dec 28 17:43:49 2007] [warn] [client 127.0.0.1] [28581] auth_ldap
authenticate:
user authentication failed; URI /InventoryAgent [User not found][No such
object]
Original comment by esizi...@gmail.com
on 28 Dec 2007 at 12:40
The mod_ldap source you quote is not a standard Apache handler, it is an auth
provider and they do not
return HTTP status codes. They return one of:
typedef enum {
AUTH_DENIED,
AUTH_GRANTED,
AUTH_USER_FOUND,
AUTH_USER_NOT_FOUND,
AUTH_GENERAL_ERROR
} authn_status;
The wrapper around the mod_wsgi WSGIAuthUserScript code uses the same result
codes.
if (result) {
if (result == Py_None) {
status = AUTH_USER_NOT_FOUND;
}
else if (result == Py_True) {
status = AUTH_GRANTED;
}
else if (result == Py_False) {
status = AUTH_DENIED;
}
else {
PyErr_SetString(PyExc_TypeError, "Basic auth "
"provider must return True, False "
"or None");
}
Py_DECREF(result);
}
There is no way in an auth provider of affecting what the HTTP status code is
that is returned by the
authentication handler phase, ie., check_user_id(). You really need to look at
mod_auth_basic.c and mod_auth_digest.c to see how auth provider framework
called and what HTTP status codes are returned.
You also probably want to look at mod_authz_dbm.c as that is closest to what
mod_wsgi group authorisation
is doing except that group list coming from Python code rather than dbm file.
You will not that
mod_authn_dbm does not return HTTP_FORBIDDEN or provide a means of doing so.
Since that is exactly what you want to do, you might also ask on some Apache
users forum why
mod_authn_dbm doesn't allow HTTP_FORBIDDEN to be returned. If they come back
and say it could but simply
doesn't, then I might also listen, but if they say that it doesn't make sense
like I am saying .... :-)
Original comment by Graham.Dumpleton@gmail.com
on 28 Dec 2007 at 10:28
Closing this issue. How it works is how it was intended and is inline with how
other
Apache modules do things.
Original comment by Graham.Dumpleton@gmail.com
on 15 Jan 2008 at 4:23
Sorry to bring out this (almost) 3 year-old issue, but I think it is still
relevant and should be implemented. I've read and understood all of the above,
but am in favor of re-opening it for similar arguments to `esizi...@gmail.com`.
I've mixed my comments between some partial replies of yours.
> Example shows that request method and URI are accessible in auth hook
functions. You can use this to work out specific URLs used when dav tries to
access certain resources and use a Location directive for those URLs in Apache
configuration to further limit access base on require group directive.
If access is granted on a per-user basis, how should I proceed? Even if I
created a user for each group, how do I come about specifiying such a directive
in the Apache config file?
> As to replacing 401 with something else, I can't see that you can do that as
it will break how HTTP Basic/Digest authentication mechanisms work.
Yes, this is precisely what I want. I'm not using HTTP basic/digest
authentication. I'm authenticating my users through OpenID. Returning a 401
status and have the browser display a box for authentication would be confusing
to say the least, since they don't have a password in the first place.
Moreover, I couldn't make use of the username and password even if the user
provided one.
> If you want to do something unconventional, your only choice is probably
going to be to use mod_python instead and implement a full authorization
handler which does all its own parsing of requires directives etc, and which
can then override what sort of error status is returned.
I'm already using mod_python for this task. However, the project is dead.
You've addressed this issue yourself
(http://blog.dscpl.com.au/2010/06/modpython-project-is-now-officially.html).
The handler works perfectly fine as it is (even addressing the next point) and
I'd like to preserve that behavior with mod_wsgi. All of my setup uses
mod_wsgi, except for this specific handler, and maintaining mod_python code
just because you don't support custom authorization handlers is inconvenient.
> As I said before, you may simply be better off using mod_python. If you go
try it and come back to me with a working pair of authentication/authorisation
handlers for mod_python that show that returning HTTP_FORBIDDEN actually works
in practice and doesn't cause problems with users not being able to resupply
credentials without needing to exit there client browser and start over, then
I'll look at whether being able to supply a different status is warranted or
not.
I don't have a "pair of authentication/authorisation handlers", I only have an
authorization handler and it works great. I'm using Django with OpenID
authentication, so I can validate the user's permission with only access to the
request cookies. Returning HTTP_FORBIDDEN works great since I don't use HTTP
basic/digest authentication. The user may sign-on at any time, even after
HTTP_FORBIDDEN was returned. At least, I should be able to redirect to an
authentication page. In fact, in this case, I could even get away with retuning
HTTP_NOT_FOUND.
In any case, the resources I am trying to protect are not directly accessed by
the user, but by some other embedded object in the page (I'm protecting access
to videos played through a Flash player, similar to YouTube's). The page in
question already requires login to be displayed, and the video itself should
never be accessible without seing that page. I'd like (for copyright reasons)
to be able to prevent the referenced content from being accessed directly (the
user should have purchased the content to view it). Serving video content
directly through Django using the static views is not recommended.
> You really need to look at mod_auth_basic.c and mod_auth_digest.c to see how
auth provider framework called and what HTTP status codes are returned.
Suppose I export access to my content through the OAuth protocol, it is likely
that third-party service providers will access the content using only an access
token. No user is at the other end to supply credentials and the service
provider does not have access to those credentials, as is part of the OAuth
design. Following what authentication modules do in an attempt to implement
authorization seems inadequate.
If I understand RFC2617 and Apache's documentation correctly, returning 401
only makes sense when access to the requested resource is protected by HTTP
basic/digest authentication. Any authentication/authorization beyond this point
does not seem to be covered by RFC2617. I really need to be able to implement
*only* an authorization handler, and return HTTP_FORBIDDEN or HTTP_NOT_FOUND at
my discretion. I understand that Apache does not seem to be equiped with
facilities for authorization without using HTTP authentication (i.e. you need
to specify "AuthType Basic" to be able to perform authorization). However,
RFC2617 is now 11 years old and HTTP authentication is showing it's age. Better
authentication methods have been developped since, and OpenID is one of them. I
believe implementing this feature allows proper use of modern protocols and
would significantly benefit mod_wsgi.
Original comment by andre.l....@gmail.com
on 19 Oct 2010 at 2:04
Please take any further discussion about this to the mod_wsgi mailing list as
an issue tracker is not really the appropriate forum for it.
Original comment by Graham.Dumpleton@gmail.com
on 19 Oct 2010 at 2:10
Latest discussion about this can be found at:
http://groups.google.com/group/modwsgi/browse_frm/thread/d5959f6be23b481e
Original comment by Graham.Dumpleton@gmail.com
on 19 Oct 2010 at 11:30
Original issue reported on code.google.com by
esizi...@gmail.com
on 25 Dec 2007 at 4:51