Browse Source

Add sql export module (migration from v7)

Add rollback after executing query as a double security with blacklist terms

add known issue in readme
pull/1554/head
Florian da Costa 8 years ago
committed by David Beal
parent
commit
21d18bbd38
  1. 56
      sql_export/README.rst
  2. 2
      sql_export/__init__.py
  3. 39
      sql_export/__openerp__.py
  4. 142
      sql_export/i18n/fr.po
  5. 142
      sql_export/i18n/sql_export.pot
  6. 3
      sql_export/security/ir.model.access.csv
  7. 21
      sql_export/security/sql_export_security.xml
  8. 149
      sql_export/sql_export.py
  9. 56
      sql_export/sql_export_view.xml
  10. BIN
      sql_export/static/description/icon.png
  11. 2
      sql_export/tests/__init__.py
  12. 71
      sql_export/tests/test_sql_query.py
  13. 1
      sql_export/wizard/__init__.py
  14. 29
      sql_export/wizard/wizard_file.py
  15. 17
      sql_export/wizard/wizard_file_view.xml

56
sql_export/README.rst

@ -0,0 +1,56 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:alt: License: AGPL-3
SQL Export
==========
Allow to export data in csv files FROM sql requests.
There are some restrictions in the sql sql request, you can only read datas.
No update, deletion or creation are possible.
A new menu named Export is created.
Known issues / Roadmap
======================
Some words are prohibeted and can't be used is the query in anyways, even in a select query :
* delete
* drop
* insert
* alter
* truncate
* execute
* create
* update
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20sql_export%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
Contributors
------------
* Florian da Costa <florian.dacosta@akretion.com>
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
sql_export/__init__.py

@ -0,0 +1,2 @@
from . import sql_export
from . import wizard

