feat: enhance tenant resolution logic with host candidate handling
This commit is contained in:
+80
-46
@@ -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,54 +262,67 @@ 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()
|
||||||
hostname, port = _parse_port_from_host(host)
|
primary_host = host_candidates[0] if host_candidates else _first_host_token(getattr(request, 'host', ''))
|
||||||
self.port = port
|
logger.info(
|
||||||
logger.info(f"Tenant resolution start: request.host={host} request.headers={dict(request.headers)}")
|
"Tenant resolution start: request.host=%s host_candidates=%s request.headers=%s",
|
||||||
if port:
|
getattr(request, 'host', ''),
|
||||||
tenant_from_port = _tenant_id_for_port(port)
|
host_candidates,
|
||||||
if tenant_from_port:
|
dict(request.headers),
|
||||||
self.tenant_id = tenant_from_port
|
)
|
||||||
self.config = get_tenant_config(tenant_from_port)
|
|
||||||
session['tenant_id'] = tenant_from_port
|
for host in host_candidates:
|
||||||
logger.info(
|
hostname, port = _parse_port_from_host(host)
|
||||||
f"Tenant resolution by port: host={host} port={port} tenant={tenant_from_port} config={self.config}"
|
if port:
|
||||||
)
|
self.port = port
|
||||||
return self._get_db_name(tenant_from_port)
|
tenant_from_port = _tenant_id_for_port(port)
|
||||||
logger.info(f"Tenant port not mapped: host={host} port={port}")
|
if tenant_from_port:
|
||||||
|
self.tenant_id = tenant_from_port
|
||||||
|
self.config = get_tenant_config(tenant_from_port)
|
||||||
|
session['tenant_id'] = tenant_from_port
|
||||||
|
logger.info(
|
||||||
|
f"Tenant resolution by port: host={host} port={port} tenant={tenant_from_port} config={self.config}"
|
||||||
|
)
|
||||||
|
return self._get_db_name(tenant_from_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:
|
||||||
direct_host_match = _find_registered_tenant_id(host_without_port)
|
host_without_port, _ = _parse_port_from_host(host)
|
||||||
if direct_host_match:
|
host_without_port = (host_without_port or '').strip().lower()
|
||||||
self.subdomain = host_without_port
|
if not host_without_port:
|
||||||
self.tenant_id = direct_host_match
|
continue
|
||||||
self.config = get_tenant_config(direct_host_match)
|
|
||||||
session['tenant_id'] = direct_host_match
|
|
||||||
logger.info(
|
|
||||||
f"Tenant resolution by direct host match: host={host} tenant={direct_host_match} config={self.config}"
|
|
||||||
)
|
|
||||||
return self._get_db_name(direct_host_match)
|
|
||||||
|
|
||||||
if host_without_port and not _is_ip_host(host_without_port):
|
direct_host_match = _find_registered_tenant_id(host_without_port)
|
||||||
parts = host_without_port.split('.')
|
if direct_host_match:
|
||||||
if len(parts) >= 2:
|
self.subdomain = host_without_port
|
||||||
potential_subdomain = parts[0]
|
self.tenant_id = direct_host_match
|
||||||
if potential_subdomain not in ('www', 'api', 'admin', 'app', 'mail'):
|
self.config = get_tenant_config(direct_host_match)
|
||||||
matched_tenant = _find_registered_tenant_id(potential_subdomain)
|
session['tenant_id'] = direct_host_match
|
||||||
if matched_tenant:
|
logger.info(
|
||||||
self.subdomain = potential_subdomain
|
f"Tenant resolution by direct host match: host={host} tenant={direct_host_match} config={self.config}"
|
||||||
self.tenant_id = matched_tenant
|
)
|
||||||
self.config = get_tenant_config(matched_tenant)
|
return self._get_db_name(direct_host_match)
|
||||||
session['tenant_id'] = matched_tenant
|
|
||||||
logger.info(
|
if host_without_port and not _is_ip_host(host_without_port):
|
||||||
f"Tenant resolution by subdomain: host={host} tenant={matched_tenant} config={self.config}"
|
parts = host_without_port.split('.')
|
||||||
)
|
if len(parts) >= 2:
|
||||||
return self._get_db_name(matched_tenant)
|
potential_subdomain = parts[0]
|
||||||
logger.info(f"Tenant subdomain not registered: {potential_subdomain}")
|
if potential_subdomain not in ('www', 'api', 'admin', 'app', 'mail'):
|
||||||
else:
|
matched_tenant = _find_registered_tenant_id(potential_subdomain)
|
||||||
logger.info(f"Tenant subdomain ignored: {potential_subdomain}")
|
if matched_tenant:
|
||||||
|
self.subdomain = potential_subdomain
|
||||||
|
self.tenant_id = matched_tenant
|
||||||
|
self.config = get_tenant_config(matched_tenant)
|
||||||
|
session['tenant_id'] = matched_tenant
|
||||||
|
logger.info(
|
||||||
|
f"Tenant resolution by subdomain: host={host} tenant={matched_tenant} config={self.config}"
|
||||||
|
)
|
||||||
|
return self._get_db_name(matched_tenant)
|
||||||
|
logger.info(f"Tenant subdomain not registered: {potential_subdomain}")
|
||||||
|
else:
|
||||||
|
logger.info(f"Tenant subdomain ignored: {potential_subdomain}")
|
||||||
|
|
||||||
# Priority 4: sticky tenant from the authenticated session
|
# Priority 4: sticky tenant from the authenticated session
|
||||||
session_tenant = session.get('tenant_id', '').strip() if session.get('tenant_id') else ''
|
session_tenant = session.get('tenant_id', '').strip() if session.get('tenant_id') else ''
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user