[MERGE] [CHERRYPICK] Backport of revision 4879 of saas-1 branch.
[odoo/odoo.git] / openerp / service / http_server.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright P. Christeas <p_christ@hol.gr> 2008-2010
4 # Copyright 2010 OpenERP SA. (http://www.openerp.com)
5 #
6 #
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsibility of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # guarantees and support are strongly advised to contract a Free Software
12 # Service Company
13 #
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
27 ###############################################################################
28
29 #.apidoc title: HTTP and XML-RPC Server
30
31 """ This module offers the family of HTTP-based servers. These are not a single
32     class/functionality, but a set of network stack layers, implementing
33     extendable HTTP protocols.
34
35     The OpenERP server defines a single instance of a HTTP server, listening at
36     the standard 8069, 8071 ports (well, it is 2 servers, and ports are 
37     configurable, of course). This "single" server then uses a `MultiHTTPHandler`
38     to dispatch requests to the appropriate channel protocol, like the XML-RPC,
39     static HTTP, DAV or other.
40 """
41
42 import base64
43 import posixpath
44 import urllib
45 import os
46 import logging
47
48 from websrv_lib import *
49 import openerp.tools as tools
50
51 try:
52     import fcntl
53 except ImportError:
54     fcntl = None
55
56 try:
57     from ssl import SSLError
58 except ImportError:
59     class SSLError(Exception): pass
60
61 _logger = logging.getLogger(__name__)
62
63 # TODO delete this for 6.2, it is still needed for 6.1.
64 class HttpLogHandler:
65     """ helper class for uniform log handling
66     Please define self._logger at each class that is derived from this
67     """
68     _logger = None
69     
70     def log_message(self, format, *args):
71         self._logger.debug(format % args) # todo: perhaps other level
72
73     def log_error(self, format, *args):
74         self._logger.error(format % args)
75         
76     def log_exception(self, format, *args):
77         self._logger.exception(format, *args)
78
79     def log_request(self, code='-', size='-'):
80         self._logger.debug('"%s" %s %s',
81             self.requestline, str(code), str(size))
82
83 class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
84     _logger = logging.getLogger(__name__)
85
86     _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD'] }
87
88     def __init__(self,request, client_address, server):
89         HTTPHandler.__init__(self,request,client_address,server)
90         document_root = tools.config.get('static_http_document_root', False)
91         assert document_root, "Please specify static_http_document_root in configuration, or disable static-httpd!"
92         self.__basepath = document_root
93
94     def translate_path(self, path):
95         """Translate a /-separated PATH to the local filename syntax.
96
97         Components that mean special things to the local file system
98         (e.g. drive or directory names) are ignored.  (XXX They should
99         probably be diagnosed.)
100
101         """
102         # abandon query parameters
103         path = path.split('?',1)[0]
104         path = path.split('#',1)[0]
105         path = posixpath.normpath(urllib.unquote(path))
106         words = path.split('/')
107         words = filter(None, words)
108         path = self.__basepath
109         for word in words:
110             if word in (os.curdir, os.pardir): continue
111             path = os.path.join(path, word)
112         return path
113
114 def init_static_http():
115     if not tools.config.get('static_http_enable', False):
116         return
117     
118     document_root = tools.config.get('static_http_document_root', False)
119     assert document_root, "Document root must be specified explicitly to enable static HTTP service (option --static-http-document-root)"
120     
121     base_path = tools.config.get('static_http_url_prefix', '/')
122     
123     reg_http_service(base_path, StaticHTTPHandler)
124     
125     _logger.info("Registered HTTP dir %s for %s", document_root, base_path)
126
127 import security
128
129 class OpenERPAuthProvider(AuthProvider):
130     """ Require basic authentication."""
131     def __init__(self,realm='OpenERP User'):
132         self.realm = realm
133         self.auth_creds = {}
134         self.auth_tries = 0
135         self.last_auth = None
136
137     def authenticate(self, db, user, passwd, client_address):
138         try:
139             uid = security.login(db,user,passwd)
140             if uid is False:
141                 return False
142             return user, passwd, db, uid
143         except Exception,e:
144             _logger.debug("Fail auth: %s" % e )
145             return False
146
147     def checkRequest(self,handler,path, db=False):        
148         auth_str = handler.headers.get('Authorization',False)
149         try:
150             if not db:
151                 db = handler.get_db_from_path(path)
152         except Exception:
153             if path.startswith('/'):
154                 path = path[1:]
155             psp= path.split('/')
156             if len(psp)>1:
157                 db = psp[0]
158             else:
159                 #FIXME!
160                 _logger.info("Wrong path: %s, failing auth" %path)
161                 raise AuthRejectedExc("Authorization failed. Wrong sub-path.") 
162         if self.auth_creds.get(db):
163             return True 
164         if auth_str and auth_str.startswith('Basic '):
165             auth_str=auth_str[len('Basic '):]
166             (user,passwd) = base64.decodestring(auth_str).split(':')
167             _logger.info("Found user=\"%s\", passwd=\"***\" for db=\"%s\"", user, db)
168             acd = self.authenticate(db,user,passwd,handler.client_address)
169             if acd != False:
170                 self.auth_creds[db] = acd
171                 self.last_auth = db
172                 return True
173         if self.auth_tries > 5:
174             _logger.info("Failing authorization after 5 requests w/o password")
175             raise AuthRejectedExc("Authorization failed.")
176         self.auth_tries += 1
177         raise AuthRequiredExc(atype='Basic', realm=self.realm)
178
179 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: