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

# -*- coding: utf-8 -*-
# Copyright 2016-2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from collections import defaultdict
from uuid import uuid4
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.http import request
from ..controllers.main import JsonSecureCookie
from ..exceptions import MfaLoginNeeded
class ResUsers(models.Model):
_inherit = 'res.users'
_mfa_uid_cache = defaultdict(set)
@classmethod
def _build_model(cls, pool, cr):
ModelCls = super(ResUsers, cls)._build_model(pool, cr)
ModelCls.SELF_WRITEABLE_FIELDS += ['mfa_enabled', 'authenticator_ids']
return ModelCls
mfa_enabled = fields.Boolean(string='MFA Enabled?')
authenticator_ids = fields.One2many(
comodel_name='res.users.authenticator',
inverse_name='user_id',
string='Authentication Apps/Devices',
help='To delete an authentication app, remove it from this list. To'
' add a new authentication app, please use the button to the'
' right. If the button is not present, you do not have the'
' permissions to do this.',
)
trusted_device_cookie_key = fields.Char(
compute='_compute_trusted_device_cookie_key',
store=True,
)
@api.multi
@api.depends('mfa_enabled')
def _compute_trusted_device_cookie_key(self):
for record in self:
if record.mfa_enabled:
record.trusted_device_cookie_key = uuid4()
else:
record.trusted_device_cookie_key = False
@api.multi
@api.constrains('mfa_enabled', 'authenticator_ids')
def _check_enabled_with_authenticator(self):
for record in self:
if record.mfa_enabled and not record.authenticator_ids:
raise ValidationError(_(
'You have MFA enabled but do not have any authentication'
' apps/devices set up. To keep from being locked out,'
' please add one before you activate this feature.'
))
@classmethod
def check(cls, db, uid, password):
"""Prevent auth caching for MFA users without active MFA session"""
if uid in cls._mfa_uid_cache[db]:
if not request or request.session.get('mfa_login_active') != uid:
cls._Users__uid_cache[db].pop(uid, None)
return super(ResUsers, cls).check(db, uid, password)
@api.model
def check_credentials(self, password):
"""Add MFA logic to core authentication process.
Overview:
* If user does not have MFA enabled, defer to parent logic.
* If user has MFA enabled and has gone through MFA login process
this session or has correct device cookie, defer to parent logic.
* If neither of these is true, call parent logic. If successful,
prevent auth while updating session to indicate that MFA login
process can now commence.
"""
if not self.env.user.mfa_enabled:
return super(ResUsers, self).check_credentials(password)
self._mfa_uid_cache[self.env.cr.dbname].add(self.env.uid)
if request:
if request.session.get('mfa_login_active') == self.env.uid:
return super(ResUsers, self).check_credentials(password)
cookie_key = 'trusted_devices_%d' % self.env.uid
device_cook = request.httprequest.cookies.get(cookie_key)
if device_cook:
secret = self.env.user.trusted_device_cookie_key
device_cook = JsonSecureCookie.unserialize(device_cook, secret)
if device_cook:
return super(ResUsers, self).check_credentials(password)
super(ResUsers, self).check_credentials(password)
if request:
request.session['mfa_login_needed'] = True
raise MfaLoginNeeded
@api.multi
def validate_mfa_confirmation_code(self, confirmation_code):
self.ensure_one()
return self.authenticator_ids.validate_conf_code(confirmation_code)