diff --git a/auth_admin/__init__.py b/auth_admin/__init__.py new file mode 100755 index 00000000..e4f4917a --- /dev/null +++ b/auth_admin/__init__.py @@ -0,0 +1,3 @@ +from . import controllers +from . import models +from . import wizard diff --git a/auth_admin/__manifest__.py b/auth_admin/__manifest__.py new file mode 100755 index 00000000..25cc444b --- /dev/null +++ b/auth_admin/__manifest__.py @@ -0,0 +1,31 @@ +{ + 'name': 'Auth Admin', + 'author': 'Hibou Corp.', + 'category': 'Hidden', + 'version': '17.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', + ], +} diff --git a/auth_admin/controllers/__init__.py b/auth_admin/controllers/__init__.py new file mode 100755 index 00000000..757b12a1 --- /dev/null +++ b/auth_admin/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import main diff --git a/auth_admin/controllers/main.py b/auth_admin/controllers/main.py new file mode 100755 index 00000000..2453387c --- /dev/null +++ b/auth_admin/controllers/main.py @@ -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) diff --git a/auth_admin/i18n/es.po b/auth_admin/i18n/es.po new file mode 100644 index 00000000..f9c8f1c1 --- /dev/null +++ b/auth_admin/i18n/es.po @@ -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" diff --git a/auth_admin/models/__init__.py b/auth_admin/models/__init__.py new file mode 100755 index 00000000..88351653 --- /dev/null +++ b/auth_admin/models/__init__.py @@ -0,0 +1 @@ +from . import res_users diff --git a/auth_admin/models/res_users.py b/auth_admin/models/res_users.py new file mode 100755 index 00000000..f1d1f48f --- /dev/null +++ b/auth_admin/models/res_users.py @@ -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'].check_access_rights('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 diff --git a/auth_admin/views/res_users.xml b/auth_admin/views/res_users.xml new file mode 100755 index 00000000..e91821c7 --- /dev/null +++ b/auth_admin/views/res_users.xml @@ -0,0 +1,16 @@ + + + + auth_admin.res.users.tree + res.users + + + + +