diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index 927f765e815..3d074563717 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import controllers import res_users import crm_stage import crm_lead diff --git a/addons/crm/controllers/__init__.py b/addons/crm/controllers/__init__.py new file mode 100644 index 00000000000..12d2d759f34 --- /dev/null +++ b/addons/crm/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -* +import main diff --git a/addons/crm/controllers/main.py b/addons/crm/controllers/main.py new file mode 100644 index 00000000000..e924f8a76cc --- /dev/null +++ b/addons/crm/controllers/main.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp.addons.mail.controllers.main import MailController +from openerp import http + + +class CrmController(http.Controller): + + @http.route('/lead/case_mark_won', type='http', auth='user', methods=['GET']) + def crm_lead_case_mark_won(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) + if comparison and record: + try: + record.case_mark_won() + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/lead/case_mark_lost', type='http', auth='user', methods=['GET']) + def crm_lead_case_mark_lost(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) + if comparison and record: + try: + record.case_mark_lost() + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/lead/convert', type='http', auth='user', methods=['GET']) + def crm_lead_convert(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) + if comparison and record: + try: + record.convert_opportunity(record.partner_id.id) + except Exception: + return MailController._redirect_to_messaging() + return redirect diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index c49abd90a14..71279ec6cf5 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -976,9 +976,9 @@ Update your business card, phone book, social media,... Send an email right now lead = self.browse(cr, uid, ids[0], context=context) - won_action = self._notification_link_helper(cr, uid, ids, 'method', context=context, method='case_mark_won') - lost_action = self._notification_link_helper(cr, uid, ids, 'method', context=context, method='case_mark_lost') - convert_action = self._notification_link_helper(cr, uid, ids, 'method', context=context, method='convert_opportunity', partner_id=lead.partner_id.id) + won_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/lead/case_mark_won', context=context) + lost_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/lead/case_mark_lost', context=context) + convert_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/lead/convert', context=context) if lead.type == 'lead': res['group_sale_salesman'] = { diff --git a/addons/hr_holidays/__init__.py b/addons/hr_holidays/__init__.py index f9bd12244c0..4739125fc00 100644 --- a/addons/hr_holidays/__init__.py +++ b/addons/hr_holidays/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import controllers import hr_department import hr_holidays import report diff --git a/addons/hr_holidays/controllers/__init__.py b/addons/hr_holidays/controllers/__init__.py new file mode 100644 index 00000000000..12d2d759f34 --- /dev/null +++ b/addons/hr_holidays/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -* +import main diff --git a/addons/hr_holidays/controllers/main.py b/addons/hr_holidays/controllers/main.py new file mode 100644 index 00000000000..ecbb47d4ca0 --- /dev/null +++ b/addons/hr_holidays/controllers/main.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp.addons.mail.controllers.main import MailController +from openerp import http + + +class HrHolidaysController(http.Controller): + + @http.route('/hr_holidays/validate', type='http', auth='user', methods=['GET']) + def hr_holidays_validate(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token) + if comparison and record: + try: + record.signal_workflow('validate') + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/hr_holidays/refuse', type='http', auth='user', methods=['GET']) + def hr_holidays_refuse(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token) + if comparison and record: + try: + record.signal_workflow('refuse') + except Exception: + return MailController._redirect_to_messaging() + return redirect diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 3697e7bfa92..0c3ab256965 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -523,8 +523,8 @@ class hr_holidays(osv.osv): def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None): res = super(hr_holidays, self)._notification_get_recipient_groups(cr, uid, ids, message, recipients, context=context) - app_action = '/mail/workflow?%s' % url_encode({'model': self._name, 'res_id': ids[0], 'signal': 'validate'}) - ref_action = '/mail/workflow?%s' % url_encode({'model': self._name, 'res_id': ids[0], 'signal': 'refuse'}) + app_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/hr_holidays/validate', context=context) + ref_action = self._notification_link_helper(cr, uid, ids, 'controller', controller='/hr_holidays/refuse', context=context) holiday = self.browse(cr, uid, ids[0], context=context) actions = [] diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index 784b26671c6..d9e55765df1 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -1,5 +1,6 @@ import base64 import json +import logging from operator import itemgetter import psycopg2 import werkzeug @@ -8,20 +9,90 @@ from werkzeug import url_encode import openerp from openerp import SUPERUSER_ID from openerp import http +from openerp import _ from openerp.exceptions import AccessError from openerp.http import request +from openerp.tools import consteq from openerp.addons.web.controllers.main import binary_content +_logger = logging.getLogger(__name__) + class MailController(http.Controller): _cp_path = '/mail' - def _redirect_to_messaging(self): + @classmethod + def _redirect_to_messaging(cls): messaging_action = request.env['mail.thread']._get_inbox_action_xml_id() url = '/web#%s' % url_encode({'action': messaging_action}) return werkzeug.utils.redirect(url) + @classmethod + def _check_token(cls, token): + base_link = request.httprequest.path + params = dict(request.params) + params.pop('token', '') + valid_token = request.env['mail.thread']._generate_notification_token(base_link, params) + return consteq(valid_token, str(token)) + + @classmethod + def _check_token_and_record_or_redirect(cls, model, res_id, token): + comparison = cls._check_token(token) + if not comparison: + _logger.warning(_('Invalid token in route %s') % request.httprequest.url) + return comparison, None, cls._redirect_to_messaging() + try: + record = request.env[model].browse(res_id).exists() + except Exception: + record = None + redirect = cls._redirect_to_messaging() + else: + redirect = cls._redirect_to_record(model, res_id) + return comparison, record, redirect + + @classmethod + def _redirect_to_record(cls, model, res_id): + uid = request.session.uid + + # no model / res_id, meaning no possible record -> redirect to login + if not model or not res_id or model not in request.env: + return cls._redirect_to_messaging() + + # find the access action using sudo to have the details about the access link + RecordModel = request.env[model] + record_sudo = RecordModel.sudo().browse(res_id).exists() + if not record_sudo: + # record does not seem to exist -> redirect to login + return cls._redirect_to_messaging() + record_action = record_sudo.get_access_action() + + # the record has an URL redirection: use it directly + if record_action['type'] == 'ir.actions.act_url': + return werkzeug.utils.redirect(record_action['url']) + # other choice: act_window (no support of anything else currently) + elif not record_action['type'] == 'ir.actions.act_window': + return cls._redirect_to_messaging() + + # the record has a window redirection: check access rights + if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False): + return cls._redirect_to_messaging() + try: + record_sudo.sudo(uid).check_access_rule('read') + except AccessError: + return cls._redirect_to_messaging() + + url_params = { + 'view_type': record_action['view_type'], + 'model': model, + 'id': res_id, + 'active_id': res_id, + 'view_id': record_sudo.get_formview_id(), + 'action': record_action.get('id'), + } + url = '/web?#%s' % url_encode(url_params) + return werkzeug.utils.redirect(url) + @http.route('/mail/receive', type='json', auth='none') def receive(self, req): """ End-point to receive mail from an external SMTP server. """ @@ -95,8 +166,6 @@ class MailController(http.Controller): - users without read access are redirected to the Messaging - not logged users are redirected to the login page """ - uid = request.session.uid - if message_id: try: message = request.env['mail.message'].sudo().browse(int(message_id)).exists() @@ -110,110 +179,48 @@ class MailController(http.Controller): elif res_id and isinstance(res_id, basestring): res_id = int(res_id) - # no model / res_id, meaning no possible record -> redirect to login - if not model or not res_id or model not in request.env: - return self._redirect_to_messaging() + return self._redirect_to_record(model, res_id) - # find the access action using sudo to have the details about the access link - RecordModel = request.env[model] - record_sudo = RecordModel.sudo().browse(res_id).exists() - if not record_sudo: - # record does not seem to exist -> redirect to login - return self._redirect_to_messaging() - record_action = record_sudo.get_access_action() + @http.route('/mail/follow', type='http', auth='user', methods=['GET']) + def mail_action_follow(self, model, res_id, token=None): + comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token) + if comparison and record: + try: + record.sudo().message_subscribe_users() + except Exception: + return self._redirect_to_messaging() + return redirect - # the record has an URL redirection: use it directly - if record_action['type'] == 'ir.actions.act_url': - return werkzeug.utils.redirect(record_action['url']) - # other choice: act_window (no support of anything else currently) - elif not record_action['type'] == 'ir.actions.act_window': - return self._redirect_to_messaging() - - # the record has a window redirection: check access rights - if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False): - return self._redirect_to_messaging() - try: - RecordModel.sudo(uid).browse(res_id).exists().check_access_rule('read') - except AccessError: - return self._redirect_to_messaging() - - query = {} - url_params = { - 'view_type': record_action['view_type'], - 'model': model, - 'id': res_id, - 'active_id': res_id, - 'view_id': record_sudo.get_formview_id(), - 'action': record_action.get('id'), - } - url = '/web?%s#%s' % (url_encode(query), url_encode(url_params)) - return werkzeug.utils.redirect(url) - - @http.route('/mail/follow', type='http', auth='user') - def mail_action_follow(self, model, res_id): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(res_id).message_subscribe_users() - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) - - @http.route('/mail/unfollow', type='http', auth='user') - def mail_action_unfollow(self, model, res_id): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(res_id).sudo().message_unsubscribe_users([request.uid]) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) + @http.route('/mail/unfollow', type='http', auth='user', methods=['GET']) + def mail_action_unfollow(self, model, res_id, token=None): + comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token) + if comparison and record: + try: + # TDE CHECKME: is sudo really necessary ? + record.sudo().message_unsubscribe_users([request.uid]) + except Exception: + return self._redirect_to_messaging() + return redirect @http.route('/mail/new', type='http', auth='user') - def mail_action_new(self, model, res_id, **kwargs): + def mail_action_new(self, model, res_id, action_id): if model not in request.env: return self._redirect_to_messaging() params = {'view_type': 'form', 'model': model} - if kwargs.get('action_id'): - params['action'] = kwargs['action_id'] + if action_id: + # Probably something to do + params['action'] = action_id return werkzeug.utils.redirect('/web?#%s' % url_encode(params)) - @http.route('/mail/method', type='http', auth='user') - def mail_action_method(self, model, res_id, method, **kwargs): - # only public methods / check exists - if method.strip().startswith('_') or model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - record = Model.browse(int(res_id)).exists() - getattr(record, method)(**json.loads(kwargs.get('params', {}))) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) - - @http.route('/mail/assign', type='http', auth='user') - def mail_action_assign(self, model, res_id, **kwargs): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(int(res_id)).exists().write({'user_id': request.uid}) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) - - @http.route('/mail/workflow', type='http', auth='user') - def mail_action_workflow(self, model, res_id, signal, **kwargs): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(int(res_id)).exists().signal_workflow(signal) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) + @http.route('/mail/assign', type='http', auth='user', methods=['GET']) + def mail_action_assign(self, model, res_id, token=None): + comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token) + if comparison and record: + try: + record.write({'user_id': request.uid}) + except Exception: + return self._redirect_to_messaging() + return redirect @http.route('/mail///avatar/', type='http', auth='public') def avatar(self, res_model, res_id, partner_id): diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index b4b44244cff..e99fb435aae 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -4,6 +4,8 @@ import base64 import datetime import dateutil import email +import hashlib +import hmac import json import lxml from lxml import etree @@ -539,6 +541,13 @@ class MailThread(models.AbstractModel): because portal users have a different Inbox action than classic users. """ return 'mail.mail_channel_action_client_chat' + @api.model + def _generate_notification_token(self, base_link, params): + secret = self.env['ir.config_parameter'].sudo().get_param('database.secret') + token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params.keys()))) + hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest() + return hm + @api.multi def _notification_link_helper(self, link_type, **kwargs): if kwargs.get('message_id'): @@ -546,26 +555,30 @@ class MailThread(models.AbstractModel): 'message_id': kwargs.pop('message_id') } else: - self.ensure_one() base_params = { - 'model': self._name, - 'res_id': self.ids[0], + 'model': kwargs.get('model', self._name), + 'res_id': kwargs.get('res_id', self.ids and self.ids[0] or False), } - link = False if link_type in ['view', 'assign', 'follow', 'unfollow']: params = dict(base_params) - link = '/mail/view?%s' % url_encode(params) - elif link_type == 'workflow': - params = dict(base_params, signal=kwargs['signal']) - link = '/mail/workflow?%s' % url_encode(params) - elif link_type == 'method': - method = kwargs.pop('method') - params = dict(base_params, method=method, params=json.dumps(kwargs)) - link = '/mail/method?%s' % url_encode(params) + base_link = '/mail/%s' % link_type elif link_type == 'new': - params = dict(base_params, action_id=kwargs.get('action_id')) - link = '/mail/new?%s' % url_encode(params) + params = dict(base_params, action_id=kwargs.get('action_id', '')) + base_link = '/mail/new' + elif link_type == 'controller': + controller = kwargs.pop('controller') + params = dict(base_params) + params.pop('model') + base_link = '%s' % controller + else: + return '' + + if link_type not in ['view', 'new']: + token = self._generate_notification_token(base_link, params) + params['token'] = token + + link = '%s?%s' % (base_link, url_encode(params)) return link @api.multi @@ -639,8 +652,8 @@ class MailThread(models.AbstractModel): 'followers': self.env['res.partner'], 'not_followers': self.env['res.partner'], 'button_access': {'url': access_link, 'title': view_title}, - 'button_follow': {'url': '/mail/follow?%s' % url_encode({'model': message.model, 'res_id': message.res_id}), 'title': _('Follow')}, - 'button_unfollow': {'url': '/mail/unfollow?%s' % url_encode({'model': message.model, 'res_id': message.res_id}), 'title': _('Unfollow')}, + 'button_follow': {'url': self._notification_link_helper('follow', model=message.model, res_id=message.res_id), 'title': _('Follow')}, + 'button_unfollow': {'url': self._notification_link_helper('unfollow', model=message.model, res_id=message.res_id), 'title': _('Unfollow')}, 'actions': list(), } group_data[category] = self.env['res.partner']