Initial commit: OCA Server Auth packages (29 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:06 +02:00
commit 3ed80311c4
1325 changed files with 127292 additions and 0 deletions

View file

@ -0,0 +1,47 @@
# Impersonate Login
Odoo addon: impersonate_login
## Installation
```bash
pip install odoo-bringout-oca-server-auth-impersonate_login
```
## Dependencies
This addon depends on:
- web
- mail
## Manifest Information
- **Name**: Impersonate Login
- **Version**: 16.0.1.0.0
- **Category**: Tools
- **License**: AGPL-3
- **Installable**: True
## Source
Based on [OCA/server-auth](https://github.com/OCA/server-auth) branch 16.0, addon `impersonate_login`.
## License
This package maintains the original AGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Impersonate_login Module - impersonate_login
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for impersonate_login. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,6 @@
# Dependencies
This addon depends on:
- [web](../../odoo-bringout-oca-ocb-web)
- [mail](../../odoo-bringout-oca-ocb-mail)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon impersonate_login or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-server-auth-impersonate_login"
# or
uv pip install odoo-bringout-oca-server-auth-impersonate_login"
```

View file

@ -0,0 +1,17 @@
# Models
Detected core models and extensions in impersonate_login.
```mermaid
classDiagram
class impersonate_log
class base
class ir_http
class mail_message
class mail_thread
class res_users
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: impersonate_login. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon impersonate_login
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,42 @@
# Security
Access control and security definitions in impersonate_login.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../impersonate_login/security/ir.model.access.csv)**
- 1 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[group.xml](../impersonate_login/security/group.xml)**
- 1 security groups defined
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[group.xml](../impersonate_login/security/group.xml)**
- Security groups, categories, and XML-based rules
- **[ir.model.access.csv](../impersonate_login/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon impersonate_login
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,116 @@
=================
Impersonate Login
=================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4875867f60d80f01c7bb74137a9f9bbdc0dceffde3bd47d96af9d897cd8de1f6
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github
:target: https://github.com/OCA/server-auth/tree/16.0/impersonate_login
:alt: OCA/server-auth
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-impersonate_login
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows one user (for example, a member of the support team)
to log in as another user. The impersonation session can be exited by
clicking on the button "Back to Original User".
To ensure that any abuse of this feature will not go unnoticed, the
following measures are in place:
- In the chatter, it is displayed who is the user that is logged as
another user.
- Mails and messages are sent from the original user.
- Impersonated logins are logged and can be consulted through the
Settings -> Technical menu.
-
There is an alternative module to allow logins as another user
(auth_admin_passkey), but it does not support these security mechanisms.
**Table of contents**
.. contents::
:local:
Configuration
=============
The impersonating user must belong to group "Impersonate Users".
Usage
=====
1. 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" to
impersonate another user.
2. On the top-right corner, the button "Back to Original User" is
displayed in case the current user is being impersonated.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20impersonate_login%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Akretion
Contributors
------------
- Kévin Roche <kevin.roche@akretion.com>
- `360ERP <https://www.360erp.com>`__:
- Andrea Stirpe
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-Kev-Roche| image:: https://github.com/Kev-Roche.png?size=40px
:target: https://github.com/Kev-Roche
:alt: Kev-Roche
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Kev-Roche|
This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/16.0/impersonate_login>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,2 @@
from . import models
from .hooks import pre_init_hook

View file

@ -0,0 +1,32 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Impersonate Login",
"summary": "tools",
"version": "16.0.1.0.0",
"category": "Tools",
"website": "https://github.com/OCA/server-auth",
"author": "Akretion, Odoo Community Association (OCA)",
"maintainers": ["Kev-Roche"],
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
"web",
"mail",
],
"data": [
"security/group.xml",
"security/ir.model.access.csv",
"views/res_users.xml",
"views/impersonate_log.xml",
],
"assets": {
"web.assets_backend": [
"impersonate_login/static/src/js/user_menu.esm.js",
],
},
"pre_init_hook": "pre_init_hook",
}

View file

