updates
This commit is contained in:
143
disable_odoo_online/disable_odoo_online/README.md
Normal file
143
disable_odoo_online/disable_odoo_online/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Disable Odoo Online Services
|
||||
|
||||
**Version:** 18.0.1.0.0
|
||||
**License:** LGPL-3
|
||||
**Odoo Version:** 18.0
|
||||
|
||||
## Overview
|
||||
|
||||
This module comprehensively disables all external communications between your Odoo instance and Odoo's servers. It prevents:
|
||||
|
||||
- License/subscription checks
|
||||
- User count reporting
|
||||
- IAP (In-App Purchase) credit checks
|
||||
- Publisher warranty communications
|
||||
- Partner autocomplete/enrichment
|
||||
- Expiration warnings in the UI
|
||||
|
||||
## Features
|
||||
|
||||
### 1. IAP JSON-RPC Blocking
|
||||
Patches the core `iap_jsonrpc` function to prevent all IAP API calls:
|
||||
- Returns fake successful responses
|
||||
- Logs all blocked calls
|
||||
- Provides unlimited credits for services that check
|
||||
|
||||
### 2. License Parameter Protection
|
||||
Protects critical `ir.config_parameter` values:
|
||||
- `database.expiration_date` → Always returns `2099-12-31 23:59:59`
|
||||
- `database.expiration_reason` → Always returns `renewal`
|
||||
- `database.enterprise_code` → Always returns `PERMANENT_LOCAL`
|
||||
|
||||
### 3. Session Info Patching
|
||||
Modifies `session_info()` to prevent frontend warnings:
|
||||
- Sets expiration date to 2099
|
||||
- Sets `warning` to `False`
|
||||
- Removes "already linked" subscription prompts
|
||||
|
||||
### 4. User Creation Protection
|
||||
Logs user creation without triggering subscription checks:
|
||||
- Blocks any external validation
|
||||
- Logs permission changes
|
||||
|
||||
### 5. Publisher Warranty Block
|
||||
Disables all warranty-related server communication:
|
||||
- `_get_sys_logs()` → Returns empty response
|
||||
- `update_notification()` → Returns success without calling server
|
||||
|
||||
### 6. Cron Job Blocking
|
||||
Blocks scheduled actions that contact Odoo:
|
||||
- Publisher Warranty Check
|
||||
- Database Auto-Expiration Check
|
||||
- Various IAP-related crons
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy the module to your Odoo addons directory
|
||||
2. Restart Odoo
|
||||
3. Go to Apps → Update Apps List
|
||||
4. Search for "Disable Odoo Online Services"
|
||||
5. Click Install
|
||||
|
||||
## Verification
|
||||
|
||||
Check that blocking is active:
|
||||
|
||||
```bash
|
||||
docker logs odoo-container 2>&1 | grep -i "BLOCKED\|DISABLED"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
IAP JSON-RPC calls have been DISABLED globally
|
||||
Module update_list: Scanning local addons only (Odoo Apps store disabled)
|
||||
Publisher warranty update_notification BLOCKED
|
||||
Creating 1 user(s) - subscription check DISABLED
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
No configuration required. The module automatically:
|
||||
- Sets permanent expiration values on install (via `_post_init_hook`)
|
||||
- Patches all necessary functions when loaded
|
||||
- Protects values from being changed
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `models/disable_iap_tools.py` | Patches `iap_jsonrpc` globally |
|
||||
| `models/disable_online_services.py` | Blocks publisher warranty, cron jobs |
|
||||
| `models/disable_database_expiration.py` | Protects `ir.config_parameter` |
|
||||
| `models/disable_session_leaks.py` | Patches session info, user creation |
|
||||
| `models/disable_partner_autocomplete.py` | Blocks partner enrichment |
|
||||
| `models/disable_all_external.py` | Additional external call blocks |
|
||||
|
||||
### Blocked Endpoints
|
||||
|
||||
All redirected to `http://localhost:65535`:
|
||||
|
||||
- `iap.endpoint`
|
||||
- `publisher_warranty_url`
|
||||
- `partner_autocomplete.endpoint`
|
||||
- `iap_extract_endpoint`
|
||||
- `olg.endpoint`
|
||||
- `mail.media_library_endpoint`
|
||||
- `sms.endpoint`
|
||||
- `crm.iap_lead_mining.endpoint`
|
||||
- And many more...
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `base`
|
||||
- `web`
|
||||
- `iap`
|
||||
- `mail`
|
||||
- `base_setup`
|
||||
|
||||
## Compatibility
|
||||
|
||||
- Odoo 18.0 Community Edition
|
||||
- Odoo 18.0 Enterprise Edition
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This module is intended for legitimate use cases such as:
|
||||
- Air-gapped environments
|
||||
- Development/testing instances
|
||||
- Self-hosted deployments with proper licensing
|
||||
|
||||
Ensure you comply with Odoo's licensing terms for your use case.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.0.0 (2025-12-29)
|
||||
- Initial release
|
||||
- IAP blocking
|
||||
- Publisher warranty blocking
|
||||
- Session info patching
|
||||
- User creation protection
|
||||
- Config parameter protection
|
||||
|
||||
67
disable_odoo_online/disable_odoo_online/__init__.py
Normal file
67
disable_odoo_online/disable_odoo_online/__init__.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
|
||||
|
||||
def _post_init_hook(env):
|
||||
"""
|
||||
Set all configuration parameters to disable external Odoo services.
|
||||
This runs after module installation.
|
||||
"""
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
set_param = env['ir.config_parameter'].sudo().set_param
|
||||
|
||||
# Set permanent database expiration
|
||||
params_to_set = {
|
||||
# Database license parameters
|
||||
'database.expiration_date': '2099-12-31 23:59:59',
|
||||
'database.expiration_reason': 'renewal',
|
||||
'database.enterprise_code': 'PERMANENT_LOCAL',
|
||||
|
||||
# Clear "already linked" parameters
|
||||
'database.already_linked_subscription_url': '',
|
||||
'database.already_linked_email': '',
|
||||
'database.already_linked_send_mail_url': '',
|
||||
|
||||
# Redirect all IAP endpoints to localhost
|
||||
'iap.endpoint': 'http://localhost:65535',
|
||||
'partner_autocomplete.endpoint': 'http://localhost:65535',
|
||||
'iap_extract_endpoint': 'http://localhost:65535',
|
||||
'olg.endpoint': 'http://localhost:65535',
|
||||
'mail.media_library_endpoint': 'http://localhost:65535',
|
||||
'website.api_endpoint': 'http://localhost:65535',
|
||||
'sms.endpoint': 'http://localhost:65535',
|
||||
'crm.iap_lead_mining.endpoint': 'http://localhost:65535',
|
||||
'reveal.endpoint': 'http://localhost:65535',
|
||||
'publisher_warranty_url': 'http://localhost:65535',
|
||||
|
||||
# OCN (Odoo Cloud Notification) - blocks push notifications to Odoo
|
||||
'odoo_ocn.endpoint': 'http://localhost:65535', # Main OCN endpoint
|
||||
'mail_mobile.enable_ocn': 'False', # Disable OCN push notifications
|
||||
'odoo_ocn.project_id': '', # Clear any registered project
|
||||
'ocn.uuid': '', # Clear OCN UUID to prevent registration
|
||||
|
||||
# Snailmail (physical mail service)
|
||||
'snailmail.endpoint': 'http://localhost:65535',
|
||||
|
||||
# Social media IAP
|
||||
'social.facebook_endpoint': 'http://localhost:65535',
|
||||
'social.twitter_endpoint': 'http://localhost:65535',
|
||||
'social.linkedin_endpoint': 'http://localhost:65535',
|
||||
}
|
||||
|
||||
_logger.info("=" * 60)
|
||||
_logger.info("DISABLE ODOO ONLINE: Setting configuration parameters")
|
||||
_logger.info("=" * 60)
|
||||
|
||||
for key, value in params_to_set.items():
|
||||
try:
|
||||
set_param(key, value)
|
||||
_logger.info("Set %s = %s", key, value if len(str(value)) < 30 else value[:30] + "...")
|
||||
except Exception as e:
|
||||
_logger.warning("Could not set %s: %s", key, e)
|
||||
|
||||
_logger.info("=" * 60)
|
||||
_logger.info("DISABLE ODOO ONLINE: Configuration complete")
|
||||
_logger.info("=" * 60)
|
||||
56
disable_odoo_online/disable_odoo_online/__manifest__.py
Normal file
56
disable_odoo_online/disable_odoo_online/__manifest__.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Disable Odoo Online Services',
|
||||
'version': '19.0.1.0.0',
|
||||
'category': 'Tools',
|
||||
'summary': 'Blocks ALL external Odoo server communications',
|
||||
'description': """
|
||||
Comprehensive Module to Disable ALL Odoo Online Services
|
||||
=========================================================
|
||||
|
||||
This module completely blocks all external communications from Odoo to Odoo's servers.
|
||||
|
||||
**Blocked Services:**
|
||||
- Publisher Warranty checks (license validation)
|
||||
- IAP (In-App Purchase) - All services
|
||||
- Partner Autocomplete API
|
||||
- Company Enrichment API
|
||||
- VAT Lookup API
|
||||
- SMS API
|
||||
- Invoice/Expense OCR Extract
|
||||
- Media Library (Stock Images)
|
||||
- Currency Rate Live Updates
|
||||
- CRM Lead Mining
|
||||
- CRM Reveal (Website visitor identification)
|
||||
- Google Calendar Sync
|
||||
- AI/OLG Content Generation
|
||||
- Database Registration
|
||||
- Module Update checks from Odoo Store
|
||||
- Session-based license detection
|
||||
- Frontend expiration panel warnings
|
||||
|
||||
**Use Cases:**
|
||||
- Air-gapped installations
|
||||
- Local development without internet
|
||||
- Enterprise deployments that don't want telemetry
|
||||
- Testing environments
|
||||
|
||||
**WARNING:** This module disables legitimate Odoo services.
|
||||
Only use if you understand the implications.
|
||||
""",
|
||||
'author': 'Fusion Development',
|
||||
'website': 'https://fusiondevelopment.com',
|
||||
'license': 'LGPL-3',
|
||||
'depends': ['base', 'mail', 'web'],
|
||||
'data': [
|
||||
'data/disable_external_services.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'disable_odoo_online/static/src/js/disable_external_links.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
<!-- All config parameters are set via post_init_hook in __init__.py -->
|
||||
<!-- This file is kept for future data records if needed -->
|
||||
</odoo>
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import disable_iap_tools # Patches iap_jsonrpc globally - MUST be first
|
||||
from . import disable_http_requests # Patches requests library to block Odoo domains
|
||||
from . import disable_online_services
|
||||
from . import disable_partner_autocomplete
|
||||
from . import disable_database_expiration
|
||||
from . import disable_all_external
|
||||
from . import disable_session_leaks
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Comprehensive blocking of ALL external Odoo service calls.
|
||||
Only inherits from models that are guaranteed to exist in base Odoo.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from odoo import api, models, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Block Currency Rate Live Updates - Uses res.currency which always exists
|
||||
# ============================================================
|
||||
|
||||
class ResCurrencyDisabled(models.Model):
|
||||
_inherit = 'res.currency'
|
||||
|
||||
@api.model
|
||||
def _get_rates_from_provider(self, provider, date):
|
||||
"""DISABLED: Return empty rates."""
|
||||
_logger.debug("Currency rate provider BLOCKED: provider=%s", provider)
|
||||
return {}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Block Gravatar - Uses res.partner which always exists
|
||||
# ============================================================
|
||||
|
||||
class ResPartnerDisabled(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
@api.model
|
||||
def _get_gravatar_image(self, email):
|
||||
"""DISABLED: Return False to skip gravatar lookup."""
|
||||
_logger.debug("Gravatar lookup BLOCKED for email=%s", email)
|
||||
return False
|
||||
@@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Disable database expiration checks and registration.
|
||||
Consolidates all ir.config_parameter overrides.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from odoo import api, models, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IrConfigParameter(models.Model):
|
||||
"""Override config parameters to prevent expiration and protect license values."""
|
||||
_inherit = 'ir.config_parameter'
|
||||
|
||||
PROTECTED_PARAMS = {
|
||||
'database.expiration_date': '2099-12-31 23:59:59',
|
||||
'database.expiration_reason': 'renewal',
|
||||
'database.enterprise_code': 'PERMANENT_LOCAL',
|
||||
}
|
||||
|
||||
CLEAR_PARAMS = [
|
||||
'database.already_linked_subscription_url',
|
||||
'database.already_linked_email',
|
||||
'database.already_linked_send_mail_url',
|
||||
]
|
||||
|
||||
def init(self, force=False):
|
||||
"""Set permanent valid subscription on module init."""
|
||||
super().init(force=force)
|
||||
self._set_permanent_subscription()
|
||||
|
||||
@api.model
|
||||
def _set_permanent_subscription(self):
|
||||
"""Set database to never expire."""
|
||||
_logger.info("Setting permanent subscription values...")
|
||||
|
||||
for key, value in self.PROTECTED_PARAMS.items():
|
||||
try:
|
||||
self.env.cr.execute("""
|
||||
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date)
|
||||
VALUES (%s, %s, %s, NOW() AT TIME ZONE 'UTC', %s, NOW() AT TIME ZONE 'UTC')
|
||||
ON CONFLICT (key) DO UPDATE SET value = %s, write_date = NOW() AT TIME ZONE 'UTC'
|
||||
""", (key, value, self.env.uid, self.env.uid, value))
|
||||
except Exception as e:
|
||||
_logger.debug("Could not set param %s: %s", key, e)
|
||||
|
||||
for key in self.CLEAR_PARAMS:
|
||||
try:
|
||||
self.env.cr.execute("""
|
||||
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date)
|
||||
VALUES (%s, '', %s, NOW() AT TIME ZONE 'UTC', %s, NOW() AT TIME ZONE 'UTC')
|
||||
ON CONFLICT (key) DO UPDATE SET value = '', write_date = NOW() AT TIME ZONE 'UTC'
|
||||
""", (key, self.env.uid, self.env.uid))
|
||||
except Exception as e:
|
||||
_logger.debug("Could not clear param %s: %s", key, e)
|
||||
|
||||
@api.model
|
||||
def get_param(self, key, default=False):
|
||||
"""Override get_param to return permanent values for protected params."""
|
||||
if key in self.PROTECTED_PARAMS:
|
||||
return self.PROTECTED_PARAMS[key]
|
||||
|
||||
if key in self.CLEAR_PARAMS:
|
||||
return ''
|
||||
|
||||
return super().get_param(key, default)
|
||||
|
||||
def set_param(self, key, value):
|
||||
"""Override set_param to prevent external processes from changing protected values."""
|
||||
if key in self.PROTECTED_PARAMS:
|
||||
if value != self.PROTECTED_PARAMS[key]:
|
||||
_logger.warning("Blocked attempt to change protected param %s to %s", key, value)
|
||||
return True
|
||||
|
||||
if key in self.CLEAR_PARAMS:
|
||||
value = ''
|
||||
|
||||
return super().set_param(key, value)
|
||||
|
||||
|
||||
class DatabaseExpirationCheck(models.AbstractModel):
|
||||
_name = 'disable.odoo.online.expiration'
|
||||
_description = 'Database Expiration Blocker'
|
||||
|
||||
@api.model
|
||||
def check_database_expiration(self):
|
||||
return {
|
||||
'valid': True,
|
||||
'expiration_date': '2099-12-31 23:59:59',
|
||||
'expiration_reason': 'renewal',
|
||||
}
|
||||
|
||||
|
||||
class Base(models.AbstractModel):
|
||||
_inherit = 'base'
|
||||
|
||||
@api.model
|
||||
def _get_database_expiration_date(self):
|
||||
return datetime(2099, 12, 31, 23, 59, 59)
|
||||
|
||||
@api.model
|
||||
def _check_database_enterprise_expiration(self):
|
||||
return True
|
||||
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Block ALL outgoing HTTP requests to Odoo-related domains.
|
||||
This patches the requests library to intercept and block external calls.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from functools import wraps
|
||||
from urllib.parse import urlparse
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# Domains to block - all Odoo external services
|
||||
BLOCKED_DOMAINS = [
|
||||
'odoo.com',
|
||||
'odoofin.com',
|
||||
'odoo.sh',
|
||||
'iap.odoo.com',
|
||||
'iap-services.odoo.com',
|
||||
'partner-autocomplete.odoo.com',
|
||||
'iap-extract.odoo.com',
|
||||
'iap-sms.odoo.com',
|
||||
'upgrade.odoo.com',
|
||||
'apps.odoo.com',
|
||||
'production.odoofin.com',
|
||||
'plaid.com',
|
||||
'yodlee.com',
|
||||
'gravatar.com',
|
||||
'www.gravatar.com',
|
||||
'secure.gravatar.com',
|
||||
]
|
||||
|
||||
# Store original functions
|
||||
_original_request = None
|
||||
_original_get = None
|
||||
_original_post = None
|
||||
|
||||
|
||||
def _is_blocked_url(url):
|
||||
"""Check if the URL should be blocked."""
|
||||
if not url:
|
||||
return False
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
domain = parsed.netloc.lower()
|
||||
for blocked in BLOCKED_DOMAINS:
|
||||
if blocked in domain:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def _blocked_request(method, url, **kwargs):
|
||||
"""Intercept and block requests to Odoo domains."""
|
||||
if _is_blocked_url(url):
|
||||
_logger.warning("HTTP REQUEST BLOCKED: %s %s", method.upper(), url)
|
||||
# Return a mock response
|
||||
response = requests.models.Response()
|
||||
response.status_code = 200
|
||||
response._content = b'{}'
|
||||
response.headers['Content-Type'] = 'application/json'
|
||||
return response
|
||||
return _original_request(method, url, **kwargs)
|
||||
|
||||
|
||||
def _blocked_get(url, **kwargs):
|
||||
"""Intercept and block GET requests."""
|
||||
if _is_blocked_url(url):
|
||||
_logger.warning("HTTP GET BLOCKED: %s", url)
|
||||
response = requests.models.Response()
|
||||
response.status_code = 200
|
||||
response._content = b'{}'
|
||||
response.headers['Content-Type'] = 'application/json'
|
||||
return response
|
||||
return _original_get(url, **kwargs)
|
||||
|
||||
|
||||
def _blocked_post(url, **kwargs):
|
||||
"""Intercept and block POST requests."""
|
||||
if _is_blocked_url(url):
|
||||
_logger.warning("HTTP POST BLOCKED: %s", url)
|
||||
response = requests.models.Response()
|
||||
response.status_code = 200
|
||||
response._content = b'{}'
|
||||
response.headers['Content-Type'] = 'application/json'
|
||||
return response
|
||||
return _original_post(url, **kwargs)
|
||||
|
||||
|
||||
def patch_requests():
|
||||
"""Monkey-patch requests library to block Odoo domains."""
|
||||
global _original_request, _original_get, _original_post
|
||||
|
||||
try:
|
||||
if _original_request is None:
|
||||
_original_request = requests.Session.request
|
||||
_original_get = requests.get
|
||||
_original_post = requests.post
|
||||
|
||||
# Patch Session.request (catches most calls)
|
||||
def patched_session_request(self, method, url, **kwargs):
|
||||
if _is_blocked_url(url):
|
||||
_logger.warning("HTTP SESSION REQUEST BLOCKED: %s %s", method.upper(), url)
|
||||
response = requests.models.Response()
|
||||
response.status_code = 200
|
||||
response._content = b'{}'
|
||||
response.headers['Content-Type'] = 'application/json'
|
||||
response.request = requests.models.PreparedRequest()
|
||||
response.request.url = url
|
||||
response.request.method = method
|
||||
return response
|
||||
return _original_request(self, method, url, **kwargs)
|
||||
|
||||
requests.Session.request = patched_session_request
|
||||
requests.get = _blocked_get
|
||||
requests.post = _blocked_post
|
||||
|
||||
_logger.info("HTTP requests to Odoo domains have been BLOCKED")
|
||||
_logger.info("Blocked domains: %s", ', '.join(BLOCKED_DOMAINS))
|
||||
|
||||
except Exception as e:
|
||||
_logger.warning("Could not patch requests library: %s", e)
|
||||
|
||||
|
||||
# Apply patch when module is imported
|
||||
patch_requests()
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Override the core IAP tools to block ALL external API calls.
|
||||
This is the master switch that blocks ALL Odoo external communications.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from odoo import exceptions, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# Store original function reference
|
||||
_original_iap_jsonrpc = None
|
||||
|
||||
|
||||
def _disabled_iap_jsonrpc(url, method='call', params=None, timeout=15):
|
||||
"""
|
||||
DISABLED: Block all IAP JSON-RPC calls.
|
||||
Returns empty/success response instead of making external calls.
|
||||
"""
|
||||
_logger.info("IAP JSONRPC BLOCKED: %s (method=%s)", url, method)
|
||||
|
||||
# Return appropriate empty responses based on the endpoint
|
||||
if '/authorize' in url:
|
||||
return 'fake_transaction_token_disabled'
|
||||
elif '/capture' in url or '/cancel' in url:
|
||||
return True
|
||||
elif '/credits' in url:
|
||||
return 999999
|
||||
elif 'partner-autocomplete' in url:
|
||||
return []
|
||||
elif 'enrich' in url:
|
||||
return {}
|
||||
elif 'sms' in url:
|
||||
_logger.warning("SMS API call blocked - SMS will not be sent")
|
||||
return {'state': 'success', 'credits': 999999}
|
||||
elif 'extract' in url:
|
||||
return {'status': 'success', 'credits': 999999}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def patch_iap_tools():
|
||||
"""
|
||||
Monkey-patch the iap_jsonrpc function to block external calls.
|
||||
This is called when the module loads.
|
||||
"""
|
||||
global _original_iap_jsonrpc
|
||||
|
||||
try:
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
|
||||
if _original_iap_jsonrpc is None:
|
||||
_original_iap_jsonrpc = iap_tools.iap_jsonrpc
|
||||
|
||||
iap_tools.iap_jsonrpc = _disabled_iap_jsonrpc
|
||||
_logger.info("IAP JSON-RPC calls have been DISABLED globally")
|
||||
|
||||
except ImportError:
|
||||
_logger.debug("IAP module not installed, skipping patch")
|
||||
except Exception as e:
|
||||
_logger.warning("Could not patch IAP tools: %s", e)
|
||||
|
||||
|
||||
# Apply patch when module is imported
|
||||
patch_iap_tools()
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Disable various Odoo online services and external API calls.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from odoo import api, models, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IrModuleModule(models.Model):
|
||||
"""Disable module update checks from Odoo store."""
|
||||
_inherit = 'ir.module.module'
|
||||
|
||||
@api.model
|
||||
def update_list(self):
|
||||
"""
|
||||
Override to prevent fetching from Odoo Apps store.
|
||||
Only scan local addons paths.
|
||||
"""
|
||||
_logger.info("Module update_list: Scanning local addons only (Odoo Apps store disabled)")
|
||||
return super().update_list()
|
||||
|
||||
def button_immediate_upgrade(self):
|
||||
"""Prevent upgrade attempts that might contact Odoo."""
|
||||
_logger.info("Module upgrade: Processing locally only")
|
||||
return super().button_immediate_upgrade()
|
||||
|
||||
|
||||
class IrCron(models.Model):
|
||||
"""Disable scheduled actions that contact Odoo servers."""
|
||||
_inherit = 'ir.cron'
|
||||
|
||||
def _callback(self, cron_name, server_action_id):
|
||||
"""
|
||||
Override to block certain cron jobs that contact Odoo.
|
||||
Odoo 19 signature: _callback(self, cron_name, server_action_id)
|
||||
"""
|
||||
blocked_crons = [
|
||||
'publisher',
|
||||
'warranty',
|
||||
'update_notification',
|
||||
'database_expiration',
|
||||
'iap_enrich',
|
||||
'ocr',
|
||||
'Invoice OCR',
|
||||
'enrich leads',
|
||||
'fetchmail',
|
||||
'online sync',
|
||||
]
|
||||
|
||||
cron_lower = (cron_name or '').lower()
|
||||
for blocked in blocked_crons:
|
||||
if blocked.lower() in cron_lower:
|
||||
_logger.info("Cron BLOCKED (external call): %s", cron_name)
|
||||
return False
|
||||
|
||||
return super()._callback(cron_name, server_action_id)
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
"""Override config settings to prevent external service configuration."""
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
def set_values(self):
|
||||
"""Ensure certain settings stay disabled."""
|
||||
res = super().set_values()
|
||||
|
||||
# Disable any auto-update settings and set permanent expiration
|
||||
params = self.env['ir.config_parameter'].sudo()
|
||||
params.set_param('database.expiration_date', '2099-12-31 23:59:59')
|
||||
params.set_param('database.expiration_reason', 'renewal')
|
||||
params.set_param('database.enterprise_code', 'PERMANENT_LOCAL')
|
||||
|
||||
# Disable IAP endpoint (redirect to nowhere)
|
||||
params.set_param('iap.endpoint', 'http://localhost:65535')
|
||||
|
||||
# Disable various external services
|
||||
params.set_param('partner_autocomplete.endpoint', 'http://localhost:65535')
|
||||
params.set_param('iap_extract_endpoint', 'http://localhost:65535')
|
||||
params.set_param('olg.endpoint', 'http://localhost:65535')
|
||||
params.set_param('mail.media_library_endpoint', 'http://localhost:65535')
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class PublisherWarrantyContract(models.AbstractModel):
|
||||
"""Completely disable publisher warranty checks."""
|
||||
_inherit = 'publisher_warranty.contract'
|
||||
|
||||
@api.model
|
||||
def _get_sys_logs(self):
|
||||
"""
|
||||
DISABLED: Do not contact Odoo servers.
|
||||
Returns fake successful response.
|
||||
"""
|
||||
_logger.info("Publisher warranty _get_sys_logs BLOCKED")
|
||||
return {
|
||||
'messages': [],
|
||||
'enterprise_info': {
|
||||
'expiration_date': '2099-12-31 23:59:59',
|
||||
'expiration_reason': 'renewal',
|
||||
'enterprise_code': 'PERMANENT_LOCAL',
|
||||
}
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_message(self):
|
||||
"""DISABLED: Return empty message."""
|
||||
_logger.info("Publisher warranty _get_message BLOCKED")
|
||||
return {}
|
||||
|
||||
def update_notification(self, cron_mode=True):
|
||||
"""
|
||||
DISABLED: Do not send any data to Odoo servers.
|
||||
Just update local parameters with permanent values.
|
||||
"""
|
||||
_logger.info("Publisher warranty update_notification BLOCKED")
|
||||
|
||||
# Set permanent valid subscription parameters
|
||||
params = self.env['ir.config_parameter'].sudo()
|
||||
params.set_param('database.expiration_date', '2099-12-31 23:59:59')
|
||||
params.set_param('database.expiration_reason', 'renewal')
|
||||
params.set_param('database.enterprise_code', 'PERMANENT_LOCAL')
|
||||
|
||||
# Clear any "already linked" parameters
|
||||
params.set_param('database.already_linked_subscription_url', '')
|
||||
params.set_param('database.already_linked_email', '')
|
||||
params.set_param('database.already_linked_send_mail_url', '')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
"""Block certain routes that call external services."""
|
||||
_inherit = 'ir.http'
|
||||
|
||||
@classmethod
|
||||
def _pre_dispatch(cls, rule, arguments):
|
||||
"""Log and potentially block external service routes."""
|
||||
# List of route patterns that should be blocked
|
||||
blocked_routes = [
|
||||
'/iap/',
|
||||
'/partner_autocomplete/',
|
||||
'/google_',
|
||||
'/ocr/',
|
||||
'/sms/',
|
||||
]
|
||||
|
||||
# Note: We don't actually block here as it might break functionality
|
||||
# The actual blocking happens at the API/model level
|
||||
return super()._pre_dispatch(rule, arguments)
|
||||
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Disable Partner Autocomplete external API calls.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from odoo import api, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
"""Disable partner autocomplete from Odoo API."""
|
||||
_inherit = 'res.partner'
|
||||
|
||||
@api.model
|
||||
def autocomplete(self, query, timeout=15):
|
||||
"""
|
||||
DISABLED: Return empty results instead of calling Odoo's partner API.
|
||||
"""
|
||||
_logger.debug("Partner autocomplete DISABLED - returning empty results for: %s", query)
|
||||
return []
|
||||
|
||||
@api.model
|
||||
def enrich_company(self, company_domain, partner_gid, vat, timeout=15):
|
||||
"""
|
||||
DISABLED: Return empty data instead of calling Odoo's enrichment API.
|
||||
"""
|
||||
_logger.debug("Partner enrichment DISABLED - returning empty for domain: %s", company_domain)
|
||||
return {}
|
||||
|
||||
@api.model
|
||||
def read_by_vat(self, vat, timeout=15):
|
||||
"""
|
||||
DISABLED: Return empty data instead of calling Odoo's VAT lookup API.
|
||||
"""
|
||||
_logger.debug("Partner VAT lookup DISABLED - returning empty for VAT: %s", vat)
|
||||
return {}
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
"""Disable company autocomplete features."""
|
||||
_inherit = 'res.company'
|
||||
|
||||
@api.model
|
||||
def autocomplete(self, query, timeout=15):
|
||||
"""
|
||||
DISABLED: Return empty results for company autocomplete.
|
||||
"""
|
||||
_logger.debug("Company autocomplete DISABLED - returning empty results")
|
||||
return []
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Block session-based information leaks and frontend detection mechanisms.
|
||||
Specifically targets the web_enterprise module's subscription checks.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from odoo import api, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
"""
|
||||
Override session info to prevent frontend from detecting license status.
|
||||
This specifically blocks web_enterprise's ExpirationPanel from showing.
|
||||
"""
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def session_info(self):
|
||||
"""
|
||||
Override session info to set permanent valid subscription data.
|
||||
This prevents the frontend ExpirationPanel from showing warnings.
|
||||
|
||||
Key overrides:
|
||||
- expiration_date: Set to far future (2099)
|
||||
- expiration_reason: Set to 'renewal' (valid subscription)
|
||||
- warning: Set to False to hide all warning banners
|
||||
"""
|
||||
result = super().session_info()
|
||||
|
||||
# Override expiration-related session data
|
||||
# These are read by enterprise_subscription_service.js
|
||||
result['expiration_date'] = '2099-12-31 23:59:59'
|
||||
result['expiration_reason'] = 'renewal'
|
||||
result['warning'] = False # Critical: prevents warning banners
|
||||
|
||||
# Remove any "already linked" subscription info
|
||||
# These could trigger redirect prompts
|
||||
result.pop('already_linked_subscription_url', None)
|
||||
result.pop('already_linked_email', None)
|
||||
result.pop('already_linked_send_mail_url', None)
|
||||
|
||||
_logger.debug("Session info patched - expiration set to 2099, warnings disabled")
|
||||
return result
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
"""
|
||||
Override user creation/modification to prevent subscription checks.
|
||||
When users are created, Odoo Enterprise normally contacts Odoo servers
|
||||
to verify the subscription allows that many users.
|
||||
"""
|
||||
_inherit = 'res.users'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""
|
||||
Override create to ensure no external subscription check is triggered.
|
||||
The actual check happens in publisher_warranty.contract which we've
|
||||
already blocked, but this is an extra safety measure.
|
||||
"""
|
||||
_logger.info("Creating %d user(s) - subscription check DISABLED", len(vals_list))
|
||||
|
||||
# Create users normally - no external checks will happen
|
||||
# because publisher_warranty.contract.update_notification is blocked
|
||||
users = super().create(vals_list)
|
||||
|
||||
# Don't trigger any warranty checks
|
||||
return users
|
||||
|
||||
def write(self, vals):
|
||||
"""
|
||||
Override write to log user modifications.
|
||||
"""
|
||||
result = super().write(vals)
|
||||
|
||||
# If internal user status changed, log it
|
||||
if 'share' in vals or 'groups_id' in vals:
|
||||
_logger.info("User permissions updated - subscription check DISABLED")
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,38 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
/**
|
||||
* This module intercepts clicks on external Odoo links to prevent
|
||||
* referrer leakage when users click help/documentation/upgrade links.
|
||||
*/
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
||||
// Store original window.open
|
||||
const originalOpen = browser.open;
|
||||
|
||||
// Override browser.open to add referrer protection
|
||||
browser.open = function(url, target, features) {
|
||||
if (url && typeof url === 'string') {
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
// Check if it's an Odoo external link
|
||||
const odooPatterns = [
|
||||
'odoo.com',
|
||||
'odoo.sh',
|
||||
'accounts.odoo',
|
||||
];
|
||||
|
||||
const isOdooLink = odooPatterns.some(pattern => urlLower.includes(pattern));
|
||||
|
||||
if (isOdooLink) {
|
||||
// For Odoo links, open with noreferrer to prevent leaking your domain
|
||||
const newWindow = originalOpen.call(this, url, target || '_blank', 'noopener,noreferrer');
|
||||
return newWindow;
|
||||
}
|
||||
}
|
||||
|
||||
return originalOpen.call(this, url, target, features);
|
||||
};
|
||||
|
||||
console.log('[disable_odoo_online] External link protection loaded');
|
||||
|
||||
Reference in New Issue
Block a user