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.

158 lines
5.3 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 openerp 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('* ' + _('Lowercase letter'))
  40. if company_id.password_upper:
  41. message.append('* ' + _('Uppercase letter'))
  42. if company_id.password_numeric:
  43. message.append('* ' + _('Numeric digit'))
  44. if company_id.password_special:
  45. message.append('* ' + _('Special character'))
  46. if len(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.ensure_one()
  57. if not password:
  58. return True
  59. company_id = self.company_id
  60. password_regex = ['^']
  61. if company_id.password_lower:
  62. password_regex.append('(?=.*?[a-z])')
  63. if company_id.password_upper:
  64. password_regex.append('(?=.*?[A-Z])')
  65. if company_id.password_numeric:
  66. password_regex.append(r'(?=.*?\d)')
  67. if company_id.password_special:
  68. password_regex.append(r'(?=.*?\W)')
  69. password_regex.append('.{%d,}$' % company_id.password_length)
  70. if not re.search(''.join(password_regex), password):
  71. raise PassError(_(self.password_match_message()))
  72. return True
  73. @api.multi
  74. def _password_has_expired(self):
  75. self.ensure_one()
  76. if not self.password_write_date:
  77. return True
  78. write_date = fields.Datetime.from_string(self.password_write_date)
  79. today = fields.Datetime.from_string(fields.Datetime.now())
  80. days = (today - write_date).days
  81. return days > self.company_id.password_expiration
  82. @api.multi
  83. def action_expire_password(self):
  84. expiration = delta_now(days=+1)
  85. for rec_id in self:
  86. rec_id.mapped('partner_id').signup_prepare(
  87. signup_type="reset", expiration=expiration
  88. )
  89. @api.multi
  90. def _validate_pass_reset(self):
  91. """ It provides validations before initiating a pass reset email
  92. :raises: PassError on invalidated pass reset attempt
  93. :return: True on allowed reset
  94. """
  95. for rec_id in self:
  96. pass_min = rec_id.company_id.password_minimum
  97. if pass_min <= 0:
  98. pass
  99. write_date = fields.Datetime.from_string(
  100. rec_id.password_write_date
  101. )
  102. delta = timedelta(hours=pass_min)
  103. if write_date + delta > datetime.now():
  104. raise PassError(
  105. _('Passwords can only be reset every %d hour(s). '
  106. 'Please contact an administrator for assistance.') %
  107. pass_min,
  108. )
  109. return True
  110. @api.multi
  111. def _set_password(self, password):
  112. """ It validates proposed password against existing history
  113. :raises: PassError on reused password
  114. """
  115. crypt = self._crypt_context()[0]
  116. for rec_id in self:
  117. recent_passes = rec_id.company_id.password_history
  118. if recent_passes < 0:
  119. recent_passes = rec_id.password_history_ids
  120. else:
  121. recent_passes = rec_id.password_history_ids[
  122. 0:recent_passes-1
  123. ]
  124. if len(recent_passes.filtered(
  125. lambda r: crypt.verify(password, r.password_crypt)
  126. )):
  127. raise PassError(
  128. _('Cannot use the most recent %d passwords') %
  129. rec_id.company_id.password_history
  130. )
  131. super(ResUsers, self)._set_password(password)
  132. @api.multi
  133. def _set_encrypted_password(self, encrypted):
  134. """ It saves password crypt history for history rules """
  135. super(ResUsers, self)._set_encrypted_password(encrypted)
  136. self.write({
  137. 'password_history_ids': [(0, 0, {
  138. 'password_crypt': encrypted,
  139. })],
  140. })