web.py: How to selectively hide resources with 404s for any HTTP method?
I want to selectively hide some resources based on some form of authentication in web.py, but their e开发者_StackOverflow中文版xistence is revealed by 405 responses to any HTTP method that I haven't implemented.
Here's an example:
import web
urls = (
'/secret', 'secret',
)
app = web.application(urls, globals())
class secret():
def GET(self):
if web.cookies().get('password') == 'secretpassword':
return "Dastardly secret plans..."
raise web.notfound()
if __name__ == "__main__":
app.run()
When an undefined method request is issued, the resource is revealed:
$ curl -v -X DELETE http://localhost:8080/secret
...
> DELETE /secret HTTP/1.1
...
< HTTP/1.1 405 Method Not Allowed
< Content-Type: text/html
< Allow: GET
...
I could implement the same check for the other common methods in the HTTP specification, but a creative miscreant might invent their own:
$ curl -v -X SHENANIGANS http://localhost:8080/secret
...
> SHENANIGANS /secret HTTP/1.1
...
< HTTP/1.1 405 Method Not Allowed
< Content-Type: text/html
< Allow: GET
...
Is there a way to implement a catch all method in a web.py class for any HTTP method, so I can ensure the security check will be run?
Or is there an alternative way to hide these resources?
Enlightened by Daniel Kluev's answer, I ended up deriving from web.application
to add support for a default method in the _delegate
method:
import types
class application(web.application):
def _delegate(self, f, fvars, args=[]):
def handle_class(cls):
meth = web.ctx.method
if meth == 'HEAD' and not hasattr(cls, meth):
meth = 'GET'
if not hasattr(cls, meth):
if hasattr(cls, '_default'):
tocall = getattr(cls(), '_default')
return tocall(*args)
raise web.nomethod(cls)
tocall = getattr(cls(), meth)
return tocall(*args)
def is_class(o): return isinstance(o, (types.ClassType, type))
...
Instantiation:
app = application(urls, globals())
Page class:
class secret():
def _default(self):
raise web.notfound()
def GET(self):
...
I prefer this solution because it keeps the page classes clean and affords further customisation of the delegation process in a single place. For example, another feature I wanted was transparent overloaded POST (eg. redirecting a POST request with method=DELETE
to the DELETE method of the page class) and it's simple to add that here too:
...
meth = web.ctx.method
if meth == 'POST' and 'method' in web.input():
meth = web.input()['method']
...
You can implement handle-all-methods method like this:
class HelloType(type):
"""Metaclass is needed to fool hasattr(cls, method) check"""
def __getattribute__(obj, name):
try:
return object.__getattribute__(obj, name)
except AttributeError:
return object.__getattribute__(obj, '_handle_unknown')
class hello(object):
__metaclass__ = HelloType
def GET(self, *args, **kw):
if web.cookies().get('password') == 'secretpassword':
return "Dastardly secret plans..."
raise web.notfound()
def _handle_unknown(self, *args, **kw):
"""This method will be called for all requests, which have no defined method"""
raise web.notfound()
def __getattribute__(obj, name):
try:
return object.__getattribute__(obj, name)
except AttributeError:
return object.__getattribute__(obj, '_handle_unknown')
__getattribute__
is implemented twice due to the way web.py checks for method existence:
def _delegate(self, f, fvars, args=[]):
def handle_class(cls):
meth = web.ctx.method
if meth == 'HEAD' and not hasattr(cls, meth):
meth = 'GET'
if not hasattr(cls, meth): # Calls type's __getattribute__
raise web.nomethod(cls)
tocall = getattr(cls(), meth) # Calls instance's __getattribute__
you can define any method in your 'secret' class, such as DELETE or SHENANIGANS, like this:
class secret():
def DELETE(self):
...
def SHENANIGANS(self):
...
精彩评论