Merge branch 'mig/18.0/auth_admin' into '18.0'

WIP: mig/18.0/auth_admin into 18.0

See merge request hibou-io/hibou-odoo/suite!1722
This commit is contained in:
Jared Kipe
2024-10-24 16:27:57 +00:00
11 changed files with 297 additions and 0 deletions

3
auth_admin/__init__.py Executable file
View File

@@ -0,0 +1,3 @@
from . import controllers
from . import models
from . import wizard

31
auth_admin/__manifest__.py Executable file
View File

@@ -0,0 +1,31 @@
{
'name': 'Auth Admin',
'author': 'Hibou Corp.',
'category': 'Hidden',
'version': '18.0.1.0.0',
'description':
"""
Login as other user
===================
Provides a way for an authenticated user, with certain permissions, to login as a different user.
Can also create a URL that logs in as that user.
Out of the box, only allows you to generate a login for an 'External User', e.g. portal users.
*2017-11-15* New button to generate the login on the Portal User Wizard (Action on Contact)
Added the option to copy the Force Login URL by simply clicking the Copy widget in the Portal User Wizard.
""",
'depends': [
'base',
'website',
'portal',
],
'auto_install': False,
'data': [
'views/res_users.xml',
'wizard/portal_wizard_views.xml',
],
}

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import main

42
auth_admin/controllers/main.py Executable file
View File

@@ -0,0 +1,42 @@
from odoo import http, exceptions
from ..models.res_users import check_admin_auth_login
from logging import getLogger
_logger = getLogger(__name__)
class AuthAdmin(http.Controller):
@http.route(['/auth_admin'], type='http', auth='public', website=True)
def index(self, *args, **post):
u = post.get('u')
e = post.get('e')
o = post.get('o')
h = post.get('h')
if not all([u, e, o, h]):
exceptions.Warning('Invalid Request')
u = str(u)
e = str(e)
o = str(o)
h = str(h)
try:
user = check_admin_auth_login(http.request.env, u, e, o, h)
# this is mostly like session finalize() as we skip MFA
env = http.request.env(user=user)
user_context = dict(env['res.users'].context_get())
http.request.session.should_rotate = True
http.request.session.update({
'login': user.login,
'uid': user.id,
'context': user_context,
'session_token': env.user._compute_session_token(http.request.session.sid),
})
return http.request.redirect('/my/home')
except (exceptions.Warning, ) as e:
return http.Response(e.message, status=400)

52
auth_admin/i18n/es.po Normal file
View File

@@ -0,0 +1,52 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_admin
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-11 22:01+0000\n"
"PO-Revision-Date: 2021-10-11 22:01+0000\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: auth_admin
#: model:ir.model.fields,field_description:auth_admin.field_portal_wizard_user__force_login_url
msgid "Force Login URL"
msgstr "Forzar URL de Login"
#. module: auth_admin
#: model_terms:ir.ui.view,arch_db:auth_admin.auth_admin_view_users_tree
msgid "Generate Login"
msgstr "Generar Login"
#. module: auth_admin
#: model_terms:ir.ui.view,arch_db:auth_admin.portal_wizard
msgid "Generate Login URL"
msgstr "Generar URL de Login"
#. module: auth_admin
#: model:ir.model,name:auth_admin.model_portal_wizard
msgid "Grant Portal Access"
msgstr "Otorgar Acceso al Portal "
#. module: auth_admin
#: code:addons/auth_admin/wizard/portal_wizard.py:0
#, python-format
msgid "Portal Access Management"
msgstr "Gestionar Acceso al Portal"
#. module: auth_admin
#: model:ir.model,name:auth_admin.model_portal_wizard_user
msgid "Portal User Config"
msgstr "Configuración del Usuario de Portal"
#. module: auth_admin
#: model:ir.model,name:auth_admin.model_res_users
msgid "Users"
msgstr "Usuarios"

1
auth_admin/models/__init__.py Executable file
View File

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

92
auth_admin/models/res_users.py Executable file
View File