@ -0,0 +1,19 @@
# Copyright 2024 360ERP (<https://www.360erp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
def pre_init_hook(cr):
"""
Pre-create the impersonated_author_id column in the mail_message table
to prevent the ORM from invoking its compute method on a large volume
of existing mail messages.
"""
logger = logging.getLogger(__name__)
logger.info("Add mail_message.impersonated_author_id column if not exists")
cr.execute(
"ALTER TABLE mail_message "
"ADD COLUMN IF NOT EXISTS "
"impersonated_author_id INTEGER"
)

View file

@ -0,0 +1,159 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * impersonate_login
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: impersonate_login
#. odoo-javascript
#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0
#, python-format
msgid "Back to Original User"
msgstr "Povratak na originalnog korisnika"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_base
msgid "Base"
msgstr "Osnova"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__body
#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__body
msgid "Contents"
msgstr "Sadržaji"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_mail_thread
msgid "Email Thread"
msgstr "Nit e-pošte"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_end
msgid "End Date"
msgstr "Datum završetka"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_ir_http
msgid "HTTP Routing"
msgstr "HTTP usmjeravanje"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__id
msgid "ID"
msgstr "ID"
#. module: impersonate_login
#: model:ir.actions.act_window,name:impersonate_login.impersonate_log_action
msgid "Impersonate Login Logs"
msgstr "Zapisnici prijave oponašanja"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_impersonate_log
msgid "Impersonate Logs"
msgstr "Zapisnici oponašanja"
#. module: impersonate_login
#: model:res.groups,name:impersonate_login.group_impersonate_login
msgid "Impersonate Users"
msgstr "Oponašaj korisnike"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__impersonated_author_id
#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__impersonated_author_id
msgid "Impersonated Author"
msgstr "Oponašani autor"
#. module: impersonate_login
#: model:ir.ui.menu,name:impersonate_login.menu_impersonate_log
msgid "Impersonated Logs"
msgstr "Oponašani zapisnici"
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/res_users.py:0
#, python-format
msgid "It's you."
msgstr "To ste vi."
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__impersonated_partner_id
msgid "Logged as"
msgstr "Prijavljen kao"
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/mail_message.py:0
#: code:addons/impersonate_login/models/mail_message.py:0
#, python-format
msgid "Logged in as {}"
msgstr "Prijavljen kao {}"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_mail_message
msgid "Message"
msgstr "Poruka"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_start
msgid "Start Date"
msgstr "Početni datum"
#. module: impersonate_login
#. odoo-javascript
#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0
#: model_terms:ir.ui.view,arch_db:impersonate_login.impersonate_res_users_tree
#, python-format
msgid "Switch Login"
msgstr "Prebaci prijavu"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_res_users
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__user_id
msgid "User"
msgstr "Korisnik"
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/res_users.py:0
#, python-format
msgid "You are already Logged as another user."
msgstr "Već ste prijavljeni kao drugi korisnik."

View file

@ -0,0 +1,159 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * impersonate_login
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: impersonate_login
#. odoo-javascript
#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0
#, python-format
msgid "Back to Original User"
msgstr ""
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_base
msgid "Base"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__body
#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__body
msgid "Contents"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_uid
msgid "Created by"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_date
msgid "Created on"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__display_name
msgid "Display Name"
msgstr ""
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_mail_thread
msgid "Email Thread"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_end
msgid "End Date"
msgstr ""
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_ir_http
msgid "HTTP Routing"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__id
msgid "ID"
msgstr ""
#. module: impersonate_login
#: model:ir.actions.act_window,name:impersonate_login.impersonate_log_action
msgid "Impersonate Login Logs"
msgstr ""
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_impersonate_log
msgid "Impersonate Logs"
msgstr ""
#. module: impersonate_login
#: model:res.groups,name:impersonate_login.group_impersonate_login
msgid "Impersonate Users"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__impersonated_author_id
#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__impersonated_author_id
msgid "Impersonated Author"
msgstr ""
#. module: impersonate_login
#: model:ir.ui.menu,name:impersonate_login.menu_impersonate_log
msgid "Impersonated Logs"
msgstr ""
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/res_users.py:0
#, python-format
msgid "It's you."
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log____last_update
msgid "Last Modified on"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_uid
msgid "Last Updated by"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_date
msgid "Last Updated on"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__impersonated_partner_id
msgid "Logged as"
msgstr ""
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/mail_message.py:0
#: code:addons/impersonate_login/models/mail_message.py:0
#, python-format
msgid "Logged in as {}"
msgstr ""
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_mail_message
msgid "Message"
msgstr ""
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_start
msgid "Start Date"
msgstr ""
#. module: impersonate_login
#. odoo-javascript
#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0
#: model_terms:ir.ui.view,arch_db:impersonate_login.impersonate_res_users_tree
#, python-format
msgid "Switch Login"
msgstr ""
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_res_users
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__user_id
msgid "User"
msgstr ""
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/res_users.py:0
#, python-format
msgid "You are already Logged as another user."
msgstr ""

