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.

119 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. import logging
  5. import urllib
  6. from openerp import _, api, fields, models
  7. from openerp.exceptions import ValidationError
  8. _logger = logging.getLogger(__name__)
  9. try:
  10. import pyotp
  11. except ImportError:
  12. _logger.debug(
  13. 'Could not import PyOTP. Please make sure this library is available in'
  14. ' your environment.'
  15. )
  16. class ResUsersAuthenticatorCreate(models.TransientModel):
  17. _name = 'res.users.authenticator.create'
  18. _description = 'MFA App/Device Creation Wizard'
  19. name = fields.Char(
  20. string='Authentication App/Device Name',
  21. help='A name that will help you remember this authentication'
  22. ' app/device',
  23. required=True,
  24. index=True,
  25. )
  26. secret_key = fields.Char(
  27. string='Secret Code',
  28. default=lambda s: pyotp.random_base32(),
  29. required=True,
  30. )
  31. qr_code_tag = fields.Html(
  32. compute='_compute_qr_code_tag',
  33. string='QR Code',
  34. help='Scan this image with your authentication app to add your'
  35. ' account',
  36. )
  37. user_id = fields.Many2one(
  38. comodel_name='res.users',
  39. default=lambda s: s._default_user_id(),
  40. required=True,
  41. string='Associated User',
  42. help='This is the user whose account the new authentication app/device'
  43. ' will be tied to',
  44. readonly=True,
  45. index=True,
  46. ondelete='cascade',
  47. )
  48. confirmation_code = fields.Char(
  49. string='Confirmation Code',
  50. help='Enter the latest six digit code generated by your authentication'
  51. ' app',
  52. required=True,
  53. )
  54. @api.model
  55. def _default_user_id(self):
  56. user_id = self.env.context.get('uid')
  57. return self.env['res.users'].browse(user_id)
  58. @api.multi
  59. @api.depends(
  60. 'secret_key',
  61. 'user_id.display_name',
  62. 'user_id.company_id.display_name',
  63. )
  64. def _compute_qr_code_tag(self):
  65. for record in self:
  66. if not record.user_id:
  67. continue
  68. totp = pyotp.TOTP(record.secret_key)
  69. provisioning_uri = totp.provisioning_uri(
  70. record.user_id.display_name.encode('utf-8'),
  71. issuer_name=record.user_id.company_id.display_name,
  72. )
  73. provisioning_uri = urllib.quote(provisioning_uri)
  74. qr_width = qr_height = 300
  75. tag_base = '<img src="/report/barcode/?type=QR&amp;'
  76. tag_params = 'value=%s&amp;width=%s&amp;height=%s">' % (
  77. provisioning_uri,
  78. qr_width,
  79. qr_height
  80. )
  81. record.qr_code_tag = tag_base + tag_params
  82. @api.multi
  83. def action_create(self):
  84. self.ensure_one()
  85. self._perform_validations()
  86. self._create_authenticator()
  87. action_data = self.env.ref('base.action_res_users_my').read()[0]
  88. action_data.update({'res_id': self.user_id.id})
  89. return action_data
  90. @api.multi
  91. def _perform_validations(self):
  92. totp = pyotp.TOTP(self.secret_key)
  93. if not totp.verify(self.confirmation_code):
  94. raise ValidationError(_(
  95. 'Your confirmation code is not correct. Please try again,'
  96. ' making sure that your MFA device is set to the correct time'
  97. ' and that you have entered the most recent code generated by'
  98. ' your authentication app.'
  99. ))
  100. @api.multi
  101. def _create_authenticator(self):
  102. self.env['res.users.authenticator'].create({
  103. 'name': self.name,
  104. 'secret_key': self.secret_key,
  105. 'user_id': self.user_id.id,
  106. })