@@ -0,0 +1,92 @@
from odoo import models, api, exceptions
from odoo.http import request
from datetime import datetime
from time import mktime
import hmac
from hashlib import sha256
from logging import getLogger
_logger = getLogger(__name__)
def admin_auth_generate_login(env, user):
"""
Generates a URL to allow the current user to login as the portal user.
:param env: Odoo environment
:param user: `res.users` in
:return:
"""
if not env['res.partner'].has_access('write'):
return None
u = str(user.id)
now = datetime.utcnow()
fifteen = int(mktime(now.timetuple())) + (15 * 60)
e = str(fifteen)
o = str(env.uid)
config = env['ir.config_parameter'].sudo()
key = str(config.search([('key', '=', 'database.secret')], limit=1).value)
h = hmac.new(key.encode(), (u + e + o).encode(), sha256)
base_url = str(config.search([('key', '=', 'web.base.url')], limit=1).value)
_logger.warning('login url for user id: ' + u + ' original user id: ' + o)
return base_url + '/auth_admin?u=' + u + '&e=' + e + '&o=' + o + '&h=' + h.hexdigest()
def check_admin_auth_login(env, u_user_id, e_expires, o_org_user_id, hash_):
"""
Checks that the parameters are valid and that the user exists.
:param env: Odoo environment
:param u_user_id: Desired user id to login as.
:param e_expires: Expiration timestamp
:param o_org_user_id: Original user id.
:param hash_: HMAC generated hash
:return: `res.users`
"""
now = datetime.utcnow()
now = int(mktime(now.timetuple()))
fifteen = now + (15 * 60)
config = env['ir.config_parameter'].sudo()
key = str(config.search([('key', '=', 'database.secret')], limit=1).value)
myh = hmac.new(key.encode(), str(str(u_user_id) + str(e_expires) + str(o_org_user_id)).encode(), sha256)
if not hmac.compare_digest(hash_, myh.hexdigest()):
raise exceptions.AccessDenied('Invalid Request')
if not (now <= int(e_expires) <= fifteen):
raise exceptions.AccessDenied('Expired')
user = env['res.users'].sudo().search([('id', '=', int(u_user_id))], limit=1)
if not user.id:
raise exceptions.AccessDenied('Invalid User')
return user
class ResUsers(models.Model):
_inherit = 'res.users'
def admin_auth_generate_login(self):
self.ensure_one()
login_url = admin_auth_generate_login(self.env, self)
if login_url:
raise exceptions.UserError(login_url)
return False
def _check_credentials(self, password, env):
try:
return super(ResUsers, self)._check_credentials(password, env)
except exceptions.AccessDenied:
if request and hasattr(request, 'session') and request.session.get('auth_admin'):
_logger.warning('_check_credentials for user id: ' + \
str(request.session.uid) + ' original user id: ' + str(request.session.auth_admin))
else:
raise

16
auth_admin/views/res_users.xml Executable file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="auth_admin_view_users_tree" model="ir.ui.view">
<field name="name">auth_admin.res.users.tree</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_tree"/>
<field name="arch" type="xml">
<xpath expr="//list" position="inside">
<field name="share" invisible="1"/>
<button string="Generate Login" type="object"
name="admin_auth_generate_login"
invisible="not share"/>
</xpath>
</field>
</record>
</odoo>

1
auth_admin/wizard/__init__.py Executable file
View File

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

View File

@@ -0,0 +1,39 @@
from odoo import api, fields, models, _
from ..models.res_users import admin_auth_generate_login
class PortalWizard(models.TransientModel):
_inherit = 'portal.wizard'
def admin_auth_generate_login(self):
self.ensure_one()
self.user_ids.admin_auth_generate_login()
return {
"type": "ir.actions.act_window",
"res_model": self._name,
"views": [[False, "form"]],
"res_id": self.id,
"target": "new",
}
class PortalWizardUser(models.TransientModel):
_inherit = 'portal.wizard.user'
force_login_url = fields.Char(string='Force Login URL')
def admin_auth_generate_login(self):
ir_model_access = self.env['ir.model.access']
for row in self.filtered(lambda r: r.is_portal):
user = row.partner_id.user_ids[0] if row.partner_id.user_ids else None
if ir_model_access.check('res.partner', mode='unlink') and user:
row.force_login_url = admin_auth_generate_login(self.env, user)
self.filtered(lambda r: not r.is_portal).update({'force_login_url': ''})
return {
'name': _('Portal Access Management'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'portal.wizard',
'res_id': self.wizard_id.id,
'target': 'new',
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="portal_wizard" model="ir.ui.view">
<field name="name">Portal Access Management - Auth Admin</field>
<field name="model">portal.wizard</field>
<field name="inherit_id" ref="portal.wizard_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='is_portal']" position="after">
<field name="force_login_url" widget="CopyClipboardChar"/>
</xpath>
<xpath expr="//list/button[last()]" position="after">
<button string="Generate Login URL" type="object" name="admin_auth_generate_login" class="btn-primary" />
</xpath>
</field>
</record>
</odoo>