diff --git a/addons/hw_drivers/controllers/driver.py b/addons/hw_drivers/controllers/driver.py index 65b57c43815..04ebb948b89 100755 --- a/addons/hw_drivers/controllers/driver.py +++ b/addons/hw_drivers/controllers/driver.py @@ -5,7 +5,6 @@ from threading import Thread, Event, Lock from usb import core from gatt import DeviceManager as Gatt_DeviceManager import subprocess -import netifaces import json from re import sub import urllib3 @@ -23,42 +22,11 @@ import ctypes from odoo import http, _ from odoo.modules.module import get_resource_path +from odoo.addons.hw_drivers.tools import helpers _logger = logging.getLogger(__name__) -#---------------------------------------------------------- -# Helper -#---------------------------------------------------------- - -def get_mac_address(): - try: - return netifaces.ifaddresses('eth0')[netifaces.AF_LINK][0]['addr'] - except: - return netifaces.ifaddresses('wlan0')[netifaces.AF_LINK][0]['addr'] - -def get_ip(): - try: - return netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr'] - except: - return netifaces.ifaddresses('wlan0')[netifaces.AF_INET][0]['addr'] - -def read_file_first_line(filename): - path = Path.home() / filename - if path.exists(): - with path.open('r') as f: - return f.readline().strip('\n') - return '' - -def get_odoo_server_url(): - return read_file_first_line('odoo-remote-server.conf') - -def get_token(): - return read_file_first_line('token') - -def get_version(): - return '19_07' - #---------------------------------------------------------- # Controllers #---------------------------------------------------------- @@ -79,6 +47,14 @@ class StatusController(http.Controller): return True return False + @http.route('/hw_drivers/check_certificate', type='http', auth='none', cors='*', csrf=False, save_session=False) + def check_certificate(self): + """ + This route is called when we want to check if certificate is up-to-date + Used in cron.daily + """ + helpers.check_certificate() + @http.route('/hw_drivers/event', type='json', auth='none', cors='*', csrf=False, save_session=False) def event(self, listener): """ @@ -98,13 +74,20 @@ class StatusController(http.Controller): 1 - url of odoo DB 2 - token. This token will be compared to the token of Odoo. He have 1 hour lifetime """ - server = get_odoo_server_url() + server = helpers.get_odoo_server_url() image = get_resource_path('hw_drivers', 'static/img', 'False.jpg') if server == '': - token = b64decode(token).decode('utf-8') - url, token = token.split('|') + credential = b64decode(token).decode('utf-8').split('|') + url = credential[0] + token = credential[1] + if len(credential) > 2: + # IoT Box send token with db_uuid and enterprise_code only since V13 + db_uuid = credential[2] + enterprise_code = credential[3] + helpers.add_credential(db_uuid, enterprise_code) try: subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server.sh'), url, '', token, 'noreboot']) + helpers.check_certificate() m.send_alldevices() image = get_resource_path('hw_drivers', 'static/img', 'True.jpg') except subprocess.CalledProcessError as e: @@ -268,14 +251,19 @@ class Manager(Thread): """ This method send IoT Box and devices informations to Odoo database """ - server = get_odoo_server_url() + server = helpers.get_odoo_server_url() if server: + subject = helpers.read_file_first_line('odoo-subject.conf') + if subject: + domain = helpers.get_ip().replace('.', '-') + subject.strip('*') + else: + domain = helpers.get_ip() iot_box = { 'name': socket.gethostname(), - 'identifier': get_mac_address(), - 'ip': get_ip(), - 'token': get_token(), - 'version': get_version() + 'identifier': helpers.get_mac_address(), + 'ip': domain, + 'token': helpers.get_token(), + 'version': helpers.get_version() } devices_list = {} for device in iot_devices: @@ -373,6 +361,7 @@ class Manager(Thread): """ Thread that will check connected/disconnected device, load drivers if needed and contact the odoo server with the updates """ + helpers.check_certificate() devices = {} updated_devices = {} self.send_alldevices() @@ -473,7 +462,7 @@ printers = conn.getPrinters() cups_lock = Lock() # We can only make one call to Cups at a time mpdm = MPDManager() -terminal_id = read_file_first_line('odoo-six-payment-terminal.conf') +terminal_id = helpers.read_file_first_line('odoo-six-payment-terminal.conf') if terminal_id: try: subprocess.check_output(["pidof", "eftdvs"]) # Check if MPD server is running diff --git a/addons/hw_drivers/drivers/KeyboardUSBDriver.py b/addons/hw_drivers/drivers/KeyboardUSBDriver.py index 9e89f1edcbd..6c61a59307d 100644 --- a/addons/hw_drivers/drivers/KeyboardUSBDriver.py +++ b/addons/hw_drivers/drivers/KeyboardUSBDriver.py @@ -21,7 +21,8 @@ except ImportError: from odoo import http, _ from odoo.addons.hw_proxy.controllers.main import drivers as old_drivers -from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, iot_devices, get_odoo_server_url +from odoo.addons.hw_drivers.tools import helpers +from odoo.addons.hw_drivers.controllers.driver import event_manager, Driver, iot_devices _logger = logging.getLogger(__name__) xlib = ctypes.cdll.LoadLibrary('libX11.so.6') @@ -74,7 +75,7 @@ class KeyboardUSBDriver(Driver): @classmethod def send_layouts_list(cls): - server = get_odoo_server_url() + server = helpers.get_odoo_server_url() if server: urllib3.disable_warnings() pm = urllib3.PoolManager(cert_reqs='CERT_NONE') diff --git a/addons/hw_drivers/tools/helpers.py b/addons/hw_drivers/tools/helpers.py new file mode 100644 index 00000000000..c35c95ac3cd --- /dev/null +++ b/addons/hw_drivers/tools/helpers.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import netifaces +from pathlib import Path +import datetime +from OpenSSL import crypto +import urllib3 +import json +import logging +import subprocess + +from odoo import _ + +_logger = logging.getLogger(__name__) + +#---------------------------------------------------------- +# Helper +#---------------------------------------------------------- + +def access_point(): + return get_ip() == '10.11.12.1' + +def add_credential(db_uuid, enterprise_code): + write_file('odoo-db-uuid.conf', db_uuid) + write_file('odoo-enterprise-code.conf', enterprise_code) + +def check_certificate(): + """ + Check if the current certificate is up to date or not authenticated + """ + server = get_odoo_server_url() + if server: + path = Path('/etc/ssl/certs/nginx-cert.crt') + if path.exists(): + with path.open('r') as f: + cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) + cert_end_date = datetime.datetime.strptime(cert.get_notAfter().decode('utf-8'), "%Y%m%d%H%M%SZ") - datetime.timedelta(days=10) + for key in cert.get_subject().get_components(): + if key[0] == b'CN': + cn = key[1].decode('utf-8') + if cn == 'OdooTempIoTBoxCertificate' or datetime.datetime.now() > cert_end_date: + _logger.info(_('Your certificate %s must be updated') % (cn)) + load_certificate() + else: + _logger.info(_('Your certificate %s is valid until %s') % (cn, cert_end_date)) + else: + load_certificate() + +def get_ip(): + try: + return netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr'] + except: + return netifaces.ifaddresses('wlan0')[netifaces.AF_INET][0]['addr'] + +def get_mac_address(): + try: + return netifaces.ifaddresses('eth0')[netifaces.AF_LINK][0]['addr'] + except: + return netifaces.ifaddresses('wlan0')[netifaces.AF_LINK][0]['addr'] + +def get_odoo_server_url(): + return read_file_first_line('odoo-remote-server.conf') + +def get_token(): + return read_file_first_line('token') + +def get_version(): + return '19_07' + +def load_certificate(): + """ + Send a request to Odoo with customer db_uuid and enterprise_code to get a true certificate + """ + db_uuid = read_file_first_line('odoo-db-uuid.conf') + enterprise_code = read_file_first_line('odoo-enterprise-code.conf') + if db_uuid and enterprise_code: + url = 'https://www.odoo.com/odoo-enterprise/iot/x509' + data = { + 'params': { + 'db_uuid': db_uuid, + 'enterprise_code': enterprise_code + } + } + urllib3.disable_warnings() + http = urllib3.PoolManager(cert_reqs='CERT_NONE') + response = http.request( + 'POST', + url, + body = json.dumps(data).encode('utf8'), + headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} + ) + result = json.loads(response.data.decode('utf8'))['result'] + if result: + write_file('odoo-subject.conf', result['subject_cn']) + subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"]) + subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/"]) + Path('/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem']) + Path('/root_bypass_ramdisks/etc/ssl/certs/nginx-cert.crt').write_text(result['x509_pem']) + Path('/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem']) + Path('/root_bypass_ramdisks/etc/ssl/private/nginx-cert.key').write_text(result['private_key_pem']) + subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"]) + subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/root_bypass_ramdisks/"]) + subprocess.check_call(["sudo", "service", "nginx", "restart"]) + +def read_file_first_line(filename): + path = Path.home() / filename + path = Path('/home/pi/' + filename) + if path.exists(): + with path.open('r') as f: + return f.readline().strip('\n') + return '' + +def unlink_file(filename): + subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"]) + path = Path.home() / filename + if path.exists(): + path.unlink() + subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"]) + +def write_file(filename, text): + subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"]) + path = Path.home() / filename + path.write_text(text) + subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"]) diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py index f4c29cd31c3..51287ffc3b3 100644 --- a/addons/hw_posbox_homepage/controllers/main.py +++ b/addons/hw_posbox_homepage/controllers/main.py @@ -21,18 +21,12 @@ from uuid import getnode as get_mac from odoo.addons.hw_proxy.controllers import main as hw_proxy from odoo.addons.web.controllers import main as web from odoo.modules.module import get_resource_path -from odoo.addons.hw_drivers.controllers.driver import iot_devices, get_ip, get_odoo_server_url +from odoo.addons.hw_drivers.tools import helpers +from odoo.addons.hw_drivers.controllers.driver import iot_devices _logger = logging.getLogger(__name__) -#---------------------------------------------------------- -# Helper -#---------------------------------------------------------- - -def access_point(): - return get_ip() == '10.11.12.1' - #---------------------------------------------------------- # Controllers #---------------------------------------------------------- @@ -54,6 +48,7 @@ driver_list_template = jinja_env.get_template('driver_list.html') remote_connect_template = jinja_env.get_template('remote_connect.html') configure_wizard_template = jinja_env.get_template('configure_wizard.html') six_payment_terminal_template = jinja_env.get_template('six_payment_terminal.html') +list_credential_template = jinja_env.get_template('list_credential.html') class IoTboxHomepage(web.Home): @@ -74,10 +69,8 @@ class IoTboxHomepage(web.Home): return statuses def get_six_terminal(self): - path = Path.home() / 'odoo-six-payment-terminal.conf' - if path.exists(): - return path.read_text() - return 'Not Configured' + terminal_id = helpers.read_file_first_line('odoo-six-payment-terminal.conf') + return terminal_id or 'Not Configured' def get_homepage_data(self): hostname = str(socket.gethostname()) @@ -88,7 +81,7 @@ class IoTboxHomepage(web.Home): if wired == 'up': network = 'Ethernet' elif ssid: - if access_point(): + if helpers.access_point(): network = 'Wifi access point' else: network = 'Wifi : ' + ssid @@ -116,25 +109,26 @@ class IoTboxHomepage(web.Home): return { 'hostname': hostname, - 'ip': get_ip(), + 'ip': helpers.get_ip(), 'mac': ":".join(i + next(h) for i in h), 'iot_device_status': iot_device, - 'server_status': get_odoo_server_url() or 'Not Configured', + 'server_status': helpers.get_odoo_server_url() or 'Not Configured', 'six_terminal': self.get_six_terminal(), 'network_status': network, + 'version': helpers.get_version(), } @http.route('/', type='http', auth='none') def index(self): wifi = Path.home() / 'wifi_network.txt' remote_server = Path.home() / 'odoo-remote-server.conf' - if (wifi.exists() == False or remote_server.exists() == False) and access_point(): + if (wifi.exists() == False or remote_server.exists() == False) and helpers.access_point(): return configure_wizard_template.render({ 'title': 'Configure IoT Box', 'breadcrumb': 'Configure IoT Box', 'loading_message': 'Configuring your IoT Box', 'ssid': self.get_wifi_essid(), - 'server': get_odoo_server_url(), + 'server': helpers.get_odoo_server_url(), 'hostname': subprocess.check_output('hostname').decode('utf-8'), }) else: @@ -150,7 +144,7 @@ class IoTboxHomepage(web.Home): 'title': "Odoo's IoT Box - Drivers list", 'breadcrumb': 'Drivers list', 'drivers_list': drivers_list, - 'server': get_odoo_server_url() + 'server': helpers.get_odoo_server_url() }) @http.route('/load_drivers', type='http', auth='none', website=True) @@ -161,7 +155,7 @@ class IoTboxHomepage(web.Home): mac = subprocess.check_output("/sbin/ifconfig eth0 |grep -Eo ..\(\:..\){5}", shell=True).decode('utf-8').split('\n')[0] #response = requests.get(url, auth=(username, db_uuid.split('\n')[0]), stream=True) - server = get_odoo_server_url() + server = helpers.get_odoo_server_url() if server: urllib3.disable_warnings() pm = urllib3.PoolManager(cert_reqs='CERT_NONE') @@ -181,7 +175,31 @@ class IoTboxHomepage(web.Home): subprocess.check_call("sudo mount -o remount,ro /", shell=True) subprocess.check_call("sudo mount -o remount,ro /root_bypass_ramdisks", shell=True) - return "" + return "" + + @http.route('/list_credential', type='http', auth='none', website=True) + def list_credential(self): + return list_credential_template.render({ + 'title': "Odoo's IoT Box - List credential", + 'breadcrumb': 'List credential', + 'db_uuid': helpers.read_file_first_line('odoo-db-uuid.conf'), + 'enterprise_code': helpers.read_file_first_line('odoo-enterprise-code.conf'), + }) + + @http.route('/save_credential', type='http', auth='none', cors='*', csrf=False) + def save_credential(self, db_uuid, enterprise_code): + helpers.add_credential(db_uuid, enterprise_code) + subprocess.check_call(["sudo", "service", "odoo", "restart"]) + return "" + + @http.route('/clear_credential', type='http', auth='none', cors='*', csrf=False) + def clear_credential(self): + subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"]) + helpers.unlink_file('odoo-db-uuid.conf') + helpers.unlink_file('odoo-enterprise-code.conf') + subprocess.check_call(["sudo", "service", "odoo", "restart"]) + subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"]) + return "" def get_wifi_essid(self): wifi_options = [] @@ -214,7 +232,7 @@ class IoTboxHomepage(web.Home): persistent = "" subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_wifi.sh'), essid, password, persistent]) - server = get_odoo_server_url() + server = helpers.get_odoo_server_url() res_payload = { 'message': 'Connecting to ' + essid, } @@ -228,29 +246,34 @@ class IoTboxHomepage(web.Home): @http.route('/wifi_clear', type='http', auth='none', cors='*', csrf=False) def clear_wifi_configuration(self): - os.system(get_resource_path('point_of_sale', 'tools/posbox/configuration/clear_wifi_configuration.sh')) - - return "" + helpers.unlink_file('wifi_network.txt') + return "" @http.route('/server_clear', type='http', auth='none', cors='*', csrf=False) def clear_server_configuration(self): - os.system(get_resource_path('point_of_sale', 'tools/posbox/configuration/clear_server_configuration.sh')) - - return "" + helpers.unlink_file('odoo-remote-server.conf') + return "" @http.route('/drivers_clear', type='http', auth='none', cors='*', csrf=False) def clear_drivers_list(self): - os.system(get_resource_path('point_of_sale', 'tools/posbox/configuration/clear_drivers_list.sh')) - - return "" + for driver in os.listdir(get_resource_path('hw_drivers', 'drivers')): + if driver != '__pycache__': + helpers.unlink_file(get_resource_path('hw_drivers', 'drivers', driver)) + return "" @http.route('/server_connect', type='http', auth='none', cors='*', csrf=False) def connect_to_server(self, token, iotname): - url = token.split('|')[0] - token = token.split('|')[1] + credential = token.split('|') + url = credential[0] + token = credential[1] + if len(credential) > 2: + # IoT Box send token with db_uuid and enterprise_code only since V13 + db_uuid = credential[2] + enterprise_code = credential[3] + helpers.add_credential(db_uuid, enterprise_code) reboot = 'reboot' subprocess.check_call([get_resource_path('point_of_sale', 'tools/posbox/configuration/connect_to_server.sh'), url, iotname, token, reboot]) - return 'http://' + get_ip() + ':8069' + return 'http://' + helpers.get_ip() + ':8069' @http.route('/steps', type='http', auth='none', cors='*', csrf=False) def step_by_step_configure_page(self): @@ -259,7 +282,7 @@ class IoTboxHomepage(web.Home): 'breadcrumb': 'Configure IoT Box', 'loading_message': 'Configuring your IoT Box', 'ssid': self.get_wifi_essid(), - 'server': get_odoo_server_url(), + 'server': helpers.get_odoo_server_url(), 'hostname': subprocess.check_output('hostname').decode('utf-8').strip('\n'), }) @@ -280,7 +303,7 @@ class IoTboxHomepage(web.Home): 'title': 'IoT -> Odoo server configuration', 'breadcrumb': 'Configure Odoo Server', 'hostname': subprocess.check_output('hostname').decode('utf-8').strip('\n'), - 'server_status': get_odoo_server_url() or 'Not configured yet', + 'server_status': helpers.get_odoo_server_url() or 'Not configured yet', 'loading_message': 'Configure Domain Server' }) @@ -316,19 +339,12 @@ class IoTboxHomepage(web.Home): @http.route('/six_payment_terminal_add', type='http', auth='none', cors='*', csrf=False) def add_six_payment_terminal(self, terminal_id): - subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"]) - path = Path.home() / 'odoo-six-payment-terminal.conf' - path.write_text(terminal_id) + helpers.write_file('odoo-six-payment-terminal.conf', terminal_id) subprocess.check_call(["sudo", "service", "odoo", "restart"]) - subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"]) - return 'http://' + get_ip() + ':8069' + return 'http://' + helpers.get_ip() + ':8069' @http.route('/six_payment_terminal_clear', type='http', auth='none', cors='*', csrf=False) def clear_six_payment_terminal(self): - subprocess.check_call(["sudo", "mount", "-o", "remount,rw", "/"]) - path = Path.home() / 'odoo-six-payment-terminal.conf' - if path.exists(): - path.unlink() + helpers.unlink_file('odoo-six-payment-terminal.conf') subprocess.check_call(["sudo", "service", "odoo", "restart"]) - subprocess.check_call(["sudo", "mount", "-o", "remount,ro", "/"]) - return "" + return "" diff --git a/addons/hw_posbox_homepage/views/homepage.html b/addons/hw_posbox_homepage/views/homepage.html index d70238fc54f..ea469596efe 100644 --- a/addons/hw_posbox_homepage/views/homepage.html +++ b/addons/hw_posbox_homepage/views/homepage.html @@ -92,7 +92,7 @@
+ Set the DB UUID and your Contract Number you want to use. +
+ + {% if db_uuid or enterprise_code %} ++ Current DB uuid: {{ db_uuid }} +
++ Current Contract Number: {{ enterprise_code }} +
+