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.

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