Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4d5cd6436 | |||
| 9fd9d63e31 | |||
| 08cce1b884 | |||
| adde73dffb | |||
| da17667d90 | |||
| c69231fa77 | |||
| ac43178b29 |
+1
-1
@@ -1 +1 @@
|
|||||||
v0.7.74
|
v0.7.77
|
||||||
|
|||||||
@@ -100,9 +100,29 @@ DEFAULTS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Load configuration file
|
# Load configuration file
|
||||||
CONFIG_PATH = os.path.join(BASE_DIR, '..', 'config.json')
|
def _resolve_config_path():
|
||||||
|
"""Resolve runtime config path with robust fallbacks for Docker deployments."""
|
||||||
|
env_path = os.getenv('INVENTAR_CONFIG_PATH', '').strip()
|
||||||
|
if env_path:
|
||||||
|
return env_path
|
||||||
|
|
||||||
|
candidates = [
|
||||||
|
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR))), 'config.json'),
|
||||||
|
os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'config.json'),
|
||||||
|
os.path.join(os.path.dirname(BASE_DIR), 'config.json'),
|
||||||
|
os.path.join(BASE_DIR, '..', 'config.json'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
if os.path.isfile(candidate):
|
||||||
|
return os.path.abspath(candidate)
|
||||||
|
|
||||||
|
return os.path.abspath(candidates[0])
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_PATH = _resolve_config_path()
|
||||||
try:
|
try:
|
||||||
with open(CONFIG_PATH, 'r') as f:
|
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
||||||
_conf = json.load(f)
|
_conf = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
_conf = {}
|
_conf = {}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Provides methods for creating, validating, and retrieving user information.
|
|||||||
'''
|
'''
|
||||||
import hashlib
|
import hashlib
|
||||||
import copy
|
import copy
|
||||||
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
@@ -59,6 +60,35 @@ def _get_tenant_db(client):
|
|||||||
return client[cfg.MONGODB_DB]
|
return client[cfg.MONGODB_DB]
|
||||||
|
|
||||||
|
|
||||||
|
def _has_tenant_configs():
|
||||||
|
for module_name in ('tenant', 'Web.tenant'):
|
||||||
|
try:
|
||||||
|
tenant_module = importlib.import_module(module_name)
|
||||||
|
tenant_registry = getattr(tenant_module, 'TENANT_REGISTRY', None)
|
||||||
|
if isinstance(tenant_registry, dict) and tenant_registry:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return isinstance(getattr(cfg, 'TENANT_CONFIGS', None), dict) and bool(cfg.TENANT_CONFIGS)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_request_tenant_db():
|
||||||
|
for module_name in ('tenant', 'Web.tenant'):
|
||||||
|
try:
|
||||||
|
tenant_module = importlib.import_module(module_name)
|
||||||
|
get_tenant_context = getattr(tenant_module, 'get_tenant_context', None)
|
||||||
|
if not callable(get_tenant_context):
|
||||||
|
continue
|
||||||
|
ctx = get_tenant_context()
|
||||||
|
if ctx and ctx.tenant_id:
|
||||||
|
return ctx.db_name or ctx.resolve_tenant(), ctx.tenant_id
|
||||||
|
if ctx and ctx.db_name and not _has_tenant_configs():
|
||||||
|
return ctx.db_name, None
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("Tenant context import %s failed: %s", module_name, exc)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def build_name_synonym(first_name, last_name=''):
|
def build_name_synonym(first_name, last_name=''):
|
||||||
"""Build a deterministic, non-personalized short alias from 2 letters each."""
|
"""Build a deterministic, non-personalized short alias from 2 letters each."""
|
||||||
first = _clean_name_fragment(first_name)
|
first = _clean_name_fragment(first_name)
|
||||||
@@ -424,22 +454,26 @@ def check_nm_pwd(username, password):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: User document if credentials are valid, None otherwise
|
dict: User document if credentials are valid, None otherwise
|
||||||
"""
|
"""
|
||||||
db_name = cfg.MONGODB_DB
|
db_name, tenant_id = _resolve_request_tenant_db()
|
||||||
tenant_db = None
|
|
||||||
ctx = None
|
ctx = None
|
||||||
try:
|
try:
|
||||||
from tenant import get_tenant_context
|
from tenant import get_tenant_context
|
||||||
ctx = get_tenant_context()
|
ctx = get_tenant_context()
|
||||||
if ctx and ctx.tenant_id:
|
except Exception:
|
||||||
tenant_db = ctx.db_name or ctx.resolve_tenant()
|
ctx = None
|
||||||
db_name = tenant_db
|
|
||||||
except Exception as exc:
|
if not db_name:
|
||||||
logger.exception(f"Failed to resolve tenant context in check_nm_pwd: {exc}")
|
if _has_tenant_configs():
|
||||||
|
logger.warning(
|
||||||
|
"Refusing default DB fallback for login because tenant configs exist and no tenant was resolved."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
db_name = cfg.MONGODB_DB
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"check_nm_pwd start: username=%r tenant=%r db=%r host=%r port=%r uri=%r",
|
"check_nm_pwd start: username=%r tenant=%r db=%r host=%r port=%r uri=%r",
|
||||||
username,
|
username,
|
||||||
ctx.tenant_id if ctx else None,
|
tenant_id,
|
||||||
db_name,
|
db_name,
|
||||||
cfg.MONGODB_HOST,
|
cfg.MONGODB_HOST,
|
||||||
cfg.MONGODB_PORT,
|
cfg.MONGODB_PORT,
|
||||||
@@ -644,15 +678,17 @@ def get_user(username):
|
|||||||
return users.find_one({'Username': username}) or users.find_one({'username': username})
|
return users.find_one({'Username': username}) or users.find_one({'username': username})
|
||||||
|
|
||||||
# Try current tenant first when available
|
# Try current tenant first when available
|
||||||
try:
|
tenant_db, tenant_id = _resolve_request_tenant_db()
|
||||||
from tenant import get_tenant_context
|
if tenant_db:
|
||||||
ctx = get_tenant_context()
|
user = find_in_db(tenant_db)
|
||||||
if ctx and ctx.db_name:
|
|
||||||
user = find_in_db(ctx.db_name)
|
|
||||||
if user:
|
if user:
|
||||||
return user
|
return user
|
||||||
except Exception:
|
|
||||||
pass
|
if _has_tenant_configs() and tenant_db is None:
|
||||||
|
logger.warning(
|
||||||
|
"Refusing default DB fallback for user lookup because tenant configs exist and no tenant was resolved."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
# Fallback to default configured database
|
# Fallback to default configured database
|
||||||
user = find_in_db(cfg.MONGODB_DB)
|
user = find_in_db(cfg.MONGODB_DB)
|
||||||
|
|||||||
+40
-6
@@ -66,6 +66,27 @@ def _parse_port_from_host(host):
|
|||||||
return host, None
|
return host, None
|
||||||
|
|
||||||
|
|
||||||
|
def _first_host_token(value):
|
||||||
|
raw = str(value or '').strip()
|
||||||
|
if not raw:
|
||||||
|
return ''
|
||||||
|
return raw.split(',', 1)[0].strip().lower()
|
||||||
|
|
||||||
|
|
||||||
|
def _request_host_candidates():
|
||||||
|
candidates = []
|
||||||
|
for header_name in ('X-Forwarded-Host', 'X-Original-Host', 'Host'):
|
||||||
|
token = _first_host_token(request.headers.get(header_name, ''))
|
||||||
|
if token and token not in candidates:
|
||||||
|
candidates.append(token)
|
||||||
|
|
||||||
|
direct_host = _first_host_token(getattr(request, 'host', ''))
|
||||||
|
if direct_host and direct_host not in candidates:
|
||||||
|
candidates.append(direct_host)
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
def _tenant_id_for_port(port):
|
def _tenant_id_for_port(port):
|
||||||
"""Map a host port to a registered tenant ID via tenant configs or env overrides."""
|
"""Map a host port to a registered tenant ID via tenant configs or env overrides."""
|
||||||
for tenant_id, config in TENANT_REGISTRY.items():
|
for tenant_id, config in TENANT_REGISTRY.items():
|
||||||
@@ -241,12 +262,20 @@ class TenantContext:
|
|||||||
session['tenant_id'] = matched_tenant
|
session['tenant_id'] = matched_tenant
|
||||||
return self._get_db_name(matched_tenant)
|
return self._get_db_name(matched_tenant)
|
||||||
|
|
||||||
# Priority 2: Port-based tenant mapping
|
# Priority 2: Port/host based tenant mapping
|
||||||
host = request.host.lower()
|
host_candidates = _request_host_candidates()
|
||||||
|
primary_host = host_candidates[0] if host_candidates else _first_host_token(getattr(request, 'host', ''))
|
||||||
|
logger.info(
|
||||||
|
"Tenant resolution start: request.host=%s host_candidates=%s request.headers=%s",
|
||||||
|
getattr(request, 'host', ''),
|
||||||
|
host_candidates,
|
||||||
|
dict(request.headers),
|
||||||
|
)
|
||||||
|
|
||||||
|
for host in host_candidates:
|
||||||
hostname, port = _parse_port_from_host(host)
|
hostname, port = _parse_port_from_host(host)
|
||||||
self.port = port
|
|
||||||
logger.info(f"Tenant resolution start: request.host={host} request.headers={dict(request.headers)}")
|
|
||||||
if port:
|
if port:
|
||||||
|
self.port = port
|
||||||
tenant_from_port = _tenant_id_for_port(port)
|
tenant_from_port = _tenant_id_for_port(port)
|
||||||
if tenant_from_port:
|
if tenant_from_port:
|
||||||
self.tenant_id = tenant_from_port
|
self.tenant_id = tenant_from_port
|
||||||
@@ -259,7 +288,12 @@ class TenantContext:
|
|||||||
logger.info(f"Tenant port not mapped: host={host} port={port}")
|
logger.info(f"Tenant port not mapped: host={host} port={port}")
|
||||||
|
|
||||||
# Priority 3: Subdomain extraction
|
# Priority 3: Subdomain extraction
|
||||||
host_without_port = (hostname or '').strip().lower()
|
for host in host_candidates:
|
||||||
|
host_without_port, _ = _parse_port_from_host(host)
|
||||||
|
host_without_port = (host_without_port or '').strip().lower()
|
||||||
|
if not host_without_port:
|
||||||
|
continue
|
||||||
|
|
||||||
direct_host_match = _find_registered_tenant_id(host_without_port)
|
direct_host_match = _find_registered_tenant_id(host_without_port)
|
||||||
if direct_host_match:
|
if direct_host_match:
|
||||||
self.subdomain = host_without_port
|
self.subdomain = host_without_port
|
||||||
@@ -296,7 +330,7 @@ class TenantContext:
|
|||||||
self.tenant_id = session_tenant
|
self.tenant_id = session_tenant
|
||||||
self.config = get_tenant_config(session_tenant)
|
self.config = get_tenant_config(session_tenant)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Tenant resolution by session: host={host} tenant={session_tenant} config={self.config}"
|
f"Tenant resolution by session: host={primary_host} tenant={session_tenant} config={self.config}"
|
||||||
)
|
)
|
||||||
return self._get_db_name(session_tenant)
|
return self._get_db_name(session_tenant)
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ services:
|
|||||||
INVENTAR_MONGODB_HOST: mongodb
|
INVENTAR_MONGODB_HOST: mongodb
|
||||||
INVENTAR_MONGODB_PORT: "27017"
|
INVENTAR_MONGODB_PORT: "27017"
|
||||||
INVENTAR_MONGODB_DB: inventar_default
|
INVENTAR_MONGODB_DB: inventar_default
|
||||||
|
INVENTAR_CONFIG_PATH: /app/config.json
|
||||||
|
|
||||||
# Redis Configuration (for sessions and caching)
|
# Redis Configuration (for sessions and caching)
|
||||||
INVENTAR_REDIS_HOST: redis
|
INVENTAR_REDIS_HOST: redis
|
||||||
|
|||||||
Reference in New Issue
Block a user