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.

105 lines
4.1 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  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 collections import defaultdict
  5. from uuid import uuid4
  6. from odoo import _, api, fields, models
  7. from odoo.exceptions import ValidationError
  8. from odoo.http import request
  9. from ..controllers.main import JsonSecureCookie
  10. from ..exceptions import MfaLoginNeeded
  11. class ResUsers(models.Model):
  12. _inherit = 'res.users'
  13. _mfa_uid_cache = defaultdict(set)
  14. @classmethod
  15. def _build_model(cls, pool, cr):
  16. ModelCls = super(ResUsers, cls)._build_model(pool, cr)
  17. ModelCls.SELF_WRITEABLE_FIELDS += ['mfa_enabled', 'authenticator_ids']
  18. return ModelCls
  19. mfa_enabled = fields.Boolean(string='MFA Enabled?')
  20. authenticator_ids = fields.One2many(
  21. comodel_name='res.users.authenticator',
  22. inverse_name='user_id',
  23. string='Authentication Apps/Devices',
  24. help='To delete an authentication app, remove it from this list. To'
  25. ' add a new authentication app, please use the button to the'
  26. ' right. If the button is not present, you do not have the'
  27. ' permissions to do this.',
  28. )
  29. trusted_device_cookie_key = fields.Char(
  30. compute='_compute_trusted_device_cookie_key',
  31. store=True,
  32. )
  33. @api.multi
  34. @api.depends('mfa_enabled')
  35. def _compute_trusted_device_cookie_key(self):
  36. for record in self:
  37. if record.mfa_enabled:
  38. record.trusted_device_cookie_key = uuid4()
  39. else:
  40. record.trusted_device_cookie_key = False
  41. @api.multi
  42. @api.constrains('mfa_enabled', 'authenticator_ids')
  43. def _check_enabled_with_authenticator(self):
  44. for record in self:
  45. if record.mfa_enabled and not record.authenticator_ids:
  46. raise ValidationError(_(
  47. 'You have MFA enabled but do not have any authentication'
  48. ' apps/devices set up. To keep from being locked out,'
  49. ' please add one before you activate this feature.'
  50. ))
  51. @classmethod
  52. def check(cls, db, uid, password):
  53. """Prevent auth caching for MFA users without active MFA session"""
  54. if uid in cls._mfa_uid_cache[db]:
  55. if not request or request.session.get('mfa_login_active') != uid:
  56. cls._Users__uid_cache[db].pop(uid, None)
  57. return super(ResUsers, cls).check(db, uid, password)
  58. @api.model
  59. def check_credentials(self, password):
  60. """Add MFA logic to core authentication process.
  61. Overview:
  62. * If user does not have MFA enabled, defer to parent logic.
  63. * If user has MFA enabled and has gone through MFA login process
  64. this session or has correct device cookie, defer to parent logic.
  65. * If neither of these is true, call parent logic. If successful,
  66. prevent auth while updating session to indicate that MFA login
  67. process can now commence.
  68. """
  69. if not self.env.user.mfa_enabled:
  70. return super(ResUsers, self).check_credentials(password)
  71. self._mfa_uid_cache[self.env.cr.dbname].add(self.env.uid)
  72. if request:
  73. if request.session.get('mfa_login_active') == self.env.uid:
  74. return super(ResUsers, self).check_credentials(password)
  75. cookie_key = 'trusted_devices_%d' % self.env.uid
  76. device_cook = request.httprequest.cookies.get(cookie_key)
  77. if device_cook:
  78. secret = self.env.user.trusted_device_cookie_key
  79. device_cook = JsonSecureCookie.unserialize(device_cook, secret)
  80. if device_cook:
  81. return super(ResUsers, self).check_credentials(password)
  82. super(ResUsers, self).check_credentials(password)
  83. if request:
  84. request.session['mfa_login_needed'] = True
  85. raise MfaLoginNeeded
  86. @api.multi
  87. def validate_mfa_confirmation_code(self, confirmation_code):
  88. self.ensure_one()
  89. return self.authenticator_ids.validate_conf_code(confirmation_code)