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.

162 lines
5.4 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 LasLabs Inc.
  3. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
  4. import re
  5. from datetime import datetime, timedelta
  6. from odoo import api, fields, models, _
  7. from ..exceptions import PassError
  8. def delta_now(**kwargs):
  9. dt = datetime.now() + timedelta(**kwargs)
  10. return fields.Datetime.to_string(dt)
  11. class ResUsers(models.Model):
  12. _inherit = 'res.users'
  13. password_write_date = fields.Datetime(
  14. 'Last password update',
  15. readonly=True,
  16. )
  17. password_history_ids = fields.One2many(
  18. string='Password History',
  19. comodel_name='res.users.pass.history',
  20. inverse_name='user_id',
  21. readonly=True,
  22. )
  23. @api.model
  24. def create(self, vals):
  25. vals['password_write_date'] = fields.Datetime.now()
  26. return super(ResUsers, self).create(vals)
  27. @api.multi
  28. def write(self, vals):
  29. if vals.get('password'):
  30. self._check_password(vals['password'])
  31. vals['password_write_date'] = fields.Datetime.now()
  32. return super(ResUsers, self).write(vals)
  33. @api.multi
  34. def password_match_message(self):
  35. self.ensure_one()
  36. company_id = self.company_id
  37. message = []
  38. if company_id.password_lower:
  39. message.append('\n* ' + _('Lowercase letter'))
  40. if company_id.password_upper:
  41. message.append('\n* ' + _('Uppercase letter'))
  42. if company_id.password_numeric:
  43. message.append('\n* ' + _('Numeric digit'))
  44. if company_id.password_special:
  45. message.append('\n* ' + _('Special character'))
  46. if message:
  47. message = [_('Must contain the following:')] + message
  48. if company_id.password_length:
  49. message = [
  50. _('Password must be %d characters or more.') %
  51. company_id.password_length
  52. ] + message
  53. return '\r'.join(message)
  54. @api.multi
  55. def _check_password(self, password):
  56. self._check_password_rules(password)
  57. self._check_password_history(password)
  58. return True
  59. @api.multi
  60. def _check_password_rules(self, password):
  61. self.ensure_one()
  62. if not password:
  63. return True
  64. company_id = self.company_id
  65. password_regex = ['^']
  66. if company_id.password_lower:
  67. password_regex.append('(?=.*?[a-z])')
  68. if company_id.password_upper:
  69. password_regex.append('(?=.*?[A-Z])')
  70. if company_id.password_numeric:
  71. password_regex.append(r'(?=.*?\d)')
  72. if company_id.password_special:
  73. password_regex.append(r'(?=.*?\W)')
  74. password_regex.append('.{%d,}$' % company_id.password_length)
  75. if not re.search(''.join(password_regex), password):
  76. raise PassError(self.password_match_message())
  77. return True
  78. @api.multi
  79. def _password_has_expired(self):
  80. self.ensure_one()
  81. if not self.password_write_date:
  82. return True
  83. write_date = fields.Datetime.from_string(self.password_write_date)
  84. today = fields.Datetime.from_string(fields.Datetime.now())
  85. days = (today - write_date).days
  86. return days > self.company_id.password_expiration
  87. @api.multi
  88. def action_expire_password(self):
  89. expiration = delta_now(days=+1)
  90. for rec_id in self:
  91. rec_id.mapped('partner_id').signup_prepare(
  92. signup_type="reset", expiration=expiration
  93. )
  94. @api.multi
  95. def _validate_pass_reset(self):
  96. """ It provides validations before initiating a pass reset email
  97. :raises: PassError on invalidated pass reset attempt
  98. :return: True on allowed reset
  99. """
  100. for rec_id in self:
  101. pass_min = rec_id.company_id.password_minimum
  102. if pass_min <= 0:
  103. pass
  104. write_date = fields.Datetime.from_string(
  105. rec_id.password_write_date
  106. )
  107. delta = timedelta(hours=pass_min)
  108. if write_date + delta > datetime.now():
  109. raise PassError(
  110. _('Passwords can only be reset every %d hour(s). '
  111. 'Please contact an administrator for assistance.') %
  112. pass_min,
  113. )
  114. return True
  115. @api.multi
  116. def _check_password_history(self, password):
  117. """ It validates proposed password against existing history
  118. :raises: PassError on reused password
  119. """
  120. crypt = self._crypt_context()
  121. for rec_id in self:
  122. recent_passes = rec_id.company_id.password_history
  123. if recent_passes < 0:
  124. recent_passes = rec_id.password_history_ids
  125. else:
  126. recent_passes = rec_id.password_history_ids[
  127. 0:recent_passes-1
  128. ]
  129. if recent_passes.filtered(
  130. lambda r: crypt.verify(password, r.password_crypt)):
  131. raise PassError(
  132. _('Cannot use the most recent %d passwords') %
  133. rec_id.company_id.password_history
  134. )
  135. @api.multi
  136. def _set_encrypted_password(self, encrypted):
  137. """ It saves password crypt history for history rules """
  138. super(ResUsers, self)._set_encrypted_password(encrypted)
  139. self.write({
  140. 'password_history_ids': [(0, 0, {
  141. 'password_crypt': encrypted,
  142. })],
  143. })