Initial commit: Security packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit bb469e4763
1399 changed files with 278378 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import auth_totp_wizard

View file

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import functools
import io
import qrcode
import re
import werkzeug.urls
from odoo import _, api, fields, models
from odoo.addons.base.models.res_users import check_identity
from odoo.exceptions import UserError
from odoo.http import request
from odoo.addons.auth_totp.models.totp import ALGORITHM, DIGITS, TIMESTEP
compress = functools.partial(re.sub, r'\s', '')
class TOTPWizard(models.TransientModel):
_name = 'auth_totp.wizard'
_description = "2-Factor Setup Wizard"
user_id = fields.Many2one('res.users', required=True, readonly=True)
secret = fields.Char(required=True, readonly=True)
url = fields.Char(store=True, readonly=True, compute='_compute_qrcode')
qrcode = fields.Binary(
attachment=False, store=True, readonly=True,
compute='_compute_qrcode',
)
code = fields.Char(string="Verification Code", size=7)
@api.depends('user_id.login', 'user_id.company_id.display_name', 'secret')
def _compute_qrcode(self):
# TODO: make "issuer" configurable through config parameter?
global_issuer = request and request.httprequest.host.split(':', 1)[0]
for w in self:
issuer = global_issuer or w.user_id.company_id.display_name
w.url = url = werkzeug.urls.url_unparse((
'otpauth', 'totp',
werkzeug.urls.url_quote(f'{issuer}:{w.user_id.login}', safe=':'),
werkzeug.urls.url_encode({
'secret': compress(w.secret),
'issuer': issuer,
# apparently a lowercase hash name is anathema to google
# authenticator (error) and passlib (no token)
'algorithm': ALGORITHM.upper(),
'digits': DIGITS,
'period': TIMESTEP,
}), ''
))
data = io.BytesIO()
qrcode.make(url.encode(), box_size=4).save(data, optimise=True, format='PNG')
w.qrcode = base64.b64encode(data.getvalue()).decode()
@check_identity
def enable(self):
try:
c = int(compress(self.code))
except ValueError:
raise UserError(_("The verification code should only contain numbers"))
if self.user_id._totp_try_setting(self.secret, c):
self.secret = '' # empty it, because why keep it until GC?
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': _("2-Factor authentication is now enabled."),
'next': {'type': 'ir.actions.act_window_close'},
}
}
raise UserError(_('Verification failed, please double-check the 6-digit code'))
def create(self, vals_list):
rule = self.env.ref('auth_totp.rule_auth_totp_wizard', raise_if_not_found=False)
if rule and rule.sudo().groups:
rule.sudo().groups = False
return super().create(vals_list)

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="view_totp_wizard">
<field name="name">auth_totp wizard</field>
<field name="model">auth_totp.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="o_auth_totp_enable_2FA container">
<div class="mb-3 w-100">
<h3 class="fw-bold">Authenticator App Setup</h3>
<ul>
<div class="d-md-none d-block">
<li>
<field class="text-wrap" name="url" widget="url" options="{'website_path': True}"
text="Click on this link to open your authenticator app"/></li>
</div>
<li>
<div class="d-flex align-items-center flex-wrap">
<span class="d-md-none d-block">Or install an authenticator app</span>
<span class="d-none d-md-block">Install an authenticator app on your mobile device</span>
</div>
</li>
<span class="text-muted">Popular ones include Authy, Google Authenticator or the Microsoft Authenticator.</span>
<li>Look for an "Add an account" button</li>
<li>
<span class="d-none d-md-block">When requested to do so, scan the barcode below</span>
<span class="d-block d-md-none">When requested to do so, copy the key below</span>
</li>
</ul>
<!-- Desktop version -->
<div class="text-center d-none d-md-block">
<field name="qrcode" readonly="True" widget="image" options="{'no_reload': true }" />
<h3 class="fw-bold"><a data-bs-toggle="collapse"
href="#collapseTotpSecret" role="button" aria-expanded="false"
aria-controls="collapseTotpSecret">Cannot scan it?</a></h3>
<div class="collapse" id="collapseTotpSecret">
<field name="secret" widget="CopyClipboardChar" readonly="1" class="mb-3 ps-3"/>
</div>
</div>
<!-- Mobile Version -->
<div class="text-center d-block d-md-none">
<field name="secret" widget="CopyClipboardChar" readonly="1" class="mb-3 ps-3"/>
</div>
<h3 class="fw-bold">Enter your six-digit code below</h3>
<div class="mt-2">
<label for="code" class="px-0">Verification Code</label>
<div class="d-flex align-items-center">
<field required="True" name="code" autocomplete="off" class="o_field_highlight px-0 me-2" placeholder="e.g. 123456"/>
</div>
</div>
</div>
</div>
</sheet>
<footer>
<button type="object" name="enable" class="btn btn-primary"
string="Activate" data-hotkey="q"/>
<button string="Cancel" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
</odoo>