mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 08:31:59 +02:00
209 lines
6.9 KiB
Python
209 lines
6.9 KiB
Python
# Copyright 2024 Akretion (http://www.akretion.com).
|
|
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
|
# @author Florian Mounier <florian.mounier@akretion.com>
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from datetime import datetime, timezone
|
|
from secrets import token_urlsafe
|
|
|
|
import jwt
|
|
|
|
from odoo import _, fields, models
|
|
from odoo.exceptions import UserError
|
|
|
|
from odoo.addons.queue_job.delay import chain
|
|
|
|
|
|
class AuthDirectory(models.Model):
|
|
_name = "auth.directory"
|
|
_description = "Auth Directory"
|
|
_inherit = "server.env.mixin"
|
|
|
|
name = fields.Char(required=True)
|
|
auth_partner_ids = fields.One2many("auth.partner", "directory_id", "Auth Partners")
|
|
set_password_token_duration = fields.Integer(
|
|
default=1440, help="In minute, default 1440 minutes => 24h", required=True
|
|
)
|
|
impersonating_token_duration = fields.Integer(
|
|
default=60, help="In seconds, default 60 seconds", required=True
|
|
)
|
|
reset_password_template_id = fields.Many2one(
|
|
"mail.template",
|
|
"Mail Template Forget Password",
|
|
required=True,
|
|
default=lambda self: self.env.ref(
|
|
"auth_partner.email_reset_password",
|
|
raise_if_not_found=False,
|
|
),
|
|
)
|
|
set_password_template_id = fields.Many2one(
|
|
"mail.template",
|
|
"Mail Template New Password",
|
|
required=True,
|
|
default=lambda self: self.env.ref(
|
|
"auth_partner.email_set_password",
|
|
raise_if_not_found=False,
|
|
),
|
|
)
|
|
validate_email_template_id = fields.Many2one(
|
|
"mail.template",
|
|
"Mail Template Validate Email",
|
|
required=True,
|
|
default=lambda self: self.env.ref(
|
|
"auth_partner.email_validate_email",
|
|
raise_if_not_found=False,
|
|
),
|
|
)
|
|
secret_key = fields.Char(
|
|
groups="base.group_system",
|
|
required=True,
|
|
default=lambda self: self._generate_default_secret_key(),
|
|
)
|
|
count_partner = fields.Integer(compute="_compute_count_partner")
|
|
|
|
impersonating_user_ids = fields.Many2many(
|
|
"res.users",
|
|
"auth_directory_impersonating_user_rel",
|
|
"directory_id",
|
|
"user_id",
|
|
string="Impersonating Users",
|
|
help="These odoo users can impersonate any partner of this directory",
|
|
default=lambda self: (
|
|
self.env.ref("base.user_root") | self.env.ref("base.user_admin")
|
|
).ids,
|
|
groups="auth_partner.group_auth_partner_manager",
|
|
)
|
|
force_verified_email = fields.Boolean(
|
|
help="If checked, email must be verified to be able to log in"
|
|
)
|
|
|
|
def _generate_default_secret_key(self):
|
|
# generate random ~64 chars secret key
|
|
return token_urlsafe(64)
|
|
|
|
def action_regenerate_secret_key(self):
|
|
self.ensure_one()
|
|
self.secret_key = self._generate_default_secret_key()
|
|
|
|
def _compute_count_partner(self):
|
|
data = self.env["auth.partner"].read_group(
|
|
[
|
|
("directory_id", "in", self.ids),
|
|
],
|
|
["directory_id"],
|
|
groupby=["directory_id"],
|
|
lazy=False,
|
|
)
|
|
res = {item["directory_id"][0]: item["__count"] for item in data}
|
|
|
|
for record in self:
|
|
record.count_partner = res.get(record.id, 0)
|
|
|
|
def _get_template(self, type_or_template):
|
|
if isinstance(type_or_template, str):
|
|
return getattr(self, type_or_template + "_template_id", None)
|
|
return type_or_template
|
|
|
|
def _prepare_mail_context(self, context):
|
|
return context or {}
|
|
|
|
def _send_mail_background(
|
|
self, type_or_template, auth_partner, callback_job=None, **context
|
|
):
|
|
"""
|
|
Send an email asynchronously to the auth_partner
|
|
using the template defined in the directory
|
|
"""
|
|
self.ensure_one()
|
|
auth_partner.ensure_one()
|
|
# Load context synchronously
|
|
context = self._prepare_mail_context(context)
|
|
|
|
job = self.delayable()._send_mail_impl(
|
|
type_or_template, auth_partner, **context
|
|
)
|
|
if callback_job:
|
|
job = chain(job, callback_job)
|
|
return job.delay()
|
|
|
|
def _send_mail(self, type_or_template, auth_partner, **context):
|
|
"""Send an email to the auth_partner using the template defined in the directory"""
|
|
self.ensure_one()
|
|
auth_partner.ensure_one()
|
|
context = self._prepare_mail_context(context)
|
|
|
|
self._send_mail_impl(type_or_template, auth_partner, **context)
|
|
|
|
def _send_mail_impl(self, type_or_template, auth_partner, **context):
|
|
template = self.sudo()._get_template(type_or_template)
|
|
if not template:
|
|
raise UserError(
|
|
_("No email template defined for %(template)s in %(directory)s")
|
|
% {"template": type_or_template, "directory": self.name}
|
|
)
|
|
template.sudo().with_context(**context).send_mail(
|
|
auth_partner.id, force_send=True, raise_exception=True
|
|
)
|
|
|
|
return f"Mail {template.name} sent to {auth_partner.login}"
|
|
|
|
def _generate_token(self, action, auth_partner, expiration_delta, key_salt=""):
|
|
# We need to sudo here as secret_key is a protected field
|
|
self = self.sudo()
|
|
return jwt.encode(
|
|
{
|
|
"exp": datetime.now(tz=timezone.utc) + expiration_delta,
|
|
"aud": str(self.id),
|
|
"action": action,
|
|
"ap": auth_partner.id,
|
|
},
|
|
self.secret_key + key_salt,
|
|
algorithm="HS256",
|
|
)
|
|
|
|
def _decode_token(
|
|
self,
|
|
token,
|
|
action,
|
|
key_salt=None,
|
|
):
|
|
# We need to sudo here as secret_key is a protected field
|
|
self = self.sudo()
|
|
key = self.secret_key
|
|
if key_salt:
|
|
try:
|
|
obj = jwt.decode(
|
|
token, algorithms=["HS256"], options={"verify_signature": False}
|
|
)
|
|
except jwt.PyJWTError as e:
|
|
raise UserError(_("Invalid Token")) from e
|
|
probable_auth_partner = self.env["auth.partner"].browse(obj["ap"])
|
|
if not probable_auth_partner:
|
|
raise UserError(_("Invalid Token"))
|
|
key += key_salt(probable_auth_partner)
|
|
|
|
try:
|
|
obj = jwt.decode(
|
|
token,
|
|
key,
|
|
audience=str(self.id),
|
|
options={"require": ["exp", "aud", "ap", "action"]},
|
|
algorithms=["HS256"],
|
|
)
|
|
except jwt.PyJWTError as e:
|
|
raise UserError(_("Invalid Token")) from e
|
|
|
|
auth_partner = self.env["auth.partner"].browse(obj["ap"])
|
|
|
|
if (
|
|
obj["action"] != action
|
|
or not auth_partner
|
|
or auth_partner.directory_id != self
|
|
):
|
|
raise UserError(_("Invalid token"))
|
|
|
|
return auth_partner
|
|
|
|
@property
|
|
def _server_env_fields(self):
|
|
return {"secret_key": {}}
|