Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View 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

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

View 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,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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