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.

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