diff --git a/bin/service/http_server.py b/bin/service/http_server.py index 800268087aa..a7cb22cebcd 100644 --- a/bin/service/http_server.py +++ b/bin/service/http_server.py @@ -83,6 +83,9 @@ class MultiHandler2(MultiHTTPHandler): def log_message(self, format, *args): netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args) + def log_error(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_ERROR,format % args) + class SecureMultiHandler2(SecureMultiHTTPHandler): def log_message(self, format, *args): @@ -94,6 +97,12 @@ class SecureMultiHandler2(SecureMultiHTTPHandler): fkey = tc.get_misc('httpsd','sslkey', 'ssl/server.key') return (fcert,fkey) + def log_message(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args) + + def log_error(self, format, *args): + netsvc.Logger().notifyChannel('http',netsvc.LOG_ERROR,format % args) + class HttpDaemon(threading.Thread, netsvc.Server): def __init__(self, interface, port): threading.Thread.__init__(self) @@ -196,7 +205,7 @@ def reg_http_service(hts, secure_only = False): httpsd.server.vdirs.append(hts) if (not httpd) and (not httpsd): - netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s",hts.path) + netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s" % hts.path) return import SimpleXMLRPCServer @@ -234,4 +243,77 @@ def init_xmlrpc(): # reg_http_service(HTTPDir('/test/',HTTPHandler)) netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, "Registered XML-RPC over HTTP") + + +class OerpAuthProxy(AuthProxy): + """ Require basic authentication.. + + This is a copy of the BasicAuthProxy, which however checks/caches the db + as well. + """ + def __init__(self,provider): + AuthProxy.__init__(self,provider) + self.auth_creds = {} + self.auth_tries = 0 + self.last_auth = None + + def checkRequest(self,handler,path = '/'): + if self.auth_creds: + print "found creds" + return True + auth_str = handler.headers.get('Authorization',False) + try: + print "Handler",handler + db = handler.get_db_from_path(path) + print "Got db:",db + except: + if path.startswith('/'): + path = path[1:] + psp= path.split('/') + print "Path \"%s\" split:" %path,psp + 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 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\" for db=\"%s\"" %(user,passwd,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) + if uid is False: + return False + return (user, passwd, db, uid) + except Exception,e: + netsvc.Logger().notifyChannel("auth",netsvc.LOG_DEBUG,"Fail auth:"+ str(e)) + return False + + def log(self, msg): + netsvc.Logger().notifyChannel("auth",netsvc.LOG_INFO,msg) + #eof diff --git a/bin/service/security.py b/bin/service/security.py index b956914a3dc..03f11a4d34c 100644 --- a/bin/service/security.py +++ b/bin/service/security.py @@ -84,5 +84,6 @@ def access(db, uid, passwd, sec_level, ids): raise ExceptionNoTb('Bad username or password') return res[0] + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index fe1db8f7bc9..71348d2b61b 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -55,10 +55,10 @@ class AuthProvider: pass def authenticate(self, user, passwd, client_address): - #if user == 'user' and passwd == 'password': - # return (user, passwd) - #else: - return False + return False + + def log(self, msg): + print msg class BasicAuthProvider(AuthProvider): def setupAuth(self, multi, handler): @@ -93,11 +93,12 @@ class BasicAuthProxy(AuthProxy): if auth_str and auth_str.startswith('Basic '): auth_str=auth_str[len('Basic '):] (user,passwd) = base64.decodestring(auth_str).split(':') - print "Found user=\"%s\", passwd=\"%s\"" %(user,passwd) + 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) @@ -150,6 +151,7 @@ def _quote_html(html): return html.replace("&", "&").replace("<", "<").replace(">", ">") class FixSendError: + #error_message_format = """ """ def send_error(self, code, message=None): #overriden from BaseHTTPRequestHandler, we also send the content-length try: @@ -168,6 +170,7 @@ class FixSendError: self.send_header('Connection', 'close') self.send_header('Content-Length', len(content) or 0) self.end_headers() + print "Error content:", content if self.command != 'HEAD' and code >= 200 and code not in (204, 304): self.wfile.write(content) @@ -181,6 +184,9 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" + auth_required_msg = """ Authorization required + You must authenticate to use this service\r\r""" + def __init__(self, request, client_address, server): self.in_handlers = {} self.sec_realms = {} @@ -205,14 +211,19 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): self.log_error("Cannot require auth at %s",self.request_version) self.send_error(401) return + self._get_ignore_body(fore) # consume any body that came, not loose sync with input self.send_response(401,'Authorization required') self.send_header('WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm)) + print 'sending WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm) + self.send_header('Connection', 'keep-alive') self.send_header('Content-Type','text/html') - self.send_header('Content-Length','0') + self.send_header('Content-Length',len(self.auth_required_msg)) self.end_headers() - #self.wfile.write("\r\n") + self.wfile.write(self.auth_required_msg) return except AuthRejectedExc,e: + print "auth rejected!",e.args[0] + self.log_error("Rejected auth: %s" % e.args[0]) self.send_error(401,e.args[0]) self.close_connection = 1 return @@ -327,6 +338,20 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): self.send_error(404, "Path not found: %s" % self.path) return + def _get_ignore_body(self,fore): + if not fore.headers.has_key("content-length"): + return + max_chunk_size = 10*1024*1024 + size_remaining = int(fore.headers["content-length"]) + got = '' + if size_remaining: + print "Must consume %d bytes",size_remaining + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + got = fore.rfile.read(chunk_size) + print "c:",got + size_remaining -= len(got) + class SecureMultiHTTPHandler(MultiHTTPHandler): def getcert_fnames(self):