From ea7bddcc3e99f6514efef90fcb26e5ce6f7e427e Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Mon, 25 Jun 2018 16:55:20 +0200 Subject: [PATCH 01/14] [FIX] event, event_sale, website_event: allow portal access to own registrations OPW 1859364 --- addons/event/security/event_security.xml | 7 +++++++ addons/event/security/ir.model.access.csv | 3 ++- addons/event_sale/__openerp__.py | 1 + addons/event_sale/security/event_security.xml | 8 ++++++++ addons/website_event/views/website_event.xml | 2 +- 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 addons/event_sale/security/event_security.xml diff --git a/addons/event/security/event_security.xml b/addons/event/security/event_security.xml index 44a9a1b9c8a..d6874af2604 100644 --- a/addons/event/security/event_security.xml +++ b/addons/event/security/event_security.xml @@ -55,6 +55,13 @@ ] + + Event/Registration: Portal + + + ['|', ('email', '=', user.partner_id.email), ('partner_id', '=', user.partner_id.id)] + + diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv index 35f82422352..004097fa3b7 100644 --- a/addons/event/security/ir.model.access.csv +++ b/addons/event/security/ir.model.access.csv @@ -5,7 +5,8 @@ access_event_event,event.event,model_event_event,event.group_event_user,1,1,1,1 access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1 access_report_event_registration,report.event.registration,model_report_event_registration,event.group_event_user,1,1,1,1 access_event_event_portal,event.event,model_event_event,,1,0,0,0 -access_event_registration_portal,event.registration,model_event_registration,,0,0,0,0 +access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0 +access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0 access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0 access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1 access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0 diff --git a/addons/event_sale/__openerp__.py b/addons/event_sale/__openerp__.py index dae8a0be869..c5e06ae8942 100644 --- a/addons/event_sale/__openerp__.py +++ b/addons/event_sale/__openerp__.py @@ -26,6 +26,7 @@ this event. 'event_sale_data.xml', 'report/event_event_templates.xml', 'security/ir.model.access.csv', + 'security/event_security.xml', 'wizard/event_edit_registration.xml', ], 'demo': ['event_demo.xml'], diff --git a/addons/event_sale/security/event_security.xml b/addons/event_sale/security/event_security.xml new file mode 100644 index 00000000000..8c16690513d --- /dev/null +++ b/addons/event_sale/security/event_security.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/addons/website_event/views/website_event.xml b/addons/website_event/views/website_event.xml index 7ebcb5900df..637685643ae 100644 --- a/addons/website_event/views/website_event.xml +++ b/addons/website_event/views/website_event.xml @@ -431,7 +431,7 @@ class="btn btn-primary btn-lg pull-right a-submit" t-attf-id="#{event.id}">Register Now
Event registration not yet started. - + Configure and Launch Event Registration
From a2d3663cff26ed7107022388705227e8aee194e6 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 26 Jun 2018 10:40:32 +0200 Subject: [PATCH 02/14] [FIX] event, event_sale, website_event: allow portal access to own registrations OPW 1859364 --- addons/event/security/event_security.xml | 7 +++++++ addons/event/security/ir.model.access.csv | 7 ++++--- addons/event_sale/__manifest__.py | 1 + addons/event_sale/security/event_security.xml | 8 ++++++++ addons/website_event/controllers/main.py | 2 +- addons/website_event/views/website_event_templates.xml | 2 +- 6 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 addons/event_sale/security/event_security.xml diff --git a/addons/event/security/event_security.xml b/addons/event/security/event_security.xml index 6605989b3fc..25ba37e54a1 100644 --- a/addons/event/security/event_security.xml +++ b/addons/event/security/event_security.xml @@ -51,6 +51,13 @@ ] + + Event/Registration: Portal + + + ['|', ('email', '=', user.partner_id.email), ('partner_id', '=', user.partner_id.id)] + + diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv index 2c5d6a3b17a..6cd3daead21 100644 --- a/addons/event/security/ir.model.access.csv +++ b/addons/event/security/ir.model.access.csv @@ -5,9 +5,10 @@ access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0 access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0 access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1 access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1 -access_event_registration_portal,event.registration,model_event_registration,,0,0,0,0 +access_report_event_registration,report.event.registration,model_report_event_registration,event.group_event_user,1,1,1,1 +access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0 +access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0 access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0 access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1 access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0 -access_event_mail_registration_manager,event.mail.registration.manager,model_event_mail_registration,event.group_event_manager,1,1,1,1 -access_report_event_registration,report.event.registration,model_report_event_registration,event.group_event_user,1,1,1,1 \ No newline at end of file +access_event_mail_registration_manager,event.mail.registration.manager,model_event_mail_registration,event.group_event_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/event_sale/__manifest__.py b/addons/event_sale/__manifest__.py index c3f9ee969ce..a88aa1e902f 100644 --- a/addons/event_sale/__manifest__.py +++ b/addons/event_sale/__manifest__.py @@ -26,6 +26,7 @@ this event. 'data/event_sale_data.xml', 'report/event_event_templates.xml', 'security/ir.model.access.csv', + 'security/event_security.xml', 'wizard/event_edit_registration.xml', ], 'demo': ['data/event_demo.xml'], diff --git a/addons/event_sale/security/event_security.xml b/addons/event_sale/security/event_security.xml new file mode 100644 index 00000000000..3e397f622ed --- /dev/null +++ b/addons/event_sale/security/event_security.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index 1dd43541c94..6f79a160b3e 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -169,7 +169,7 @@ class WebsiteEventController(http.Controller): 'event': event, 'main_object': event, 'range': range, - 'registrable': event._is_event_registrable() + 'registrable': event.sudo()._is_event_registrable() } return request.render("website_event.event_description_full", values) diff --git a/addons/website_event/views/website_event_templates.xml b/addons/website_event/views/website_event_templates.xml index de02a0f3682..6beb0a95015 100644 --- a/addons/website_event/views/website_event_templates.xml +++ b/addons/website_event/views/website_event_templates.xml @@ -452,7 +452,7 @@ Event registration is closed. - + Configure and Launch Event Registration From 47de788cbb1842dd26656513e24f31020d8ac044 Mon Sep 17 00:00:00 2001 From: Adrien Dieudonne Date: Tue, 26 Jun 2018 12:09:30 +0200 Subject: [PATCH 03/14] [FIX] web: amount on bank.statement not displayed in mobile Before this commit, it was not possible to add an amount to a new bank statement. The input (.o_form_field_monetary) appeared as disabled. This bug was due to this commit: @a1224f5ca This rule should be only applied to .o_form_field_radio. Closes #18887 Task ID: 35832 --- addons/web/static/src/less/form_view_layout.less | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/less/form_view_layout.less b/addons/web/static/src/less/form_view_layout.less index 2ef50e763f5..7b5a832676d 100644 --- a/addons/web/static/src/less/form_view_layout.less +++ b/addons/web/static/src/less/form_view_layout.less @@ -168,9 +168,7 @@ // Flex fields .o_form_field_many2one, .o_form_field_radio, .o_form_field_many2manytags, .o_form_field_percent_pie, .o_form_field_monetary, .o_form_field_binary_file { - @media (min-width: @screen-sm-min) { .o-inline-flex-display(); - } > span, > button { .o-flex(0, 0, auto); } @@ -206,6 +204,9 @@ // Radio buttons .o_form_field_radio { + @media (max-width: @screen-xs-max) { + display: inline-block; + } .o_radio_item { .o-flex(0, 0, auto); .o-flex-display(); From ef444da57a0e5b3a8426a6fac8cef3f6d856b148 Mon Sep 17 00:00:00 2001 From: Ruchir Shukla Date: Tue, 16 Jan 2018 15:23:08 +0530 Subject: [PATCH 04/14] [FIX] stock: do_new_transfer callable in xml-rpc When returning None, the XML-RPC can trigger an error, making the api unusable in certain cases. So, we added return True and if the context is None we use an empty dict, so if the context is returned in a dict, it is not returning None either. Tests were adapted too. Closes #22264 --- addons/sale_stock/tests/test_sale_stock.py | 4 ++-- addons/stock/stock.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 6085c639f6e..a19cdcd268f 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -51,7 +51,7 @@ class TestSaleStock(TestSale): pick_2 = self.so.picking_ids[0] pick_2.force_assign() pick_2.pack_operation_product_ids.write({'qty_done': 1}) - self.assertIsNone(pick_2.do_new_transfer(), 'Sale Stock: second picking should be final without need for a backorder') + self.assertTrue(pick_2.do_new_transfer(), 'Sale Stock: second picking should be final without need for a backorder') self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" after complete delivery') del_qties = [sol.qty_delivered for sol in self.so.order_line] del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line] @@ -98,7 +98,7 @@ class TestSaleStock(TestSale): pick = self.so.picking_ids pick.force_assign() pick.pack_operation_product_ids.write({'qty_done': 2}) - self.assertIsNone(pick.do_new_transfer(), 'Sale Stock: complete delivery should not need a backorder') + self.assertTrue(pick.do_new_transfer(), 'Sale Stock: complete delivery should not need a backorder') del_qties = [sol.qty_delivered for sol in self.so.order_line] del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line] self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after partial delivery') diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 45fe1e380d9..06bafd6a341 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1589,6 +1589,8 @@ class stock_picking(models.Model): def do_new_transfer(self, cr, uid, ids, context=None): pack_op_obj = self.pool['stock.pack.operation'] data_obj = self.pool['ir.model.data'] + if not context: + context = {} for pick in self.browse(cr, uid, ids, context=context): to_delete = [] if not pick.move_lines and not pick.pack_operation_ids: @@ -1642,7 +1644,7 @@ class stock_picking(models.Model): if to_delete: pack_op_obj.unlink(cr, uid, to_delete, context=context) self.do_transfer(cr, uid, ids, context=context) - return + return True def check_backorder(self, cr, uid, picking, context=None): need_rereserve, all_op_processed = self.picking_recompute_remaining_quantities(cr, uid, picking, done_qtys=True, context=context) From 7a768bba8f6cdc0a03b0d2a41049fa1e34693553 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 27 Jun 2018 12:14:42 +0200 Subject: [PATCH 05/14] [I18N] mass_mailing: remove Croatian translations from German translation There was some Croatian translations in the German translation by mistake. Remove them. opw-1859044 --- addons/mass_mailing/i18n/de.po | 13 ++++++------- addons/website/i18n/de.po | 3 +-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/addons/mass_mailing/i18n/de.po b/addons/mass_mailing/i18n/de.po index ff53ac9bf9c..f4169590195 100644 --- a/addons/mass_mailing/i18n/de.po +++ b/addons/mass_mailing/i18n/de.po @@ -18,7 +18,6 @@ # Anja Funk , 2016 # Martin K , 2016 # Niki Waibel, 2016 -# Tina Milas, 2017 # Andi, 2017 # DE T1 , 2017 # DE R1 , 2017 @@ -146,27 +145,27 @@ msgstr "" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets msgid " Footers" -msgstr " Podnožja" +msgstr "" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets msgid " Headers" -msgstr " Zaglavlja" +msgstr " Zaglavlja" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets msgid " Body" -msgstr " Tijelo" +msgstr "" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.snippet_options msgid "Background Color" -msgstr "Boja pozadine" +msgstr "Hintergrundfarbe" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets msgid " Marketing Content" -msgstr " Sadržaj marketinga" +msgstr "" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.s_mail_block_title_text @@ -241,7 +240,7 @@ msgstr "Gesamt Management" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.s_mail_block_three_cols msgid "A short description" -msgstr "Kratak opis" +msgstr "" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.s_mail_block_text_image diff --git a/addons/website/i18n/de.po b/addons/website/i18n/de.po index 95ccff92d4b..a790317a32e 100644 --- a/addons/website/i18n/de.po +++ b/addons/website/i18n/de.po @@ -19,7 +19,6 @@ # Thorsten Vocks , 2016 # Frederik Kramer , 2016 # key six , 2017 -# Tina Milas, 2017 # Andi, 2017 # Gab_Odoo , 2017 # Katharina Moritz , 2017 @@ -316,7 +315,7 @@ msgstr "Marge" #. module: website #: model:ir.ui.view,arch_db:website.snippet_options msgid "Background Color" -msgstr "Boja pozadine" +msgstr "Hintergrundfarbe" #. module: website #: model:ir.ui.view,arch_db:website.snippet_options From c1b6cfaab8d42885533e23985058cfc78b48d7c7 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 5 Jun 2018 15:34:41 +0200 Subject: [PATCH 06/14] [FIX] google_account: backport of c444b5a293 In this commit, our hero backport c444b5a293 to 9.0 [FIX] google_account: fix google request exception management Error thrown by google request is an urllib2.HTTPError that can be read and loaded in JSON. However in some cases the result of the read may be void or not JSON-ready. This was causing a crash in the error management and hid the actual issue. This commit tries to read and JSON-load the error but fall back on simply displaying the raw error in case of issue when handling it. opw-1851612 --- addons/google_account/google_account.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/google_account/google_account.py b/addons/google_account/google_account.py index 47b67fbc28b..4eff3c2706f 100644 --- a/addons/google_account/google_account.py +++ b/addons/google_account/google_account.py @@ -127,7 +127,11 @@ class google_service(osv.osv_memory): registry = openerp.modules.registry.RegistryManager.get(request.session.db) with registry.cursor() as cur: self.pool['res.users'].write(cur, uid, [uid], {'google_%s_rtoken' % service: False}, context=context) - error_key = json.loads(e.read()).get("error", "nc") + try: + error_file = e.read() + error_key = json.loads(error_file).get("error", "nc") + except: + error_key = e _logger.exception("Bad google request : %s !" % error_key) error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]") % error_key raise self.pool.get('res.config.settings').get_config_warning(cr, error_msg, context=context) From 10253a735f8296fbb42c311ebc36e2fe4bf1f135 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 27 Jun 2018 13:41:03 +0200 Subject: [PATCH 07/14] [FIX] sale: invoice template - Activate the Sale option 'Tax-Included Prices' - Create and print an invoice => the total is displayed on each line - Activate the Sale option 'Sections on Sales Orders' - Create and print an invoice => the total is NOT displayed on each line The option 'Tax Display' only applies to SO, not to invoices. Moreover, we make consistent the wording. opw-1856623 --- addons/sale/i18n/sale.pot | 5 +++++ addons/sale/report/invoice_report_templates.xml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/sale/i18n/sale.pot b/addons/sale/i18n/sale.pot index b9064aabc9e..cf1dacf00c3 100644 --- a/addons/sale/i18n/sale.pot +++ b/addons/sale/i18n/sale.pot @@ -338,6 +338,11 @@ msgstr "" msgid "Allows you to specify an analytic account on sales orders." msgstr "" +#. module: sale +#: model:ir.ui.view,arch_db:sale.report_invoice_layouted +msgid "Amount" +msgstr "" + #. module: sale #: model:ir.model.fields,field_description:sale.field_crm_team_sales_to_invoice_amount msgid "Amount of sales to invoice" diff --git a/addons/sale/report/invoice_report_templates.xml b/addons/sale/report/invoice_report_templates.xml index 09beef6f8cc..4d3abdc152e 100644 --- a/addons/sale/report/invoice_report_templates.xml +++ b/addons/sale/report/invoice_report_templates.xml @@ -14,7 +14,7 @@ Unit Price Disc.(%) Taxes - Price + Amount @@ -45,7 +45,7 @@ - + From 29c00a56daa377e40f4d28e86a10f105617fd243 Mon Sep 17 00:00:00 2001 From: Toufik Benjaa Date: Tue, 20 Mar 2018 16:36:26 +0100 Subject: [PATCH 08/14] [IMP] http: Sessions implicit deactivation - Store a token inside sessions to allow implicit session deactivation when needed. backport of @da1f153d61d747d9357694382fe04f96c0ca886a @c8243e71c6da37547a19f61c58f25d5d03e13d38 --- addons/auth_crypt/auth_crypt.py | 5 ++++ addons/auth_oauth/res_users.py | 5 ++++ addons/web/controllers/main.py | 10 ++----- openerp/addons/base/res/res_users.py | 33 +++++++++++++++++++++ openerp/http.py | 44 ++++++++++++++++++---------- openerp/service/security.py | 11 +++++++ openerp/tests/common.py | 3 +- 7 files changed, 88 insertions(+), 23 deletions(-) diff --git a/addons/auth_crypt/auth_crypt.py b/addons/auth_crypt/auth_crypt.py index 7c9d0c5fa2c..c8ac4876a55 100644 --- a/addons/auth_crypt/auth_crypt.py +++ b/addons/auth_crypt/auth_crypt.py @@ -3,6 +3,7 @@ import logging from passlib.context import CryptContext import openerp +from openerp import api from openerp.osv import fields, osv from openerp.addons.base.res import res_users @@ -95,3 +96,7 @@ class res_users(osv.osv): internally """ return default_crypt_context + + @api.model + def _get_session_token_fields(self): + return super(res_users, self)._get_session_token_fields() | {'password_crypt'} diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 60fdd7319a1..f89234a743e 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -6,6 +6,7 @@ import urllib2 import json import openerp +from openerp import api from openerp.addons.auth_signup.res_users import SignupError from openerp.osv import osv, fields from openerp import SUPERUSER_ID @@ -125,4 +126,8 @@ class res_users(osv.Model): if not res: raise + @api.model + def _get_session_token_fields(self): + return super(res_users, self)._get_session_token_fields() | {'oauth_access_token'} + # diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index a1ead7e5d1a..158ae695268 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -37,6 +37,7 @@ from openerp.tools.misc import str2bool, xlwt from openerp import http from openerp.http import request, serialize_exception as _serialize_exception, content_disposition from openerp.exceptions import AccessError, UserError +from openerp.service.report import exp_report, exp_report_get _logger = logging.getLogger(__name__) @@ -1479,7 +1480,6 @@ class Reports(http.Controller): def index(self, action, token): action = json.loads(action) - report_srv = request.session.proxy("report") context = dict(request.context) context.update(action["context"]) @@ -1492,15 +1492,11 @@ class Reports(http.Controller): report_ids = action['datas'].pop('ids') report_data.update(action['datas']) - report_id = report_srv.report( - request.session.db, request.session.uid, request.session.password, - action["report_name"], report_ids, - report_data, context) + report_id = exp_report(request.session.db, request.session.uid, action["report_name"], report_ids, report_data, context) report_struct = None while True: - report_struct = report_srv.report_get( - request.session.db, request.session.uid, request.session.password, report_id) + report_struct = exp_report_get(request.session.db, request.session.uid, report_id) if report_struct["state"]: break diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 2c5bce22725..d573b9d4658 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -2,11 +2,13 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import itertools import logging +import hmac from functools import partial from itertools import repeat from lxml import etree from lxml.builder import E +from hashlib import sha256 import openerp from openerp import api @@ -378,6 +380,8 @@ class res_users(osv.osv): for id in ids: if id in self.__uid_cache[db]: del self.__uid_cache[db][id] + if any(key in values for key in self._get_session_token_fields(cr, uid)): + self._invalidate_session_cache(cr, uid) self.context_get.clear_cache(self) self.has_group.clear_cache(self) return res @@ -511,6 +515,35 @@ class res_users(osv.osv): finally: cr.close() + @api.model + def _get_session_token_fields(self): + return {'id', 'login', 'password', 'active'} + + @tools.ormcache('sid') + def _compute_session_token(self, sid): + """ Compute a session token given a session id and a user id """ + # retrieve the fields used to generate the session token + session_fields = ', '.join(sorted(self._get_session_token_fields())) + self.env.cr.execute("""SELECT %s, (SELECT value FROM ir_config_parameter WHERE key='database.secret') + FROM res_users + WHERE id=%%s""" % (session_fields), (self.id,)) + if self.env.cr.rowcount != 1: + self._invalidate_session_cache() + return False + data_fields = self.env.cr.fetchone() + # generate hmac key + key = (u'%s' % (data_fields,)).encode('utf-8') + # hmac the session id + data = sid.encode('utf-8') + h = hmac.new(key, data, sha256) + # keep in the cache the token + return h.hexdigest() + + @api.model + def _invalidate_session_cache(self): + """ Clear the session cache """ + self._compute_session_token.clear_cache(self) + def change_password(self, cr, uid, old_passwd, new_passwd, context=None): """Change current user password. Old password must be provided explicitly to prevent hijacking an existing user session, or for cases where the cleartext diff --git a/openerp/http.py b/openerp/http.py index 05778f0d290..22a9ce5ccf3 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -313,7 +313,15 @@ class WebRequest(object): # case, the request cursor is unusable. Rollback transaction to create a new one. if self._cr: self._cr.rollback() - self.env.clear() + # With the session patch, we now clear the environment only if it exists + # We do so by checking if the environment is stored in request.__dict__ + # Which is how lazy_property works. + # We do this, to avoid creating the environment before it is needed. + # For example some auth='none' controllers do "request.uid = request.session.uid" then + # "request.env.user ..." which is broken if we create the environment by doing self.env.clear() + # since it will not be linked to a user. + if self.__dict__.get('env'): + self.env.clear() result = self.endpoint(*a, **kw) if isinstance(result, Response) and result.is_qweb: # Early rendering of lazy responses to benefit from @service_model.check protection @@ -1123,7 +1131,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): self.db = db self.uid = uid self.login = login - self.password = password + self.session_token = uid and security.compute_session_token(self, request.env) request.uid = uid request.disable_db = False @@ -1138,7 +1146,20 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): """ if not self.db or not self.uid: raise SessionExpiredException("Session expired") - security.check(self.db, self.uid, self.password) + # We create our own environment instead of the request's one. + # This is due to the fact that the member "uid" on the request object isn't set yet. + # If we try to use the request's environment, the session checking will never succeed since + # the environment isn't bound to any user and it needs to be. + env = openerp.api.Environment(request.cr, self.uid, self.context) + # == BACKWARD COMPATIBILITY TO CONVERT OLD SESSION TYPE TO THE NEW ONES ! REMOVE ME AFTER 11.0 == + if self.get('password'): + security.check(self.db, self.uid, self.password) + self.session_token = security.compute_session_token(self, env) + self.pop('password') + # ================================================================================================= + # here we check if the session is still valid + if not security.check_session(self, env): + raise SessionExpiredException("Session expired") def logout(self, keep_db=False): for k in self.keys(): @@ -1151,7 +1172,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): self.setdefault("db", None) self.setdefault("uid", None) self.setdefault("login", None) - self.setdefault("password", None) + self.setdefault("session_token", None) self.setdefault("context", {}) def get_context(self): @@ -1211,12 +1232,6 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): @_login.setter def _login(self, value): self.login = value - @property - def _password(self): - return self.password - @_password.setter - def _password(self, value): - self.password = value def send(self, service_name, method, *args): """ @@ -1239,11 +1254,10 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): Ensures this session is valid (logged into the openerp server) """ - if self.uid and not force: + if self.uid and self.session_token and not force: return - # TODO use authenticate instead of login - self.uid = self.proxy("common").login(self.db, self.login, self.password) - if not self.uid: + + if not self.uid or not security.check_session(self, request.env): raise AuthenticationError("Authentication failure") def ensure_valid(self): @@ -1272,7 +1286,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): Use the registry and cursor in :data:`request` instead. """ self.assert_valid() - r = self.proxy('object').exec_workflow(self.db, self.uid, self.password, model, signal, id) + r = service_model.exec_workflow(self.db, self.uid, model, signal, id) return r def model(self, model): diff --git a/openerp/service/security.py b/openerp/service/security.py index 8ab38d3d659..28c85788a0a 100644 --- a/openerp/service/security.py +++ b/openerp/service/security.py @@ -11,3 +11,14 @@ def login(db, login, password): def check(db, uid, passwd): res_users = openerp.registry(db)['res.users'] return res_users.check(db, uid, passwd) + +def compute_session_token(session, env): + self = env['res.users'].browse(session.uid) + return self._compute_session_token(session.sid) + +def check_session(session, env): + self = env['res.users'].browse(session.uid) + if openerp.tools.misc.consteq(self._compute_session_token(session.sid), session.session_token): + return True + self._invalidate_session_cache() + return False diff --git a/openerp/tests/common.py b/openerp/tests/common.py index 3cb271e075d..866fbd012ff 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -26,6 +26,7 @@ import werkzeug import openerp from openerp import api +from openerp.service import security from openerp.modules.registry import RegistryManager _logger = logging.getLogger(__name__) @@ -291,7 +292,7 @@ class HttpCase(TransactionCase): session.db = db session.uid = uid session.login = user - session.password = password + session.session_token = uid and security.compute_session_token(session, self.env) session.context = Users.context_get(self.cr, uid) or {} session.context['uid'] = uid session._fix_lang(session.context) From fa1f0502180338cb2fd614c1efae7548d8e0c959 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 28 Jun 2018 10:18:06 +0200 Subject: [PATCH 09/14] [FIX] sale_stock: performance In some undertermined circumstances, a `KeyError` might appear in: https://github.com/odoo/odoo/blob/f9737d79e77e9abf634c8eaa4b6ea331e2aa814d/odoo/models.py#L4898-L4904 Despite the lack of reproducible use case, it seems that this was introduced from 59b010ac366e0826fb314. In the meantime, other optimizations were introduced in the prefetching of related fields, and it seems that the fix is not necessary anymore. Running the same script than described in https://github.com/odoo/odoo/issues/19536#issuecomment-333118947 , we observe the following performances: Before the fix: ``` On change with 10 lines (10 new, 0 existing) took 0.306 On change with 20 lines (10 new, 10 existing) took 0.221 On change with 30 lines (10 new, 20 existing) took 0.208 On change with 40 lines (10 new, 30 existing) took 0.271 On change with 50 lines (10 new, 40 existing) took 0.306 On change with 60 lines (10 new, 50 existing) took 0.319 On change with 70 lines (10 new, 60 existing) took 0.354 On change with 80 lines (10 new, 70 existing) took 0.382 On change with 90 lines (10 new, 80 existing) took 0.506 On change with 100 lines (10 new, 90 existing) took 0.503 ``` After the fix: ``` On change with 10 lines (10 new, 0 existing) took 0.238 On change with 20 lines (10 new, 10 existing) took 0.259 On change with 30 lines (10 new, 20 existing) took 0.250 On change with 40 lines (10 new, 30 existing) took 0.233 On change with 50 lines (10 new, 40 existing) took 0.287 On change with 60 lines (10 new, 50 existing) took 0.325 On change with 70 lines (10 new, 60 existing) took 0.345 On change with 80 lines (10 new, 70 existing) took 0.451 On change with 90 lines (10 new, 80 existing) took 0.496 On change with 100 lines (10 new, 90 existing) took 0.423 ``` The fix doesn't seem to be necessary anymore, so we go back to the previous implementation of the computed field. Closes #25448 Fixes #19536 opw-1861468 --- addons/sale_stock/models/sale_order.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 06f1ef715ba..64d4398cba1 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -109,13 +109,9 @@ class SaleOrderLine(models.Model): @api.multi @api.depends('product_id') def _compute_qty_delivered_updateable(self): - # prefetch field before filtering - self.mapped('product_id') - # on consumable or stockable products, qty_delivered_updateable defaults - # to False; on other lines use the original computation - lines = self.filtered(lambda line: line.product_id.type not in ('consu', 'product')) - lines = lines.with_prefetch(self._prefetch) - super(SaleOrderLine, lines)._compute_qty_delivered_updateable() + for line in self: + if line.product_id.type not in ('consu', 'product'): + super(SaleOrderLine, line)._compute_qty_delivered_updateable() @api.onchange('product_id') def _onchange_product_id_set_customer_lead(self): From 5a0dcd3c20902cfd4dcbdb8d2493b1852cc85d65 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 29 May 2018 17:40:18 +0200 Subject: [PATCH 10/14] [FIX] mail: Render mail in the template language On an optimization done for not rendering each time the template, the possibility of sending the template in the proper language when multiple recipients was lost. This is being refactoring in master, but for this version, Odoo won't merge a change in the behavior, although it's the correct one as in previous versions. Extracted by pedrobaeza from https://gist.github.com/nim-odoo/8a9749037370343f05c7e6681324f576 (cherry picked from commit 386a936) --- addons/mail/models/mail_template.py | 3 ++- addons/mail/wizard/mail_compose_message.py | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index cc07b375204..9de0c9bbb8e 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -484,10 +484,11 @@ class MailTemplate(models.Model): # templates: res_id -> template; template -> res_ids templates_to_res_ids = {} for res_id, template in res_ids_to_templates.iteritems(): - templates_to_res_ids.setdefault(template, []).append(res_id) + templates_to_res_ids.setdefault((template, template.env.context.get('lang')), []).append(res_id) results = dict() for template, template_res_ids in templates_to_res_ids.iteritems(): + template = template[0] Template = self.env['mail.template'] # generate fields value for all res_ids linked to the current template if template.lang: diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 4bc958684f9..64d3f2dfbf6 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -443,8 +443,8 @@ class MailComposer(models.TransientModel): multi_mode = False res_ids = [res_ids] - subjects = self.render_template(self.subject, self.model, res_ids) - bodies = self.render_template(self.body, self.model, res_ids, post_process=True) + subjects = self.render_template(self.subject, self.model, res_ids) if not self.template_id else False + bodies = self.render_template(self.body, self.model, res_ids, post_process=True) if not self.template_id else False emails_from = self.render_template(self.email_from, self.model, res_ids) replies_to = self.render_template(self.reply_to, self.model, res_ids) default_recipients = {} @@ -454,8 +454,8 @@ class MailComposer(models.TransientModel): results = dict.fromkeys(res_ids, False) for res_id in res_ids: results[res_id] = { - 'subject': subjects[res_id], - 'body': bodies[res_id], + 'subject': subjects[res_id] if subjects else False, + 'body': bodies[res_id] if subjects else False, 'email_from': emails_from[res_id], 'reply_to': replies_to[res_id], } @@ -465,7 +465,7 @@ class MailComposer(models.TransientModel): if self.template_id: template_values = self.generate_email_for_composer( self.template_id.id, res_ids, - fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id']) + fields=['subject', 'body_html', 'email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id']) else: template_values = {} @@ -475,6 +475,8 @@ class MailComposer(models.TransientModel): results[res_id].pop('partner_ids') results[res_id].pop('email_to') results[res_id].pop('email_cc') + results[res_id].pop('subject') + results[res_id].pop('body') # remove attachments from template values as they should not be rendered template_values[res_id].pop('attachment_ids', None) else: From 2774e3b4a82f3c0ea94faff8facb7c4ee5ce7b48 Mon Sep 17 00:00:00 2001 From: Carlos Dauden Date: Mon, 25 Jun 2018 19:07:22 +0200 Subject: [PATCH 11/14] [IMP] mail: Subject and body readonly if mass_x composition_mode --- addons/mail/wizard/mail_compose_message.py | 2 +- addons/mail/wizard/mail_compose_message_view.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 64d3f2dfbf6..5a1aca10ed0 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -455,7 +455,7 @@ class MailComposer(models.TransientModel): for res_id in res_ids: results[res_id] = { 'subject': subjects[res_id] if subjects else False, - 'body': bodies[res_id] if subjects else False, + 'body': bodies[res_id] if bodies else False, 'email_from': emails_from[res_id], 'reply_to': replies_to[res_id], } diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 2ae03360d14..7e4699ee69b 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -47,7 +47,7 @@ context="{'force_email':True, 'show_email':True}" attrs="{'invisible': [('composition_mode', '!=', 'comment')]}"/> - + @@ -57,7 +57,7 @@ attrs="{'invisible':['|', ('no_auto_thread', '=', False), ('composition_mode', '!=', 'mass_mail')], 'required':[('no_auto_thread', '=', True), ('composition_mode', '=', 'mass_mail')]}"/> - + Date: Fri, 29 Jun 2018 09:30:42 +0200 Subject: [PATCH 12/14] [FIX] google_calendar: backport 052bc38805 to 9.0 Backport following opw-1851612 [FIX] google_calendar: do not create an event with an invalid id The id is useful to update existing events but sometimes we are getting some ids that are not accepted by Google Getting an error: odoo.addons.google_account.models.google_service: Bad google request : { "error": { "errors": [ { "domain": "global", "reason": "invalid", "message": "Invalid resource id value." } ], "code": 400, "message": "Invalid resource id value." } } Looks like existing events can have a _ in their id but new one, no longer. It seems that these events are created by outlook calendar when synchronized with Google Calendar. --- addons/google_calendar/google_calendar.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 39f4a075ba5..17eeab5cacc 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -256,7 +256,8 @@ class google_calendar(osv.AbstractModel): if not self.get_need_synchro_attendee(cr, uid, context=context): data.pop("attendees") if isCreating: - other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids if other_att.google_internal_event_id] + other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids + if other_att.google_internal_event_id and not other_att.google_internal_event_id.startswith('_')] if other_google_ids: data["id"] = other_google_ids[0] return data @@ -627,7 +628,8 @@ class google_calendar(osv.AbstractModel): ('event_id.final_date', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ], context=context_norecurrent) for att in att_obj.browse(cr, uid, my_att_ids, context=context): - other_google_ids = [other_att.google_internal_event_id for other_att in att.event_id.attendee_ids if other_att.google_internal_event_id and other_att.id != att.id] + other_google_ids = [other_att.google_internal_event_id for other_att in att.event_id.attendee_ids if + other_att.google_internal_event_id and other_att.id != att.id and not other_att.google_internal_event_id.startswith('_')] for other_google_id in other_google_ids: if self.get_one_event_synchro(cr, uid, other_google_id, context=context): att_obj.write(cr, uid, [att.id], {'google_internal_event_id': other_google_id}) From 6ad6df59b5041bb929ca937074dd31a3f5c921e0 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 29 Jun 2018 10:39:46 +0200 Subject: [PATCH 13/14] Revert "[IMP] mail: Subject and body readonly if mass_x composition_mode" This reverts commit 2774e3b4a82f3c0ea94faff8facb7c4ee5ce7b48. Wrong manipulation of the "merge button" The fix was already merged at 5a0dcd3c2 --- addons/mail/wizard/mail_compose_message.py | 2 +- addons/mail/wizard/mail_compose_message_view.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 5a1aca10ed0..64d3f2dfbf6 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -455,7 +455,7 @@ class MailComposer(models.TransientModel): for res_id in res_ids: results[res_id] = { 'subject': subjects[res_id] if subjects else False, - 'body': bodies[res_id] if bodies else False, + 'body': bodies[res_id] if subjects else False, 'email_from': emails_from[res_id], 'reply_to': replies_to[res_id], } diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 7e4699ee69b..2ae03360d14 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -47,7 +47,7 @@ context="{'force_email':True, 'show_email':True}" attrs="{'invisible': [('composition_mode', '!=', 'comment')]}"/> - + @@ -57,7 +57,7 @@ attrs="{'invisible':['|', ('no_auto_thread', '=', False), ('composition_mode', '!=', 'mass_mail')], 'required':[('no_auto_thread', '=', True), ('composition_mode', '=', 'mass_mail')]}"/> - + Date: Fri, 29 Jun 2018 13:02:16 +0200 Subject: [PATCH 14/14] Revert "[FIX] mail: Render mail in the template language" This reverts commit 5a0dcd3c20902cfd4dcbdb8d2493b1852cc85d65. Same as 6ad6df59b5041b Was mistakly merged, should not be integrated into stable --- addons/mail/models/mail_template.py | 3 +-- addons/mail/wizard/mail_compose_message.py | 12 +++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index 9de0c9bbb8e..cc07b375204 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -484,11 +484,10 @@ class MailTemplate(models.Model): # templates: res_id -> template; template -> res_ids templates_to_res_ids = {} for res_id, template in res_ids_to_templates.iteritems(): - templates_to_res_ids.setdefault((template, template.env.context.get('lang')), []).append(res_id) + templates_to_res_ids.setdefault(template, []).append(res_id) results = dict() for template, template_res_ids in templates_to_res_ids.iteritems(): - template = template[0] Template = self.env['mail.template'] # generate fields value for all res_ids linked to the current template if template.lang: diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 64d3f2dfbf6..4bc958684f9 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -443,8 +443,8 @@ class MailComposer(models.TransientModel): multi_mode = False res_ids = [res_ids] - subjects = self.render_template(self.subject, self.model, res_ids) if not self.template_id else False - bodies = self.render_template(self.body, self.model, res_ids, post_process=True) if not self.template_id else False + subjects = self.render_template(self.subject, self.model, res_ids) + bodies = self.render_template(self.body, self.model, res_ids, post_process=True) emails_from = self.render_template(self.email_from, self.model, res_ids) replies_to = self.render_template(self.reply_to, self.model, res_ids) default_recipients = {} @@ -454,8 +454,8 @@ class MailComposer(models.TransientModel): results = dict.fromkeys(res_ids, False) for res_id in res_ids: results[res_id] = { - 'subject': subjects[res_id] if subjects else False, - 'body': bodies[res_id] if subjects else False, + 'subject': subjects[res_id], + 'body': bodies[res_id], 'email_from': emails_from[res_id], 'reply_to': replies_to[res_id], } @@ -465,7 +465,7 @@ class MailComposer(models.TransientModel): if self.template_id: template_values = self.generate_email_for_composer( self.template_id.id, res_ids, - fields=['subject', 'body_html', 'email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id']) + fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id']) else: template_values = {} @@ -475,8 +475,6 @@ class MailComposer(models.TransientModel): results[res_id].pop('partner_ids') results[res_id].pop('email_to') results[res_id].pop('email_cc') - results[res_id].pop('subject') - results[res_id].pop('body') # remove attachments from template values as they should not be rendered template_values[res_id].pop('attachment_ids', None) else: