Browse Source

[ADD] remote_base

pull/1291/head
Enric Tobella 6 years ago
parent
commit
0f2eab5c74
  1. 58
      remote_base/README.rst
  2. 1
      remote_base/__init__.py
  3. 18
      remote_base/__manifest__.py
  4. 5
      remote_base/models/__init__.py
  5. 17
      remote_base/models/base.py
  6. 49
      remote_base/models/res_remote.py
  7. 53
      remote_base/models/res_users.py
  8. 3
      remote_base/security/ir.model.access.csv
  9. 1
      remote_base/tests/__init__.py
  10. 66
      remote_base/tests/test_remote.py
  11. 46
      remote_base/views/res_remote_views.xml

58
remote_base/README.rst

@ -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.

1
remote_base/__init__.py

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

18
remote_base/__manifest__.py

@ -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.0',
'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,
}

5
remote_base/models/__init__.py

@ -0,0 +1,5 @@
from . import base
from . import res_remote
from . import res_users

17
remote_base/models/base.py

@ -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)

49
remote_base/models/res_remote.py

@ -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

53
remote_base/models/res_users.py

@ -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),
)

3
remote_base/security/ir.model.access.csv

@ -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

1
remote_base/tests/__init__.py

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

66
remote_base/tests/test_remote.py

@ -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)])
)

46
remote_base/views/res_remote_views.xml

@ -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…
Cancel
Save