Skip to content

Commit f997464

Browse files
Kev-Rochetoita86
authored andcommitted
[IMP] add improvements from V17
1 parent dbbec19 commit f997464

File tree

17 files changed

+449
-65
lines changed

17 files changed

+449
-65
lines changed

impersonate_login/README.rst

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@ Impersonate Login
2828

2929
|badge1| |badge2| |badge3| |badge4| |badge5|
3030

31-
This module allows to login as another user.
32-
In the chatter, the user who is logged as another user is displayed.
33-
The mails and messages are sent from the orignal user.
34-
A table diplays the impersonated logins in technical.
35-
The user can return to his own user by clicking on the button "Return to my user".
36-
This module is very useful for the support team.
37-
An alternative module will be auth_admin_passkey.
31+
This module allows one user (for example, a member of the support team)
32+
to log in as another user. The impersonation session can be exited by
33+
clicking on the button "Back to Original User".
34+
35+
To ensure that any abuse of this feature will not go unnoticed, the
36+
following measures are in place:
37+
38+
- In the chatter, it is displayed who is the user that is logged as
39+
another user.
40+
- Mails and messages are sent from the original user.
41+
- Impersonated logins are logged and can be consulted through the
42+
Settings -> Technical menu.
43+
-
44+
45+
There is an alternative module to allow logins as another user
46+
(auth_admin_passkey), but it does not support these security mechanisms.
3847

3948
**Table of contents**
4049

@@ -44,8 +53,9 @@ An alternative module will be auth_admin_passkey.
4453
Usage
4554
=====
4655

47-
1. On the top right corner, click my user and "switch login"
48-
2. Same place to "return to my login"
56+
The impersonating user must belong to group "Impersonate Users".
57+
- In the menu that is displayed when clicking on the user avatar on the top right corner, or in the res.users list, click "Switch Login" toi mpersonate another user.
58+
- On the top-right corner, the button "Back to Original User" is displayed in case the current user is being impersonated.
4959

5060
Bug Tracker
5161
===========
@@ -68,7 +78,9 @@ Authors
6878
Contributors
6979
~~~~~~~~~~~~
7080

71-
* Kévin Roche <[email protected]>
81+
- Kévin Roche <[email protected]>
82+
- [360ERP](https://www.360erp.com):
83+
- Andrea Stirpe
7284

7385
Maintainers
7486
~~~~~~~~~~~

impersonate_login/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from . import models
2+
from .hooks import pre_init_hook

impersonate_login/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@
2727
"qweb": [
2828
"static/src/xml/user_menu.xml",
2929
],
30+
"pre_init_hook": "pre_init_hook",
3031
}

