[REF] websrv_lib:

- grouped together the concept of auth proxy and auth provider
- removed the sec_realms dictionary: it was redundant as the
auth proxy was set (in one of the implementation, not all) on
the handler by setupAuth. Now the auth proxy is always set on
the handler.
- reg_http_service accepts the argument of the HTTPDir
constructor.

bzr revid: vmt@openerp.com-20110908123818-suc53zebgv9kcy3c
This commit is contained in:
Vo Minh Thu 2011-09-08 14:38:18 +02:00
parent 17c54e1217
commit e643ffcc32
4 changed files with 62 additions and 122 deletions

View File

@ -250,14 +250,14 @@ class XMLRPCRequestHandler(FixSendError,HttpLogHandler,SimpleXMLRPCServer.Simple
def init_xmlrpc():
if tools.config.get('xmlrpc', False):
# Example of http file serving:
# reg_http_service(HTTPDir('/test/',HTTPHandler))
reg_http_service(HTTPDir('/xmlrpc/', XMLRPCRequestHandler))
# reg_http_service('/test/', HTTPHandler)
reg_http_service('/xmlrpc/', XMLRPCRequestHandler)
logging.getLogger("web-services").info("Registered XML-RPC over HTTP")
if tools.config.get('xmlrpcs', False) \
and not tools.config.get('xmlrpc', False):
# only register at the secure server
reg_http_service(HTTPDir('/xmlrpc/', XMLRPCRequestHandler, secure_only=True))
reg_http_service('/xmlrpc/', XMLRPCRequestHandler, secure_only=True)
logging.getLogger("web-services").info("Registered XML-RPC over HTTPS only")
class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
@ -299,65 +299,21 @@ def init_static_http():
base_path = tools.config.get('static_http_url_prefix', '/')
reg_http_service(HTTPDir(base_path,StaticHTTPHandler))
reg_http_service(base_path, StaticHTTPHandler)
logging.getLogger("web-services").info("Registered HTTP dir %s for %s" % \
(document_root, base_path))
class OerpAuthProxy(AuthProxy):
""" Require basic authentication..
import security
This is a copy of the BasicAuthProxy, which however checks/caches the db
as well.
"""
def __init__(self,provider):
AuthProxy.__init__(self,provider)
class OpenERPAuthProvider(AuthProvider):
""" Require basic authentication."""
def __init__(self,realm='OpenERP User'):
self.realm = realm
self.auth_creds = {}
self.auth_tries = 0
self.last_auth = None
def checkRequest(self,handler,path, db=False):
auth_str = handler.headers.get('Authorization',False)
try:
if not db:
db = handler.get_db_from_path(path)
except Exception:
if path.startswith('/'):
path = path[1:]
psp= path.split('/')
if len(psp)>1:
db = psp[0]
else:
#FIXME!
self.provider.log("Wrong path: %s, failing auth" %path)
raise AuthRejectedExc("Authorization failed. Wrong sub-path.")
if self.auth_creds.get(db):
return True
if auth_str and auth_str.startswith('Basic '):
auth_str=auth_str[len('Basic '):]
(user,passwd) = base64.decodestring(auth_str).split(':')
self.provider.log("Found user=\"%s\", passwd=\"***\" for db=\"%s\"" %(user,db))
acd = self.provider.authenticate(db,user,passwd,handler.client_address)
if acd != False:
self.auth_creds[db] = acd
self.last_auth = db
return True
if self.auth_tries > 5:
self.provider.log("Failing authorization after 5 requests w/o password")
raise AuthRejectedExc("Authorization failed.")
self.auth_tries += 1
raise AuthRequiredExc(atype='Basic', realm=self.provider.realm)
import security
class OpenERPAuthProvider(AuthProvider):
def __init__(self,realm='OpenERP User'):
self.realm = realm
def setupAuth(self, multi, handler):
if not multi.sec_realms.has_key(self.realm):
multi.sec_realms[self.realm] = OerpAuthProxy(self)
handler.auth_proxy = multi.sec_realms[self.realm]
def authenticate(self, db, user, passwd, client_address):
try:
uid = security.login(db,user,passwd)
@ -371,4 +327,36 @@ class OpenERPAuthProvider(AuthProvider):
def log(self, msg, lvl=logging.INFO):
logging.getLogger("auth").log(lvl,msg)
def checkRequest(self,handler,path, db=False):
auth_str = handler.headers.get('Authorization',False)
try:
if not db:
db = handler.get_db_from_path(path)
except Exception:
if path.startswith('/'):
path = path[1:]
psp= path.split('/')
if len(psp)>1:
db = psp[0]
else:
#FIXME!
self.log("Wrong path: %s, failing auth" %path)
raise AuthRejectedExc("Authorization failed. Wrong sub-path.")
if self.auth_creds.get(db):
return True
if auth_str and auth_str.startswith('Basic '):
auth_str=auth_str[len('Basic '):]
(user,passwd) = base64.decodestring(auth_str).split(':')
self.log("Found user=\"%s\", passwd=\"***\" for db=\"%s\"" %(user,db))
acd = self.authenticate(db,user,passwd,handler.client_address)
if acd != False:
self.auth_creds[db] = acd
self.last_auth = db
return True
if self.auth_tries > 5:
self.log("Failing authorization after 5 requests w/o password")
raise AuthRejectedExc("Authorization failed.")
self.auth_tries += 1
raise AuthRequiredExc(atype='Basic', realm=self.realm)
#eof

View File

@ -379,7 +379,7 @@ class common(netsvc.ExportService):
return res or False
elif method == 'logout':
if auth:
auth.logout(params[1])
auth.logout(params[1]) # TODO I didn't see any AuthProxy implementing this method.
logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db))
return True
elif method in ['about', 'timezone_get', 'get_server_environment',

View File

@ -52,61 +52,17 @@ class AuthProvider:
def __init__(self,realm):
self.realm = realm
def setupAuth(self, multi,handler):
""" Attach an AuthProxy object to handler
"""
pass
def authenticate(self, user, passwd, client_address):
return False
def log(self, msg):
print msg
class BasicAuthProvider(AuthProvider):
def setupAuth(self, multi, handler):
if not multi.sec_realms.has_key(self.realm):
multi.sec_realms[self.realm] = BasicAuthProxy(self)
class AuthProxy:
""" This class will hold authentication information for a handler,
i.e. a connection
"""
def __init__(self, provider):
self.provider = provider
def checkRequest(self,handler,path = '/'):
""" Check if we are allowed to process that request
"""
pass
class BasicAuthProxy(AuthProxy):
""" Require basic authentication..
"""
def __init__(self,provider):
AuthProxy.__init__(self,provider)
self.auth_creds = None
self.auth_tries = 0
def checkRequest(self,handler,path = '/'):
if self.auth_creds:
return True
auth_str = handler.headers.get('Authorization',False)
if auth_str and auth_str.startswith('Basic '):
auth_str=auth_str[len('Basic '):]
(user,passwd) = base64.decodestring(auth_str).split(':')
self.provider.log("Found user=\"%s\", passwd=\"%s\"" %(user,passwd))
self.auth_creds = self.provider.authenticate(user,passwd,handler.client_address)
if self.auth_creds:
return True
if self.auth_tries > 5:
self.provider.log("Failing authorization after 5 requests w/o password")
raise AuthRejectedExc("Authorization failed.")
self.auth_tries += 1
raise AuthRequiredExc(atype = 'Basic', realm=self.provider.realm)
class HTTPHandler(SimpleHTTPRequestHandler):
def __init__(self,request, client_address, server):
SimpleHTTPRequestHandler.__init__(self,request,client_address,server)
@ -144,13 +100,19 @@ class HTTPDir:
return self.path
return False
def reg_http_service(service):
""" Register some handler to httpd.
hts must be an HTTPDir
def instanciate_handler(self, request, client_address, server):
handler = self.handler(noconnection(request), client_address, server)
if self.auth_provider:
handler.auth_provider = self.auth_provider
return handler
def reg_http_service(path, handler, auth_provider=None, secure_only=False):
""" Register a HTTP handler at a given path.
The auth_provider will be set on the handler instances.
"""
assert isinstance(service, HTTPDir), "Wrong class for http service"
global handlers
service = HTTPDir(path, handler, auth_provider, secure_only)
pos = len(handlers)
lastpos = pos
while pos > 0:
@ -286,11 +248,10 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler):
def __init__(self, request, client_address, server):
self.in_handlers = {}
self.sec_realms = {}
SocketServer.StreamRequestHandler.__init__(self,request,client_address,server)
self.log_message("MultiHttpHandler init for %s" %(str(client_address)))
def _handle_one_foreign(self,fore, path, auth_provider):
def _handle_one_foreign(self, fore, path):
""" This method overrides the handle_one_request for *children*
handlers. It is required, since the first line should not be
read again..
@ -306,9 +267,9 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler):
return
self.request_version = fore.request_version
if auth_provider and auth_provider.realm:
if hasattr(fore, 'auth_provider'):
try:
self.sec_realms[auth_provider.realm].checkRequest(fore,path)
fore.auth_provider.checkRequest(fore,path)
except AuthRequiredExc,ae:
# Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the
# authorisation features of HTTP/1.1
@ -456,15 +417,13 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler):
npath = '/' + npath
if not self.in_handlers.has_key(p):
self.in_handlers[p] = vdir.handler(noconnection(self.request),self.client_address,self.server)
if vdir.auth_provider:
vdir.auth_provider.setupAuth(self, self.in_handlers[p])
self.in_handlers[p] = vdir.instanciate_handler(noconnection(self.request),self.client_address,self.server)
hnd = self.in_handlers[p]
hnd.rfile = self.rfile
hnd.wfile = self.wfile
self.rlpath = self.raw_requestline
try:
self._handle_one_foreign(hnd,npath, vdir.auth_provider)
self._handle_one_foreign(hnd, npath)
except IOError, e:
if e.errno == errno.EPIPE:
self.log_message("Could not complete request %s," \

View File

@ -173,15 +173,8 @@ def http_to_wsgi(http_dir):
server.server_port = int(environ['SERVER_PORT'])
con = openerp.service.websrv_lib.noconnection(environ['gunicorn.socket']) # None TODO
# Let's pretend we have a Multi handler (i.e. the
# websrv_lib.MultiHTTPHandler class) to hand to the auth. provider.
multi = Dummy()
multi.sec_realms = {}
# Initialize the underlying handler and associated auth. provider.
handler = http_dir.handler(openerp.service.websrv_lib.noconnection(con), environ['REMOTE_ADDR'], server)
auth_provider = http_dir.auth_provider
auth_provider.setupAuth(multi, handler)
handler = http_dir.instanciate_handler(openerp.service.websrv_lib.noconnection(con), environ['REMOTE_ADDR'], server)
# Populate the handler as if it is called by a regular HTTP server
# and the request is already parsed.
@ -197,9 +190,9 @@ def http_to_wsgi(http_dir):
# Handle authentication if there is an auth. provider associated to
# the handler.
if auth_provider and auth_provider.realm:
if hasattr(handler, 'auth_provider'):
try:
multi.sec_realms[auth_provider.realm].checkRequest(handler, path)
handler.auth_provider.checkRequest(handler, path)
except websrv_lib.AuthRequiredExc, ae:
# Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the
# authorisation features of HTTP/1.1
@ -245,7 +238,7 @@ def http_to_wsgi(http_dir):
except (websrv_lib.AuthRejectedExc, websrv_lib.AuthRequiredExc):
raise
except Exception, e:
start_response("500 Internal error")
start_response("500 Internal error", [])
return []
return wsgi_handler