parent
bea5daf8bc
commit
973d1160b6
|
@ -0,0 +1,58 @@
|
||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
===========
|
||||||
|
Remote Base
|
||||||
|
===========
|
||||||
|
|
||||||
|
This module allows to store all the connected remotes (external ip addresses) to odoo.
|
||||||
|
It should be used with other modules in order to check remote's configurations.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
When installed, all remotes will be stored by `hostname` on `res.remote`.
|
||||||
|
They can be viewed on `Settings / Users & Companies / Remotes`.
|
||||||
|
The last Ip of the remote will be stored.
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||||
|
:alt: Try me on Runbot
|
||||||
|
:target: https://runbot.odoo-community.org/runbot/144/11.0
|
||||||
|
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues
|
||||||
|
<https://github.com/OCA/report-print-send/issues>`_. In case of trouble, please
|
||||||
|
check there if your issue has already been reported. If you spotted it first,
|
||||||
|
help us smashing it by providing a detailed and welcomed feedback.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Images
|
||||||
|
------
|
||||||
|
|
||||||
|
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Enric Tobella <etobella@creublanca.es>
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: https://odoo-community.org
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
To contribute to this module, please visit https://odoo-community.org.
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Copyright (c) 2018 Creu Blanca
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': "Remote Base",
|
||||||
|
'version': '11.0.1.0.1',
|
||||||
|
'category': 'Generic Modules/Base',
|
||||||
|
'author': "Creu Blanca, Odoo Community Association (OCA)",
|
||||||
|
'website': 'http://github.com/OCA/server-tools',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
"depends": ['web', 'base'],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/res_remote_views.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * base_remote
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 11.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: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_create_uid
|
||||||
|
msgid "Created by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_name
|
||||||
|
msgid "Hostname"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: sql_constraint:res.remote:0
|
||||||
|
msgid "Hostname must be unique"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_in_network
|
||||||
|
msgid "In Network"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_ip
|
||||||
|
msgid "Ip"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote___last_update
|
||||||
|
msgid "Last Modified on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,field_description:base_remote.field_res_remote_write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.ui.view,arch_db:base_remote.res_remote_form
|
||||||
|
msgid "Remote"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.actions.act_window,name:base_remote.res_remote_action
|
||||||
|
#: model:ir.model,name:base_remote.model_res_remote
|
||||||
|
#: model:ir.ui.menu,name:base_remote.res_remote_menu
|
||||||
|
#: model:ir.ui.view,arch_db:base_remote.res_remote_tree
|
||||||
|
msgid "Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model.fields,help:base_remote.field_res_remote_in_network
|
||||||
|
msgid "Shows if the remote can be found through the socket"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model,name:base_remote.model_res_users
|
||||||
|
msgid "Users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base_remote
|
||||||
|
#: model:ir.model,name:base_remote.model_base
|
||||||
|
msgid "base"
|
||||||
|
msgstr ""
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
from . import base
|
||||||
|
from . import res_remote
|
||||||
|
from . import res_users
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright 2018 Creu Blanca
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import models
|
||||||
|
from threading import current_thread
|
||||||
|
|
||||||
|
|
||||||
|
class Base(models.AbstractModel):
|
||||||
|
_inherit = 'base'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remote(self):
|
||||||
|
try:
|
||||||
|
remote_addr = current_thread().environ["REMOTE_ADDR"]
|
||||||
|
except KeyError:
|
||||||
|
remote_addr = False
|
||||||
|
return self.env['res.remote']._get_remote(remote_addr)
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright 2018 Creu Blanca
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo import api, models, fields
|
||||||
|
import socket
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class ResRemote(models.Model):
|
||||||
|
_name = 'res.remote'
|
||||||
|
_description = 'Remotes'
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
required=True,
|
||||||
|
string='Hostname',
|
||||||
|
index=True,
|
||||||
|
readonly=True
|
||||||
|
)
|
||||||
|
ip = fields.Char(required=True)
|
||||||
|
in_network = fields.Boolean(
|
||||||
|
required=True,
|
||||||
|
help='Shows if the remote can be found through the socket'
|
||||||
|
)
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('name_unique', 'unique(name)', 'Hostname must be unique')
|
||||||
|
]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_vals(self, addr, hostname):
|
||||||
|
return {
|
||||||
|
'name': hostname or addr,
|
||||||
|
'ip': addr,
|
||||||
|
'in_network': bool(hostname),
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_remote(self, addr):
|
||||||
|
try:
|
||||||
|
hostname, alias, ips = socket.gethostbyaddr(addr)
|
||||||
|
except socket.herror:
|
||||||
|
logging.warning('Remote with ip %s could not be found' % addr)
|
||||||
|
hostname = False
|
||||||
|
remote = self.search([('name', '=', hostname or addr)])
|
||||||
|
if not remote:
|
||||||
|
remote = self.create(self._create_vals(addr, hostname))
|
||||||
|
if remote.ip != addr:
|
||||||
|
# IPs can change through time, but hostname should not change
|
||||||
|
remote.write({'ip': addr})
|
||||||
|
return remote
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Copyright 2018 Creu Blanca
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from threading import current_thread
|
||||||
|
from odoo import api, models, SUPERUSER_ID
|
||||||
|
from odoo.exceptions import AccessDenied
|
||||||
|
from odoo.service import wsgi_server
|
||||||
|
|
||||||
|
|
||||||
|
class ResUsers(models.Model):
|
||||||
|
_inherit = "res.users"
|
||||||
|
|
||||||
|
# HACK https://github.com/odoo/odoo/issues/24183
|
||||||
|
# TODO Remove in v12, and use normal odoo.http.request to get details
|
||||||
|
@api.model_cr
|
||||||
|
def _register_hook(self):
|
||||||
|
"""🐒-patch XML-RPC controller to know remote address."""
|
||||||
|
original_fn = wsgi_server.application_unproxied
|
||||||
|
|
||||||
|
def _patch(environ, start_response):
|
||||||
|
current_thread().environ = environ
|
||||||
|
return original_fn(environ, start_response)
|
||||||
|
|
||||||
|
wsgi_server.application_unproxied = _patch
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _auth_check_remote(cls, login, method):
|
||||||
|
"""Force a method to raise an AccessDenied on falsey return."""
|
||||||
|
with cls.pool.cursor() as cr:
|
||||||
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
remote = env["res.users"].remote
|
||||||
|
remote.ensure_one()
|
||||||
|
result = method()
|
||||||
|
if not result:
|
||||||
|
# Force exception to record auth failure
|
||||||
|
raise AccessDenied()
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Override all auth-related core methods
|
||||||
|
@classmethod
|
||||||
|
def _login(cls, db, login, password):
|
||||||
|
return cls._auth_check_remote(
|
||||||
|
login,
|
||||||
|
lambda: super(ResUsers, cls)._login(db, login, password),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def authenticate(cls, db, login, password, user_agent_env):
|
||||||
|
return cls._auth_check_remote(
|
||||||
|
login,
|
||||||
|
lambda: super(ResUsers, cls).authenticate(
|
||||||
|
db, login, password, user_agent_env),
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_remote,access_remote,model_res_remote,base.group_user,1,0,0,0
|
||||||
|
manage_remote,manage_remote,model_res_remote,base.group_system,1,1,0,0
|
|
|
@ -0,0 +1 @@
|
||||||
|
from . import test_remote
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Copyright 2018 Creu Blanca
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from xmlrpc.client import Fault
|
||||||
|
|
||||||
|
from mock import patch
|
||||||
|
from werkzeug.utils import redirect
|
||||||
|
|
||||||
|
from odoo import http
|
||||||
|
from odoo.tests.common import at_install, HttpCase, post_install
|
||||||
|
|
||||||
|
|
||||||
|
@at_install(False)
|
||||||
|
@post_install(True)
|
||||||
|
# Skip CSRF validation on tests
|
||||||
|
@patch(http.__name__ + ".WebRequest.validate_csrf", return_value=True)
|
||||||
|
# Skip specific browser forgery on redirections
|
||||||
|
@patch(http.__name__ + ".redirect_with_hash", side_effect=redirect)
|
||||||
|
class TestRemote(HttpCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
# HACK https://github.com/odoo/odoo/issues/24183
|
||||||
|
# TODO Remove in v12
|
||||||
|
# Complex password to avoid conflicts with `password_security`
|
||||||
|
self.good_password = "Admin$%02584"
|
||||||
|
self.data_demo = {
|
||||||
|
"login": "demo",
|
||||||
|
"password": "Demo%&/(908409**",
|
||||||
|
}
|
||||||
|
self.remote_addr = '127.0.0.1'
|
||||||
|
with self.cursor() as cr:
|
||||||
|
env = self.env(cr)
|
||||||
|
# Make sure involved users have good passwords
|
||||||
|
env.user.password = self.good_password
|
||||||
|
env["res.users"].search([
|
||||||
|
("login", "=", self.data_demo["login"]),
|
||||||
|
]).password = self.data_demo["password"]
|
||||||
|
remote = self.env['res.remote'].search([
|
||||||
|
('ip', '=', self.remote_addr)
|
||||||
|
])
|
||||||
|
if remote:
|
||||||
|
remote.unlink()
|
||||||
|
|
||||||
|
def test_xmlrpc_login_ok(self, *args):
|
||||||
|
"""Test Login"""
|
||||||
|
data1 = self.data_demo
|
||||||
|
self.assertTrue(self.xmlrpc_common.authenticate(
|
||||||
|
self.env.cr.dbname, data1["login"], data1["password"], {}))
|
||||||
|
with self.cursor() as cr:
|
||||||
|
env = self.env(cr)
|
||||||
|
self.assertTrue(
|
||||||
|
env['res.remote'].search([('ip', '=', self.remote_addr)])
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_xmlrpc_login_failure(self, *args):
|
||||||
|
"""Test Login Failure"""
|
||||||
|
data1 = self.data_demo
|
||||||
|
data1['password'] = 'Failure!'
|
||||||
|
with self.assertRaises(Fault):
|
||||||
|
self.assertFalse(self.xmlrpc_common.authenticate(
|
||||||
|
self.env.cr.dbname, data1["login"], data1["password"], {}))
|
||||||
|
with self.cursor() as cr:
|
||||||
|
env = self.env(cr)
|
||||||
|
self.assertTrue(
|
||||||
|
env['res.remote'].search([('ip', '=', self.remote_addr)])
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="res_remote_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.remote.form</field>
|
||||||
|
<field name="model">res.remote</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Remote">
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1><field name="name"/></h1>
|
||||||
|
</div>
|
||||||
|
<group name="technical">
|
||||||
|
<group name="network">
|
||||||
|
<field name="ip"/>
|
||||||
|
<field name="in_network"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<notebook/>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="res_remote_tree" model="ir.ui.view">
|
||||||
|
<field name="name">res.remote.tree</field>
|
||||||
|
<field name="model">res.remote</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Remotes">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="ip"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="res_remote_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Remotes</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">res.remote</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="res_remote_menu"
|
||||||
|
name="Remotes"
|
||||||
|
sequence="30"
|
||||||
|
parent="base.menu_users"
|
||||||
|
action="res_remote_action"/>
|
||||||
|
</odoo>
|
Loading…
Reference in New Issue