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.

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