View file

@ -0,0 +1,162 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * impersonate_login
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-09-05 09:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: impersonate_login
#. odoo-javascript
#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0
#, python-format
msgid "Back to Original User"
msgstr "Riporta a utente originale"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_base
msgid "Base"
msgstr "Base"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__body
#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__body
msgid "Contents"
msgstr "Contenuti"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__create_date
msgid "Created on"
msgstr "Creato il"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_mail_thread
msgid "Email Thread"
msgstr "Discussione e-mail"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_end
msgid "End Date"
msgstr "Data fine"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_ir_http
msgid "HTTP Routing"
msgstr "Instradamento HTTP"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__id
msgid "ID"
msgstr "ID"
#. module: impersonate_login
#: model:ir.actions.act_window,name:impersonate_login.impersonate_log_action
msgid "Impersonate Login Logs"
msgstr "Imita registri di accesso"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_impersonate_log
msgid "Impersonate Logs"
msgstr "Imita registri"
#. module: impersonate_login
#: model:res.groups,name:impersonate_login.group_impersonate_login
msgid "Impersonate Users"
msgstr "Imita utenti"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_mail_mail__impersonated_author_id
#: model:ir.model.fields,field_description:impersonate_login.field_mail_message__impersonated_author_id
msgid "Impersonated Author"
msgstr "Imita autore"
#. module: impersonate_login
#: model:ir.ui.menu,name:impersonate_login.menu_impersonate_log
msgid "Impersonated Logs"
msgstr "Imita registri"
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/res_users.py:0
#, python-format
msgid "It's you."
msgstr "Sei tu."
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__impersonated_partner_id
msgid "Logged as"
msgstr "Registrato come"
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/mail_message.py:0
#: code:addons/impersonate_login/models/mail_message.py:0
#, python-format
msgid "Logged in as {}"
msgstr "Registrato come {}"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_mail_message
msgid "Message"
msgstr "Messaggio"
#. module: impersonate_login
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__date_start
msgid "Start Date"
msgstr "Data inizio"
#. module: impersonate_login
#. odoo-javascript
#: code:addons/impersonate_login/static/src/js/user_menu.esm.js:0
#: model_terms:ir.ui.view,arch_db:impersonate_login.impersonate_res_users_tree
#, python-format
msgid "Switch Login"
msgstr "Scambia accesso"
#. module: impersonate_login
#: model:ir.model,name:impersonate_login.model_res_users
#: model:ir.model.fields,field_description:impersonate_login.field_impersonate_log__user_id
msgid "User"
msgstr "Utente"
#. module: impersonate_login
#. odoo-python
#: code:addons/impersonate_login/models/res_users.py:0
#, python-format
msgid "You are already Logged as another user."
msgstr "Si è già registrati come altro utente."

View file

@ -0,0 +1,6 @@
from . import res_users
from . import ir_http
from . import mail_thread
from . import mail_message
from . import impersonate_log
from . import model

View file

@ -0,0 +1,25 @@
# Copyright (C) 2024 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ImpersonateLog(models.Model):
_name = "impersonate.log"
_description = "Impersonate Logs"
user_id = fields.Many2one(
comodel_name="res.users",
)
impersonated_partner_id = fields.Many2one(
comodel_name="res.partner",
string="Logged as",
)
date_start = fields.Datetime(
string="Start Date",
)
date_end = fields.Datetime(
string="End Date",
)

View file

@ -0,0 +1,20 @@
# Copyright (C) 2024 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.http import request
class Http(models.AbstractModel):
_inherit = "ir.http"
def session_info(self):
session_info = super().session_info()
session_info.update(
{
"is_impersonate_user": request.env.user._is_impersonate_user(),
"impersonate_from_uid": request.session.impersonate_from_uid,
}
)
return session_info

View file

@ -0,0 +1,79 @@
# Copyright (C) 2024 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.http import request
from odoo.tools import html_escape
class Message(models.Model):
_inherit = "mail.message"
impersonated_author_id = fields.Many2one(
comodel_name="res.partner",
compute="_compute_impersonated_author_id",
store=True,
)
body = fields.Html(
compute="_compute_message_body",
inverse="_inverse_message_body",
store=True,
readonly=False,
)
@api.depends("author_id")
def _compute_impersonated_author_id(self):
for rec in self:
if request and request.session.impersonate_from_uid:
rec.impersonated_author_id = (
self.env["res.users"]
.browse(request.session.impersonate_from_uid)
.partner_id.id
)
else:
rec.impersonated_author_id = False
@api.depends("author_id", "impersonated_author_id")
def _compute_message_body(self):
for rec in self:
additional_info = ""
if (
request
and request.session.impersonate_from_uid
and rec.impersonated_author_id
):
current_partner = (
self.env["res.users"].browse(request.session.uid).partner_id
)
additional_info = _("Logged in as {}").format(
html_escape(current_partner.name)
)
if rec.body and additional_info:
rec.body = f"<b>{additional_info}</b><br/>{rec.body}"
else:
rec.body = rec.body
def _inverse_message_body(self):
for rec in self:
additional_info = ""
if (
request
and request.session.impersonate_from_uid
and rec.impersonated_author_id
):
current_partner = (
self.env["res.users"].browse(request.session.uid).partner_id
)
additional_info = _("Logged in as {}").format(
html_escape(current_partner.name)
)
if additional_info:
start_with = f"<b>{additional_info}</b><br/>"
if rec.body and rec.body.startswith(start_with):
rec.body = rec.body
else:
rec.body = f"{start_with}{rec.body}"
else:
rec.body = rec.body

View file

@ -0,0 +1,30 @@
# Copyright (C) 2024 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.http import request
class MailThread(models.AbstractModel):
_inherit = "mail.thread"
def _message_compute_author(
self, author_id=None, email_from=None, raise_on_email=True
):
if request and request.session.impersonate_from_uid:
author = self.env["res.users"].browse(request.session.uid).partner_id
if author_id == author.id or author_id is None:
impersonate_from_author = (
self.env["res.users"]
.browse(request.session.impersonate_from_uid)
.partner_id
)
email = impersonate_from_author.email_formatted
return impersonate_from_author.id, email
return super()._message_compute_author(
author_id=author_id,
email_from=email_from,
raise_on_email=raise_on_email,
)

View file

@ -0,0 +1,32 @@
# Copyright (C) 2024 Akretion (<http://www.akretion.com>).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.http import request
class BaseModel(models.AbstractModel):
_inherit = "base"
def _prepare_create_values(self, vals_list):
result_vals_list = super()._prepare_create_values(vals_list)
if (
request
and request.session.impersonate_from_uid
and "create_uid" in self._fields
):
for vals in result_vals_list:
vals["create_uid"] = request.session.impersonate_from_uid
return result_vals_list
def write(self, vals):
"""Overwrite the write_uid with the impersonating user"""
res = super().write(vals)
if (
request
and request.session.impersonate_from_uid
and "write_uid" in self._fields
):
self._fields["write_uid"].write(self, request.session.impersonate_from_uid)
return res

View file

@ -0,0 +1,120 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Kévin Roche <kevin.roche@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.http import request
from odoo.service import security
logger = logging.getLogger(__name__)
class Users(models.Model):
_inherit = "res.users"
def _get_partner_name(self, user_id):
return self.env["res.users"].browse(user_id).partner_id.name
def _is_impersonate_user(self):
self.ensure_one()
return self.has_group("impersonate_login.group_impersonate_login")
def impersonate_login(self):
if request:
if request.session.impersonate_from_uid:
if self.id == request.session.impersonate_from_uid:
return self.back_to_origin_login()
else:
raise UserError(_("You are already Logged as another user."))
if self.id == request.session.uid:
raise UserError(_("It's you."))
if (
request.env.user._is_impersonate_user()
and request.env.user._is_internal()
):
target_uid = self.id
request.session.impersonate_from_uid = self._uid
request.session.uid = target_uid
impersonate_log = (
self.env["impersonate.log"]
.sudo()
.create(
{
"user_id": self._uid,
"impersonated_partner_id": self.env["res.users"]
.browse(target_uid)
.partner_id.id,
"date_start": fields.datetime.now(),
}
)
)
request.session.impersonate_log_id = impersonate_log.id
logger.info(
f"IMPERSONATE: {self._get_partner_name(self._uid)} "
f"Login as {self._get_partner_name(self.id)}"
)
# invalidate session token cache as we've changed the uid
request.env["res.users"].clear_caches()
request.session.session_token = security.compute_session_token(
request.session, request.env
)
# reload the client; open the first available root menu
menu = self.env["ir.ui.menu"].search([("parent_id", "=", False)])[:1]
return {
"type": "ir.actions.client",
"tag": "reload",
"params": {"menu_id": menu.id},
}
@api.model
def action_impersonate_login(self):
if request:
from_uid = request.session.impersonate_from_uid
if not from_uid:
action = self.env["ir.actions.act_window"]._for_xml_id(
"base.action_res_users"
)
action["views"] = [[self.env.ref("base.view_users_tree").id, "tree"]]
action["domain"] = [
("id", "!=", self.env.user.id),
("share", "=", False),
]
action["target"] = "new"
return action
@api.model
def back_to_origin_login(self):
if request:
from_uid = request.session.impersonate_from_uid
if from_uid:
request.session.uid = from_uid
self.env["impersonate.log"].sudo().browse(
request.session.impersonate_log_id
).write(
{
"date_end": fields.datetime.now(),
}
)
# invalidate session token cache as we've changed the uid
request.env["res.users"].clear_caches()
request.session.impersonate_from_uid = False
request.session.impersonate_log_id = False
request.session.session_token = security.compute_session_token(
request.session, request.env
)
logger.info(
f"IMPERSONATE: {self._get_partner_name(from_uid)} "
f"Logout as {self._get_partner_name(self._uid)}"
)
# reload the client; open the first available root menu
menu = self.env["ir.ui.menu"].search([("parent_id", "=", False)])[:1]
return {
"type": "ir.actions.client",
"tag": "reload",
"params": {"menu_id": menu.id},
}

View file

@ -0,0 +1 @@
The impersonating user must belong to group "Impersonate Users".

View file

@ -0,0 +1,3 @@
- Kévin Roche \<<kevin.roche@akretion.com>\>
- [360ERP](https://www.360erp.com):
- Andrea Stirpe

View file

@ -0,0 +1,11 @@
This module allows one user (for example, a member of the support team) to log in as another user.
The impersonation session can be exited by clicking on the button "Back to Original User".
To ensure that any abuse of this feature will not go unnoticed, the following measures are in place:
* In the chatter, it is displayed who is the user that is logged as another user.
* Mails and messages are sent from the original user.
* Impersonated logins are logged and can be consulted through the Settings -> Technical menu.
*
There is an alternative module to allow logins as another user (auth_admin_passkey),
but it does not support these security mechanisms.

View file

@ -0,0 +1,4 @@
1. 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" to impersonate another user.
2. On the top-right corner, the button "Back to Original User" is displayed in case the current
user is being impersonated.

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright (C) 2024 Akretion (<http://www.akretion.com>).
@author Kévin Roche <kevin.roche@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="res.groups" id="group_impersonate_login">
<field name="name">Impersonate Users</field>
<field
name="users"
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
</record>
</odoo>

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_impersonate_log,impersonate logs,model_impersonate_log,base.group_user,1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_impersonate_log impersonate logs model_impersonate_log base.group_user 1 1 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,459 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Impersonate Login</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="impersonate-login">
<h1 class="title">Impersonate Login</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4875867f60d80f01c7bb74137a9f9bbdc0dceffde3bd47d96af9d897cd8de1f6
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-auth/tree/16.0/impersonate_login"><img alt="OCA/server-auth" src="https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-impersonate_login"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-auth&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows one user (for example, a member of the support team)
to log in as another user. The impersonation session can be exited by
clicking on the button “Back to Original User”.</p>
<p>To ensure that any abuse of this feature will not go unnoticed, the
following measures are in place:</p>
<ul class="simple">
<li>In the chatter, it is displayed who is the user that is logged as
another user.</li>
<li>Mails and messages are sent from the original user.</li>
<li>Impersonated logins are logged and can be consulted through the
Settings -&gt; Technical menu.</li>
<li></li>
</ul>
<p>There is an alternative module to allow logins as another user
(auth_admin_passkey), but it does not support these security mechanisms.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>The impersonating user must belong to group “Impersonate Users”.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
<ol class="arabic simple">
<li>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” to
impersonate another user.</li>
<li>On the top-right corner, the button “Back to Original User” is
displayed in case the current user is being impersonated.</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-auth/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-auth/issues/new?body=module:%20impersonate_login%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li>Kévin Roche &lt;<a class="reference external" href="mailto:kevin.roche&#64;akretion.com">kevin.roche&#64;akretion.com</a>&gt;</li>
<li><a class="reference external" href="https://www.360erp.com">360ERP</a>:<ul>
<li>Andrea Stirpe</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/Kev-Roche"><img alt="Kev-Roche" src="https://github.com/Kev-Roche.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-auth/tree/16.0/impersonate_login">OCA/server-auth</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,45 @@
/** @odoo-module **/
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import {_t} from "@web/core/l10n/translation";
import {registry} from "@web/core/registry";
import {session} from "@web/session";
export function impersonateLoginItem(env) {
return {
type: "item",
id: "impersonate_login",
description: _t("Switch Login"),
hide: session.impersonate_from_uid || !session.is_impersonate_user,
callback: async function () {
const actionImpersonateLogin = await env.services.orm.call(
"res.users",
"action_impersonate_login"
);
env.services.action.doAction(actionImpersonateLogin);
},
sequence: 55,
};
}
export function impersonateBackLoginItem(env) {
return {
type: "item",
id: "impersonate_back",
description: _t("Back to Original User"),
hide: !session.impersonate_from_uid,
callback: async function () {
const actionBackToOriginLogin = await env.services.orm.call(
"res.users",
"back_to_origin_login"
);
env.services.action.doAction(actionBackToOriginLogin);
},
sequence: 55,
};
}
registry
.category("user_menuitems")
.add("impersonate_login", impersonateLoginItem, {force: true})
.add("impersonate_back", impersonateBackLoginItem, {force: true});

View file

@ -0,0 +1 @@
from . import test_impersonate_login

View file

@ -0,0 +1,260 @@
# Copyright 2024 360ERP (<https://www.360erp.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import json
from uuid import uuid4
from odoo.tests import HttpCase, tagged
from odoo.tools import mute_logger
@tagged("post_install", "-at_install")
class TestImpersonateLogin(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.admin_user = cls.env.ref("base.user_admin")
cls.demo_user = cls.env.ref("base.user_demo")
def _impersonate_user(self, user):
response = self.url_open(
"/web/dataset/call_button",
data=json.dumps(
{
"params": {
"model": "res.users",
"method": "impersonate_login",
"args": [user.id],
"kwargs": {},
},
}
),
headers={"Content-Type": "application/json"},
)
self.assertEqual(response.status_code, 200)
return response.json()
def _action_impersonate_login(self):
response = self.url_open(
"/web/dataset/call_button",
data=json.dumps(
{
"params": {
"model": "res.users",
"method": "action_impersonate_login",
"args": [],
"kwargs": {},
},
}
),
headers={"Content-Type": "application/json"},
)
self.assertEqual(response.status_code, 200)
return response.json()
def _get_session_info(self):
response = self.url_open(
"/web/session/get_session_info",
data=json.dumps(dict(jsonrpc="2.0", method="call", id=str(uuid4()))),
headers={"Content-Type": "application/json"},
)
self.assertEqual(response.status_code, 200)
return response.json()
def test_01_admin_impersonates_user_demo(self):
"""Admin user impersonates Demo user"""
# Login as admin
self.authenticate(user="admin", password="admin")
self.assertEqual(self.session.uid, self.admin_user.id)
# Check get_session_info()
data = self._get_session_info()
result = data["result"]
self.assertEqual(result["username"], self.admin_user.login)
self.assertTrue(result["is_system"])
self.assertTrue(result["is_admin"])
self.assertTrue(result["is_impersonate_user"])
self.assertFalse(result["impersonate_from_uid"])
# Switch Login button
data = self._action_impersonate_login()
result = data["result"]
self.assertEqual(result["target"], "new")
# Impersonate demo user
data = self._impersonate_user(self.demo_user)
result = data["result"]
self.assertEqual(result["tag"], "reload")
# Check get_session_info()
data = self._get_session_info()
result = data["result"]
self.assertEqual(result["username"], self.demo_user.login)
self.assertFalse(result["is_system"])
self.assertFalse(result["is_admin"])
self.assertFalse(result["is_impersonate_user"])
self.assertEqual(result["impersonate_from_uid"], self.admin_user.id)
# Check impersonate log
log1 = self.env["impersonate.log"].search([], order="id desc", limit=1)
self.assertTrue(log1.date_start)
self.assertFalse(log1.date_end)
# Impersonate demo user again: error
with mute_logger("odoo.http"):
data = self._impersonate_user(self.demo_user)
result = data["error"]
self.assertEqual(
result["data"]["message"], "You are already Logged as another user."
)
# Back to original user
data = self._impersonate_user(self.admin_user)
result = data["result"]
self.assertEqual(result["tag"], "reload")
# Check get_session_info()
data = self._get_session_info()
result = data["result"]
self.assertEqual(result["username"], self.admin_user.login)
self.assertTrue(result["is_system"])
self.assertTrue(result["is_admin"])
self.assertTrue(result["is_impersonate_user"])
self.assertFalse(result["impersonate_from_uid"])
# Check impersonate log
log2 = self.env["impersonate.log"].search([], order="id desc", limit=1)
self.assertEqual(log1, log2)
self.assertTrue(log1.date_start)
self.assertTrue(log1.date_end)
def test_02_user_demo_impersonates_admin(self):
"""Demo user impersonates Admin user"""
# Login as demo user
self.authenticate(user="demo", password="demo")
self.assertEqual(self.session.uid, self.demo_user.id)
# Check get_session_info()
data = self._get_session_info()
result = data["result"]
self.assertFalse(result["is_impersonate_user"])
self.assertFalse(result["impersonate_from_uid"])
# Impersonate demo user: is already current user
self.demo_user.groups_id += self.env.ref(
"impersonate_login.group_impersonate_login"
)
with mute_logger("odoo.http"):
data = self._impersonate_user(self.demo_user)
result = data["error"]
self.assertEqual(result["data"]["message"], "It's you.")
# Impersonate admin user
data = self._impersonate_user(self.admin_user)
result = data["result"]
self.assertEqual(result["tag"], "reload")
# Check get_session_info()
data = self._get_session_info()
result = data["result"]
self.assertEqual(result["username"], self.admin_user.login)
self.assertTrue(result["is_system"])
self.assertTrue(result["is_admin"])
self.assertTrue(result["is_impersonate_user"])
self.assertEqual(result["impersonate_from_uid"], self.demo_user.id)
# Impersonate admin user again: error
with mute_logger("odoo.http"):
data = self._impersonate_user(self.admin_user)
result = data["error"]
self.assertEqual(
result["data"]["message"], "You are already Logged as another user."
)
# Back to original user
data = self._impersonate_user(self.demo_user)
result = data["result"]
self.assertEqual(result["tag"], "reload")
# Check get_session_info()
data = self._get_session_info()
result = data["result"]
self.assertEqual(result["username"], self.demo_user.login)
self.assertFalse(result["is_system"])
self.assertFalse(result["is_admin"])
self.assertTrue(result["is_impersonate_user"])
self.assertFalse(result["impersonate_from_uid"])
def test_03_create_uid(self):
"""Check the create_uid of records created
during an impersonated session"""
# Login as admin
self.authenticate(user="admin", password="admin")
# Impersonate demo user and create a contact
self._impersonate_user(self.demo_user)
response = self.url_open(
"/web/dataset/call_kw/res.partner/create",
data=json.dumps(
{
"params": {
"model": "res.partner",
"method": "create",
"args": [
{
"name": "Contact123",
},
],
"kwargs": {},
},
}
),
headers={"Content-Type": "application/json"},
)
self.assertEqual(response.status_code, 200)
data = response.json()
contact_id = data["result"]
contact = self.env["res.partner"].browse(contact_id)
self.assertEqual(contact.name, "Contact123")
self.assertEqual(contact.create_uid, self.admin_user)
def test_04_write_uid(self):
"""Check the write_uid of records created
during an impersonated session"""
# Login as admin
self.authenticate(user="admin", password="admin")
# Create a contact
contact = self.env["res.partner"].create({"name": "ContactABC"})
# Impersonate demo user and modify a contact
self._impersonate_user(self.demo_user)
response = self.url_open(
"/web/dataset/call_kw/res.partner/write",
data=json.dumps(
{
"params": {
"model": "res.partner",
"method": "write",
"args": [
[contact.id],
{
"ref": "abc",
},
],
"kwargs": {},
},
}
),
headers={"Content-Type": "application/json"},
)
self.assertEqual(response.status_code, 200)
data = response.json()
result = data["result"]
self.assertEqual(result, True)
self.assertEqual(contact.ref, "abc")
self.assertEqual(contact.write_uid, self.admin_user)

View file

@ -0,0 +1,36 @@
<?xml version="1.0" ?>
<!--
Copyright 2024 Akretion
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="impersonate_log_tree" model="ir.ui.view">
<field name="name">impersonate.log.tree</field>
<field name="model">impersonate.log</field>
<field name="arch" type="xml">
<tree>
<field name="user_id" />
<field name="impersonated_partner_id" />
<field name="date_start" />
<field name="date_end" />
</tree>
</field>
</record>
<record id="impersonate_log_action" model="ir.actions.act_window">
<field name="name">Impersonate Login Logs</field>
<field name="res_model">impersonate.log</field>
<field name="view_id" ref="impersonate_log_tree" />
<field name="view_mode">tree</field>
</record>
<menuitem
id="menu_impersonate_log"
name="Impersonated Logs"
action="impersonate_log_action"
parent="base.menu_custom"
sequence="100"
/>
</odoo>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright (C) 2024 Akretion (<http://www.akretion.com>).
@author Kévin Roche <kevin.roche@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="impersonate_res_users_tree">
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_tree" />
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="name" position="before">
<button
string="Switch Login"
name="impersonate_login"
type="object"
class="btn btn-info"
groups="impersonate_login.group_impersonate_login"
/>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
[project]
name = "odoo-bringout-oca-server-auth-impersonate_login"
version = "16.0.0"
description = "Impersonate Login - tools"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-web>=16.0.0",
"odoo-bringout-oca-ocb-mail>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["impersonate_login"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]