impersonate_login/hooks.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2024 360ERP (<https://www.360erp.com>)
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
import logging
5+
6+
7+
def pre_init_hook(cr):
8+
"""
9+
Pre-create the impersonated_author_id column in the mail_message table
10+
to prevent the ORM from invoking its compute method on a large volume
11+
of existing mail messages.
12+
"""
13+
logger = logging.getLogger(__name__)
14+
logger.info("Add mail_message.impersonated_author_id column if not exists")
15+
cr.execute(
16+
"ALTER TABLE mail_message "
17+
"ADD COLUMN IF NOT EXISTS "
18+
"impersonated_author_id INTEGER"
19+
)

impersonate_login/models/impersonate_log.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class ImpersonateLog(models.Model):
1111
_description = "Impersonate Logs"
1212

1313
user_id = fields.Many2one(
14-
comodel_name="res.partner",
14+
comodel_name="res.users",
1515
string="User",
1616
)
1717
impersonated_partner_id = fields.Many2one(

impersonate_login/models/mail_message.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,23 @@
44

55
from odoo import _, api, fields, models
66
from odoo.http import request
7+
from odoo.tools import html_escape
78

89

910
class Message(models.Model):
1011
_inherit = "mail.message"
1112

1213
impersonated_author_id = fields.Many2one(
1314
comodel_name="res.partner",
14-
string="Impersonated Author",
1515
compute="_compute_impersonated_author_id",
1616
store=True,
1717
)
1818

1919
body = fields.Html(
2020
compute="_compute_message_body",
21+
inverse="_inverse_message_body",
2122
store=True,
23+
readonly=False,
2224
)
2325

2426
@api.depends("author_id")
@@ -45,8 +47,33 @@ def _compute_message_body(self):
4547
current_partner = (
4648
self.env["res.users"].browse(request.session.uid).partner_id
4749
)
48-
additional_info = _(f"Logged as {current_partner.name}")
50+
additional_info = _("Logged in as {}").format(
51+
html_escape(current_partner.name)
52+
)
4953
if rec.body and additional_info:
5054
rec.body = f"<b>{additional_info}</b><br/>{rec.body}"
5155
else:
52-
rec.body = additional_info
56+
rec.body = rec.body
57+
58+
def _inverse_message_body(self):
59+
for rec in self:
60+
additional_info = ""
61+
if (
62+
request
63+
and request.session.impersonate_from_uid
64+
and rec.impersonated_author_id
65+
):
66+
current_partner = (
67+
self.env["res.users"].browse(request.session.uid).partner_id
68+
)
69+
additional_info = _("Logged in as {}").format(
70+
html_escape(current_partner.name)
71+
)
72+
if additional_info:
73+
start_with = f"<b>{additional_info}</b><br/>"
74+
if rec.body and rec.body.startswith(start_with):
75+
rec.body = rec.body
76+
else:
77+
rec.body = f"{start_with}{rec.body}"
78+
else:
79+
rec.body = rec.body

impersonate_login/models/mail_thread.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ class MailThread(models.AbstractModel):
1212
def _message_compute_author(
1313
self, author_id=None, email_from=None, raise_exception=True
1414
):
15-
if (
16-
request
17-
and request.session.impersonate_from_uid
18-
and author_id in [request.session.uid, None]
19-
):
20-
author = (
21-
self.env["res.users"]
22-
.browse(request.session.impersonate_from_uid)
23-
.partner_id
24-
)
25-
email = author.email_formatted
26-
return author.id, email
27-
else:
28-
return super()._message_compute_author(
29-
author_id, email_from, raise_exception
30-
)
15+
if request and request.session.impersonate_from_uid:
16+
author = self.env["res.users"].browse(request.session.uid).partner_id
17+
if author_id == author.id or author_id is None:
18+
impersonate_from_author = (
19+
self.env["res.users"]
20+
.browse(request.session.impersonate_from_uid)
21+
.partner_id
22+
)
23+
email = impersonate_from_author.email_formatted
24+
return impersonate_from_author.id, email
25+
26+
return super()._message_compute_author(
27+
author_id,
28+
email_from,
29+
raise_exception,
30+
)

impersonate_login/models/model.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,21 @@ class BaseModel(models.AbstractModel):
1212
@api.model_create_multi
1313
def _create(self, data_list):
1414
res = super()._create(data_list)
15-
if request and request.session.impersonate_from_uid:
15+
if (
16+
request
17+
and request.session.impersonate_from_uid
18+
and "create_uid" in self._fields
19+
):
1620
for rec in res:
17-
rec.create_uid = request.session.impersonate_from_uid
21+
rec["create_uid"] = request.session.impersonate_from_uid
1822
return res
1923

2024
def write(self, vals):
2125
res = super().write(vals)
22-
if request and request.session.impersonate_from_uid:
23-
self.write_uid = request.session.impersonate_from_uid
26+
if (
27+
request
28+
and request.session.impersonate_from_uid
29+
and "write_uid" in self._fields
30+
):
31+
self._fields["write_uid"].write(self, request.session.impersonate_from_uid)
2432
return res

impersonate_login/models/res_users.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ def _is_impersonate_user(self):
2525
def impersonate_login(self):
2626
if request:
2727
if request.session.impersonate_from_uid:
28-
raise UserError(_("You are already Logged as another user."))
28+
if self.id == request.session.impersonate_from_uid:
29+
return self.back_to_origin_login()
30+
else:
31+
raise UserError(_("You are already Logged as another user."))
2932
if self.id == request.session.uid:
3033
raise UserError(_("It's you."))
3134
if request.env.user._is_impersonate_user():
@@ -37,9 +40,7 @@ def impersonate_login(self):
3740
.sudo()
3841
.create(
3942
{
40-
"user_id": self.env["res.users"]
41-
.browse(self._uid)
42-
.partner_id.id,
43+
"user_id": self._uid,
4344
"impersonated_partner_id": self.env["res.users"]
4445
.browse(target_uid)
4546
.partner_id.id,
@@ -52,16 +53,34 @@ def impersonate_login(self):
5253
f"IMPERSONATE: {self._get_partner_name(self._uid)} "
5354
f"Login as {self._get_partner_name(self.id)}"
5455
)
55-
56+
# invalidate session token cache as we've changed the uid
5657
request.env["res.users"].clear_caches()
5758
request.session.session_token = security.compute_session_token(
5859
request.session, request.env
5960
)
61+
62+
# reload the client;
6063
return {
6164
"type": "ir.actions.client",
6265
"tag": "reload",
6366
}
6467

68+
@api.model
69+
def action_impersonate_login(self):
70+
if request:
71+
from_uid = request.session.impersonate_from_uid
72+
if not from_uid:
73+
action = self.env["ir.actions.act_window"]._for_xml_id(
74+
"base.action_res_users"
75+
)
76+
action["views"] = [[self.env.ref("base.view_users_tree").id, "tree"]]
77+
action["domain"] = [
78+
("id", "!=", self.env.user.id),
79+
("share", "=", False),
80+
]
81+
action["target"] = "new"
82+
return action
83+
6584
@api.model
6685
def back_to_origin_login(self):
6786
if request:
@@ -75,6 +94,7 @@ def back_to_origin_login(self):
7594
"date_end": fields.datetime.now(),
7695
}
7796
)
97+
# invalidate session token cache as we've changed the uid
7898
request.env["res.users"].clear_caches()
7999
request.session.impersonate_from_uid = False
80100
request.session.impersonate_log_id = False
@@ -85,3 +105,9 @@ def back_to_origin_login(self):
85105
f"IMPERSONATE: {self._get_partner_name(from_uid)} "
86106
f"Logout as {self._get_partner_name(self._uid)}"
87107
)
108+
109+
# reload the client;
110+
return {
111+
"type": "ir.actions.client",
112+
"tag": "reload",
113+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
* Kévin Roche <[email protected]>
1+
- Kévin Roche <[email protected]>
2+
- [360ERP](https://www.360erp.com):
3+
- Andrea Stirpe

0 commit comments

Comments
 (0)