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.

97 lines
3.4 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016-2017 LasLabs Inc.
  3. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
  4. from datetime import datetime, timedelta
  5. import random
  6. import string
  7. from openerp import _, api, fields, models
  8. from openerp.exceptions import AccessDenied, ValidationError
  9. from ..exceptions import MfaTokenInvalidError, MfaTokenExpiredError
  10. class ResUsers(models.Model):
  11. _inherit = 'res.users'
  12. mfa_enabled = fields.Boolean(string='MFA Enabled?')
  13. authenticator_ids = fields.One2many(
  14. comodel_name='res.users.authenticator',
  15. inverse_name='user_id',
  16. string='Authentication Apps/Devices',
  17. help='To delete an authentication app, remove it from this list. To'
  18. ' add a new authentication app, please use the button to the'
  19. ' right.',
  20. )
  21. mfa_login_token = fields.Char()
  22. mfa_login_token_exp = fields.Datetime()
  23. trusted_device_ids = fields.One2many(
  24. comodel_name='res.users.device',
  25. inverse_name='user_id',
  26. string='Trusted Devices',
  27. )
  28. @api.multi
  29. @api.constrains('mfa_enabled', 'authenticator_ids')
  30. def _check_enabled_with_authenticator(self):
  31. for record in self:
  32. if record.mfa_enabled and not record.authenticator_ids:
  33. raise ValidationError(_(
  34. 'You have MFA enabled but do not have any authentication'
  35. ' apps/devices set up. To keep from being locked out,'
  36. ' please add one before you activate this feature.'
  37. ))
  38. @api.model
  39. def check_credentials(self, password):
  40. try:
  41. return super(ResUsers, self).check_credentials(password)
  42. except AccessDenied:
  43. user = self.sudo().search([
  44. ('id', '=', self.env.uid),
  45. ('mfa_login_token', '=', password),
  46. ])
  47. user._user_from_mfa_login_token_validate()
  48. @api.multi
  49. def generate_mfa_login_token(self, lifetime_mins=15):
  50. char_set = string.ascii_letters + string.digits
  51. for record in self:
  52. record.mfa_login_token = ''.join(
  53. random.SystemRandom().choice(char_set) for __ in range(20)
  54. )
  55. expiration = datetime.now() + timedelta(minutes=lifetime_mins)
  56. record.mfa_login_token_exp = fields.Datetime.to_string(expiration)
  57. @api.model
  58. def user_from_mfa_login_token(self, token):
  59. if not token:
  60. raise MfaTokenInvalidError(_(
  61. 'Your MFA login token is not valid. Please try again.'
  62. ))
  63. user = self.search([('mfa_login_token', '=', token)])
  64. user._user_from_mfa_login_token_validate()
  65. return user
  66. @api.multi
  67. def _user_from_mfa_login_token_validate(self):
  68. try:
  69. self.ensure_one()
  70. except ValueError:
  71. raise MfaTokenInvalidError(_(
  72. 'Your MFA login token is not valid. Please try again.'
  73. ))
  74. token_exp = fields.Datetime.from_string(self.mfa_login_token_exp)
  75. if token_exp < datetime.now():
  76. raise MfaTokenExpiredError(_(
  77. 'Your MFA login token has expired. Please try again.'
  78. ))
  79. @api.multi
  80. def validate_mfa_confirmation_code(self, confirmation_code):
  81. self.ensure_one()
  82. return self.authenticator_ids.validate_conf_code(confirmation_code)