// Multi-tenant LDAP helpers. The LDAP listener is process-level and runs in the // process's default tenant context, so each bind/search must establish its own // tenant context. The tenant is encoded as an extra dc component in the DN // (uid=,ou=people,dc=,dc=saltcorn,dc=local); the bare base // (dc=saltcorn,dc=local) means the default tenant (no wrap). resolveTenant // validates the token against the live tenant set and DENIES unknown tenants so // a crafted DN cannot reach another tenant's schema. const db = require("@saltcorn/data/db"); // Capture the dc= immediately preceding the dc=saltcorn,dc=local base. const TENANT_RE = /dc=([^,]+),\s*dc=saltcorn,\s*dc=local\s*$/i; const tenantFromDn = (dn) => { const s = typeof dn === "string" ? dn : (dn ? dn.toString() : ""); const m = s.match(TENANT_RE); return m ? m[1].trim().toLowerCase() : null; }; // Resolve a parsed tenant token to a context decision: // { tenant: null } -> run in the default context (no wrap) // { tenant: "t1" } -> run inside runWithTenant("t1") // { deny: true } -> reject (unknown tenant / illegal on single-tenant) const resolveTenant = (token) => { const def = String((db.connectObj && db.connectObj.default_schema) || "public").toLowerCase(); if (!token || token === def) { return { tenant: null }; } if (!db.is_it_multi_tenant || !db.is_it_multi_tenant()) { return { deny: true }; } let known; try { // getAllTenants is a MODULE-level export (returns the live {subdomain:State} // map), NOT a method on the State instance. const { getAllTenants } = require("@saltcorn/data/db/state"); known = new Set(Object.keys(getAllTenants() || {}).map((t) => String(t).toLowerCase())); } catch (e) { known = new Set(); } if (known.has(token)) { return { tenant: token }; } return { deny: true }; }; const withTenant = (tenant, fn) => { return tenant ? db.runWithTenant(tenant, fn) : fn(); }; module.exports = { tenantFromDn, resolveTenant, withTenant };