The auth param is an auth proxy/provider. The idea is that an auth proxy is associated to some HTTP request handler. The handler can then use it when necessary, depending on the accessed resource. As of now, it is only used by the webdav module. But really, the caldav module accesses the proxy directly on the handler. Currently in web_services, authentication is simply done via security.check(). bzr revid: vmt@openerp.com-20110925152318-i3jvimumm51e2lu4
789 lines
30 KiB
Python
789 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
##############################################################################
|
|
#
|
|
# OpenERP, Open Source Management Solution
|
|
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
##############################################################################
|
|
|
|
import base64
|
|
import locale
|
|
import logging
|
|
import os
|
|
import platform
|
|
import security
|
|
import sys
|
|
import thread
|
|
import threading
|
|
import time
|
|
import traceback
|
|
from cStringIO import StringIO
|
|
from openerp.tools.translate import _
|
|
import openerp.netsvc as netsvc
|
|
import openerp.pooler as pooler
|
|
import openerp.release as release
|
|
import openerp.sql_db as sql_db
|
|
import openerp.tools as tools
|
|
import openerp.modules
|
|
|
|
#.apidoc title: Exported Service methods
|
|
#.apidoc module-mods: member-order: bysource
|
|
|
|
""" This python module defines the RPC methods available to remote clients.
|
|
|
|
Each 'Export Service' is a group of 'methods', which in turn are RPC
|
|
procedures to be called. Each method has its own arguments footprint.
|
|
"""
|
|
|
|
# This should be moved to openerp.modules.db, along side initialize().
|
|
def _initialize_db(serv, id, db_name, demo, lang, user_password):
|
|
cr = None
|
|
try:
|
|
serv.actions[id]['progress'] = 0
|
|
cr = sql_db.db_connect(db_name).cursor()
|
|
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
|
|
tools.config['lang'] = lang
|
|
cr.commit()
|
|
cr.close()
|
|
|
|
pool = pooler.restart_pool(db_name, demo, serv.actions[id],
|
|
update_module=True)[1]
|
|
|
|
cr = sql_db.db_connect(db_name).cursor()
|
|
|
|
if lang:
|
|
modobj = pool.get('ir.module.module')
|
|
mids = modobj.search(cr, 1, [('state', '=', 'installed')])
|
|
modobj.update_translations(cr, 1, mids, lang)
|
|
|
|
cr.execute('UPDATE res_users SET password=%s, context_lang=%s, active=True WHERE login=%s', (
|
|
user_password, lang, 'admin'))
|
|
cr.execute('SELECT login, password, name ' \
|
|
' FROM res_users ' \
|
|
' ORDER BY login')
|
|
serv.actions[id].update(users=cr.dictfetchall(), clean=True)
|
|
cr.commit()
|
|
cr.close()
|
|
except Exception, e:
|
|
serv.actions[id].update(clean=False, exception=e)
|
|
logging.getLogger('db.create').exception('CREATE DATABASE failed:')
|
|
serv.actions[id]['traceback'] = traceback.format_exc()
|
|
if cr:
|
|
cr.close()
|
|
|
|
class db(netsvc.ExportService):
|
|
def __init__(self, name="db"):
|
|
netsvc.ExportService.__init__(self, name)
|
|
self.actions = {}
|
|
self.id = 0
|
|
self.id_protect = threading.Semaphore()
|
|
|
|
self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
|
|
|
|
def dispatch(self, method, params):
|
|
if method in [ 'create', 'get_progress', 'drop', 'dump',
|
|
'restore', 'rename',
|
|
'change_admin_password', 'migrate_databases',
|
|
'create_database' ]:
|
|
passwd = params[0]
|
|
params = params[1:]
|
|
security.check_super(passwd)
|
|
elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
|
|
# params = params
|
|
# No security check for these methods
|
|
pass
|
|
else:
|
|
raise KeyError("Method not found: %s" % method)
|
|
fn = getattr(self, 'exp_'+method)
|
|
return fn(*params)
|
|
|
|
def _create_empty_database(self, name):
|
|
db = sql_db.db_connect('template1')
|
|
cr = db.cursor()
|
|
try:
|
|
cr.autocommit(True) # avoid transaction block
|
|
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "template0" """ % name)
|
|
finally:
|
|
cr.close()
|
|
|
|
def exp_create(self, db_name, demo, lang, user_password='admin'):
|
|
self.id_protect.acquire()
|
|
self.id += 1
|
|
id = self.id
|
|
self.id_protect.release()
|
|
|
|
self.actions[id] = {'clean': False}
|
|
|
|
self._create_empty_database(db_name)
|
|
|
|
logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower())
|
|
create_thread = threading.Thread(target=_initialize_db,
|
|
args=(self, id, db_name, demo, lang, user_password))
|
|
create_thread.start()
|
|
self.actions[id]['thread'] = create_thread
|
|
return id
|
|
|
|
def exp_create_database(self, db_name, demo, lang, user_password='admin'):
|
|
""" Similar to exp_create but blocking."""
|
|
self.id_protect.acquire()
|
|
self.id += 1
|
|
id = self.id
|
|
self.id_protect.release()
|
|
|
|
self.actions[id] = {'clean': False}
|
|
|
|
logging.getLogger('db.create').info('CREATE DATABASE %s', db_name.lower())
|
|
self._create_empty_database(db_name)
|
|
_initialize_db(self, id, db_name, demo, lang, user_password)
|
|
return True
|
|
|
|
def exp_get_progress(self, id):
|
|
if self.actions[id]['thread'].isAlive():
|
|
# return openerp.modules.init_progress[db_name]
|
|
return (min(self.actions[id].get('progress', 0),0.95), [])
|
|
else:
|
|
clean = self.actions[id]['clean']
|
|
if clean:
|
|
users = self.actions[id]['users']
|
|
self.actions.pop(id)
|
|
return (1.0, users)
|
|
else:
|
|
e = self.actions[id]['exception']
|
|
self.actions.pop(id)
|
|
raise Exception, e
|
|
|
|
def exp_drop(self, db_name):
|
|
sql_db.close_db(db_name)
|
|
openerp.modules.registry.RegistryManager.clear_caches(db_name)
|
|
openerp.netsvc.Agent.cancel(db_name)
|
|
logger = netsvc.Logger()
|
|
|
|
db = sql_db.db_connect('template1')
|
|
cr = db.cursor()
|
|
cr.autocommit(True) # avoid transaction block
|
|
try:
|
|
try:
|
|
cr.execute('DROP DATABASE "%s"' % db_name)
|
|
except Exception, e:
|
|
logger.notifyChannel("web-services", netsvc.LOG_ERROR,
|
|
'DROP DB: %s failed:\n%s' % (db_name, e))
|
|
raise Exception("Couldn't drop database %s: %s" % (db_name, e))
|
|
else:
|
|
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
|
'DROP DB: %s' % (db_name))
|
|
finally:
|
|
cr.close()
|
|
return True
|
|
|
|
def _set_pg_psw_env_var(self):
|
|
if os.name == 'nt' and not os.environ.get('PGPASSWORD', ''):
|
|
os.environ['PGPASSWORD'] = tools.config['db_password']
|
|
self._pg_psw_env_var_is_set = True
|
|
|
|
def _unset_pg_psw_env_var(self):
|
|
if os.name == 'nt' and self._pg_psw_env_var_is_set:
|
|
os.environ['PGPASSWORD'] = ''
|
|
|
|
def exp_dump(self, db_name):
|
|
logger = netsvc.Logger()
|
|
|
|
self._set_pg_psw_env_var()
|
|
|
|
cmd = ['pg_dump', '--format=c', '--no-owner']
|
|
if tools.config['db_user']:
|
|
cmd.append('--username=' + tools.config['db_user'])
|
|
if tools.config['db_host']:
|
|
cmd.append('--host=' + tools.config['db_host'])
|
|
if tools.config['db_port']:
|
|
cmd.append('--port=' + str(tools.config['db_port']))
|
|
cmd.append(db_name)
|
|
|
|
stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd))
|
|
stdin.close()
|
|
data = stdout.read()
|
|
res = stdout.close()
|
|
if res:
|
|
logger.notifyChannel("web-services", netsvc.LOG_ERROR,
|
|
'DUMP DB: %s failed\n%s' % (db_name, data))
|
|
raise Exception, "Couldn't dump database"
|
|
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
|
'DUMP DB: %s' % (db_name))
|
|
|
|
self._unset_pg_psw_env_var()
|
|
|
|
return base64.encodestring(data)
|
|
|
|
def exp_restore(self, db_name, data):
|
|
logger = netsvc.Logger()
|
|
|
|
self._set_pg_psw_env_var()
|
|
|
|
if self.exp_db_exist(db_name):
|
|
logger.notifyChannel("web-services", netsvc.LOG_WARNING,
|
|
'RESTORE DB: %s already exists' % (db_name,))
|
|
raise Exception, "Database already exists"
|
|
|
|
self._create_empty_database(db_name)
|
|
|
|
cmd = ['pg_restore', '--no-owner']
|
|
if tools.config['db_user']:
|
|
cmd.append('--username=' + tools.config['db_user'])
|
|
if tools.config['db_host']:
|
|
cmd.append('--host=' + tools.config['db_host'])
|
|
if tools.config['db_port']:
|
|
cmd.append('--port=' + str(tools.config['db_port']))
|
|
cmd.append('--dbname=' + db_name)
|
|
args2 = tuple(cmd)
|
|
|
|
buf=base64.decodestring(data)
|
|
if os.name == "nt":
|
|
tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
|
|
file(tmpfile, 'wb').write(buf)
|
|
args2=list(args2)
|
|
args2.append(' ' + tmpfile)
|
|
args2=tuple(args2)
|
|
stdin, stdout = tools.exec_pg_command_pipe(*args2)
|
|
if not os.name == "nt":
|
|
stdin.write(base64.decodestring(data))
|
|
stdin.close()
|
|
res = stdout.close()
|
|
if res:
|
|
raise Exception, "Couldn't restore database"
|
|
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
|
'RESTORE DB: %s' % (db_name))
|
|
|
|
self._unset_pg_psw_env_var()
|
|
|
|
return True
|
|
|
|
def exp_rename(self, old_name, new_name):
|
|
sql_db.close_db(old_name)
|
|
openerp.modules.registry.RegistryManager.clear_caches(old_name)
|
|
openerp.netsvc.Agent.cancel(old_name)
|
|
logger = netsvc.Logger()
|
|
|
|
db = sql_db.db_connect('template1')
|
|
cr = db.cursor()
|
|
cr.autocommit(True) # avoid transaction block
|
|
try:
|
|
try:
|
|
cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
|
|
except Exception, e:
|
|
logger.notifyChannel("web-services", netsvc.LOG_ERROR,
|
|
'RENAME DB: %s -> %s failed:\n%s' % (old_name, new_name, e))
|
|
raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
|
|
else:
|
|
fs = os.path.join(tools.config['root_path'], 'filestore')
|
|
if os.path.exists(os.path.join(fs, old_name)):
|
|
os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
|
|
|
|
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
|
'RENAME DB: %s -> %s' % (old_name, new_name))
|
|
finally:
|
|
cr.close()
|
|
return True
|
|
|
|
def exp_db_exist(self, db_name):
|
|
## Not True: in fact, check if connection to database is possible. The database may exists
|
|
return bool(sql_db.db_connect(db_name))
|
|
|
|
def exp_list(self, document=False):
|
|
if not tools.config['list_db'] and not document:
|
|
raise Exception('AccessDenied')
|
|
|
|
db = sql_db.db_connect('template1')
|
|
cr = db.cursor()
|
|
try:
|
|
try:
|
|
db_user = tools.config["db_user"]
|
|
if not db_user and os.name == 'posix':
|
|
import pwd
|
|
db_user = pwd.getpwuid(os.getuid())[0]
|
|
if not db_user:
|
|
cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],))
|
|
res = cr.fetchone()
|
|
db_user = res and str(res[0])
|
|
if db_user:
|
|
cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres') order by datname", (db_user,))
|
|
else:
|
|
cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres') order by datname")
|
|
res = [str(name) for (name,) in cr.fetchall()]
|
|
except Exception:
|
|
res = []
|
|
finally:
|
|
cr.close()
|
|
res.sort()
|
|
return res
|
|
|
|
def exp_change_admin_password(self, new_password):
|
|
tools.config['admin_passwd'] = new_password
|
|
tools.config.save()
|
|
return True
|
|
|
|
def exp_list_lang(self):
|
|
return tools.scan_languages()
|
|
|
|
def exp_server_version(self):
|
|
""" Return the version of the server
|
|
Used by the client to verify the compatibility with its own version
|
|
"""
|
|
return release.version
|
|
|
|
def exp_migrate_databases(self,databases):
|
|
|
|
from openerp.osv.orm import except_orm
|
|
from openerp.osv.osv import except_osv
|
|
|
|
l = netsvc.Logger()
|
|
for db in databases:
|
|
try:
|
|
l.notifyChannel('migration', netsvc.LOG_INFO, 'migrate database %s' % (db,))
|
|
tools.config['update']['base'] = True
|
|
pooler.restart_pool(db, force_demo=False, update_module=True)
|
|
except except_orm, inst:
|
|
netsvc.abort_response(1, inst.name, 'warning', inst.value)
|
|
except except_osv, inst:
|
|
netsvc.abort_response(1, inst.name, inst.exc_type, inst.value)
|
|
except Exception:
|
|
import traceback
|
|
tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
|
|
l.notifyChannel('web-services', netsvc.LOG_ERROR, tb_s)
|
|
raise
|
|
return True
|
|
|
|
class common(netsvc.ExportService):
|
|
def __init__(self,name="common"):
|
|
netsvc.ExportService.__init__(self,name)
|
|
|
|
def dispatch(self, method, params):
|
|
logger = netsvc.Logger()
|
|
if method == 'login':
|
|
res = security.login(params[0], params[1], params[2])
|
|
msg = res and 'successful login' or 'bad login or password'
|
|
# TODO log the client ip address..
|
|
logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, params[1], params[0].lower()))
|
|
return res or False
|
|
elif method in ['about', 'timezone_get', 'get_server_environment',
|
|
'login_message','get_stats', 'check_connectivity',
|
|
'list_http_services']:
|
|
pass
|
|
elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']:
|
|
passwd = params[0]
|
|
params = params[1:]
|
|
security.check_super(passwd)
|
|
else:
|
|
raise Exception("Method not found: %s" % method)
|
|
|
|
fn = getattr(self, 'exp_'+method)
|
|
return fn(*params)
|
|
|
|
def exp_about(self, extended=False):
|
|
"""Return information about the OpenERP Server.
|
|
|
|
@param extended: if True then return version info
|
|
@return string if extended is False else tuple
|
|
"""
|
|
|
|
info = _('''
|
|
|
|
OpenERP is an ERP+CRM program for small and medium businesses.
|
|
|
|
The whole source code is distributed under the terms of the
|
|
GNU Public Licence.
|
|
|
|
(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl''')
|
|
|
|
if extended:
|
|
return info, release.version
|
|
return info
|
|
|
|
def exp_timezone_get(self, db, login, password):
|
|
return tools.misc.get_server_timezone()
|
|
|
|
def exp_get_available_updates(self, contract_id, contract_password):
|
|
import openerp.tools.maintenance as tm
|
|
try:
|
|
rc = tm.remote_contract(contract_id, contract_password)
|
|
if not rc.id:
|
|
raise tm.RemoteContractException('This contract does not exist or is not active')
|
|
|
|
return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version())
|
|
|
|
except tm.RemoteContractException, e:
|
|
netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
|
|
|
|
|
|
def exp_get_migration_scripts(self, contract_id, contract_password):
|
|
l = netsvc.Logger()
|
|
import openerp.tools.maintenance as tm
|
|
try:
|
|
rc = tm.remote_contract(contract_id, contract_password)
|
|
if not rc.id:
|
|
raise tm.RemoteContractException('This contract does not exist or is not active')
|
|
if rc.status != 'full':
|
|
raise tm.RemoteContractException('Can not get updates for a partial contract')
|
|
|
|
l.notifyChannel('migration', netsvc.LOG_INFO, 'starting migration with contract %s' % (rc.name,))
|
|
|
|
zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version())
|
|
|
|
from shutil import rmtree, copytree, copy
|
|
|
|
backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M'))
|
|
if zips and not os.path.isdir(backup_directory):
|
|
l.notifyChannel('migration', netsvc.LOG_INFO, 'create a new backup directory to \
|
|
store the old modules: %s' % (backup_directory,))
|
|
os.makedirs(backup_directory)
|
|
|
|
for module in zips:
|
|
l.notifyChannel('migration', netsvc.LOG_INFO, 'upgrade module %s' % (module,))
|
|
mp = openerp.modules.get_module_path(module)
|
|
if mp:
|
|
if os.path.isdir(mp):
|
|
copytree(mp, os.path.join(backup_directory, module))
|
|
if os.path.islink(mp):
|
|
os.unlink(mp)
|
|
else:
|
|
rmtree(mp)
|
|
else:
|
|
copy(mp + 'zip', backup_directory)
|
|
os.unlink(mp + '.zip')
|
|
|
|
try:
|
|
try:
|
|
base64_decoded = base64.decodestring(zips[module])
|
|
except Exception:
|
|
l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to read the module %s' % (module,))
|
|
raise
|
|
|
|
zip_contents = StringIO(base64_decoded)
|
|
zip_contents.seek(0)
|
|
try:
|
|
try:
|
|
tools.extract_zip_file(zip_contents, tools.config['addons_path'] )
|
|
except Exception:
|
|
l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to extract the module %s' % (module, ))
|
|
rmtree(module)
|
|
raise
|
|
finally:
|
|
zip_contents.close()
|
|
except Exception:
|
|
l.notifyChannel('migration', netsvc.LOG_ERROR, 'restore the previous version of the module %s' % (module, ))
|
|
nmp = os.path.join(backup_directory, module)
|
|
if os.path.isdir(nmp):
|
|
copytree(nmp, tools.config['addons_path'])
|
|
else:
|
|
copy(nmp+'.zip', tools.config['addons_path'])
|
|
raise
|
|
|
|
return True
|
|
except tm.RemoteContractException, e:
|
|
netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
|
|
except Exception, e:
|
|
import traceback
|
|
tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
|
|
l.notifyChannel('migration', netsvc.LOG_ERROR, tb_s)
|
|
raise
|
|
|
|
def exp_get_server_environment(self):
|
|
os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
|
|
if not os_lang:
|
|
os_lang = 'NOT SET'
|
|
environment = '\nEnvironment Information : \n' \
|
|
'System : %s\n' \
|
|
'OS Name : %s\n' \
|
|
%(platform.platform(), platform.os.name)
|
|
if os.name == 'posix':
|
|
if platform.system() == 'Linux':
|
|
lsbinfo = os.popen('lsb_release -a').read()
|
|
environment += '%s'%(lsbinfo)
|
|
else:
|
|
environment += 'Your System is not lsb compliant\n'
|
|
environment += 'Operating System Release : %s\n' \
|
|
'Operating System Version : %s\n' \
|
|
'Operating System Architecture : %s\n' \
|
|
'Operating System Locale : %s\n'\
|
|
'Python Version : %s\n'\
|
|
'OpenERP-Server Version : %s'\
|
|
%(platform.release(), platform.version(), platform.architecture()[0],
|
|
os_lang, platform.python_version(),release.version)
|
|
return environment
|
|
|
|
def exp_login_message(self):
|
|
return tools.config.get('login_message', False)
|
|
|
|
def exp_set_loglevel(self, loglevel, logger=None):
|
|
l = netsvc.Logger()
|
|
l.set_loglevel(int(loglevel), logger)
|
|
return True
|
|
|
|
def exp_get_stats(self):
|
|
import threading
|
|
res = "OpenERP server: %d threads\n" % threading.active_count()
|
|
res += netsvc.Server.allStats()
|
|
return res
|
|
|
|
def exp_list_http_services(self):
|
|
return http_server.list_http_services()
|
|
|
|
def exp_check_connectivity(self):
|
|
return bool(sql_db.db_connect('template1'))
|
|
|
|
def exp_get_os_time(self):
|
|
return os.times()
|
|
|
|
def exp_get_sqlcount(self):
|
|
logger = logging.getLogger('db.cursor')
|
|
if not logger.isEnabledFor(logging.DEBUG_SQL):
|
|
logger.warning("Counters of SQL will not be reliable unless DEBUG_SQL is set at the server's config.")
|
|
return sql_db.sql_counter
|
|
|
|
|
|
class objects_proxy(netsvc.ExportService):
|
|
def __init__(self, name="object"):
|
|
netsvc.ExportService.__init__(self,name)
|
|
|
|
def dispatch(self, method, params):
|
|
(db, uid, passwd ) = params[0:3]
|
|
params = params[3:]
|
|
if method == 'obj_list':
|
|
raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
|
|
if method not in ['execute','exec_workflow']:
|
|
raise NameError("Method not available %s" % method)
|
|
security.check(db,uid,passwd)
|
|
assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy."
|
|
fn = getattr(openerp.osv.osv.service, method)
|
|
res = fn(db, uid, *params)
|
|
return res
|
|
|
|
|
|
#
|
|
# Wizard ID: 1
|
|
# - None = end of wizard
|
|
#
|
|
# Wizard Type: 'form'
|
|
# - form
|
|
# - print
|
|
#
|
|
# Wizard datas: {}
|
|
# TODO: change local request to OSE request/reply pattern
|
|
#
|
|
class wizard(netsvc.ExportService):
|
|
def __init__(self, name='wizard'):
|
|
netsvc.ExportService.__init__(self,name)
|
|
self.id = 0
|
|
self.wiz_datas = {}
|
|
self.wiz_name = {}
|
|
self.wiz_uid = {}
|
|
|
|
def dispatch(self, method, params):
|
|
(db, uid, passwd ) = params[0:3]
|
|
params = params[3:]
|
|
if method not in ['execute','create']:
|
|
raise KeyError("Method not supported %s" % method)
|
|
security.check(db,uid,passwd)
|
|
fn = getattr(self, 'exp_'+method)
|
|
res = fn(db, uid, *params)
|
|
return res
|
|
|
|
def _execute(self, db, uid, wiz_id, datas, action, context):
|
|
self.wiz_datas[wiz_id].update(datas)
|
|
wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
|
|
return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
|
|
|
|
def exp_create(self, db, uid, wiz_name, datas=None):
|
|
if not datas:
|
|
datas={}
|
|
#FIXME: this is not thread-safe
|
|
self.id += 1
|
|
self.wiz_datas[self.id] = {}
|
|
self.wiz_name[self.id] = wiz_name
|
|
self.wiz_uid[self.id] = uid
|
|
return self.id
|
|
|
|
def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
|
|
if not context:
|
|
context={}
|
|
|
|
if wiz_id in self.wiz_uid:
|
|
if self.wiz_uid[wiz_id] == uid:
|
|
return self._execute(db, uid, wiz_id, datas, action, context)
|
|
else:
|
|
raise Exception, 'AccessDenied'
|
|
else:
|
|
raise Exception, 'WizardNotFound'
|
|
|
|
#
|
|
# TODO: set a maximum report number per user to avoid DOS attacks
|
|
#
|
|
# Report state:
|
|
# False -> True
|
|
#
|
|
|
|
class ExceptionWithTraceback(Exception):
|
|
def __init__(self, msg, tb):
|
|
self.message = msg
|
|
self.traceback = tb
|
|
self.args = (msg, tb)
|
|
|
|
class report_spool(netsvc.ExportService):
|
|
def __init__(self, name='report'):
|
|
netsvc.ExportService.__init__(self, name)
|
|
self._reports = {}
|
|
self.id = 0
|
|
self.id_protect = threading.Semaphore()
|
|
|
|
def dispatch(self, method, params):
|
|
(db, uid, passwd ) = params[0:3]
|
|
params = params[3:]
|
|
if method not in ['report', 'report_get', 'render_report']:
|
|
raise KeyError("Method not supported %s" % method)
|
|
security.check(db,uid,passwd)
|
|
fn = getattr(self, 'exp_' + method)
|
|
res = fn(db, uid, *params)
|
|
return res
|
|
|
|
def exp_render_report(self, db, uid, object, ids, datas=None, context=None):
|
|
if not datas:
|
|
datas={}
|
|
if not context:
|
|
context={}
|
|
|
|
self.id_protect.acquire()
|
|
self.id += 1
|
|
id = self.id
|
|
self.id_protect.release()
|
|
|
|
self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
|
|
|
|
cr = pooler.get_db(db).cursor()
|
|
import traceback
|
|
import sys
|
|
try:
|
|
obj = netsvc.LocalService('report.'+object)
|
|
(result, format) = obj.create(cr, uid, ids, datas, context)
|
|
if not result:
|
|
tb = sys.exc_info()
|
|
self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
|
|
self._reports[id]['result'] = result
|
|
self._reports[id]['format'] = format
|
|
self._reports[id]['state'] = True
|
|
except Exception, exception:
|
|
|
|
tb = sys.exc_info()
|
|
tb_s = "".join(traceback.format_exception(*tb))
|
|
logger = netsvc.Logger()
|
|
logger.notifyChannel('web-services', netsvc.LOG_ERROR,
|
|
'Exception: %s\n%s' % (str(exception), tb_s))
|
|
if hasattr(exception, 'name') and hasattr(exception, 'value'):
|
|
self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value))
|
|
else:
|
|
self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
|
|
self._reports[id]['state'] = True
|
|
cr.commit()
|
|
cr.close()
|
|
|
|
return self._check_report(id)
|
|
|
|
def exp_report(self, db, uid, object, ids, datas=None, context=None):
|
|
if not datas:
|
|
datas={}
|
|
if not context:
|
|
context={}
|
|
|
|
self.id_protect.acquire()
|
|
self.id += 1
|
|
id = self.id
|
|
self.id_protect.release()
|
|
|
|
self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
|
|
|
|
def go(id, uid, ids, datas, context):
|
|
cr = pooler.get_db(db).cursor()
|
|
import traceback
|
|
import sys
|
|
try:
|
|
obj = netsvc.LocalService('report.'+object)
|
|
(result, format) = obj.create(cr, uid, ids, datas, context)
|
|
if not result:
|
|
tb = sys.exc_info()
|
|
self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
|
|
self._reports[id]['result'] = result
|
|
self._reports[id]['format'] = format
|
|
self._reports[id]['state'] = True
|
|
except Exception, exception:
|
|
|
|
tb = sys.exc_info()
|
|
tb_s = "".join(traceback.format_exception(*tb))
|
|
logger = netsvc.Logger()
|
|
logger.notifyChannel('web-services', netsvc.LOG_ERROR,
|
|
'Exception: %s\n%s' % (str(exception), tb_s))
|
|
if hasattr(exception, 'name') and hasattr(exception, 'value'):
|
|
self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value))
|
|
else:
|
|
self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
|
|
self._reports[id]['state'] = True
|
|
cr.commit()
|
|
cr.close()
|
|
return True
|
|
|
|
thread.start_new_thread(go, (id, uid, ids, datas, context))
|
|
return id
|
|
|
|
def _check_report(self, report_id):
|
|
result = self._reports[report_id]
|
|
exc = result['exception']
|
|
if exc:
|
|
netsvc.abort_response(exc, exc.message, 'warning', exc.traceback)
|
|
res = {'state': result['state']}
|
|
if res['state']:
|
|
if tools.config['reportgz']:
|
|
import zlib
|
|
res2 = zlib.compress(result['result'])
|
|
res['code'] = 'zlib'
|
|
else:
|
|
#CHECKME: why is this needed???
|
|
if isinstance(result['result'], unicode):
|
|
res2 = result['result'].encode('latin1', 'replace')
|
|
else:
|
|
res2 = result['result']
|
|
if res2:
|
|
res['result'] = base64.encodestring(res2)
|
|
res['format'] = result['format']
|
|
del self._reports[report_id]
|
|
return res
|
|
|
|
def exp_report_get(self, db, uid, report_id):
|
|
if report_id in self._reports:
|
|
if self._reports[report_id]['uid'] == uid:
|
|
return self._check_report(report_id)
|
|
else:
|
|
raise Exception, 'AccessDenied'
|
|
else:
|
|
raise Exception, 'ReportNotFound'
|
|
|
|
|
|
def start_web_services():
|
|
db()
|
|
common()
|
|
objects_proxy()
|
|
wizard()
|
|
report_spool()
|
|
|
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|