You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
4.2 KiB

  1. # Copyright 2018 Camptocamp
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  3. from odoo import api, models, fields
  4. from odoo.exceptions import MissingError
  5. from uuid import uuid4
  6. from base64 import urlsafe_b64encode, urlsafe_b64decode
  7. import binascii
  8. import lxml
  9. from werkzeug.urls import url_parse
  10. from datetime import date
  11. from dateutil.relativedelta import relativedelta
  12. class Mail(models.Model):
  13. _inherit = 'mail.mail'
  14. access_token = fields.Char(
  15. 'Security Token',
  16. compute="_compute_access_token",
  17. store=True, readonly=True
  18. )
  19. view_in_browser_url = fields.Char(
  20. 'View URL',
  21. compute="_compute_browser_url"
  22. )
  23. is_token_alive = fields.Boolean(
  24. "Is Token alive",
  25. compute="_compute_token_alive"
  26. )
  27. @api.model
  28. def create(self, vals):
  29. rec = super(Mail, self).create(vals)
  30. rec._replace_view_url()
  31. return rec
  32. @api.model
  33. def get_record_for_token(self, token):
  34. """Parse the URL token to get the matching record.
  35. The token is a base 64 encoded string containing:
  36. * 32 positions access token
  37. * Record ID
  38. Returns a record matching the token or empty recordset if not found
  39. """
  40. try:
  41. token = urlsafe_b64decode(token).decode()
  42. access_token, rec_id = token[:32], token[32:]
  43. rec = self.sudo().search([
  44. ('id', '=', int(rec_id)),
  45. ('access_token', '=', access_token)
  46. ])
  47. res = rec.is_token_alive and rec
  48. except (ValueError, MissingError, binascii.Error):
  49. res = False
  50. finally:
  51. return res or self.browse()
  52. @api.multi
  53. def _get_full_url(self):
  54. self.ensure_one()
  55. base_url = self.env['ir.config_parameter'].sudo().get_param(
  56. 'web.base.url')
  57. base = url_parse(base_url)
  58. return url_parse(
  59. self.view_in_browser_url or '#'
  60. ).replace(
  61. scheme=base.scheme, netloc=base.netloc
  62. ).to_url()
  63. @api.multi
  64. def _replace_view_url(self):
  65. """Replace placeholders with record URL.
  66. Replace the 'href' attribute of all `<a></a>` tags
  67. having the 'class' attribute equal to 'view_in_browser_url'
  68. with the URL generated for this mail.mail record
  69. inside the rendered 'body_html' from the template.
  70. In case the value `auto_delete` for the record is `True`,
  71. the placeholders will be removed.
  72. """
  73. self.ensure_one()
  74. root_html = lxml.html.fromstring(self.body_html)
  75. link_nodes = root_html.xpath("//a[hasclass('view_in_browser_url')]")
  76. if link_nodes:
  77. if self.auto_delete:
  78. for node in link_nodes:
  79. node.drop_tree()
  80. else:
  81. full_url = self._get_full_url()
  82. for node in link_nodes:
  83. node.set('href', full_url)
  84. self.body_html = lxml.html.tostring(
  85. root_html,
  86. pretty_print=False,
  87. method='html',
  88. encoding='unicode'
  89. )
  90. @api.depends('create_date')
  91. def _compute_access_token(self):
  92. for rec in self:
  93. rec.access_token = uuid4().hex
  94. @api.depends('access_token')
  95. def _compute_browser_url(self):
  96. for rec in self:
  97. url_token = urlsafe_b64encode(
  98. (rec.access_token + str(rec.id)).encode()
  99. ).decode()
  100. rec.view_in_browser_url = '/email/view/{}'.format(url_token)
  101. @api.depends('mail_message_id',
  102. 'mail_message_id.date')
  103. def _compute_token_alive(self):
  104. expiration_time = int(
  105. self.env['ir.config_parameter'].sudo().get_param(
  106. 'mail_browser_view.token_expiration_hours'
  107. ) or '0'
  108. )
  109. if expiration_time > 0:
  110. max_delta = relativedelta(hours=expiration_time)
  111. for rec in self:
  112. mail_date = fields.Datetime.from_string(
  113. rec.mail_message_id.date
  114. )
  115. rec.is_token_alive = (
  116. (mail_date + max_delta).date() >= date.today()
  117. )
  118. else:
  119. self.update({'is_token_alive': True})