39
sql_export/__openerp__.py

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2015 Akretion (<http://www.akretion.com>).
#
# 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/>.
#
##############################################################################
{'name': 'SQL Export',
'version': '0.1',
'author': 'Akretion,Odoo Community Association (OCA)',
'website': 'http://www.akretion.com',
'license': 'AGPL-3',
'category': 'Generic Modules/Others',
'summary': 'Export data in csv file with SQL requests',
'depends': ['base',
],
'data': [
'sql_export_view.xml',
'wizard/wizard_file_view.xml',
'security/sql_export_security.xml',
'security/ir.model.access.csv',
],
'installable': True,
'images': [],
}

142
sql_export/i18n/fr.po

@ -0,0 +1,142 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sql_export
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-02-05 13:10+0000\n"
"PO-Revision-Date: 2016-02-05 13:10+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sql_export
#: model:ir.model,name:sql_export.model_sql_file_wizard
msgid "Allow the user to save the file with sql request's data"
msgstr "Permet à l'utilisateur de sauvegarder le fichier contenant les données de la requête SQL"
#. module: sql_export
#: field:sql.export,group_ids:0
msgid "Allowed Groups"
msgstr "Groupes Autorisés"
#. module: sql_export
#: view:sql.export:sql_export.sql_export_view_form
#: field:sql.export,user_ids:0
msgid "Allowed Users"
msgstr "Utilisateurs Autorisés"
#. module: sql_export
#: view:sql.export:sql_export.sql_export_view_form
msgid "Allowed Users Groups"
msgstr "Groupes d'utilisateurs Autorisés"
#. module: sql_export
#: field:sql.export,copy_options:0
msgid "Copy Options"
msgstr "Copy Options"
#. module: sql_export
#: field:sql.export,create_uid:0
#: field:sql.file.wizard,create_uid:0
msgid "Created by"
msgstr "Créé par"
#. module: sql_export
#: field:sql.export,create_date:0
#: field:sql.file.wizard,create_date:0
msgid "Created on"
msgstr "Créé le"
#. module: sql_export
#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form
msgid "Csv File"
msgstr "Fichier CSV"
#. module: sql_export
#: view:sql.export:sql_export.sql_export_view_form
msgid "Execute Query"
msgstr "Exécuter"
#. module: sql_export
#: field:sql.file.wizard,binary_file:0
msgid "File"
msgstr "Fichier"
#. module: sql_export
#: field:sql.file.wizard,file_name:0
msgid "File Name"
msgstr "Nom du fichier"
#. module: sql_export
#: field:sql.export,id:0
#: field:sql.file.wizard,id:0
msgid "ID"
msgstr "ID"
#. module: sql_export
#: field:sql.export,write_uid:0
#: field:sql.file.wizard,write_uid:0
msgid "Last Updated by"
msgstr "Last Updated by"
#. module: sql_export
#: field:sql.export,write_date:0
#: field:sql.file.wizard,write_date:0
msgid "Last Updated on"
msgstr "Last Updated on"
#. module: sql_export
#: field:sql.export,name:0
msgid "Name"
msgstr "Nom"
#. module: sql_export
#: field:sql.export,query:0
msgid "Query"
msgstr "Requête"
#. module: sql_export
#: model:ir.actions.act_window,name:sql_export.sql_export_tree_action
#: view:sql.export:sql_export.sql_export_view_tree
msgid "SQL Export"
msgstr "SQL Export"
#. module: sql_export
#: model:ir.model,name:sql_export.model_sql_export
#: view:sql.export:sql_export.sql_export_view_form
msgid "SQL export"
msgstr "Export SQL"
#. module: sql_export
#: model:ir.ui.menu,name:sql_export.sql_export_menu
#: model:ir.ui.menu,name:sql_export.sql_export_menu_view
msgid "Sql Export"
msgstr "Export SQL"
#. module: sql_export
#: model:res.groups,name:sql_export.group_sql_request_editor
msgid "Sql Request Editor"
msgstr "Edition de Requête SQL"
#. module: sql_export
#: code:addons/sql_export/sql_export.py:132
#, python-format
msgid "The Sql query is not valid."
msgstr "La requête SQL n'est pas valide"
#. module: sql_export
#: constraint:sql.export:0
msgid "The query you want make is not allowed : prohibited actions (delete, drop, insert, alter, truncate, execute, create, update)"
msgstr "La requête que vous voulez faire n'est pas autorisée : actions interdites (delete, drop, insert, alter, truncate, execute, create, update)"
#. module: sql_export
#: help:sql.export,query:0
msgid "You can't use the following word : delete, drop, create, insert, alter, truncate, execute, update"
msgstr "Vous ne pouvez pas utiliser les mots suivants : delete, drop, create, insert, alter, truncate, execute, update"

142
sql_export/i18n/sql_export.pot

@ -0,0 +1,142 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sql_export
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-02-05 13:10+0000\n"
"PO-Revision-Date: 2016-02-05 13:10+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sql_export
#: model:ir.model,name:sql_export.model_sql_file_wizard
msgid "Allow the user to save the file with sql request's data"
msgstr ""
#. module: sql_export
#: field:sql.export,group_ids:0
msgid "Allowed Groups"
msgstr ""
#. module: sql_export
#: view:sql.export:sql_export.sql_export_view_form
#: field:sql.export,user_ids:0
msgid "Allowed Users"
msgstr ""
#. module: sql_export
#: view:sql.export:sql_export.sql_export_view_form
msgid "Allowed Users Groups"
msgstr ""
#. module: sql_export
#: field:sql.export,copy_options:0
msgid "Copy Options"
msgstr ""
#. module: sql_export
#: field:sql.export,create_uid:0
#: field:sql.file.wizard,create_uid:0
msgid "Created by"
msgstr ""
#. module: sql_export
#: field:sql.export,create_date:0
#: field:sql.file.wizard,create_date:0
msgid "Created on"
msgstr ""
#. module: sql_export
#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form
msgid "Csv File"
msgstr ""
#. module: sql_export
#: view:sql.export:sql_export.sql_export_view_form
msgid "Execute Query"
msgstr ""
#. module: sql_export
#: field:sql.file.wizard,binary_file:0
msgid "File"
msgstr ""
#. module: sql_export
#: field:sql.file.wizard,file_name:0
msgid "File Name"
msgstr ""
#. module: sql_export
#: field:sql.export,id:0
#: field:sql.file.wizard,id:0
msgid "ID"
msgstr ""
#. module: sql_export
#: field:sql.export,write_uid:0
#: field:sql.file.wizard,write_uid:0
msgid "Last Updated by"
msgstr ""
#. module: sql_export
#: field:sql.export,write_date:0
#: field:sql.file.wizard,write_date:0
msgid "Last Updated on"
msgstr ""
#. module: sql_export
#: field:sql.export,name:0
msgid "Name"
msgstr ""
#. module: sql_export
#: field:sql.export,query:0
msgid "Query"
msgstr ""
#. module: sql_export
#: model:ir.actions.act_window,name:sql_export.sql_export_tree_action
#: view:sql.export:sql_export.sql_export_view_tree
msgid "SQL Export"
msgstr ""
#. module: sql_export
#: model:ir.model,name:sql_export.model_sql_export
#: view:sql.export:sql_export.sql_export_view_form
msgid "SQL export"
msgstr ""
#. module: sql_export
#: model:ir.ui.menu,name:sql_export.sql_export_menu
#: model:ir.ui.menu,name:sql_export.sql_export_menu_view
msgid "Sql Export"
msgstr ""
#. module: sql_export
#: model:res.groups,name:sql_export.group_sql_request_editor
msgid "Sql Request Editor"
msgstr ""
#. module: sql_export
#: code:addons/sql_export/sql_export.py:132
#, python-format
msgid "The Sql query is not valid."
msgstr ""
#. module: sql_export
#: constraint:sql.export:0
msgid "The query you want make is not allowed : prohibited actions (delete, drop, insert, alter, truncate, execute, create, update)"
msgstr ""
#. module: sql_export
#: help:sql.export,query:0
msgid "You can't use the following word : delete, drop, create, insert, alter, truncate, execute, update"
msgstr ""

3
sql_export/security/ir.model.access.csv

@ -0,0 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_sql_export_all","access_sql_export_all","model_sql_export",,1,0,0,0
"access_sql_export_editor","access_sql_export_editor","model_sql_export",group_sql_request_editor,1,1,1,1

21
sql_export/security/sql_export_security.xml

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record model="res.groups" id="group_sql_request_editor">
<field name="name">Sql Request Editor</field>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record model="ir.rule" id="sql_export_restric_access_user_or_group">
<field name="name" >SQL Export users and groups rules</field>
<field name="model_id" ref="model_sql_export"/>
<field eval="1" name="perm_read"/>
<field eval="0" name="perm_create"/>
<field eval="0" name="perm_write"/>
<field eval="0" name="perm_unlink"/>
<field name="domain_force">['|', ('user_ids','=',user.id), ('group_ids','in', [x.id for x in user.groups_id])]</field>
</record>
</data>
</openerp>

149
sql_export/sql_export.py

@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2015 Akretion (<http://www.akretion.com>).
#
# 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 StringIO
import base64
import datetime
import re
from openerp import models, fields, api, _, exceptions
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
import uuid
class SqlExport(models.Model):
_name = "sql.export"
_description = "SQL export"
PROHIBITED_WORDS = [
'delete',
'drop',
'insert',
'alter',
'truncate',
'execute',
'create',
'update'
]
@api.multi
def _check_query_allowed(self):
for obj in self:
query = obj.query.lower()
for word in self.PROHIBITED_WORDS:
expr = r'\b%s\b' % word
is_not_safe = re.search(expr, query)
if is_not_safe:
return False
return True
@api.model
def _get_editor_group(self):
ir_model_obj = self.env['ir.model.data']
return [ir_model_obj.xmlid_to_res_id(
'sql_export.group_sql_request_editor')]
name = fields.Char('Name', required=True)
query = fields.Text(
'Query',
required=True,
help="You can't use the following word : delete, drop, create, "
"insert, alter, truncate, execute, update")
copy_options = fields.Char(
'Copy Options',
required=True,
default="CSV HEADER DELIMITER ';'")
group_ids = fields.Many2many(
'res.groups',
'groups_sqlquery_rel',
'sql_id',
'group_id',
'Allowed Groups',
default=_get_editor_group)
user_ids = fields.Many2many(
'res.users',
'users_sqlquery_rel',
'sql_id',
'user_id',
'Allowed Users')
_constraints = [(_check_query_allowed,
'The query you want make is not allowed : prohibited '
'actions (%s)' % ', '.join(PROHIBITED_WORDS),
['query'])]
@api.multi
def export_sql_query(self):
for obj in self:
today = datetime.datetime.now()
today_tz = fields.Datetime.context_timestamp(
obj, today)
date = today_tz.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
output = StringIO.StringIO()
query = "COPY (" + obj.query + ") TO STDOUT WITH " + \
obj.copy_options
name = 'export_query_%s' % uuid.uuid1().hex
self.env.cr.execute("SAVEPOINT %s" % name)
try:
self.env.cr.copy_expert(query, output)
output.getvalue()
new_output = base64.b64encode(output.getvalue())
output.close()
finally:
self.env.cr.execute("ROLLBACK TO SAVEPOINT %s" % name)
wiz = self.env['sql.file.wizard'].create(
{
'binary_file': new_output,
'file_name': obj.name + '_' + date + '.csv'})
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'sql.file.wizard',
'res_id': wiz.id,
'type': 'ir.actions.act_window',
'target': 'new',
'context': self._context,
'nodestroy': True,
}
@api.model
def check_query_syntax(self, vals):
if vals.get('query', False):
vals['query'] = vals['query'].strip()
if vals['query'][-1] == ';':
vals['query'] = vals['query'][:-1]
try:
self.env.cr.execute(vals['query'])
except:
raise exceptions.Warning(
_("The Sql query is not valid."))
finally:
self.env.cr.rollback()
return vals
@api.multi
def write(self, vals):
vals = self.check_query_syntax(vals)
return super(SqlExport, self).write(vals)
@api.model
def create(self, vals):
vals = self.check_query_syntax(vals)
return super(SqlExport, self).create(vals)

56
sql_export/sql_export_view.xml

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="sql_export_view_form" model="ir.ui.view">
<field name="name">Sql_export_form_view</field>
<field name="model">sql.export</field>
<field name="arch" type="xml">
<form string="SQL export">
<group col="2">
<group colspan="2" col="5">
<label for="name" colspan="1"/>
<field name="name" colspan="2" nolabel="1"/>
<button name="export_sql_query" string="Execute Query" type="object" class="oe_highlight" icon="gtk-execute" colspan="2"/>
<label for="query" colspan="1"/>
<field name="query" nolabel="1" colspan="4"/>
<label for="Copy Options" colspan="1"/>
<field name="copy_options" nolabel="1" colspan="4"/>
</group>
<group colspan="2" col="2" groups="sql_export.group_sql_request_editor">
<separator string="Allowed Users" colspan="1"/>
<separator string="Allowed Users Groups" colspan="1"/>
<field name="user_ids" nolabel="1"/>
<field name="group_ids" nolabel="1"/>
</group>
</group>
</form>
</field>
</record>
<record id="sql_export_view_tree" model="ir.ui.view">
<field name="name">Sql_export_tree_view</field>
<field name="model">sql.export</field>
<field name="arch" type="xml">
<tree string="SQL Export">
<field name="name"/>
</tree>
</field>
</record>
<record id="sql_export_tree_action" model="ir.actions.act_window">
<field name="name">SQL Export</field>
<field name="res_model">sql.export</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="sql_export_menu" name="Sql Export" parent="base.menu_reporting" sequence="80"/>
<menuitem id="sql_export_menu_view" name="Sql Export" parent="sql_export_menu" action="sql_export_tree_action" sequence="1"/>
</data>
</openerp>

BIN
sql_export/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

2
sql_export/tests/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_sql_query

71
sql_export/tests/test_sql_query.py

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Florian da Costa
# Copyright 2015 Akretion
#
# 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 distnaributed 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
from openerp.tests.common import TransactionCase
from openerp import exceptions
class TestExportSqlQuery(TransactionCase):
def setUp(self):
super(TestExportSqlQuery, self).setUp()
query_vals = {
'name': 'test',
'query': "SELECT name, street FROM res_partner;"
}
self.sql_model = self.registry('sql.export')
self.query_id = self.sql_model.create(
self.cr,
self.uid,
query_vals)
def test_sql_query(self):
test = self.sql_model.export_sql_query(
self.cr, self.uid, [self.query_id])
wizard = self.registry('sql.file.wizard').browse(
self.cr, self.uid, test['res_id'])
export = base64.b64decode(wizard.binary_file)
self.assertEqual(export.split(';')[0], 'name')
self.assertTrue(len(export.split(';')) > 6)
def test_prohibited_queries_creation(self):
prohibited_queries = [
"upDaTe res_partner SET name = 'test' WHERE id = 1",
"DELETE FROM sql_export WHERE name = 'test';",
" DELETE FROM sql_export WHERE name = 'test' ;",
"""DELETE
FROM
sql_export
WHERE name = 'test'
""",
]
for query in prohibited_queries:
with self.assertRaises(exceptions.ValidationError):
self.sql_model.create(
self.cr, self.uid,
{'name': 'test_prohibited',
'query': query})
ok_query = {
'name': 'test ok',
'query': "SELECT create_date FROM res_partner"
}
query_id = self.sql_model.create(self.cr, self.uid, ok_query)
self.assertIsNotNone(query_id)

1
sql_export/wizard/__init__.py

@ -0,0 +1 @@
from . import wizard_file

29
sql_export/wizard/wizard_file.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2015 Akretion (<http://www.akretion.com>).
#
# 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/>.
#
##############################################################################
from openerp import models, fields
class SqlFileWizard(models.TransientModel):
_name = "sql.file.wizard"
_description = "Allow the user to save the file with sql request's data"
binary_file = fields.Binary('File', required=True, readonly=True)
file_name = fields.Char('File Name', readonly=True)

17
sql_export/wizard/wizard_file_view.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="sql_file_wizard_view_form" model="ir.ui.view">
<field name="name">sql.file.wizard.view.form</field>
<field name="model">sql.file.wizard</field>
<field name="arch" type="xml">
<form string="Csv File">
<field name="binary_file" filename="file_name"/>
<field name="file_name" invisible="1"/>
</form>
</field>
</record>
</data>
</openerp>
Loading…
Cancel
Save