ldap-rest 0.13.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/index.js CHANGED
@@ -1 +1 @@
1
- import{fileURLToPath as e}from"url";import{join as t,dirname as r}from"path";import i from"express";import s from"body-parser";import{parseConfig as o}from"../lib/parseConfig.js";import{keepRawBody as n}from"../lib/rawBody.js";import a from"../config/args.js";import h from"../lib/ldapActions.js";import{buildLogger as l}from"../logger/winston.js";import{setLogger as u}from"../lib/expressFormatedResponses.js";import{prefixCoversPath as d}from"../lib/auth/base.js";import{assertAuthzComposition as g}from"../lib/authz/composition.js";import{recordHookOwner as c}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isChildOf,isDnInBranch,isDummyMemberDn,launchHooks,launchHooksChained,normalizeDn,organizationLink,parseDn,rdnValue,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import p from"../plugins/priority.json.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";class DM{constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this._composed=!1,this.config=o(a),this.app=i(),this.app.use(this.config.scim_prefix||"/scim/v2",s.json({type:["application/json","application/scim+json"],limit:`${this.config.scim_bulk_max_payload_size||1048576}b`,verify:n})),this.app.use(s.json({verify:n})),this.app.use(s.urlencoded({extended:!0,verify:n})),this.logger=l(this.config),this.ldap=new h(this),u(this.logger),this.operationSequence=0;const e=[];if(this.config.plugin){const t=this.config.plugin.find(e=>e.includes("configApi"));let r=this.config.plugin.filter(e=>!e.includes("configApi"));const i=this.logger,s=(async()=>{for(const e of p){const t=this.resolveModule(DM.moduleOf(e)),s=r.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==s.length){r=r.filter(e=>!s.includes(e));for(const e of s)i.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(s),e.push(s.then(async()=>{const e=r.map(e=>(i.debug(`Loading ${e} in the parallel batch`),this.loadPlugin(e)));await Promise.all(e)})),t&&e.push(Promise.all(e).then(()=>this.loadPlugin(t)))}this.ready=new Promise((t,r)=>{e.length>0?Promise.all(e).then(async()=>{this.setupErrorMiddleware(),this.warnUnauthenticatedRoutes(),await g(this),this._composed=!0,this.afterLoad(),t()}).catch(e=>r(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}routerStack(){const e=this.app;return(e.router??e._router)?.stack}withConfig(e){return Object.assign(Object.create(Object.getPrototypeOf(this)),this,{config:e})}mountAuthDispatcher(e){const t=e.roles||[];t.includes("protect")||t.includes("logging")||this.mountAuthDispatcherNow()}mountAuthDispatcherNow(){this._authState.dispatcherMounted||(this._authState.dispatcherMounted=!0,this.app.use((e,t,r)=>{this.dispatchAuth(e,t,r)}))}registerAuthenticator(e){this.mountAuthDispatcherNow(),this.authenticators.includes(e)||this.authenticators.push(e)}dispatchAuth(e,t,r){let i=-1,s=[];for(const t of this.authenticators)for(const r of t.pathPrefixes)d(r,e.path)&&(r.length>i?(i=r.length,s=[t]):r.length!==i||s.includes(t)||s.push(t));if(i<0&&(s=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===s.length)return r();const finish=i=>{if(i>=s.length)return r();const o=s[i];if(!o.afterChain)return finish(i+1);o.afterChain(e,t,()=>finish(i+1))},o=[];e.authenticators=o;const run=r=>{if(r>=s.length)return finish(0);const i=s[r];i.authenticate(e,t,()=>{i.vouchedFor(e)&&o.push(i.name),run(r+1)})};run(0)}afterLoad(){for(const e of Object.values(this.loadedPlugins))try{e.afterLoad?.()}catch(t){this.logger.error(`Plugin ${e.name}: afterLoad failed: ${String(t)}`)}}warnUnauthenticatedRoutes(){const e=Object.values(this.loadedPlugins).filter(e=>e.roles?.includes("auth"));if(0===e.length)return[];const t=[];for(const r of e){if(!r.pathPrefixes||0===r.pathPrefixes.length)return[];t.push(...r.pathPrefixes)}const r=this.routerStack(),i=new Set;for(const e of r||[]){const t=e.route?.path;"string"==typeof t?i.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&i.add(e))}const s=[...i].filter(e=>!t.some(t=>e===t||e.startsWith(`${t}/`)));return s.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication: ${s.sort().join(", ")}`),s.sort()}setupErrorMiddleware(){if(this._errorMiddlewareSetup&&this._errorMiddleware){const e=this.routerStack();if(e){const t=e.findIndex(e=>e.handle===this._errorMiddleware);-1!==t&&e.splice(t,1)}}this._errorMiddleware=(e,t,r,i)=>{let s="statusCode"in e?e.statusCode:500,o=e.message;if(500===s&&/\[authz-forbidden\]/.test(e.message)?(s=403,o="Token does not have permission on this branch"):403===s&&/\[authz-forbidden\]/.test(e.message)&&(o="Token does not have permission on this branch"),s>=400&&s<500)return this.logger.warn(`Client error ${s} in request ${t.method} ${t.path}: ${o}`),void(r.headersSent||r.status(s).json({error:o}));this.logger.error(`Server error ${s} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),r.headersSent||r.status(s).json({error:"Internal Server Error",message:this.config.debug?e.message:"An error occurred"})},this.app.use(this._errorMiddleware),this._errorMiddlewareSetup=!0}run(){return process.on("uncaughtException",e=>{this.logger.error(`Uncaught exception: ${e.message}`,{stack:e.stack})}),process.on("unhandledRejection",(e,t)=>{this.logger.error(`Unhandled promise rejection: ${e}`,{reason:e,promise:t})}),new Promise((e,t)=>{this.server=this.app.listen(this.config.port,r=>{r?(this.logger.error(`Error starting server: ${r}`),t(r)):(this.logger.debug(`Server started on port ${this.config.port}`),e())})})}stop(){this.app.removeAllListeners(),this.server?.close(),this.logger.debug("Server stopped")}static moduleOf(e){const t=e.indexOf(":");return-1===t?e:e.substring(0,t)}resolveModule(i){return i.startsWith("core/")?i.replace("core/",t(r(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):i.startsWith(".")?e(new URL(i,import.meta.url)):i}loadPlugin(e){let t,r;if(/:/.test(e)){let i=e.substring(e.indexOf(":")+1);if(e=e.substring(0,e.indexOf(":")),/:/.test(i)){t=i.substring(0,i.indexOf(":")),t||(t=void 0),i=i.substring(i.indexOf(":")+1);try{r=JSON.parse(i),"object"!=typeof r?(this.logger.error(`Overrides for plugin ${e} are not valid: ${i}`),r=void 0):this.logger.debug(`Overrides for plugin ${t||e}: ${i}`)}catch(t){this.logger.error(`Failed to parse overrides for plugin ${e}: ${t}, using ${i}`),r=void 0}}else t=i,t||(t=void 0)}else t=void 0;return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((i,s)=>{import(e).then(async o=>{let n;if(o&&o.default&&(o=o.default),r){const e={...this.config,...r};n=new o(this.withConfig(e))}else n=new o(this);if(!n)return s(new Error(`Unable to load ${e}`));i(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>s(new Error(`Failed to load plugin ${e}: ${t}`)))})}async admitToComposition(e){this.loadedPlugins[e.name]=e;try{await g(this,e)}finally{delete this.loadedPlugins[e.name]}}async registerPlugin(e,t,r){if(t.name||(t.name=e),r&&(t.name=r),this.loadedPlugins[t.name])return this.logger.warn(`Plugin ${e} not registered: the name "${t.name}" is already taken. This instance is dropped and its routes will not exist. To load the same plugin twice, give each one a name: --plugin '${e}:${t.name}2:{…}'`),!1;if(this.logger.debug(`Registering plugin ${e} as ${t.name}`),t.dependencies)for(const e in t.dependencies)this.loadedPlugins[e]||(this.logger.debug(`Plugin ${t.name} depends on ${e}, loading it first`),await this.loadPlugin(t.dependencies[e]));if(this._composed&&await this.admitToComposition(t),t.api&&(this.mountAuthDispatcher(t),this.logger.debug(`Plugin ${t.name} has API, registering it`),await t.api(this.app),this._errorMiddlewareSetup&&this.setupErrorMiddleware()),t.hooks)for(const e in t.hooks){this.logger.debug(`Plugin ${t.name} has hook ${e}, registering it`);const r=t.hooks[e];if(this.hooks[e]||(this.hooks[e]=[]),!r||"function"!=typeof r)throw new Error(`Plugin ${t.name}: hook ${e} is invalid`);this.hooks[e].push(r),c(r,t.name,e)}return this.loadedPlugins[t.name]=t,!0}}export{DM,c as recordHookOwner};
1
+ import{fileURLToPath as e}from"url";import{join as t,dirname as r}from"path";import i from"express";import s from"body-parser";import{parseConfig as o}from"../lib/parseConfig.js";import{keepRawBody as n}from"../lib/rawBody.js";import a from"../config/args.js";import h from"../lib/ldapActions.js";import{buildLogger as l}from"../logger/winston.js";import{setLogger as d}from"../lib/expressFormatedResponses.js";import{prefixCoversPath as u}from"../lib/auth/base.js";import{assertAuthzComposition as g}from"../lib/authz/composition.js";import{recordHookOwner as c}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isChildOf,isDnInBranch,isDummyMemberDn,launchHooks,launchHooksChained,normalizeDn,organizationLink,parseDn,rdnValue,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import p from"../plugins/priority.json.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";class DM{constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this.bodyParsers=i.Router(),this._composed=!1,this.config=o(a),this.app=i(),this.app.use(this.bodyParsers),this.app.use(s.json({verify:n})),this.app.use(s.urlencoded({extended:!0,verify:n})),this.logger=l(this.config),this.ldap=new h(this),d(this.logger),this.operationSequence=0;const e=[];if(this.config.plugin){const t=this.config.plugin.find(e=>e.includes("configApi"));let r=this.config.plugin.filter(e=>!e.includes("configApi"));const i=this.logger,s=(async()=>{for(const e of p){const t=this.resolveModule(DM.moduleOf(e)),s=r.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==s.length){r=r.filter(e=>!s.includes(e));for(const e of s)i.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(s),e.push(s.then(async()=>{const e=r.map(e=>(i.debug(`Loading ${e} in the parallel batch`),this.loadPlugin(e)));await Promise.all(e)})),t&&e.push(Promise.all(e).then(()=>this.loadPlugin(t)))}this.ready=new Promise((t,r)=>{e.length>0?Promise.all(e).then(async()=>{this.setupErrorMiddleware(),this.warnUnauthenticatedRoutes(),await g(this),this._composed=!0,this.afterLoad(),t()}).catch(e=>r(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}routerStack(){const e=this.app;return(e.router??e._router)?.stack}withConfig(e){return Object.assign(Object.create(Object.getPrototypeOf(this)),this,{config:e})}mountAuthDispatcher(e){const t=e.roles||[];t.includes("protect")||t.includes("logging")||this.mountAuthDispatcherNow()}mountAuthDispatcherNow(){this._authState.dispatcherMounted||(this._authState.dispatcherMounted=!0,this.app.use((e,t,r)=>{this.dispatchAuth(e,t,r)}))}registerAuthenticator(e){this.mountAuthDispatcherNow(),this.authenticators.includes(e)||this.authenticators.push(e)}registerBodyParser(e,t){this.bodyParsers.use(e,t)}dispatchAuth(e,t,r){let i=-1,s=[];for(const t of this.authenticators)for(const r of t.pathPrefixes)u(r,e.path)&&(r.length>i?(i=r.length,s=[t]):r.length!==i||s.includes(t)||s.push(t));if(i<0&&(s=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===s.length)return r();const finish=i=>{if(i>=s.length)return r();const o=s[i];if(!o.afterChain)return finish(i+1);o.afterChain(e,t,()=>finish(i+1))},o=[];e.authenticators=o;const run=r=>{if(r>=s.length)return finish(0);const i=s[r];i.authenticate(e,t,()=>{i.vouchedFor(e)&&o.push(i.name),run(r+1)})};run(0)}afterLoad(){for(const e of Object.values(this.loadedPlugins))try{e.afterLoad?.()}catch(t){this.logger.error(`Plugin ${e.name}: afterLoad failed: ${String(t)}`)}}warnUnauthenticatedRoutes(){const e=Object.values(this.loadedPlugins).filter(e=>e.roles?.includes("auth"));if(0===e.length)return[];const t=[];for(const r of e){if(!r.pathPrefixes||0===r.pathPrefixes.length)return[];t.push(...r.pathPrefixes)}const r=this.routerStack(),i=new Set;for(const e of r||[]){const t=e.route?.path;"string"==typeof t?i.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&i.add(e))}const s=[...i].filter(e=>!t.some(t=>e===t||e.startsWith(`${t}/`)));return s.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication: ${s.sort().join(", ")}`),s.sort()}setupErrorMiddleware(){if(this._errorMiddlewareSetup&&this._errorMiddleware){const e=this.routerStack();if(e){const t=e.findIndex(e=>e.handle===this._errorMiddleware);-1!==t&&e.splice(t,1)}}this._errorMiddleware=(e,t,r,i)=>{let s="statusCode"in e?e.statusCode:500,o=e.message;if(500===s&&/\[authz-forbidden\]/.test(e.message)?(s=403,o="Token does not have permission on this branch"):403===s&&/\[authz-forbidden\]/.test(e.message)&&(o="Token does not have permission on this branch"),s>=400&&s<500)return this.logger.warn(`Client error ${s} in request ${t.method} ${t.path}: ${o}`),void(r.headersSent||r.status(s).json({error:o}));this.logger.error(`Server error ${s} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),r.headersSent||r.status(s).json({error:"Internal Server Error",message:this.config.debug?e.message:"An error occurred"})},this.app.use(this._errorMiddleware),this._errorMiddlewareSetup=!0}run(){return process.on("uncaughtException",e=>{this.logger.error(`Uncaught exception: ${e.message}`,{stack:e.stack})}),process.on("unhandledRejection",(e,t)=>{this.logger.error(`Unhandled promise rejection: ${e}`,{reason:e,promise:t})}),new Promise((e,t)=>{this.server=this.app.listen(this.config.port,r=>{r?(this.logger.error(`Error starting server: ${r}`),t(r)):(this.logger.debug(`Server started on port ${this.config.port}`),e())})})}stop(){this.app.removeAllListeners(),this.server?.close(),this.logger.debug("Server stopped")}static moduleOf(e){const t=e.indexOf(":");return-1===t?e:e.substring(0,t)}resolveModule(i){return i.startsWith("core/")?i.replace("core/",t(r(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):i.startsWith(".")?e(new URL(i,import.meta.url)):i}loadPlugin(e){let t,r;if(/:/.test(e)){let i=e.substring(e.indexOf(":")+1);if(e=e.substring(0,e.indexOf(":")),/:/.test(i)){t=i.substring(0,i.indexOf(":")),t||(t=void 0),i=i.substring(i.indexOf(":")+1);try{r=JSON.parse(i),"object"!=typeof r?(this.logger.error(`Overrides for plugin ${e} are not valid: ${i}`),r=void 0):this.logger.debug(`Overrides for plugin ${t||e}: ${i}`)}catch(t){this.logger.error(`Failed to parse overrides for plugin ${e}: ${t}, using ${i}`),r=void 0}}else t=i,t||(t=void 0)}else t=void 0;return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((i,s)=>{import(e).then(async o=>{let n;if(o&&o.default&&(o=o.default),r){const e={...this.config,...r};n=new o(this.withConfig(e))}else n=new o(this);if(!n)return s(new Error(`Unable to load ${e}`));i(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>s(new Error(`Failed to load plugin ${e}: ${t}`)))})}async admitToComposition(e){this.loadedPlugins[e.name]=e;try{await g(this,e)}finally{delete this.loadedPlugins[e.name]}}async registerPlugin(e,t,r){if(t.name||(t.name=e),r&&(t.name=r),this.loadedPlugins[t.name])return this.logger.warn(`Plugin ${e} not registered: the name "${t.name}" is already taken. This instance is dropped and its routes will not exist. To load the same plugin twice, give each one a name: --plugin '${e}:${t.name}2:{…}'`),!1;if(this.logger.debug(`Registering plugin ${e} as ${t.name}`),t.dependencies)for(const e in t.dependencies)this.loadedPlugins[e]||(this.logger.debug(`Plugin ${t.name} depends on ${e}, loading it first`),await this.loadPlugin(t.dependencies[e]));if(this._composed&&await this.admitToComposition(t),t.api&&(this.mountAuthDispatcher(t),this.logger.debug(`Plugin ${t.name} has API, registering it`),await t.api(this.app),this._errorMiddlewareSetup&&this.setupErrorMiddleware()),t.hooks)for(const e in t.hooks){this.logger.debug(`Plugin ${t.name} has hook ${e}, registering it`);const r=t.hooks[e];if(this.hooks[e]||(this.hooks[e]=[]),!r||"function"!=typeof r)throw new Error(`Plugin ${t.name}: hook ${e} is invalid`);this.hooks[e].push(r),c(r,t.name,e)}return this.loadedPlugins[t.name]=t,!0}}export{DM,c as recordHookOwner};
@@ -1 +1 @@
1
- import{fileURLToPath as _}from"url";import{join as e,dirname as a}from"path";const t=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls"],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",0,"number"],["--ldap-pool-size","DM_LDAP_POOL_SIZE",5,"number"],["--ldap-connection-ttl","DM_LDAP_CONNECTION_TTL",60,"number"],["--schemas-path","DM_SCHEMAS_PATH",e(a(_(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuota"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--organization-schema","DM_ORGANIZATION_SCHEMA",""],["--enterprise-domain-name-attribute","DM_ENTERPRISE_DOMAIN_NAME_ATTRIBUTE","associatedDomain"],["--enterprise-domain-link-attribute","DM_ENTERPRISE_DOMAIN_LINK_ATTRIBUTE",""],["--storage-backend","DM_STORAGE_BACKEND",""],["--storage-sweep-interval","DM_STORAGE_SWEEP_INTERVAL",600,"number"],["--storage-ldap-base","DM_STORAGE_LDAP_BASE",""],["--storage-ldap-object-class","DM_STORAGE_LDAP_OBJECT_CLASS","applicationProcess"],["--storage-file-directory","DM_STORAGE_FILE_DIRECTORY",""],["--storage-postgres-url","DM_STORAGE_POSTGRES_URL",""],["--storage-postgres-table","DM_STORAGE_POSTGRES_TABLE","ldap_rest_storage"],["--storage-valkey-url","DM_STORAGE_VALKEY_URL",""],["--storage-valkey-prefix","DM_STORAGE_VALKEY_PREFIX","ldap-rest:"],["--bcl-retention","DM_BCL_RETENTION",604800,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",e(a(_(import.meta.url)),"..","..","static","schemas","twake","groups.json")],["--external-members-branch","DM_EXTERNAL_MEMBERS_BRANCH","ou=contacts,dc=example,dc=com"],["--external-branch-class","DM_EXTERNAL_BRANCH_CLASSES",["top","inetOrgPerson"],"array","--external-branch-classes"],["--static-path","DM_STATIC_PATH",e(a(_(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--ldap-flat-schema","DM_LDAP_FLAT_SCHEMA",[],"array","--ldap-flat-schemas"],["--ldap-flat-auto-repair","DM_LDAP_FLAT_AUTO_REPAIR",!0,"boolean"],["--ldap-raw-base","DM_LDAP_RAW_BASE",[],"array","--ldap-raw-bases"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes"],["--ldap-raw-show-secrets","DM_LDAP_RAW_SHOW_SECRETS",!1,"boolean"],["--ldap-raw-max-results","DM_LDAP_RAW_MAX_RESULTS",200,"number"],["--ldap-raw-schema-cache-ttl","DM_LDAP_RAW_SCHEMA_CACHE_TTL",3600,"number"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--calendar-firstname-attribute","DM_CALENDAR_FIRSTNAME_ATTRIBUTE","givenName"],["--calendar-lastname-attribute","DM_CALENDAR_LASTNAME_ATTRIBUTE","sn"],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-context-name","DM_COZY_CONTEXT_NAME","default"],["--cozy-apps","DM_COZY_APPS","home,drive,settings,notes,dataproxy"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--cozy-user-created-routing-key","DM_COZY_USER_CREATED_ROUTING_KEY","user.created"],["--cozy-user-deleted-routing-key","DM_COZY_USER_DELETED_ROUTING_KEY","domain.user.deleted"],["--cloudery-manager-url","DM_CLOUDERY_MANAGER_URL",""],["--cloudery-manager-token","DM_CLOUDERY_MANAGER_TOKEN",""],["--cloudery-offer","DM_CLOUDERY_OFFER","b2b_twake_default"],["--cloudery-domain","DM_CLOUDERY_DOMAIN",""],["--cloudery-user-branch","DM_CLOUDERY_USER_BRANCH",""],["--cloudery-org-id-header","DM_CLOUDERY_ORG_ID_HEADER","x-cloudery-org-id"],["--cloudery-org-role-header","DM_CLOUDERY_ORG_ROLE_HEADER","x-cloudery-org-role"],["--cloudery-default-org-role","DM_CLOUDERY_DEFAULT_ORG_ROLE","member"],["--cloudery-fqdn-attribute","DM_CLOUDERY_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--cloudery-org-id-attribute","DM_CLOUDERY_ORG_ID_ATTRIBUTE","twakeOrganizationId"],["--cloudery-org-role-attribute","DM_CLOUDERY_ORG_ROLE_ATTRIBUTE","twakeOrganizationRole"],["--cloudery-phones-attribute","DM_CLOUDERY_PHONES_ATTRIBUTE","twakePhones"],["--cloudery-invited-attribute","DM_CLOUDERY_INVITED_ATTRIBUTE","twakeInvited"],["--cloudery-default-locale","DM_CLOUDERY_DEFAULT_LOCALE","en"],["--cloudery-workflow-poll-interval-ms","DM_CLOUDERY_WORKFLOW_POLL_INTERVAL_MS",2e3,"number"],["--cloudery-workflow-max-attempts","DM_CLOUDERY_WORKFLOW_MAX_ATTEMPTS",60,"number"],["--twake-lifecycle-role-attribute","DM_TWAKE_LIFECYCLE_ROLE_ATTRIBUTE",""],["--twake-lifecycle-lock-attribute","DM_TWAKE_LIFECYCLE_LOCK_ATTRIBUTE",""],["--twake-lifecycle-lock-value","DM_TWAKE_LIFECYCLE_LOCK_VALUE",""],["--twake-lifecycle-deleted-attribute","DM_TWAKE_LIFECYCLE_DELETED_ATTRIBUTE",""],["--twake-lifecycle-deleted-value","DM_TWAKE_LIFECYCLE_DELETED_VALUE","TRUE"],["--twake-lifecycle-deleted-at-attribute","DM_TWAKE_LIFECYCLE_DELETED_AT_ATTRIBUTE",""],["--twake-lifecycle-deleted-at-format","DM_TWAKE_LIFECYCLE_DELETED_AT_FORMAT","iso8601"],["--twake-lifecycle-reason-attribute","DM_TWAKE_LIFECYCLE_REASON_ATTRIBUTE",""],["--twake-lifecycle-rules","DM_TWAKE_LIFECYCLE_RULES",""],["--twake-lifecycle-member-attribute","DM_TWAKE_LIFECYCLE_MEMBER_ATTRIBUTE","member"],["--twake-group-base","DM_TWAKE_GROUP_BASE",""],["--twake-group-user-base","DM_TWAKE_GROUP_USER_BASE",""],["--twake-group-organization-dn","DM_TWAKE_GROUP_ORGANIZATION_DN",""],["--twake-group-organization-status-attribute","DM_TWAKE_GROUP_ORGANIZATION_STATUS_ATTRIBUTE","twakeOrgStatus"],["--twake-group-organization-deleted-value","DM_TWAKE_GROUP_ORGANIZATION_DELETED_VALUE","deleted"],["--twake-group-display-name-attribute","DM_TWAKE_GROUP_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-group-color-attribute","DM_TWAKE_GROUP_COLOR_ATTRIBUTE","twakeGroupColor"],["--twake-group-created-at-attribute","DM_TWAKE_GROUP_CREATED_AT_ATTRIBUTE","twakeCreatedAt"],["--twake-group-max-page-limit","DM_TWAKE_GROUP_MAX_PAGE_LIMIT",1e3,"number"],["--twake-group-member-fields","DM_TWAKE_GROUP_MEMBER_FIELDS",{},"json"],["--twake-tombstone-dn","DM_TWAKE_TOMBSTONE_DN",[],"array"],["--twake-tombstone-default-reason","DM_TWAKE_TOMBSTONE_DEFAULT_REASON","deleted"],["--twake-tombstone-reason-header","DM_TWAKE_TOMBSTONE_REASON_HEADER","x-deletion-reason"],["--twake-tombstone-reasons","DM_TWAKE_TOMBSTONE_REASONS",[],"array"],["--twake-tombstone-clear-attributes","DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",[],"array"],["--twake-tombstone-erase-min-age","DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",2592e3,"number"],["--twake-tombstone-group-bases","DM_TWAKE_TOMBSTONE_GROUP_BASES",[],"array"],["--twake-instance-dn","DM_TWAKE_INSTANCE_DN",[],"array"],["--twake-instance-skip-attribute","DM_TWAKE_INSTANCE_SKIP_ATTRIBUTE",""],["--twake-instance-skip-value","DM_TWAKE_INSTANCE_SKIP_VALUE",""],["--twake-instance-provider","DM_TWAKE_INSTANCE_PROVIDER","cloudery"],["--twake-instance-id","DM_TWAKE_INSTANCE_ID","{uid}"],["--twake-instance-fqdn-attribute","DM_TWAKE_INSTANCE_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--twake-instance-sent-attribute","DM_TWAKE_INSTANCE_SENT_ATTRIBUTE","twakeCreatedEventAt"],["--twake-instance-locale","DM_TWAKE_INSTANCE_LOCALE",""],["--twake-instance-timeout","DM_TWAKE_INSTANCE_TIMEOUT",3e4,"number"],["--twake-instance-cloudery-url","DM_TWAKE_INSTANCE_CLOUDERY_URL",""],["--twake-instance-cloudery-token","DM_TWAKE_INSTANCE_CLOUDERY_TOKEN",""],["--twake-instance-cloudery-offer","DM_TWAKE_INSTANCE_CLOUDERY_OFFER","b2b_twake_default"],["--twake-instance-cloudery-domain","DM_TWAKE_INSTANCE_CLOUDERY_DOMAIN",""],["--twake-instance-cloudery-organization-offer","DM_TWAKE_INSTANCE_CLOUDERY_ORGANIZATION_OFFER",""],["--twake-instance-cozy-url","DM_TWAKE_INSTANCE_COZY_URL",""],["--twake-instance-cozy-user","DM_TWAKE_INSTANCE_COZY_USER","admin"],["--twake-instance-cozy-passphrase","DM_TWAKE_INSTANCE_COZY_PASSPHRASE",""],["--twake-instance-cozy-domain","DM_TWAKE_INSTANCE_COZY_DOMAIN",""],["--twake-instance-cozy-context","DM_TWAKE_INSTANCE_COZY_CONTEXT","default"],["--twake-instance-cozy-apps","DM_TWAKE_INSTANCE_COZY_APPS","home,drive,settings,notes,dataproxy"],["--twake-instance-cozy-org-id","DM_TWAKE_INSTANCE_COZY_ORG_ID",""],["--twake-instance-cozy-org-domain","DM_TWAKE_INSTANCE_COZY_ORG_DOMAIN",""],["--twake-instance-auth-exchange","DM_TWAKE_INSTANCE_AUTH_EXCHANGE","auth"],["--twake-instance-b2b-exchange","DM_TWAKE_INSTANCE_B2B_EXCHANGE","b2b"],["--twake-instance-user-created-key","DM_TWAKE_INSTANCE_USER_CREATED_KEY","user.created"],["--twake-instance-organization-created-key","DM_TWAKE_INSTANCE_ORGANIZATION_CREATED_KEY","organization.created"],["--twake-instance-workplace-created-key","DM_TWAKE_INSTANCE_WORKPLACE_CREATED_KEY","workplace.created"],["--twake-instance-queue","DM_TWAKE_INSTANCE_QUEUE","workplace.created.ldap-rest"],["--twake-instance-organization-base","DM_TWAKE_INSTANCE_ORGANIZATION_BASE",""],["--twake-instance-organization-id-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_ID_ATTRIBUTE","ou"],["--twake-instance-organization-domain-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE","twakeDomain"],["--twake-instance-organization-name-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE","description"],["--twake-instance-organization-fqdn-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE",""],["--twake-instance-organization-account","DM_TWAKE_INSTANCE_ORGANIZATION_ACCOUNT",""],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--llng-username-header","DM_LLNG_USERNAME_HEADER",""],["--auth-path-prefix","DM_AUTH_PATH_PREFIX",[],"array","--auth-path-prefixes"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-fake-user","DM_AUTH_FAKE_USER",""],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-route","DM_AUTHZ_PER_ROUTES",[],"array","--authz-per-routes"],["--authz-unresolved-user","DM_AUTHZ_UNRESOLVED_USER","deny"],["--authz-identity","DM_AUTHZ_IDENTITY","req.user"],["--authz-for","DM_AUTHZ_FOR",[],"array"],["--authz-combine","DM_AUTHZ_COMBINE",!1,"boolean"],["--authz-scope-source","DM_AUTHZ_SCOPE_SOURCE",""],["--authz-filter-attached-entries","DM_AUTHZ_FILTER_ATTACHED_ENTRIES",!1,"boolean"],["--authz-transit-branch","DM_AUTHZ_TRANSIT_BRANCH",""],["--authz-per-branch-config","DM_AUTHZ_PER_BRANCH_CONFIG",{default:{read:!0,write:!1,delete:!1}},"json"],["--authz-per-branch-cache-ttl","DM_AUTHZ_PER_BRANCH_CACHE_TTL",60,"number"],["--authz-dynamic-bypass","DM_AUTHZ_DYNAMIC_BYPASS",[],"array"],["--authz-dynamic-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-username-claim","DM_OIDC_USERNAME_CLAIM","sub"],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-base-header","DM_SCIM_USER_BASE_HEADER",""],["--scim-group-base-header","DM_SCIM_GROUP_BASE_HEADER",""],["--scim-base-header-root","DM_SCIM_BASE_HEADER_ROOT",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-user-lock-attribute","DM_SCIM_USER_LOCK_ATTRIBUTE","pwdAccountLockedTime"],["--scim-user-lock-value","DM_SCIM_USER_LOCK_VALUE",""],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-group-external-id-attribute","DM_SCIM_GROUP_EXTERNAL_ID_ATTRIBUTE",""],["--scim-id-attribute","DM_SCIM_ID_ATTRIBUTE","rdn"],["--scim-user-mapping","DM_SCIM_USER_MAPPING",""],["--scim-group-mapping","DM_SCIM_GROUP_MAPPING",""],["--scim-max-results","DM_SCIM_MAX_RESULTS",200,"number"],["--scim-max-scanned","DM_SCIM_MAX_SCANNED",1e4,"number"],["--scim-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
1
+ import{fileURLToPath as _}from"url";import{join as e,dirname as a}from"path";const t=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls"],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",0,"number"],["--ldap-pool-size","DM_LDAP_POOL_SIZE",5,"number"],["--ldap-connection-ttl","DM_LDAP_CONNECTION_TTL",60,"number"],["--schemas-path","DM_SCHEMAS_PATH",e(a(_(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuota"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--organization-schema","DM_ORGANIZATION_SCHEMA",""],["--enterprise-domain-name-attribute","DM_ENTERPRISE_DOMAIN_NAME_ATTRIBUTE","associatedDomain"],["--enterprise-domain-link-attribute","DM_ENTERPRISE_DOMAIN_LINK_ATTRIBUTE",""],["--storage-backend","DM_STORAGE_BACKEND",""],["--storage-sweep-interval","DM_STORAGE_SWEEP_INTERVAL",600,"number"],["--storage-ldap-base","DM_STORAGE_LDAP_BASE",""],["--storage-ldap-object-class","DM_STORAGE_LDAP_OBJECT_CLASS","applicationProcess"],["--storage-file-directory","DM_STORAGE_FILE_DIRECTORY",""],["--storage-postgres-url","DM_STORAGE_POSTGRES_URL",""],["--storage-postgres-table","DM_STORAGE_POSTGRES_TABLE","ldap_rest_storage"],["--storage-valkey-url","DM_STORAGE_VALKEY_URL",""],["--storage-valkey-prefix","DM_STORAGE_VALKEY_PREFIX","ldap-rest:"],["--bcl-retention","DM_BCL_RETENTION",604800,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",e(a(_(import.meta.url)),"..","..","static","schemas","twake","groups.json")],["--external-members-branch","DM_EXTERNAL_MEMBERS_BRANCH","ou=contacts,dc=example,dc=com"],["--external-branch-class","DM_EXTERNAL_BRANCH_CLASSES",["top","inetOrgPerson"],"array","--external-branch-classes"],["--static-path","DM_STATIC_PATH",e(a(_(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--ldap-flat-schema","DM_LDAP_FLAT_SCHEMA",[],"array","--ldap-flat-schemas"],["--ldap-flat-auto-repair","DM_LDAP_FLAT_AUTO_REPAIR",!0,"boolean"],["--ldap-raw-base","DM_LDAP_RAW_BASE",[],"array","--ldap-raw-bases"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes"],["--ldap-raw-show-secrets","DM_LDAP_RAW_SHOW_SECRETS",!1,"boolean"],["--ldap-raw-max-results","DM_LDAP_RAW_MAX_RESULTS",200,"number"],["--ldap-raw-schema-cache-ttl","DM_LDAP_RAW_SCHEMA_CACHE_TTL",3600,"number"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--calendar-firstname-attribute","DM_CALENDAR_FIRSTNAME_ATTRIBUTE","givenName"],["--calendar-lastname-attribute","DM_CALENDAR_LASTNAME_ATTRIBUTE","sn"],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-context-name","DM_COZY_CONTEXT_NAME","default"],["--cozy-apps","DM_COZY_APPS","home,drive,settings,notes,dataproxy"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--cozy-user-created-routing-key","DM_COZY_USER_CREATED_ROUTING_KEY","user.created"],["--cozy-user-deleted-routing-key","DM_COZY_USER_DELETED_ROUTING_KEY","domain.user.deleted"],["--cloudery-manager-url","DM_CLOUDERY_MANAGER_URL",""],["--cloudery-manager-token","DM_CLOUDERY_MANAGER_TOKEN",""],["--cloudery-offer","DM_CLOUDERY_OFFER","b2b_twake_default"],["--cloudery-domain","DM_CLOUDERY_DOMAIN",""],["--cloudery-user-branch","DM_CLOUDERY_USER_BRANCH",""],["--cloudery-org-id-header","DM_CLOUDERY_ORG_ID_HEADER","x-cloudery-org-id"],["--cloudery-org-role-header","DM_CLOUDERY_ORG_ROLE_HEADER","x-cloudery-org-role"],["--cloudery-default-org-role","DM_CLOUDERY_DEFAULT_ORG_ROLE","member"],["--cloudery-fqdn-attribute","DM_CLOUDERY_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--cloudery-org-id-attribute","DM_CLOUDERY_ORG_ID_ATTRIBUTE","twakeOrganizationId"],["--cloudery-org-role-attribute","DM_CLOUDERY_ORG_ROLE_ATTRIBUTE","twakeOrganizationRole"],["--cloudery-phones-attribute","DM_CLOUDERY_PHONES_ATTRIBUTE","twakePhones"],["--cloudery-invited-attribute","DM_CLOUDERY_INVITED_ATTRIBUTE","twakeInvited"],["--cloudery-default-locale","DM_CLOUDERY_DEFAULT_LOCALE","en"],["--cloudery-workflow-poll-interval-ms","DM_CLOUDERY_WORKFLOW_POLL_INTERVAL_MS",2e3,"number"],["--cloudery-workflow-max-attempts","DM_CLOUDERY_WORKFLOW_MAX_ATTEMPTS",60,"number"],["--twake-lifecycle-role-attribute","DM_TWAKE_LIFECYCLE_ROLE_ATTRIBUTE",""],["--twake-lifecycle-lock-attribute","DM_TWAKE_LIFECYCLE_LOCK_ATTRIBUTE",""],["--twake-lifecycle-lock-value","DM_TWAKE_LIFECYCLE_LOCK_VALUE",""],["--twake-lifecycle-deleted-attribute","DM_TWAKE_LIFECYCLE_DELETED_ATTRIBUTE",""],["--twake-lifecycle-deleted-value","DM_TWAKE_LIFECYCLE_DELETED_VALUE","TRUE"],["--twake-lifecycle-deleted-at-attribute","DM_TWAKE_LIFECYCLE_DELETED_AT_ATTRIBUTE",""],["--twake-lifecycle-deleted-at-format","DM_TWAKE_LIFECYCLE_DELETED_AT_FORMAT","iso8601"],["--twake-lifecycle-reason-attribute","DM_TWAKE_LIFECYCLE_REASON_ATTRIBUTE",""],["--twake-lifecycle-rules","DM_TWAKE_LIFECYCLE_RULES",""],["--twake-lifecycle-member-attribute","DM_TWAKE_LIFECYCLE_MEMBER_ATTRIBUTE","member"],["--twake-group-base","DM_TWAKE_GROUP_BASE",""],["--twake-group-user-base","DM_TWAKE_GROUP_USER_BASE",""],["--twake-group-organization-dn","DM_TWAKE_GROUP_ORGANIZATION_DN",""],["--twake-group-organization-status-attribute","DM_TWAKE_GROUP_ORGANIZATION_STATUS_ATTRIBUTE","twakeOrgStatus"],["--twake-group-organization-deleted-value","DM_TWAKE_GROUP_ORGANIZATION_DELETED_VALUE","deleted"],["--twake-group-display-name-attribute","DM_TWAKE_GROUP_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-group-color-attribute","DM_TWAKE_GROUP_COLOR_ATTRIBUTE","twakeGroupColor"],["--twake-group-created-at-attribute","DM_TWAKE_GROUP_CREATED_AT_ATTRIBUTE","twakeCreatedAt"],["--twake-group-max-page-limit","DM_TWAKE_GROUP_MAX_PAGE_LIMIT",1e3,"number"],["--twake-group-member-fields","DM_TWAKE_GROUP_MEMBER_FIELDS",{},"json"],["--twake-tombstone-dn","DM_TWAKE_TOMBSTONE_DN",[],"array"],["--twake-tombstone-default-reason","DM_TWAKE_TOMBSTONE_DEFAULT_REASON","deleted"],["--twake-tombstone-reason-header","DM_TWAKE_TOMBSTONE_REASON_HEADER","x-deletion-reason"],["--twake-tombstone-reasons","DM_TWAKE_TOMBSTONE_REASONS",[],"array"],["--twake-tombstone-clear-attributes","DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",[],"array"],["--twake-tombstone-erase-min-age","DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",2592e3,"number"],["--twake-tombstone-group-bases","DM_TWAKE_TOMBSTONE_GROUP_BASES",[],"array"],["--twake-instance-dn","DM_TWAKE_INSTANCE_DN",[],"array"],["--twake-instance-skip-attribute","DM_TWAKE_INSTANCE_SKIP_ATTRIBUTE",""],["--twake-instance-skip-value","DM_TWAKE_INSTANCE_SKIP_VALUE",""],["--twake-instance-provider","DM_TWAKE_INSTANCE_PROVIDER","cloudery"],["--twake-instance-id","DM_TWAKE_INSTANCE_ID","{uid}"],["--twake-instance-fqdn-attribute","DM_TWAKE_INSTANCE_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--twake-instance-sent-attribute","DM_TWAKE_INSTANCE_SENT_ATTRIBUTE","twakeCreatedEventAt"],["--twake-instance-locale","DM_TWAKE_INSTANCE_LOCALE",""],["--twake-instance-timeout","DM_TWAKE_INSTANCE_TIMEOUT",3e4,"number"],["--twake-instance-cloudery-url","DM_TWAKE_INSTANCE_CLOUDERY_URL",""],["--twake-instance-cloudery-token","DM_TWAKE_INSTANCE_CLOUDERY_TOKEN",""],["--twake-instance-cloudery-offer","DM_TWAKE_INSTANCE_CLOUDERY_OFFER","b2b_twake_default"],["--twake-instance-cloudery-domain","DM_TWAKE_INSTANCE_CLOUDERY_DOMAIN",""],["--twake-instance-cloudery-organization-offer","DM_TWAKE_INSTANCE_CLOUDERY_ORGANIZATION_OFFER",""],["--twake-instance-cozy-url","DM_TWAKE_INSTANCE_COZY_URL",""],["--twake-instance-cozy-user","DM_TWAKE_INSTANCE_COZY_USER","admin"],["--twake-instance-cozy-passphrase","DM_TWAKE_INSTANCE_COZY_PASSPHRASE",""],["--twake-instance-cozy-domain","DM_TWAKE_INSTANCE_COZY_DOMAIN",""],["--twake-instance-cozy-context","DM_TWAKE_INSTANCE_COZY_CONTEXT","default"],["--twake-instance-cozy-apps","DM_TWAKE_INSTANCE_COZY_APPS","home,drive,settings,notes,dataproxy"],["--twake-instance-cozy-org-id","DM_TWAKE_INSTANCE_COZY_ORG_ID",""],["--twake-instance-cozy-org-domain","DM_TWAKE_INSTANCE_COZY_ORG_DOMAIN",""],["--twake-instance-auth-exchange","DM_TWAKE_INSTANCE_AUTH_EXCHANGE","auth"],["--twake-instance-b2b-exchange","DM_TWAKE_INSTANCE_B2B_EXCHANGE","b2b"],["--twake-instance-user-created-key","DM_TWAKE_INSTANCE_USER_CREATED_KEY","user.created"],["--twake-instance-organization-created-key","DM_TWAKE_INSTANCE_ORGANIZATION_CREATED_KEY","organization.created"],["--twake-instance-workplace-created-key","DM_TWAKE_INSTANCE_WORKPLACE_CREATED_KEY","workplace.created"],["--twake-instance-queue","DM_TWAKE_INSTANCE_QUEUE","workplace.created.ldap-rest"],["--twake-instance-organization-base","DM_TWAKE_INSTANCE_ORGANIZATION_BASE",""],["--twake-instance-organization-id-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_ID_ATTRIBUTE","ou"],["--twake-instance-organization-domain-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE","twakeDomain"],["--twake-instance-organization-name-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE","description"],["--twake-instance-organization-fqdn-attribute","DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE",""],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--llng-username-header","DM_LLNG_USERNAME_HEADER",""],["--auth-path-prefix","DM_AUTH_PATH_PREFIX",[],"array","--auth-path-prefixes"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-fake-user","DM_AUTH_FAKE_USER",""],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-route","DM_AUTHZ_PER_ROUTES",[],"array","--authz-per-routes"],["--authz-unresolved-user","DM_AUTHZ_UNRESOLVED_USER","deny"],["--authz-identity","DM_AUTHZ_IDENTITY","req.user"],["--authz-for","DM_AUTHZ_FOR",[],"array"],["--authz-combine","DM_AUTHZ_COMBINE",!1,"boolean"],["--authz-scope-source","DM_AUTHZ_SCOPE_SOURCE",""],["--authz-filter-attached-entries","DM_AUTHZ_FILTER_ATTACHED_ENTRIES",!1,"boolean"],["--authz-transit-branch","DM_AUTHZ_TRANSIT_BRANCH",""],["--authz-per-branch-config","DM_AUTHZ_PER_BRANCH_CONFIG",{default:{read:!0,write:!1,delete:!1}},"json"],["--authz-per-branch-cache-ttl","DM_AUTHZ_PER_BRANCH_CACHE_TTL",60,"number"],["--authz-dynamic-bypass","DM_AUTHZ_DYNAMIC_BYPASS",[],"array"],["--authz-dynamic-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-username-claim","DM_OIDC_USERNAME_CLAIM","sub"],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-base-header","DM_SCIM_USER_BASE_HEADER",""],["--scim-group-base-header","DM_SCIM_GROUP_BASE_HEADER",""],["--scim-base-header-root","DM_SCIM_BASE_HEADER_ROOT",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-user-lock-attribute","DM_SCIM_USER_LOCK_ATTRIBUTE","pwdAccountLockedTime"],["--scim-user-lock-value","DM_SCIM_USER_LOCK_VALUE",""],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-group-external-id-attribute","DM_SCIM_GROUP_EXTERNAL_ID_ATTRIBUTE",""],["--scim-id-attribute","DM_SCIM_ID_ATTRIBUTE","rdn"],["--scim-user-mapping","DM_SCIM_USER_MAPPING",""],["--scim-group-mapping","DM_SCIM_GROUP_MAPPING",""],["--scim-max-results","DM_SCIM_MAX_RESULTS",200,"number"],["--scim-max-scanned","DM_SCIM_MAX_SCANNED",1e4,"number"],["--scim-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
@@ -1 +1 @@
1
- import{createHash as e}from"node:crypto";import{Client as t,Attribute as i,Change as s,Control as n}from"ldapts";import{LRUCache as a}from"lru-cache";import r from"p-limit";import{normalizeDn as o,isDnInBranch as c,launchHooksChained as l,launchHooks as h,escapeDnValue as d}from"./utils.js";import{changeContext as p}from"./changeContext.js";import{NotFoundError as f,ConflictError as u}from"./errors.js";import{SchemaIndex as g,parseSchema as y}from"./ldapSchema.js";const m={scope:"sub",filter:"(objectClass=*)",attributes:["*"],sizeLimit:0,timeLimit:10,paged:{pageSize:100}};function ldapError(e,t){const i=new Error(`${e}: ${String(t)}`),s=t?.code;return"number"==typeof s&&(i.code=s),i}const digestKey=t=>e("sha256").update(t).digest("hex").slice(0,12),foldDn=e=>e.trim().toLowerCase().split(",").map(e=>e.trim()).join(","),cloneSearchResult=e=>({searchEntries:e.searchEntries.map(e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Array.isArray(t)?[...t]:t]))),searchReferences:[...e.searchReferences]});class ldapActions{constructor(e){this.cacheGeneration=0,this.connectionPool=[],this.currentUrlIndex=0,this.waitingResolvers=[],this.availableConnections=[],this.isCleaningUp=!1,this.declaredClasses=new Map,this.schemaUnavailable=!1,this.classWarnings=new a({max:1e4}),this.parent=e,this.logger=e.logger,this.config=e.config,this.poolSize=this.config.ldap_pool_size||5,this.connectionTtl=1e3*(this.config.ldap_connection_ttl||60),this.logger.info(`LDAP connection pool initialized: size=${this.poolSize}, ttl=${this.connectionTtl/1e3}s`);const t=this.config.ldap_concurrency||10;this.queryLimit=r(t),this.logger.info(`Global LDAP query concurrency limit set to ${t}`);const i="string"==typeof this.config.ldap_cache_max?parseInt(this.config.ldap_cache_max,10)||1e3:this.config.ldap_cache_max??1e3,s=this.config.ldap_cache_ttl,n="string"==typeof s?parseInt(s,10)||0:s??0,o=1e3*Math.max(n,0);if(this.cacheEnabled=o>0,this.searchCache=new a({max:i,ttl:this.cacheEnabled?o:void 0,updateAgeOnGet:!1,updateAgeOnHas:!1}),this.logger.info(this.cacheEnabled?`LDAP search cache initialized: max=${i}, ttl=${o/1e3}s`:"LDAP search cache disabled (--ldap-cache-ttl=0)"),this.attrSignatureCache=new a({max:1e3}),!e.config.ldap_url||0===e.config.ldap_url.length)throw new Error("LDAP URL is not defined");if(!e.config.ldap_dn)throw new Error("LDAP DN is not defined");if(!e.config.ldap_pwd)throw new Error("LDAP password is not defined");if(!e.config.ldap_base)throw new Error("LDAP base is not defined, please set --ldap-base (or DM_LDAP_BASE)");this.base=e.config.ldap_base,this.ldapUrls=e.config.ldap_url,this.logger.info(`LDAP failover configured with ${this.ldapUrls.length} URL(s): ${this.ldapUrls.join(", ")}`),this.options={url:this.ldapUrls[0],timeout:0,connectTimeout:0,strictDN:!1},this.ldapUrls[0].startsWith("ldaps://")&&(this.options.tlsOptions={minVersion:"TLSv1.2"}),this.dn=e.config.ldap_dn,this.pwd=e.config.ldap_pwd}async createConnection(){const e=[];for(let i=0;i<this.ldapUrls.length;i++){const s=(this.currentUrlIndex+i)%this.ldapUrls.length,n=this.ldapUrls[s];try{this.logger.debug(`Attempting connection to ${n}`);const e={...this.options,url:n,tlsOptions:n.startsWith("ldaps://")?{minVersion:"TLSv1.2"}:void 0},i=new t(e);return await i.bind(this.dn,this.pwd),s!==this.currentUrlIndex&&(this.logger.info(`LDAP failover: switched from ${this.ldapUrls[this.currentUrlIndex]} to ${n}`),this.currentUrlIndex=s),i}catch(t){this.logger.warn(`Failed to connect to ${n}: ${String(t)}`),e.push(t)}}throw this.logger.error(`LDAP connection failed for all ${this.ldapUrls.length} URL(s)`),new Error(`LDAP connection failed for all URLs: ${e.map(e=>e.message).join(", ")}`)}cleanupExpiredConnections(){if(!this.isCleaningUp){this.isCleaningUp=!0;try{const e=Date.now(),t=[];for(let i=this.availableConnections.length-1;i>=0;i--){const s=this.availableConnections[i];if(e-s.createdAt>this.connectionTtl){t.push(s),this.availableConnections.splice(i,1);const e=this.connectionPool.indexOf(s);-1!==e&&this.connectionPool.splice(e,1)}}for(const e of t)e.client.unbind().catch(e=>{this.logger.debug(`Error unbinding expired connection: ${String(e)}`)});t.length>0&&this.logger.debug(`Cleaned up ${t.length} expired LDAP connections`)}finally{this.isCleaningUp=!1}}}async acquireConnection(){if(this.cleanupExpiredConnections(),this.availableConnections.length>0){const e=this.availableConnections.pop();return e.inUse=!0,this.logger.debug("Reusing pooled LDAP connection"),e}if(this.connectionPool.length<this.poolSize){const e={client:await this.createConnection(),createdAt:Date.now(),inUse:!0};return this.connectionPool.push(e),this.logger.debug(`Created new LDAP connection (pool: ${this.connectionPool.length}/${this.poolSize})`),e}return this.logger.debug("LDAP connection pool full, waiting for available connection"),new Promise((e,t)=>{const i=globalThis.setTimeout(()=>{const e=this.waitingResolvers.indexOf(resolveWrapper);-1!==e&&this.waitingResolvers.splice(e,1),t(new Error("LDAP connection pool timeout after 30s"))},3e4),resolveWrapper=t=>{globalThis.clearTimeout(i),e(t)};this.waitingResolvers.push(resolveWrapper)})}releaseConnection(e){if(Date.now()-e.createdAt>this.connectionTtl){const t=this.connectionPool.indexOf(e);return-1!==t&&this.connectionPool.splice(t,1),e.client.unbind().catch(()=>{}),void this.logger.debug("Released expired LDAP connection (discarded)")}if(this.waitingResolvers.length>0){const t=this.waitingResolvers.shift();return this.logger.debug("Released LDAP connection to waiting request"),void t(e)}e.inUse=!1,this.availableConnections.push(e),this.logger.debug("Released LDAP connection back to pool")}getAttributeSignature(e){if(!e||0===e.length)return"*";const t=e.join("|");let i=this.attrSignatureCache.get(t);return i||(i=[...e].sort().join(","),this.attrSignatureCache.set(t,i)),i}getCacheKey(e,t){const i=this.getAttributeSignature(t.attributes),s=t.explicitBufferAttributes?.length?[...t.explicitBufferAttributes].sort().join(","):"-",n="string"==typeof t.filter?t.filter:t.filter?t.filter.toString():"(objectClass=*)";return[e,t.scope||"sub",n,i,s,!1===t.returnAttributeValues?"novalues":"values",t.derefAliases??"-",String(t.sizeLimit??"-")].join("\0")}invalidateCache(e){if(!this.cacheEnabled)return;this.cacheGeneration++;const t=foldDn(e),i=`,${t}`,s=[];for(const e of this.searchCache.keys()){const n=foldDn(e.split("\0")[0]);(n===t||n.endsWith(i))&&s.push(e)}for(const e of s)this.searchCache.delete(e)}get system(){return this}forRequest(e){return{search:(t,i)=>this.search(t,i??this.base,e),add:(t,i)=>this.add(t,i,e),modify:(t,i,s)=>this.modify(t,i,e,s),rename:(t,i)=>this.rename(t,i,e),delete:t=>this.delete(t,e)}}declareObjectClasses(e,t,i=!1){const s=(Array.isArray(t)?t:[t]).filter(e=>"string"==typeof e&&""!==e);if(!e||0===s.length)return;const n=JSON.stringify([o(e),s.map(e=>e.toLowerCase()).sort(),i]);this.declaredClasses.set(n,{base:e,classes:s,includeBase:i})}declaredFor(e){let t=0,i=[];for(const s of this.declaredClasses.values()){if(!c(e,s.base))continue;const n=o(s.base);if(!s.includeBase&&o(e)===n)continue;const a=n.split(",").length;a>t&&(t=a,i=[]),a===t&&i.push(...s.classes)}const s=new Set;return i.filter(e=>{const t=e.toLowerCase();return!s.has(t)&&(s.add(t),!0)})}warnOnce(e){this.classWarnings.has(e)||(this.classWarnings.set(e,!0),this.logger.warn(e))}async classRepair(e,t){if(!1===this.config.ldap_flat_auto_repair)return[];const i=this.declaredFor(e);if(0===i.length)return[];const s=Array.isArray(t.delete)?t.delete:Object.entries(t.delete||{}).filter(([,e])=>!e||Array.isArray(e)&&!e.length).map(([e])=>e);if([...Object.keys(t.add||{}),...Object.keys(t.replace||{}),...Array.isArray(t.delete)?t.delete:Object.keys(t.delete||{})].some(e=>"objectclass"===e.toLowerCase()))return[];let n;try{n=await this.schemaIndex(),this.schemaUnavailable=!1}catch(e){return this.schemaUnavailable||this.logger.warn(`Object classes are not repaired: the directory schema could not be read (${String(e)})`),this.schemaUnavailable=!0,[]}const attributeKey=e=>n.getAttributeType(e)?.oid??e.toLowerCase(),allowedBy=e=>{const{must:t,may:i}=n.resolveAttributes(e);return new Set([...t,...i].map(attributeKey))},valued=e=>Object.entries(e||{}).filter(([,e])=>Array.isArray(e)?e.length>0:null!=e&&""!==e).map(([e])=>e),a=[...valued(t.add),...valued(t.replace)],r=i.filter(e=>{const t=n.getObjectClass(e);if("AUXILIARY"!==t?.kind)return!1;const i=allowedBy([e]);return a.some(e=>i.has(attributeKey(e)))});if(0===r.length)return[];let o;try{o=(await this.system.search({paged:!1,scope:"base",attributes:["objectClass",...n.resolveAttributes(r).must]},e)).searchEntries[0]}catch{return[]}if(!o)return[];const c=o.objectClass??o.objectclass,l=(Array.isArray(c)?c:c?[c]:[]).map(String),h=allowedBy(l);let d=a.filter(e=>n.getAttributeType(e)&&!h.has(attributeKey(e)));if(0===d.length)return[];const p=new Set(s.map(attributeKey)),f=new Set([...valued(o).filter(e=>!p.has(attributeKey(e))),...a].map(attributeKey)),{auxiliary:u,structural:g}=n.missingClasses(i,l),y=[];for(const t of u){const i=t.names[0]??t.oid,s=allowedBy([i]),a=d.filter(e=>s.has(attributeKey(e)));if(0===a.length)continue;const r=n.resolveAttributes([i]).must.filter(e=>!f.has(attributeKey(e)));r.length>0?this.warnOnce(`${e} needs ${i} for ${a.join(", ")}, and cannot be given it without ${r.join(", ")}`):(y.push(i),d=d.filter(e=>!a.includes(e)))}const m=g.filter(e=>{const t=allowedBy([e]);return d.some(e=>t.has(attributeKey(e)))});return m.length>0&&this.warnOnce(`${e} cannot hold ${d.join(", ")}: its schema declares ${m.join(", ")}, a structural class an existing entry cannot be given`),y.length>0&&this.logger.info(`${e}: adding ${y.join(", ")}, which the change needs and its schema declares`),y}async subschemaDn(){try{const e=await this.system.search({paged:!1,scope:"base",filter:"(objectClass=*)",attributes:["subschemaSubentry"]},""),t=e.searchEntries[0]?.subschemaSubentry,i=Array.isArray(t)?t[0]:t;if(i)return String(i)}catch(e){this.logger.warn(`Unable to read subschemaSubentry from root DSE: ${String(e)}`)}return"cn=Subschema"}async schemaIndex(){const e=1e3*(this.config.ldap_raw_schema_cache_ttl??3600);if(this.schemaCache&&Date.now()-this.schemaCache.fetchedAt<e)return this.schemaCache.index;if(this.schemaLoading)return this.schemaLoading;this.schemaLoading=(async()=>{const e=await this.subschemaDn(),t=(await this.system.search({paged:!1,scope:"base",filter:"(objectClass=*)",attributes:["objectClasses","attributeTypes","ldapSyntaxes","matchingRules"]},e).catch(t=>{if(32===t?.code)throw new f(`Subschema entry ${e} not found`);throw t})).searchEntries[0];if(!t)throw new f(`Subschema entry ${e} not found`);const list=e=>{if(void 0===e)return[];return(Array.isArray(e)?e:[e]).map(e=>Buffer.isBuffer(e)?e.toString("utf8"):e)},i=new g(y({objectClasses:list(t.objectClasses),attributeTypes:list(t.attributeTypes),ldapSyntaxes:list(t.ldapSyntaxes),matchingRules:list(t.matchingRules)}));return this.schemaCache={index:i,fetchedAt:Date.now()},this.logger.info(`LDAP schema loaded from ${e}: ${i.schema.objectClasses.length} object classes, ${i.schema.attributeTypes.length} attribute types`),i})();try{return await this.schemaLoading}finally{this.schemaLoading=void 0}}filterForCaller(e,t,i){if(!this.parent.hooks.ldapsearchfilter)return e;const s=this.parent.hooks.ldapsearchfilter,one=async e=>{const[n]=await l(s,[e,t,i]);return n??e};if("function"==typeof e[Symbol.asyncIterator]){const t=e;return async function*(){for await(const e of t)yield await one(e)}()}return one(e)}async search(e,t=this.base,i){let s={...m,...e};s=await l(this.parent.hooks.ldapsearchopts,s),[t,s]=await l(this.parent.hooks.ldapsearchrequest,[t,s,i]);const n=this.cacheEnabled&&!s.paged&&"base"===s.scope,a=n?this.getCacheKey(t,s):"",r=this.cacheGeneration;if(n){const e=this.searchCache.get(a);if(e)return this.logger.debug(`LDAP search cache hit: ${digestKey(a)}`),this.filterForCaller(cloneSearchResult(e),i,s)}const o=await this.acquireConnection();try{let e=s.paged?o.client.searchPaginated(t,s):o.client.search(t,s);if(e=await l(this.parent.hooks.ldapsearchresult,e),n){const t=e;return r===this.cacheGeneration?(this.searchCache.set(a,cloneSearchResult(t)),this.logger.debug(`LDAP search cached: ${digestKey(a)}`)):this.logger.debug(`LDAP search not cached, a write overtook it: ${digestKey(a)}`),this.filterForCaller(t,i,s)}return s.paged?this.filterForCaller(this.wrapPaginatedSearch(e,o),i,s):this.filterForCaller(e,i,s)}finally{s.paged||this.releaseConnection(o)}}async*wrapPaginatedSearch(e,t){try{for await(const t of e)yield t}finally{this.releaseConnection(t)}}async add(e,t,s){e=this.setDn(e),t.objectClass&&0!==t.objectClass.length||!this.config.user_class||(t.objectClass=this.config.user_class);const n={};for(const[e,i]of Object.entries(t))Buffer.isBuffer(i)?n[e]=i.toString():Array.isArray(i)&&i.length>0&&Buffer.isBuffer(i[0])?n[e]=i.map(e=>e.toString()):n[e]=i;[e,t]=await l(this.parent.hooks.ldapaddrequest,[e,n,s]);const a=[];for(const[e,s]of Object.entries(t)){const t=Array.isArray(s)?s:[s];a.push(new i({type:e,values:t}))}const r=await this.acquireConnection();try{this.invalidateCache(e),await r.client.add(e,a),this.invalidateCache(e),h(this.parent.hooks.ldapadddone,[e,t],p(s))}catch(t){throw function(e,t){const i=ldapError("LDAP add error",t);if(68!==i.code)return i;const s=new u(`Entry ${e} already exists`);return s.code=68,s}(e,t)}finally{this.releaseConnection(r)}return await h(this.parent.hooks.ldapaddafter,[e,t],p(s)),!0}async modify(e,t,a,r={}){e=this.setDn(e);const o=[],c=this.opNumber();if([e,t]=await l(this.parent.hooks.ldapmodifyrequest,[e,t,c,a]),t.add)for(const[e,n]of Object.entries(t.add))o.push(new s({operation:"add",modification:new i({type:e,values:Array.isArray(n)?n:[n]})}));if(t.replace)for(const[e,n]of Object.entries(t.replace))o.push(new s({operation:"replace",modification:new i({type:e,values:Array.isArray(n)?n:[n]})}));if(t.delete)if(Array.isArray(t.delete))for(const e of t.delete)e&&o.push(new s({operation:"delete",modification:new i({type:e,values:[]})}));else for(const[e,n]of Object.entries(t.delete)){const t=new s({operation:"delete",modification:new i(n?{type:e,values:Array.isArray(n)?n:[n]}:{type:e})});o.push(t)}if(0===o.length){return Object.keys(t.add||{}).length>0||Object.keys(t.replace||{}).length>0||(Array.isArray(t.delete)?t.delete.length>0:Object.keys(t.delete||{}).length>0)?this.logger.warn(`Modify on ${e} asked for changes but emitted none; nothing was written`):this.logger.debug(`Modify on ${e} had nothing to apply`),h(this.parent.hooks.ldapmodifydone,[e,{},c],r.context??p(a)),!1}{const l=await this.classRepair(e,t),d=l.length>0?new s({operation:"add",modification:new i({type:"objectClass",values:l})}):void 0;d&&o.push(d);const f=await this.acquireConnection();try{this.invalidateCache(e);const i=r.permissive?[new n("1.2.840.113556.1.4.1413",{critical:!1})]:void 0;try{await f.client.modify(e,o,i)}catch(t){if(!d||20!==t?.code)throw t;o.splice(o.indexOf(d),1),await f.client.modify(e,o,i)}return this.invalidateCache(e),h(this.parent.hooks.ldapmodifydone,[e,t,c],r.context??p(a)),!0}catch(t){throw this.logger.warn(`Changes that failed: ${e}, ${JSON.stringify(o)}`),function(e,t){const i=ldapError("LDAP modify error",t);if(65!==i.code)return i;const s=new u(`Entry ${e} cannot hold this change: ${String(t)}`);return s.code=65,s}(e,t)}finally{this.releaseConnection(f)}}}async rename(e,t,i){e=this.setDn(e),t=this.setDn(t),[e,t]=await l(this.parent.hooks.ldaprenamerequest,[e,t,i]);const s=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),await s.client.modifyDN(e,t),this.invalidateCache(e),this.invalidateCache(t),h(this.parent.hooks.ldaprenamedone,[e,t],p(i)),!0}catch(e){throw ldapError("LDAP rename error",e)}finally{this.releaseConnection(s)}}async move(e,t,i){e=this.setDn(e),t=this.setDn(t);const s=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),await s.client.modifyDN(e,t),this.invalidateCache(e),this.invalidateCache(t),h(this.parent.hooks.ldaprenamedone,[e,t],p(i)),this.logger.debug(`LDAP move: ${e} -> ${t}`),!0}catch(e){throw ldapError("LDAP move error",e)}finally{this.releaseConnection(s)}}async delete(e,t){e=Array.isArray(e)?e.map(e=>this.setDn(e)):this.setDn(e),Array.isArray(e)||(e=[e]),[e]=await l(this.parent?.hooks.ldapdeleterequest,[e,t]);const i=await this.acquireConnection();try{for(const s of e){try{this.invalidateCache(s),await i.client.del(s),this.invalidateCache(s)}catch(e){throw ldapError("LDAP delete error",e)}h(this.parent.hooks.ldapdeletedone,s,p(t))}return!0}finally{this.releaseConnection(i)}}setDn(e){return/=/.test(e)?/,/.test(e)||(e+=`,${this.base}`):e=`${this.config.ldap_user_main_attribute}=${d(e)},${this.base}`,e}opNumber(){return this.parent.operationSequence++}}export{ldapActions as default};
1
+ import{createHash as e}from"node:crypto";import{Client as t,Attribute as i,Change as s,Control as n}from"ldapts";import{LRUCache as a}from"lru-cache";import r from"p-limit";import{normalizeDn as o,isDnInBranch as c,launchHooksChained as l,launchHooks as h,escapeDnValue as d}from"./utils.js";import{changeContext as p}from"./changeContext.js";import{runOperation as u,outsideOperation as f}from"./operation.js";import{NotFoundError as g,ConflictError as y}from"./errors.js";import{SchemaIndex as m,parseSchema as b}from"./ldapSchema.js";const w={scope:"sub",filter:"(objectClass=*)",attributes:["*"],sizeLimit:0,timeLimit:10,paged:{pageSize:100}};function ldapError(e,t){const i=new Error(`${e}: ${String(t)}`),s=t?.code;return"number"==typeof s&&(i.code=s),i}const digestKey=t=>e("sha256").update(t).digest("hex").slice(0,12),foldDn=e=>e.trim().toLowerCase().split(",").map(e=>e.trim()).join(","),cloneSearchResult=e=>({searchEntries:e.searchEntries.map(e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Array.isArray(t)?[...t]:t]))),searchReferences:[...e.searchReferences]});class ldapActions{constructor(e){this.cacheGeneration=0,this.connectionPool=[],this.currentUrlIndex=0,this.waitingResolvers=[],this.availableConnections=[],this.isCleaningUp=!1,this.declaredClasses=new Map,this.schemaUnavailable=!1,this.classWarnings=new a({max:1e4}),this.parent=e,this.logger=e.logger,this.config=e.config,this.poolSize=this.config.ldap_pool_size||5,this.connectionTtl=1e3*(this.config.ldap_connection_ttl||60),this.logger.info(`LDAP connection pool initialized: size=${this.poolSize}, ttl=${this.connectionTtl/1e3}s`);const t=this.config.ldap_concurrency||10;this.queryLimit=r(t),this.logger.info(`Global LDAP query concurrency limit set to ${t}`);const i="string"==typeof this.config.ldap_cache_max?parseInt(this.config.ldap_cache_max,10)||1e3:this.config.ldap_cache_max??1e3,s=this.config.ldap_cache_ttl,n="string"==typeof s?parseInt(s,10)||0:s??0,o=1e3*Math.max(n,0);if(this.cacheEnabled=o>0,this.searchCache=new a({max:i,ttl:this.cacheEnabled?o:void 0,updateAgeOnGet:!1,updateAgeOnHas:!1}),this.logger.info(this.cacheEnabled?`LDAP search cache initialized: max=${i}, ttl=${o/1e3}s`:"LDAP search cache disabled (--ldap-cache-ttl=0)"),this.attrSignatureCache=new a({max:1e3}),!e.config.ldap_url||0===e.config.ldap_url.length)throw new Error("LDAP URL is not defined");if(!e.config.ldap_dn)throw new Error("LDAP DN is not defined");if(!e.config.ldap_pwd)throw new Error("LDAP password is not defined");if(!e.config.ldap_base)throw new Error("LDAP base is not defined, please set --ldap-base (or DM_LDAP_BASE)");this.base=e.config.ldap_base,this.ldapUrls=e.config.ldap_url,this.logger.info(`LDAP failover configured with ${this.ldapUrls.length} URL(s): ${this.ldapUrls.join(", ")}`),this.options={url:this.ldapUrls[0],timeout:0,connectTimeout:0,strictDN:!1},this.ldapUrls[0].startsWith("ldaps://")&&(this.options.tlsOptions={minVersion:"TLSv1.2"}),this.dn=e.config.ldap_dn,this.pwd=e.config.ldap_pwd}async createConnection(){const e=[];for(let i=0;i<this.ldapUrls.length;i++){const s=(this.currentUrlIndex+i)%this.ldapUrls.length,n=this.ldapUrls[s];try{this.logger.debug(`Attempting connection to ${n}`);const e={...this.options,url:n,tlsOptions:n.startsWith("ldaps://")?{minVersion:"TLSv1.2"}:void 0},i=new t(e);return await i.bind(this.dn,this.pwd),s!==this.currentUrlIndex&&(this.logger.info(`LDAP failover: switched from ${this.ldapUrls[this.currentUrlIndex]} to ${n}`),this.currentUrlIndex=s),i}catch(t){this.logger.warn(`Failed to connect to ${n}: ${String(t)}`),e.push(t)}}throw this.logger.error(`LDAP connection failed for all ${this.ldapUrls.length} URL(s)`),new Error(`LDAP connection failed for all URLs: ${e.map(e=>e.message).join(", ")}`)}cleanupExpiredConnections(){if(!this.isCleaningUp){this.isCleaningUp=!0;try{const e=Date.now(),t=[];for(let i=this.availableConnections.length-1;i>=0;i--){const s=this.availableConnections[i];if(e-s.createdAt>this.connectionTtl){t.push(s),this.availableConnections.splice(i,1);const e=this.connectionPool.indexOf(s);-1!==e&&this.connectionPool.splice(e,1)}}for(const e of t)e.client.unbind().catch(e=>{this.logger.debug(`Error unbinding expired connection: ${String(e)}`)});t.length>0&&this.logger.debug(`Cleaned up ${t.length} expired LDAP connections`)}finally{this.isCleaningUp=!1}}}async acquireConnection(){if(this.cleanupExpiredConnections(),this.availableConnections.length>0){const e=this.availableConnections.pop();return e.inUse=!0,this.logger.debug("Reusing pooled LDAP connection"),e}if(this.connectionPool.length<this.poolSize){const e={client:await this.createConnection(),createdAt:Date.now(),inUse:!0};return this.connectionPool.push(e),this.logger.debug(`Created new LDAP connection (pool: ${this.connectionPool.length}/${this.poolSize})`),e}return this.logger.debug("LDAP connection pool full, waiting for available connection"),new Promise((e,t)=>{const i=globalThis.setTimeout(()=>{const e=this.waitingResolvers.indexOf(resolveWrapper);-1!==e&&this.waitingResolvers.splice(e,1),t(new Error("LDAP connection pool timeout after 30s"))},3e4),resolveWrapper=t=>{globalThis.clearTimeout(i),e(t)};this.waitingResolvers.push(resolveWrapper)})}releaseConnection(e){if(Date.now()-e.createdAt>this.connectionTtl){const t=this.connectionPool.indexOf(e);return-1!==t&&this.connectionPool.splice(t,1),e.client.unbind().catch(()=>{}),void this.logger.debug("Released expired LDAP connection (discarded)")}if(this.waitingResolvers.length>0){const t=this.waitingResolvers.shift();return this.logger.debug("Released LDAP connection to waiting request"),void t(e)}e.inUse=!1,this.availableConnections.push(e),this.logger.debug("Released LDAP connection back to pool")}getAttributeSignature(e){if(!e||0===e.length)return"*";const t=e.join("|");let i=this.attrSignatureCache.get(t);return i||(i=[...e].sort().join(","),this.attrSignatureCache.set(t,i)),i}getCacheKey(e,t){const i=this.getAttributeSignature(t.attributes),s=t.explicitBufferAttributes?.length?[...t.explicitBufferAttributes].sort().join(","):"-",n="string"==typeof t.filter?t.filter:t.filter?t.filter.toString():"(objectClass=*)";return[e,t.scope||"sub",n,i,s,!1===t.returnAttributeValues?"novalues":"values",t.derefAliases??"-",String(t.sizeLimit??"-")].join("\0")}invalidateCache(e){if(!this.cacheEnabled)return;this.cacheGeneration++;const t=foldDn(e),i=`,${t}`,s=[];for(const e of this.searchCache.keys()){const n=foldDn(e.split("\0")[0]);(n===t||n.endsWith(i))&&s.push(e)}for(const e of s)this.searchCache.delete(e)}get system(){return this}forRequest(e){return{search:(t,i)=>this.search(t,i??this.base,e),add:(t,i)=>this.add(t,i,e),modify:(t,i,s)=>this.modify(t,i,e,s),rename:(t,i)=>this.rename(t,i,e),delete:t=>this.delete(t,e)}}declareObjectClasses(e,t,i=!1){const s=(Array.isArray(t)?t:[t]).filter(e=>"string"==typeof e&&""!==e);if(!e||0===s.length)return;const n=JSON.stringify([o(e),s.map(e=>e.toLowerCase()).sort(),i]);this.declaredClasses.set(n,{base:e,classes:s,includeBase:i})}declaredFor(e){let t=0,i=[];for(const s of this.declaredClasses.values()){if(!c(e,s.base))continue;const n=o(s.base);if(!s.includeBase&&o(e)===n)continue;const a=n.split(",").length;a>t&&(t=a,i=[]),a===t&&i.push(...s.classes)}const s=new Set;return i.filter(e=>{const t=e.toLowerCase();return!s.has(t)&&(s.add(t),!0)})}warnOnce(e){this.classWarnings.has(e)||(this.classWarnings.set(e,!0),this.logger.warn(e))}async classRepair(e,t){if(!1===this.config.ldap_flat_auto_repair)return[];const i=this.declaredFor(e);if(0===i.length)return[];const s=Array.isArray(t.delete)?t.delete:Object.entries(t.delete||{}).filter(([,e])=>!e||Array.isArray(e)&&!e.length).map(([e])=>e);if([...Object.keys(t.add||{}),...Object.keys(t.replace||{}),...Array.isArray(t.delete)?t.delete:Object.keys(t.delete||{})].some(e=>"objectclass"===e.toLowerCase()))return[];let n;try{n=await this.schemaIndex(),this.schemaUnavailable=!1}catch(e){return this.schemaUnavailable||this.logger.warn(`Object classes are not repaired: the directory schema could not be read (${String(e)})`),this.schemaUnavailable=!0,[]}const attributeKey=e=>n.getAttributeType(e)?.oid??e.toLowerCase(),allowedBy=e=>{const{must:t,may:i}=n.resolveAttributes(e);return new Set([...t,...i].map(attributeKey))},valued=e=>Object.entries(e||{}).filter(([,e])=>Array.isArray(e)?e.length>0:null!=e&&""!==e).map(([e])=>e),a=[...valued(t.add),...valued(t.replace)],r=i.filter(e=>{const t=n.getObjectClass(e);if("AUXILIARY"!==t?.kind)return!1;const i=allowedBy([e]);return a.some(e=>i.has(attributeKey(e)))});if(0===r.length)return[];let o;try{o=(await this.system.search({paged:!1,scope:"base",attributes:["objectClass",...n.resolveAttributes(r).must]},e)).searchEntries[0]}catch{return[]}if(!o)return[];const c=o.objectClass??o.objectclass,l=(Array.isArray(c)?c:c?[c]:[]).map(String),h=allowedBy(l);let d=a.filter(e=>n.getAttributeType(e)&&!h.has(attributeKey(e)));if(0===d.length)return[];const p=new Set(s.map(attributeKey)),u=new Set([...valued(o).filter(e=>!p.has(attributeKey(e))),...a].map(attributeKey)),{auxiliary:f,structural:g}=n.missingClasses(i,l),y=[];for(const t of f){const i=t.names[0]??t.oid,s=allowedBy([i]),a=d.filter(e=>s.has(attributeKey(e)));if(0===a.length)continue;const r=n.resolveAttributes([i]).must.filter(e=>!u.has(attributeKey(e)));r.length>0?this.warnOnce(`${e} needs ${i} for ${a.join(", ")}, and cannot be given it without ${r.join(", ")}`):(y.push(i),d=d.filter(e=>!a.includes(e)))}const m=g.filter(e=>{const t=allowedBy([e]);return d.some(e=>t.has(attributeKey(e)))});return m.length>0&&this.warnOnce(`${e} cannot hold ${d.join(", ")}: its schema declares ${m.join(", ")}, a structural class an existing entry cannot be given`),y.length>0&&this.logger.info(`${e}: adding ${y.join(", ")}, which the change needs and its schema declares`),y}async subschemaDn(){try{const e=await this.system.search({paged:!1,scope:"base",filter:"(objectClass=*)",attributes:["subschemaSubentry"]},""),t=e.searchEntries[0]?.subschemaSubentry,i=Array.isArray(t)?t[0]:t;if(i)return String(i)}catch(e){this.logger.warn(`Unable to read subschemaSubentry from root DSE: ${String(e)}`)}return"cn=Subschema"}async schemaIndex(){const e=1e3*(this.config.ldap_raw_schema_cache_ttl??3600);if(this.schemaCache&&Date.now()-this.schemaCache.fetchedAt<e)return this.schemaCache.index;if(this.schemaLoading)return this.schemaLoading;this.schemaLoading=(async()=>{const e=await this.subschemaDn(),t=(await this.system.search({paged:!1,scope:"base",filter:"(objectClass=*)",attributes:["objectClasses","attributeTypes","ldapSyntaxes","matchingRules"]},e).catch(t=>{if(32===t?.code)throw new g(`Subschema entry ${e} not found`);throw t})).searchEntries[0];if(!t)throw new g(`Subschema entry ${e} not found`);const list=e=>{if(void 0===e)return[];return(Array.isArray(e)?e:[e]).map(e=>Buffer.isBuffer(e)?e.toString("utf8"):e)},i=new m(b({objectClasses:list(t.objectClasses),attributeTypes:list(t.attributeTypes),ldapSyntaxes:list(t.ldapSyntaxes),matchingRules:list(t.matchingRules)}));return this.schemaCache={index:i,fetchedAt:Date.now()},this.logger.info(`LDAP schema loaded from ${e}: ${i.schema.objectClasses.length} object classes, ${i.schema.attributeTypes.length} attribute types`),i})();try{return await this.schemaLoading}finally{this.schemaLoading=void 0}}filterForCaller(e,t,i){if(!this.parent.hooks.ldapsearchfilter)return e;const s=this.parent.hooks.ldapsearchfilter,one=async e=>{const[n]=await l(s,[e,t,i]);return n??e};if("function"==typeof e[Symbol.asyncIterator]){const t=e;return async function*(){for await(const e of t)yield await one(e)}()}return one(e)}async search(e,t=this.base,i){let s={...w,...e};s=await l(this.parent.hooks.ldapsearchopts,s),[t,s]=await l(this.parent.hooks.ldapsearchrequest,[t,s,i]);const n=this.cacheEnabled&&!s.paged&&"base"===s.scope,a=n?this.getCacheKey(t,s):"",r=this.cacheGeneration;if(n){const e=this.searchCache.get(a);if(e)return this.logger.debug(`LDAP search cache hit: ${digestKey(a)}`),this.filterForCaller(cloneSearchResult(e),i,s)}const o=await this.acquireConnection();try{let e=s.paged?o.client.searchPaginated(t,s):o.client.search(t,s);if(e=await l(this.parent.hooks.ldapsearchresult,e),n){const t=e;return r===this.cacheGeneration?(this.searchCache.set(a,cloneSearchResult(t)),this.logger.debug(`LDAP search cached: ${digestKey(a)}`)):this.logger.debug(`LDAP search not cached, a write overtook it: ${digestKey(a)}`),this.filterForCaller(t,i,s)}return s.paged?this.filterForCaller(this.wrapPaginatedSearch(e,o),i,s):this.filterForCaller(e,i,s)}finally{s.paged||this.releaseConnection(o)}}async*wrapPaginatedSearch(e,t){try{for await(const t of e)yield t}finally{this.releaseConnection(t)}}async add(e,t,s){e=this.setDn(e),t.objectClass&&0!==t.objectClass.length||!this.config.user_class||(t.objectClass=this.config.user_class);const n={};for(const[e,i]of Object.entries(t))Buffer.isBuffer(i)?n[e]=i.toString():Array.isArray(i)&&i.length>0&&Buffer.isBuffer(i[0])?n[e]=i.map(e=>e.toString()):n[e]=i;[e,t]=await l(this.parent.hooks.ldapaddrequest,[e,n,s]);const a=[];for(const[e,s]of Object.entries(t)){const t=Array.isArray(s)?s:[s];a.push(new i({type:e,values:t}))}const r=await this.acquireConnection();try{this.invalidateCache(e),await r.client.add(e,a),this.invalidateCache(e),h(this.parent.hooks.ldapadddone,[e,t],p(s))}catch(t){throw function(e,t){const i=ldapError("LDAP add error",t);if(68!==i.code)return i;const s=new y(`Entry ${e} already exists`);return s.code=68,s}(e,t)}finally{this.releaseConnection(r)}return await h(this.parent.hooks.ldapaddafter,[e,t],p(s)),!0}async modify(e,t,i,s={}){const n=this.opNumber(),a=[];try{return await u(n,()=>this.applyModify(e,t,i,s,n,a))}finally{this.endOperation(a,this.parent.hooks.ldapmodifyend,n)}}async applyModify(e,t,a,r,o,c){e=this.setDn(e);const d=[];if([e,t]=await l(this.parent.hooks.ldapmodifyrequest,[e,t,o,a]),t.add)for(const[e,n]of Object.entries(t.add))d.push(new s({operation:"add",modification:new i({type:e,values:Array.isArray(n)?n:[n]})}));if(t.replace)for(const[e,n]of Object.entries(t.replace))d.push(new s({operation:"replace",modification:new i({type:e,values:Array.isArray(n)?n:[n]})}));if(t.delete)if(Array.isArray(t.delete))for(const e of t.delete)e&&d.push(new s({operation:"delete",modification:new i({type:e,values:[]})}));else for(const[e,n]of Object.entries(t.delete)){const t=new s({operation:"delete",modification:new i(n?{type:e,values:Array.isArray(n)?n:[n]}:{type:e})});d.push(t)}if(0===d.length){return Object.keys(t.add||{}).length>0||Object.keys(t.replace||{}).length>0||(Array.isArray(t.delete)?t.delete.length>0:Object.keys(t.delete||{}).length>0)?this.logger.warn(`Modify on ${e} asked for changes but emitted none; nothing was written`):this.logger.debug(`Modify on ${e} had nothing to apply`),c.push(h(this.parent.hooks.ldapmodifydone,[e,{},o],r.context??p(a))),!1}{const l=await this.classRepair(e,t),u=l.length>0?new s({operation:"add",modification:new i({type:"objectClass",values:l})}):void 0;u&&d.push(u);const f=await this.acquireConnection();try{this.invalidateCache(e);const i=r.permissive?[new n("1.2.840.113556.1.4.1413",{critical:!1})]:void 0;try{await f.client.modify(e,d,i)}catch(t){if(!u||20!==t?.code)throw t;d.splice(d.indexOf(u),1),await f.client.modify(e,d,i)}return this.invalidateCache(e),c.push(h(this.parent.hooks.ldapmodifydone,[e,t,o],r.context??p(a))),!0}catch(t){throw this.logger.warn(`Changes that failed: ${e}, ${JSON.stringify(d)}`),function(e,t){const i=ldapError("LDAP modify error",t);if(65!==i.code)return i;const s=new y(`Entry ${e} cannot hold this change: ${String(t)}`);return s.code=65,s}(e,t)}finally{this.releaseConnection(f)}}}async rename(e,t,i){e=this.setDn(e),t=this.setDn(t);const s=this.opNumber(),n=[];try{return await u(s,()=>this.applyRename(e,t,i,n))}finally{this.endOperation(n,this.parent.hooks.ldaprenameend,s)}}async applyRename(e,t,i,s){[e,t]=await l(this.parent.hooks.ldaprenamerequest,[e,t,i]);const n=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),await n.client.modifyDN(e,t),this.invalidateCache(e),this.invalidateCache(t),s.push(h(this.parent.hooks.ldaprenamedone,[e,t],p(i))),!0}catch(e){throw ldapError("LDAP rename error",e)}finally{this.releaseConnection(n)}}async move(e,t,i){e=this.setDn(e),t=this.setDn(t);const s=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),await s.client.modifyDN(e,t),this.invalidateCache(e),this.invalidateCache(t),f(()=>h(this.parent.hooks.ldaprenamedone,[e,t],p(i))),this.logger.debug(`LDAP move: ${e} -> ${t}`),!0}catch(e){throw ldapError("LDAP move error",e)}finally{this.releaseConnection(s)}}async delete(e,t){const i=(Array.isArray(e)?e:[e]).map(e=>this.setDn(e)),s=this.opNumber(),n=[];try{return await u(s,()=>this.applyDelete(i,t,n))}finally{this.endOperation(n,this.parent?.hooks.ldapdeleteend,s)}}async applyDelete(e,t,i){[e]=await l(this.parent?.hooks.ldapdeleterequest,[e,t]);const s=await this.acquireConnection();try{for(const n of e){try{this.invalidateCache(n),await s.client.del(n),this.invalidateCache(n)}catch(e){throw ldapError("LDAP delete error",e)}i.push(h(this.parent.hooks.ldapdeletedone,n,p(t)))}return!0}finally{this.releaseConnection(s)}}setDn(e){return/=/.test(e)?/,/.test(e)||(e+=`,${this.base}`):e=`${this.config.ldap_user_main_attribute}=${d(e)},${this.base}`,e}endOperation(e,t,...i){t?.length&&Promise.all(e).then(()=>h(t,...i))}opNumber(){return this.parent.operationSequence++}}export{ldapActions as default};
@@ -0,0 +1 @@
1
+ import{AsyncLocalStorage as n}from"async_hooks";const r=new n;function runOperation(n,t){return r.run(n,t)}function outsideOperation(n){return r.exit(n)}function currentOperation(){return r.getStore()}export{currentOperation,outsideOperation,runOperation};
@@ -1 +1 @@
1
- const e=new WeakMap;function keepRawBody(o,n,t){e.set(o,t)}function rawBodyOf(o){return e.get(o)}export{keepRawBody,rawBodyOf};
1
+ import e from"body-parser";const o=new WeakMap;function keepRawBody(e,r,n,t){o.set(e,{bytes:n,encoding:t})}function rawBodyOf(e){return o.get(e)}function jsonBodyParser(o={}){return e.json({...o,verify:keepRawBody})}export{jsonBodyParser,keepRawBody,rawBodyOf};
@@ -1 +1 @@
1
- import{createHmac as e,createHash as t,timingSafeEqual as i}from"crypto";import{unauthorized as r}from"../../lib/expressFormatedResponses.js";import{rawBodyOf as s}from"../../lib/rawBody.js";import n from"../../lib/auth/base.js";class AuthHmac extends n{knownIdentities(){return[...this.services.values()].map(e=>e.name)}identitySource(){return"req.user and req.userName: the service name given in --auth-hmac"}constructor(...e){super(...e),this.name="authHmac",this.roles=["auth"],this.services=new Map;const t=this.config.auth_hmac;this.timeWindow=this.config.auth_hmac_window??12e4,t&&Array.isArray(t)&&t.forEach((e,t)=>{const i=e.split(":");if(i.length>=3){const e=i[0].trim(),r=i[1].trim(),s=i.slice(2).join(":").trim();if(!e||!r||!s)return void this.logger.warn(`Invalid HMAC config at index ${t}: missing id, secret, or name`);r.length<32&&this.logger.warn(`HMAC secret for service "${e}" is too short (minimum 32 characters recommended)`),this.services.set(e,{id:e,secret:r,name:s})}else this.logger.warn(`Invalid HMAC config format at index ${t}: expected "service-id:secret:name"`)}),0===this.services.size?this.logger.warn("No valid HMAC services configured"):(this.logger.info(`HMAC authentication initialized with ${this.services.size} service(s): ${Array.from(this.services.keys()).join(", ")}`),this.logger.info(`Time window for replay protection: ${this.timeWindow}ms`))}authMethod(e,t,i){const s=e.headers.authorization;if(!s||!s.startsWith("HMAC-SHA256 "))return this.logger.warn("Missing or invalid Authorization header (expected HMAC-SHA256)"),r(t);const n=s.substring(12).split(":");if(3!==n.length)return this.logger.warn("Invalid HMAC authorization format (expected service-id:timestamp:signature)"),r(t);const[o,a,c]=n,h=this.services.get(o);if(!h)return this.logger.warn(`Unknown service ID: ${o}`),r(t);const u=parseInt(a,10);if(isNaN(u)||u<=0)return this.logger.warn(`Invalid timestamp: ${a}`),r(t);const g=Date.now(),m=Math.abs(g-u);if(m>this.timeWindow)return this.logger.warn(`Timestamp outside allowed window: ${m}ms (max: ${this.timeWindow}ms) for service ${o}`),r(t);const d=this.calculateBodyHash(e);if(void 0===d)return this.logger.warn(`Refusing ${e.method} ${e.originalUrl||e.url} from service ${o}: its body (${e.headers["content-type"]||"no Content-Type"}) cannot be checked against the signature`),r(t);const l=`${e.method.toUpperCase()}|${e.originalUrl||e.url}|${u}|${d}`,f=this.calculateHmac(h.secret,l);if(!this.secureCompare(c,f))return this.logger.warn(`Invalid signature for service ${o} (${h.name}). Expected: ${f.substring(0,8)}..., Got: ${c.substring(0,8)}...`),this.logger.debug(`Signing string: ${l}`),r(t);this.logger.debug(`HMAC authentication successful for service: ${o} (${h.name})`),this.publishIdentity(e,h.name),i()}calculateHmac(t,i){const r=e("sha256",t);return r.update(i),r.digest("hex")}calculateBodyHash(e){const i=e.method.toUpperCase();if("GET"===i||"DELETE"===i||"HEAD"===i)return"";const r=s(e);if(r)return r.length?t("sha256").update(r).digest("hex"):"";const n=parseInt(e.headers["content-length"]??"",10);return void 0!==e.headers["transfer-encoding"]||n>0?void 0:""}secureCompare(e,t){if(e.length!==t.length)return!1;try{const r=Buffer.from(e,"utf8"),s=Buffer.from(t,"utf8");return i(r,s)}catch{return!1}}}export{AuthHmac as default};
1
+ import{createHmac as e,createHash as t,timingSafeEqual as i}from"crypto";import{unauthorized as r}from"../../lib/expressFormatedResponses.js";import{rawBodyOf as s}from"../../lib/rawBody.js";import n from"../../lib/auth/base.js";class AuthHmac extends n{knownIdentities(){return[...this.services.values()].map(e=>e.name)}identitySource(){return"req.user and req.userName: the service name given in --auth-hmac"}constructor(...e){super(...e),this.name="authHmac",this.roles=["auth"],this.services=new Map;const t=this.config.auth_hmac;this.timeWindow=this.config.auth_hmac_window??12e4,t&&Array.isArray(t)&&t.forEach((e,t)=>{const i=e.split(":");if(i.length>=3){const e=i[0].trim(),r=i[1].trim(),s=i.slice(2).join(":").trim();if(!e||!r||!s)return void this.logger.warn(`Invalid HMAC config at index ${t}: missing id, secret, or name`);r.length<32&&this.logger.warn(`HMAC secret for service "${e}" is too short (minimum 32 characters recommended)`),this.services.set(e,{id:e,secret:r,name:s})}else this.logger.warn(`Invalid HMAC config format at index ${t}: expected "service-id:secret:name"`)}),0===this.services.size?this.logger.warn("No valid HMAC services configured"):(this.logger.info(`HMAC authentication initialized with ${this.services.size} service(s): ${Array.from(this.services.keys()).join(", ")}`),this.logger.info(`Time window for replay protection: ${this.timeWindow}ms`))}authMethod(e,t,i){const s=e.headers.authorization;if(!s||!s.startsWith("HMAC-SHA256 "))return this.logger.warn("Missing or invalid Authorization header (expected HMAC-SHA256)"),r(t);const n=s.substring(12).split(":");if(3!==n.length)return this.logger.warn("Invalid HMAC authorization format (expected service-id:timestamp:signature)"),r(t);const[o,a,c]=n,h=this.services.get(o);if(!h)return this.logger.warn(`Unknown service ID: ${o}`),r(t);const u=parseInt(a,10);if(isNaN(u)||u<=0)return this.logger.warn(`Invalid timestamp: ${a}`),r(t);const g=Date.now(),d=Math.abs(g-u);if(d>this.timeWindow)return this.logger.warn(`Timestamp outside allowed window: ${d}ms (max: ${this.timeWindow}ms) for service ${o}`),r(t);const m=this.calculateBodyHash(e);if(void 0===m)return this.logger.warn(`Refusing ${e.method} ${e.originalUrl||e.url} from service ${o}: its body (${e.headers["content-type"]||"no Content-Type"}) cannot be checked against the signature`),r(t);const l=`${e.method.toUpperCase()}|${e.originalUrl||e.url}|${u}|${m}`,f=this.calculateHmac(h.secret,l);if(!this.secureCompare(c,f))return this.logger.warn(`Invalid signature for service ${o} (${h.name}). Expected: ${f.substring(0,8)}..., Got: ${c.substring(0,8)}...`),this.logger.debug(`Signing string: ${l}`),r(t);this.logger.debug(`HMAC authentication successful for service: ${o} (${h.name})`),this.publishIdentity(e,h.name),i()}calculateHmac(t,i){const r=e("sha256",t);return r.update(i),r.digest("hex")}calculateBodyHash(e){const i=e.method.toUpperCase(),r=s(e),n=parseInt(e.headers["content-length"]??"",10),o=!r&&(void 0!==e.headers["transfer-encoding"]||n>0);if("GET"===i||"DELETE"===i||"HEAD"===i)return r?.bytes.length||o?void 0:"";if(!o){if(!r?.bytes.length)return"";if("utf-8"===r.encoding)return t("sha256").update(r.bytes).digest("hex")}}secureCompare(e,t){if(e.length!==t.length)return!1;try{const r=Buffer.from(e,"utf8"),s=Buffer.from(t,"utf8");return i(r,s)}catch{return!1}}}export{AuthHmac as default};
@@ -1 +1 @@
1
- import t from"../../abstract/plugin.js";import{launchHooks as e}from"../../lib/utils.js";const normalize=t=>(void 0===t?[]:Array.isArray(t)?t:[t]).map(t=>Buffer.isBuffer(t)?t.toString("base64"):String(t)).sort();function diffEntries(t,e){const index=t=>new Map(Object.keys(t||{}).filter(t=>"dn"!==t).map(t=>[t.toLowerCase(),t])),n=index(t),a=index(e),i={};for(const r of new Set([...n.keys(),...a.keys()])){const s=n.get(r),o=a.get(r),h=s?t[s]:void 0,d=o?e[o]:void 0,l=normalize(h),u=normalize(d);l.length===u.length&&l.every((t,e)=>t===u[e])||(i[o||s]=[l.length?h:null,u.length?d:null])}return i}const n={mail_attribute:"onLdapMailChange",quota_attribute:"onLdapQuotaChange",alias_attribute:"onLdapAliasChange",forward_attribute:"onLdapForwardChange",display_name_attribute:"onLdapDisplayNameChange",drive_quota_attribute:"onLdapDriveQuotaChange"};class OnLdapChange extends t{constructor(){super(...arguments),this.name="onLdapChange",this.roles=["consistency"],this.stack={},this.pendingDeletions=new Map,this.pendingRenames=new Map,this.hooks={ldapadddone:async([t,e],n)=>{const a=await this.read(t)||{dn:t,...e};this.publish(t,null,a,n)},ldapmodifyrequest:async([t,e,n,a])=>{const i=await this.read(t);return i?this.stack[n]=i:this.logger.warn(`Could not read ${t} before modification`),[t,e,n,a]},ldapmodifydone:async([t,e,n],a)=>{const i=this.stack[n];if(delete this.stack[n],!i)return void this.logger.warn(`Received a ldapmodifydone for an unknown operation (${n})`);if(0===Object.keys(e).length)return;const r=await this.read(t);r?this.publish(t,i,r,a):this.logger.warn(`Could not read ${t} after modification`)},ldaprenamerequest:async([t,e,n])=>{const a=await this.read(t);return a&&this.pendingRenames.set(t,a),[t,e,n]},ldaprenamedone:async([t,e],n)=>{const a=this.pendingRenames.get(t);if(this.pendingRenames.delete(t),!a)return void this.logger.debug(`${t}: no snapshot: a move made outside a request`);const i=await this.read(e);i?this.publish(e,a,i,n):this.logger.warn(`Could not read ${e} after the rename of ${t}`)},ldapdeleterequest:async([t,e])=>{for(const e of Array.isArray(t)?t:[t]){const t=await this.read(e);t&&this.pendingDeletions.set(e,t)}return[t,e]},ldapdeletedone:(t,e)=>{for(const n of Array.isArray(t)?t:[t]){const t=this.pendingDeletions.get(n);t&&(this.pendingDeletions.delete(n),this.publish(n,t,null,e))}}}}async read(t){const e=new Set(Object.values(this.server.loadedPlugins).flatMap(t=>t.followedOperationalAttributes||[]));try{const n=(await this.server.ldap.search({paged:!1,scope:"base",...e.size?{attributes:["*",...e]}:{}},t)).searchEntries[0];if(!n)return;return Object.fromEntries(Object.entries(n).filter(([,t])=>!Array.isArray(t)||t.length>0))}catch(t){return}}publish(t,n,a,i={}){const r=diffEntries(n,a);0===Object.keys(r).length&&n?.dn===a?.dn||(e(this.server.hooks.onLdapEntryChange,t,n,a,i),this.notify(t,r,n,a))}notify(t,a,i,r){e(this.server.hooks.onLdapChange,t,a);for(const[i,s]of Object.entries(n))if(this.config[i]&&a[this.config[i]])if("onLdapQuotaChange"===s||"onLdapForwardChange"===s||"onLdapAliasChange"===s)this.notifyAttributeChangeWithMail(this.config[i],s,t,a,r);else if("onLdapMailChange"===s)this.notifyAttributeChange(this.config[i],s,t,a);else if("onLdapDriveQuotaChange"===s){const[n,r]=a[this.config[i]]||[],s=n?Number(n):null,o=r?Number(r):null;s!==o&&e(this.server.hooks.onLdapDriveQuotaChange,t,s,o)}const s=this.reconstructDisplayName(i),o=this.reconstructDisplayName(r);s!==o&&e(this.server.hooks.onLdapDisplayNameChange,t,s,o)}notifyAttributeChange(t,n,a,i,r=!1){const[s,o]=i[t]||[];void 0===s&&void 0===o||(r&&(Array.isArray(s)||Array.isArray(o))?this.logger.error(`Attribute ${t} change detected but one of the values is an array, cannot handle that`):s!==o&&e(this.server.hooks[n],a,s,o))}notifyAttributeChangeWithMail(t,n,a,i,r){const[s,o]=i[t]||[];if(void 0===s&&void 0===o)return;const h=this.config.mail_attribute||"mail",d=i[h];let l;if(d)l=Array.isArray(d[1])?String(d[1][0]):String(d[1]);else{if(!r?.[h])return void this.logger.warn(`Could not find mail for ${a}, skipping ${n} notification`);{const t=r[h];l=Array.isArray(t)?String(t[0]):String(t)}}if("onLdapQuotaChange"===n){const t=s?Number(s):0,i=o?Number(o):0;t!==i&&e(this.server.hooks[n],a,l,t,i)}else if("onLdapForwardChange"===n||"onLdapAliasChange"===n){const t=s?Array.isArray(s)?s:[s]:[],i=o?Array.isArray(o)?o:[o]:[];(t.length>0||i.length>0)&&e(this.server.hooks[n],a,l,t,i)}}reconstructDisplayName(t){const getValue=e=>{const n=t?.[e];return n?Array.isArray(n)?n.length>0?String(n[0]):null:String(n):null},e=getValue("cn");if(e)return e;const n=getValue("givenName"),a=getValue("sn");if(n||a){const t=[];return n&&t.push(n),a&&t.push(a),t.join(" ")}return null}}export{OnLdapChange as default,diffEntries};
1
+ import e from"../../abstract/plugin.js";import{launchHooks as t}from"../../lib/utils.js";import{currentOperation as n}from"../../lib/operation.js";const normalize=e=>(void 0===e?[]:Array.isArray(e)?e:[e]).map(e=>Buffer.isBuffer(e)?e.toString("base64"):String(e)).sort();function diffEntries(e,t){const index=e=>new Map(Object.keys(e||{}).filter(e=>"dn"!==e).map(e=>[e.toLowerCase(),e])),n=index(e),i=index(t),a={};for(const r of new Set([...n.keys(),...i.keys()])){const s=n.get(r),o=i.get(r),d=s?e[s]:void 0,h=o?t[o]:void 0,l=normalize(d),c=normalize(h);l.length===c.length&&l.every((e,t)=>e===c[t])||(a[o||s]=[l.length?d:null,c.length?h:null])}return a}const i={mail_attribute:"onLdapMailChange",quota_attribute:"onLdapQuotaChange",alias_attribute:"onLdapAliasChange",forward_attribute:"onLdapForwardChange",display_name_attribute:"onLdapDisplayNameChange",drive_quota_attribute:"onLdapDriveQuotaChange"};class OnLdapChange extends e{constructor(){super(...arguments),this.name="onLdapChange",this.roles=["consistency"],this.stack={},this.pendingDeletions=new Map,this.pendingRenames=new Map,this.hooks={ldapadddone:async([e,t],n)=>{const i=await this.read(e)||{dn:e,...t};this.publish(e,null,i,n)},ldapmodifyrequest:async([e,t,n,i])=>{const a=await this.read(e);return a?this.stack[n]=a:this.logger.warn(`Could not read ${e} before modification`),[e,t,n,i]},ldapmodifydone:async([e,t,n],i)=>{const a=this.stack[n];if(delete this.stack[n],!a)return void this.logger.warn(`Received a ldapmodifydone for an unknown operation (${n})`);if(0===Object.keys(t).length)return;const r=await this.read(e);r?this.publish(e,a,r,i):this.logger.warn(`Could not read ${e} after modification`)},ldapmodifyend:e=>{delete this.stack[e]},ldaprenamerequest:async([e,t,i])=>{const a=n(),r=void 0===a?void 0:await this.read(e);return void 0!==a&&r&&this.pendingRenames.set(a,{dn:e,entry:r}),[e,t,i]},ldaprenamedone:async([e,t],i)=>{const a=n(),r=void 0===a?void 0:this.pendingRenames.get(a);if(!r||r.dn!==e)return void this.logger.debug(`${e}: no snapshot: a move made outside a request`);this.pendingRenames.delete(a);const s=await this.read(t);s?this.publish(t,r.entry,s,i):this.logger.warn(`Could not read ${t} after the rename of ${e}`)},ldaprenameend:e=>{this.pendingRenames.delete(e)},ldapdeleterequest:async([e,t])=>{const i=n();if(void 0===i)return[e,t];for(const t of Array.isArray(e)?e:[e]){const e=await this.read(t);if(!e)continue;let n=this.pendingDeletions.get(i);n||this.pendingDeletions.set(i,n=new Map),n.set(t,e)}return[e,t]},ldapdeletedone:(e,t)=>{const i=n(),a=void 0===i?void 0:this.pendingDeletions.get(i);for(const n of Array.isArray(e)?e:[e]){const e=a?.get(n);e&&(a.delete(n),this.publish(n,e,null,t))}},ldapdeleteend:e=>{this.pendingDeletions.delete(e)}}}async read(e){const t=new Set(Object.values(this.server.loadedPlugins).flatMap(e=>e.followedOperationalAttributes||[]));try{const n=(await this.server.ldap.search({paged:!1,scope:"base",...t.size?{attributes:["*",...t]}:{}},e)).searchEntries[0];if(!n)return;return Object.fromEntries(Object.entries(n).filter(([,e])=>!Array.isArray(e)||e.length>0))}catch(e){return}}publish(e,n,i,a={}){const r=diffEntries(n,i);0===Object.keys(r).length&&n?.dn===i?.dn||(t(this.server.hooks.onLdapEntryChange,e,n,i,a),this.notify(e,r,n,i))}notify(e,n,a,r){t(this.server.hooks.onLdapChange,e,n);for(const[a,s]of Object.entries(i))if(this.config[a]&&n[this.config[a]])if("onLdapQuotaChange"===s||"onLdapForwardChange"===s||"onLdapAliasChange"===s)this.notifyAttributeChangeWithMail(this.config[a],s,e,n,r);else if("onLdapMailChange"===s)this.notifyAttributeChange(this.config[a],s,e,n);else if("onLdapDriveQuotaChange"===s){const[i,r]=n[this.config[a]]||[],s=i?Number(i):null,o=r?Number(r):null;s!==o&&t(this.server.hooks.onLdapDriveQuotaChange,e,s,o)}const s=this.reconstructDisplayName(a),o=this.reconstructDisplayName(r);s!==o&&t(this.server.hooks.onLdapDisplayNameChange,e,s,o)}notifyAttributeChange(e,n,i,a,r=!1){const[s,o]=a[e]||[];void 0===s&&void 0===o||(r&&(Array.isArray(s)||Array.isArray(o))?this.logger.error(`Attribute ${e} change detected but one of the values is an array, cannot handle that`):s!==o&&t(this.server.hooks[n],i,s,o))}notifyAttributeChangeWithMail(e,n,i,a,r){const[s,o]=a[e]||[];if(void 0===s&&void 0===o)return;const d=this.config.mail_attribute||"mail",h=a[d];let l;if(h)l=Array.isArray(h[1])?String(h[1][0]):String(h[1]);else{if(!r?.[d])return void this.logger.warn(`Could not find mail for ${i}, skipping ${n} notification`);{const e=r[d];l=Array.isArray(e)?String(e[0]):String(e)}}if("onLdapQuotaChange"===n){const e=s?Number(s):0,a=o?Number(o):0;e!==a&&t(this.server.hooks[n],i,l,e,a)}else if("onLdapForwardChange"===n||"onLdapAliasChange"===n){const e=s?Array.isArray(s)?s:[s]:[],a=o?Array.isArray(o)?o:[o]:[];(e.length>0||a.length>0)&&t(this.server.hooks[n],i,l,e,a)}}reconstructDisplayName(e){const getValue=t=>{const n=e?.[t];return n?Array.isArray(n)?n.length>0?String(n[0]):null:String(n):null},t=getValue("cn");if(t)return t;const n=getValue("givenName"),i=getValue("sn");if(n||i){const e=[];return n&&e.push(n),i&&e.push(i),e.join(" ")}return null}}export{OnLdapChange as default,diffEntries};
@@ -1 +1 @@
1
- import{fileURLToPath as s}from"url";import{join as e,dirname as t}from"path";import i from"../../abstract/plugin.js";import{setChangeSource as o}from"../../lib/changeContext.js";import{BaseResolver as r}from"./baseResolver.js";import{ScimUsers as a}from"./users.js";import{ScimGroups as n}from"./groups.js";import{ScimBulk as c}from"./bulk.js";import{ScimDiscovery as p}from"./discovery.js";import{SCIM_CONTENT_TYPE as m,scimAsyncHandler as u,writeScimError as d}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";import{projectResource as h,assertProjection as g,projectList as l}from"./projection.js";class Scim extends i{constructor(i){super(i),this.name="scim",this.roles=["api","configurable"],this.ldap=i.ldap,this.scimPrefix=this.config.scim_prefix||"/scim/v2",this.baseResolver=new r(this.config),this.users=new a({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,hooks:this.registeredHooks}),this.groups=new n({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,users:this.users,hooks:this.registeredHooks}),this.bulk=new c({config:this.config,logger:this.logger,users:this.users,groups:this.groups,scimPrefix:this.scimPrefix,hooks:this.registeredHooks});const o=this.config.schemas_path||e(t(s(import.meta.url)),"..","..","..","static","schemas");this.discovery=new p({config:this.config,schemaDir:e(o,"scim"),scimPrefix:this.scimPrefix,loadedPlugins:i.loadedPlugins})}scimJson(s,e,t){s.status(e).type(m).json(t)}scimResource(s,e,t,i,o={}){const r=t.id?`${this.scimPrefix}/${i}/${encodeURIComponent(t.id)}`:void 0,a=(this.config.scim_base_url||"")&&t.meta?.location?t.meta.location:r;a&&s.set("Location",a),this.scimJson(s,e,h(t,o))}parseProjection(s){const toCsv=s=>{if("string"!=typeof s)return;const e=s.split(",").map(s=>s.trim()).filter(Boolean);return e.length>0?e:void 0},e={attributes:toCsv(s.query.attributes),excludedAttributes:toCsv(s.query.excludedAttributes)};return g(e),e}readScimBody(s){const e=String(s.headers["content-type"]||"");if(e.startsWith("application/scim+json")||e.startsWith("application/json"))return s.body}api(s){const e=this.scimPrefix;s.use(e,(s,e,t)=>{o(s,"scim"),t()}),s.use(e,u((s,e,t)=>{this.parseProjection(s),t()})),s.get(`${e}/Users`,u(async(s,e)=>{const t=this.parseListQuery(s);g(t);const i=await this.users.list(s,t);this.scimJson(e,200,l(i,t))})),s.get(`${e}/Users/:id`,u(async(s,e)=>{const t=this.parseProjection(s),i=await this.users.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,h(i,t))})),s.post(`${e}/Users`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=await this.users.create(s,t);this.scimResource(e,201,i,"Users",this.parseProjection(s))})),s.put(`${e}/Users/:id`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=await this.users.replace(s,decodeURIComponent(s.params.id),t);this.scimResource(e,200,i,"Users",this.parseProjection(s))})),s.patch(`${e}/Users/:id`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=await this.users.patch(s,decodeURIComponent(s.params.id),t);this.scimResource(e,200,i,"Users",this.parseProjection(s))})),s.delete(`${e}/Users/:id`,u(async(s,e)=>{await this.users.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.get(`${e}/Groups`,u(async(s,e)=>{const t=this.parseListQuery(s);g(t);const i=await this.groups.list(s,t);this.scimJson(e,200,l(i,t))})),s.get(`${e}/Groups/:id`,u(async(s,e)=>{const t=this.parseProjection(s),i=await this.groups.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,h(i,t))})),s.post(`${e}/Groups`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=await this.groups.create(s,t);this.scimResource(e,201,i,"Groups",this.parseProjection(s))})),s.put(`${e}/Groups/:id`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=await this.groups.replace(s,decodeURIComponent(s.params.id),t);this.scimResource(e,200,i,"Groups",this.parseProjection(s))})),s.patch(`${e}/Groups/:id`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=await this.groups.patch(s,decodeURIComponent(s.params.id),t);this.scimResource(e,200,i,"Groups",this.parseProjection(s))})),s.delete(`${e}/Groups/:id`,u(async(s,e)=>{await this.groups.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.post(`${e}/Bulk`,u(async(s,e)=>{const t=this.readScimBody(s);if(!t||"object"!=typeof t)return d(e,400,"Missing body","invalidSyntax");const i=this.config.scim_bulk_max_payload_size||1048576,o=JSON.stringify(t).length;if(o>i)return d(e,413,`Bulk payload too large: ${o} > ${i}`,"invalidValue");const r=await this.bulk.execute(s,t);this.scimJson(e,200,r)})),s.get(`${e}/ServiceProviderConfig`,u((s,e)=>{this.scimJson(e,200,this.discovery.serviceProviderConfig(s))})),s.get(`${e}/ResourceTypes`,u((s,e)=>{const t=this.discovery.resourceTypes(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:t.length,Resources:t,startIndex:1,itemsPerPage:t.length})})),s.get(`${e}/ResourceTypes/:name`,u((s,e)=>{const t=this.discovery.resourceType(s.params.name,s);if(!t)return d(e,404,"ResourceType not found");this.scimJson(e,200,t)})),s.get(`${e}/Schemas`,u((s,e)=>{const t=this.discovery.schemas(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:t.length,Resources:t,startIndex:1,itemsPerPage:t.length})})),s.get(`${e}/Schemas/:id`,u((s,e)=>{const t=this.discovery.schema(decodeURIComponent(s.params.id),s);if(!t)return d(e,404,"Schema not found");this.scimJson(e,200,t)}))}parseListQuery(s){const e=s.query,toInt=s=>{if("string"!=typeof s)return;const e=parseInt(s,10);return Number.isFinite(e)?e:void 0},toCsv=s=>{if("string"==typeof s)return s.split(",").map(s=>s.trim()).filter(Boolean)},t="string"==typeof e.sortOrder?e.sortOrder:void 0;return{filter:"string"==typeof e.filter?e.filter:void 0,startIndex:toInt(e.startIndex),count:toInt(e.count),attributes:toCsv(e.attributes),excludedAttributes:toCsv(e.excludedAttributes),sortBy:"string"==typeof e.sortBy?e.sortBy:void 0,sortOrder:"ascending"===t||"descending"===t?t:void 0}}getConfigApiData(){const s=this.scimPrefix;let e;if(this.server.loadedPlugins.static){const s=this.config.static_name||"static";e={user:`/${s}/schemas/scim/User.json`,group:`/${s}/schemas/scim/Group.json`,defaultMapping:`/${s}/schemas/scim/default-mapping.json`}}const t=this.users.userMapping,i=this.groups.groupMapping,o=t.entries.map(s=>s.scim).concat(["id","active"]),r=i.entries.map(s=>s.scim).concat(["id"]);return{enabled:!0,version:"2.0",prefix:s,endpoints:{users:`${s}/Users`,user:`${s}/Users/:id`,groups:`${s}/Groups`,group:`${s}/Groups/:id`,bulk:`${s}/Bulk`,serviceProviderConfig:`${s}/ServiceProviderConfig`,resourceTypes:`${s}/ResourceTypes`,schemas:`${s}/Schemas`},schemaUrls:e,capabilities:{patch:!0,bulk:!0,filter:!0,sort:!0,etag:Boolean(this.config.scim_etag),changePassword:!1,maxResults:this.config.scim_max_results||200,maxScanned:this.config.scim_max_scanned||1e4,bulkMaxOperations:this.config.scim_bulk_max_operations||100,bulkMaxPayloadSize:this.config.scim_bulk_max_payload_size||1048576},resourceTypes:[{id:"User",endpoint:`${s}/Users`,schema:"urn:ietf:params:scim:schemas:core:2.0:User",rdnAttribute:this.config.scim_user_rdn_attribute||"uid",objectClass:this.config.scim_user_object_class,filterableAttributes:o,mapping:t.entries},{id:"Group",endpoint:`${s}/Groups`,schema:"urn:ietf:params:scim:schemas:core:2.0:Group",rdnAttribute:this.config.scim_group_rdn_attribute||"cn",objectClass:this.config.scim_group_object_class,filterableAttributes:r,mapping:i.entries}],idStrategy:this.config.scim_id_attribute||"rdn",baseResolution:{userBaseTemplate:this.config.scim_user_base_template||void 0,groupBaseTemplate:this.config.scim_group_base_template||void 0,hasBaseMap:Boolean(this.config.scim_base_map)}}}}export{Scim as default};
1
+ import{fileURLToPath as s}from"url";import{join as e,dirname as i}from"path";import t from"../../abstract/plugin.js";import{setChangeSource as o}from"../../lib/changeContext.js";import{jsonBodyParser as r}from"../../lib/rawBody.js";import{BaseResolver as a}from"./baseResolver.js";import{ScimUsers as c}from"./users.js";import{ScimGroups as n}from"./groups.js";import{ScimBulk as p}from"./bulk.js";import{ScimDiscovery as m}from"./discovery.js";import{SCIM_CONTENT_TYPE as u,scimAsyncHandler as d,writeScimError as h}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";import{projectResource as l,assertProjection as g,projectList as f}from"./projection.js";function scimBodyParser(s){return r({type:["application/json","application/scim+json"],limit:`${s.scim_bulk_max_payload_size||1048576}b`})}class Scim extends t{constructor(t){super(t),this.name="scim",this.roles=["api","configurable"],this.ldap=t.ldap,this.scimPrefix=this.config.scim_prefix||"/scim/v2",this.baseResolver=new a(this.config),this.users=new c({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,hooks:this.registeredHooks}),this.groups=new n({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,users:this.users,hooks:this.registeredHooks}),this.bulk=new p({config:this.config,logger:this.logger,users:this.users,groups:this.groups,scimPrefix:this.scimPrefix,hooks:this.registeredHooks});const o=this.config.schemas_path||e(i(s(import.meta.url)),"..","..","..","static","schemas");this.discovery=new m({config:this.config,schemaDir:e(o,"scim"),scimPrefix:this.scimPrefix,loadedPlugins:t.loadedPlugins})}scimJson(s,e,i){s.status(e).type(u).json(i)}scimResource(s,e,i,t,o={}){const r=i.id?`${this.scimPrefix}/${t}/${encodeURIComponent(i.id)}`:void 0,a=(this.config.scim_base_url||"")&&i.meta?.location?i.meta.location:r;a&&s.set("Location",a),this.scimJson(s,e,l(i,o))}parseProjection(s){const toCsv=s=>{if("string"!=typeof s)return;const e=s.split(",").map(s=>s.trim()).filter(Boolean);return e.length>0?e:void 0},e={attributes:toCsv(s.query.attributes),excludedAttributes:toCsv(s.query.excludedAttributes)};return g(e),e}readScimBody(s){const e=String(s.headers["content-type"]||"");if(e.startsWith("application/scim+json")||e.startsWith("application/json"))return s.body}api(s){const e=this.scimPrefix;this.server.registerBodyParser(e,scimBodyParser(this.config)),s.use(e,(s,e,i)=>{o(s,"scim"),i()}),s.use(e,d((s,e,i)=>{this.parseProjection(s),i()})),s.get(`${e}/Users`,d(async(s,e)=>{const i=this.parseListQuery(s);g(i);const t=await this.users.list(s,i);this.scimJson(e,200,f(t,i))})),s.get(`${e}/Users/:id`,d(async(s,e)=>{const i=this.parseProjection(s),t=await this.users.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,l(t,i))})),s.post(`${e}/Users`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.create(s,i);this.scimResource(e,201,t,"Users",this.parseProjection(s))})),s.put(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users",this.parseProjection(s))})),s.patch(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.users.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users",this.parseProjection(s))})),s.delete(`${e}/Users/:id`,d(async(s,e)=>{await this.users.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.get(`${e}/Groups`,d(async(s,e)=>{const i=this.parseListQuery(s);g(i);const t=await this.groups.list(s,i);this.scimJson(e,200,f(t,i))})),s.get(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.parseProjection(s),t=await this.groups.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,l(t,i))})),s.post(`${e}/Groups`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.create(s,i);this.scimResource(e,201,t,"Groups",this.parseProjection(s))})),s.put(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups",this.parseProjection(s))})),s.patch(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=await this.groups.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups",this.parseProjection(s))})),s.delete(`${e}/Groups/:id`,d(async(s,e)=>{await this.groups.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.post(`${e}/Bulk`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return h(e,400,"Missing body","invalidSyntax");const t=this.config.scim_bulk_max_payload_size||1048576,o=JSON.stringify(i).length;if(o>t)return h(e,413,`Bulk payload too large: ${o} > ${t}`,"invalidValue");const r=await this.bulk.execute(s,i);this.scimJson(e,200,r)})),s.get(`${e}/ServiceProviderConfig`,d((s,e)=>{this.scimJson(e,200,this.discovery.serviceProviderConfig(s))})),s.get(`${e}/ResourceTypes`,d((s,e)=>{const i=this.discovery.resourceTypes(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/ResourceTypes/:name`,d((s,e)=>{const i=this.discovery.resourceType(s.params.name,s);if(!i)return h(e,404,"ResourceType not found");this.scimJson(e,200,i)})),s.get(`${e}/Schemas`,d((s,e)=>{const i=this.discovery.schemas(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/Schemas/:id`,d((s,e)=>{const i=this.discovery.schema(decodeURIComponent(s.params.id),s);if(!i)return h(e,404,"Schema not found");this.scimJson(e,200,i)}))}parseListQuery(s){const e=s.query,toInt=s=>{if("string"!=typeof s)return;const e=parseInt(s,10);return Number.isFinite(e)?e:void 0},toCsv=s=>{if("string"==typeof s)return s.split(",").map(s=>s.trim()).filter(Boolean)},i="string"==typeof e.sortOrder?e.sortOrder:void 0;return{filter:"string"==typeof e.filter?e.filter:void 0,startIndex:toInt(e.startIndex),count:toInt(e.count),attributes:toCsv(e.attributes),excludedAttributes:toCsv(e.excludedAttributes),sortBy:"string"==typeof e.sortBy?e.sortBy:void 0,sortOrder:"ascending"===i||"descending"===i?i:void 0}}getConfigApiData(){const s=this.scimPrefix;let e;if(this.server.loadedPlugins.static){const s=this.config.static_name||"static";e={user:`/${s}/schemas/scim/User.json`,group:`/${s}/schemas/scim/Group.json`,defaultMapping:`/${s}/schemas/scim/default-mapping.json`}}const i=this.users.userMapping,t=this.groups.groupMapping,o=i.entries.map(s=>s.scim).concat(["id","active"]),r=t.entries.map(s=>s.scim).concat(["id"]);return{enabled:!0,version:"2.0",prefix:s,endpoints:{users:`${s}/Users`,user:`${s}/Users/:id`,groups:`${s}/Groups`,group:`${s}/Groups/:id`,bulk:`${s}/Bulk`,serviceProviderConfig:`${s}/ServiceProviderConfig`,resourceTypes:`${s}/ResourceTypes`,schemas:`${s}/Schemas`},schemaUrls:e,capabilities:{patch:!0,bulk:!0,filter:!0,sort:!0,etag:Boolean(this.config.scim_etag),changePassword:!1,maxResults:this.config.scim_max_results||200,maxScanned:this.config.scim_max_scanned||1e4,bulkMaxOperations:this.config.scim_bulk_max_operations||100,bulkMaxPayloadSize:this.config.scim_bulk_max_payload_size||1048576},resourceTypes:[{id:"User",endpoint:`${s}/Users`,schema:"urn:ietf:params:scim:schemas:core:2.0:User",rdnAttribute:this.config.scim_user_rdn_attribute||"uid",objectClass:this.config.scim_user_object_class,filterableAttributes:o,mapping:i.entries},{id:"Group",endpoint:`${s}/Groups`,schema:"urn:ietf:params:scim:schemas:core:2.0:Group",rdnAttribute:this.config.scim_group_rdn_attribute||"cn",objectClass:this.config.scim_group_object_class,filterableAttributes:r,mapping:t.entries}],idStrategy:this.config.scim_id_attribute||"rdn",baseResolution:{userBaseTemplate:this.config.scim_user_base_template||void 0,groupBaseTemplate:this.config.scim_group_base_template||void 0,hasBaseMap:Boolean(this.config.scim_base_map)}}}}export{Scim as default,scimBodyParser};
@@ -1 +1 @@
1
- import t from"../../abstract/plugin.js";import{NotFoundError as i,BadRequestError as e}from"../../lib/errors.js";import{jsonBody as a}from"../../lib/expressFormatedResponses.js";import{CozyStackProvider as n,ClouderyProvider as r}from"../../lib/instanceProviders.js";import{extractLdapCode as s}from"../../lib/ldapCodes.js";import{rdnValue as o,asyncHandler as c,escapeRegex as h,unescapeDnValue as d,escapeDnValue as u,escapeLdapFilter as l}from"../../lib/utils.js";import{lifecycleAttributes as g,isTombstone as w}from"./lifecycleAttributes.js";const first=t=>{const i=Array.isArray(t)?t[0]:t;return(null==i?"":i.toString())||void 0},f=/(^|\/)twake\/(cozy|cloudery)Provision(\.js)?$/;class TwakeInstances extends t{constructor(t){super(t),this.name="twakeInstances",this.roles=["consistency","api"],this.dependencies={rabbitmq:"core/rabbitmq"},this.queues=new Map,this.hooks={ldapaddrequest:([t,i,e])=>{if(this.patterns.some(i=>i.test(t))||this.isOrganization(t)){const t=[this.fqdnAttribute,this.sentAttribute,this.organizationFqdn].map(t=>t.toLowerCase());for(const e of Object.keys(i))t.includes(e.toLowerCase())&&delete i[e]}return[t,i,e]},ldapaddafter:async([t,i])=>{const e=this.organizationOf(t);e&&await this.serialized(`organization:${e}`,async()=>{try{await this.giveOrganizationAddress(e)}catch(i){this.logger.error({plugin:this.name,event:"organization account",dn:t,error:String(i)})}});const a=this.account(t,i);a&&await this.serialized(t,async()=>{try{await this.provide(t,i,a)}catch(i){this.logger.error({plugin:this.name,event:"create",dn:t,error:String(i)})}})},ldapdeletedone:async t=>{const i=this.provider;if(i instanceof n)for(const e of Array.isArray(t)?t:[t]){const t=this.match(e),a=/^uid=/i.test(e)?o(e):"";t&&a&&await this.serialized(e,async()=>{try{await i.destroy(this.id(a,t),a)}catch(t){this.logger.error({plugin:this.name,event:"destroy",dn:e,error:String(t)})}})}}};const i=this.config,e=(i.plugin||[]).find(t=>f.test(t));if(e)throw new Error(`${this.name}: ${e} gives accounts their instance too; load one of them`);this.patterns=(i.twake_instance_dn||[]).map(t=>new RegExp(t,"i"));const a=new Set(["uid"]);for(const t of this.patterns)for(const[,i]of t.source.matchAll(/\(\?<(\w+)>/g))a.add(i);for(const[,t]of(i.twake_instance_id||"{uid}").matchAll(/\{(\w+)\}/g))if(!a.has(t))throw new Error(`${this.name}: --twake-instance-id names {${t}}, which no --twake-instance-dn group captures`);this.lifecycle=g(i),this.fqdnAttribute=i.twake_instance_fqdn_attribute||"twakeWorkspaceUrl",this.sentAttribute=i.twake_instance_sent_attribute||"twakeCreatedEventAt";const s=i.twake_instance_timeout||3e4;if("cozy-stack"===i.twake_instance_provider){if(!i.twake_instance_cozy_url||!i.twake_instance_cozy_domain)throw new Error(`${this.name}: --twake-instance-cozy-url and --twake-instance-cozy-domain are required`);this.provider=new n(i.twake_instance_cozy_url.replace(/\/$/,""),i.twake_instance_cozy_user||"admin",i.twake_instance_cozy_passphrase||"",i.twake_instance_cozy_domain,i.twake_instance_cozy_context||"",i.twake_instance_cozy_apps||"",s),this.locale=i.twake_instance_locale||"fr"}else{if("cloudery"!==i.twake_instance_provider)throw new Error(`${this.name}: --twake-instance-provider must be cloudery or cozy-stack`);if(!i.twake_instance_cloudery_url||!i.twake_instance_cloudery_domain)throw new Error(`${this.name}: --twake-instance-cloudery-url and --twake-instance-cloudery-domain are required`);this.provider=new r(i.twake_instance_cloudery_url.replace(/\/$/,""),i.twake_instance_cloudery_token||"",i.twake_instance_cloudery_domain,i.twake_instance_cloudery_offer||"",s),this.locale=i.twake_instance_locale||"en"}}async api(t){const n=this.requirePlugin("rabbitmq");if(!n||!await n.getRawClient())throw new Error(`${this.name}: RabbitMQ is required and unreachable`);const s=await this.server.ldap.schemaIndex(),o=[this.fqdnAttribute,this.sentAttribute,...this.config.twake_instance_organization_base?[this.organizationDomain,this.organizationFqdn,this.organizationName]:[]].filter(t=>t&&!s.getAttributeType(t));if(o.length)throw new Error(`${this.name}: the directory schema defines no ${o.join(", ")}; load a schema that does, or name other attributes`);if(this.organizationFqdn&&this.organizationFqdn.toLowerCase()===this.organizationName.toLowerCase())throw new Error(`${this.name}: an organization's name and address cannot share the attribute ${this.organizationName}`);t.post(`${this.config.api_prefix}/v1/twake/instances/ensure`,c(async(t,n)=>{const r=a(t,n);if(!1===r)return;let s=r?.dn;if(void 0===s&&"string"==typeof r?.mail&&r.mail){const e=await this.findByMail(r.mail,t);if(!e)throw new i(`No account for ${r.mail}`);s=e.dn}if("string"!=typeof s||!s)throw new e("Give the account as dn or mail");n.json({state:await this.ensureInstance(s,t)})})),this.provider instanceof r&&await n.subscribe(this.config.twake_instance_auth_exchange||"auth",this.config.twake_instance_workplace_created_key||"workplace.created",this.config.twake_instance_queue||"workplace.created.ldap-rest",t=>this.onWorkplaceCreated(t))}async ensureInstance(t,a){return this.serialized(t,async()=>{const n=await this.read(t,a);if(!n)throw new i(`No entry at ${t}`);const r=this.account(t,n);if(!r)throw new e(`${t} gets no instance`);if(first(this.value(n,this.sentAttribute)))return"ready";a&&await this.server.ldap.modify(t,{replace:{[this.sentAttribute]:[]}},a);const s=first(this.value(n,this.fqdnAttribute));if(s){const i=s.toLowerCase()===this.provider.address(r)?{fqdn:s,ready:!0}:await this.provider.find(r,s);if(i?.ready)return await this.ready(t,n,r,i.fqdn,a),"ready"}return this.provide(t,n,r,a)})}async ensureOrganization(t){return this.serialized(`organization:${t.id}`,async()=>{const e=await this.findOrganization(t.id);if(!e)throw new i(`No organization ${t.id}`);t.name&&!first(this.value(e,this.organizationName))&&(await this.server.ldap.modify(e.dn,{replace:{[this.organizationName]:t.name}}),e[this.organizationName]=t.name);const a=this.organizationFqdn&&first(this.value(e,this.organizationFqdn)),n=this.organizationRequest(t),s=a?{fqdn:a,ready:!0}:await this.provider.find(n);if(first(this.value(e,this.sentAttribute)))return s?.ready?await this.organizationReady(e,s.fqdn):await this.releaseMembers(e.dn),"ready";let o=s?.ready&&s.fqdn;return s||(this.provider instanceof r&&await this.provider.createOrganization(t),o=await this.provider.create(n)),o?(await this.organizationReady(e,o),"ready"):"pending"})}organizationRequest(t){return{id:t.id,email:this.organizationMail(t.id,t.domain),publicName:t.name,locale:this.locale,orgId:t.id,orgDomain:t.domain,offer:this.config.twake_instance_cloudery_organization_offer}}async requestOrganization(t){if(!(this.provider instanceof r))return;const i=await this.findOrganization(t);if(!i||first(this.value(i,this.sentAttribute)))return;const e=first(this.value(i,this.organizationName)),a=first(this.value(i,this.organizationDomain));if(!e||!a)return void this.logger.warn(`${this.name}: organization ${t} has no ${this.organizationName} or ${this.organizationDomain}, so its instance cannot be asked for`);const n=this.organizationRequest({id:t,name:e,domain:a});if(!await this.provider.find(n)){await this.provider.createOrganization({id:t,name:e,domain:a});try{await this.provider.create(n)}catch(t){if(!await this.provider.find(n))throw t}}}async onWorkplaceCreated(t){const{internalEmail:i,workplaceFqdn:e}=t;if(!i||!e)return;const a=t.twakeId&&await this.findOrganization(t.twakeId);if(a){const n=t.twakeId.toLowerCase(),r=first(this.value(a,this.organizationDomain)),s=i.toLowerCase();if(r?this.organizationMail(n,r)===s:s.startsWith(`${n}@`))return this.serialized(`organization:${t.twakeId}`,async()=>{const t=await this.read(a.dn);t&&await this.organizationReady(t,e)})}const n=await this.findByMail(i);n?await this.settle(n.dn,e):this.logger.debug(`${this.name}: no account here for ${i}`)}async settle(t,i){await this.serialized(t,async()=>{const e=await this.read(t),a=e&&this.account(t,e);e&&a&&(first(this.value(e,this.sentAttribute))||await this.ready(t,e,a,i))})}async provide(t,i,e,a){e.orgId&&await this.requestOrganization(e.orgId);const n=await this.provider.find(e),r=n?n.ready&&n.fqdn:await this.provider.create(e);return r?(await this.ready(t,i,e,r,a),"ready"):"pending"}async ready(t,i,e,a,n){if(first(this.value(i,this.fqdnAttribute))!==a&&await this.server.ldap.modify(t,{replace:{[this.fqdnAttribute]:a}},n),first(this.value(i,this.sentAttribute)))return;const s=this.provider instanceof r&&e.orgId&&await this.findOrganization(e.orgId);if(s&&!first(this.value(s,this.sentAttribute))){if(t.toLowerCase().endsWith(`,${s.dn.toLowerCase()}`))return void this.logger.info(`${this.name}: ${t} waits for organization ${e.orgId}`);this.logger.warn(`${this.name}: ${t} is not below its organization entry, so it is announced before organization ${e.orgId} is linked; clear its ${this.sentAttribute} and send it again once the organization is announced`)}await this.announce(this.config.twake_instance_auth_exchange||"auth",this.config.twake_instance_user_created_key||"user.created",{twakeId:e.twakeId,internalEmail:e.email,workplaceFqdn:a,...e.orgId?{organizationId:e.orgId}:{},...e.orgDomain?{domain:e.orgDomain,organizationDomain:e.orgDomain}:{},...e.phone?{mobile:e.phone}:{}}),await this.server.ldap.modify(t,{replace:{[this.sentAttribute]:(new Date).toISOString()}},n)}async organizationReady(t,i){const e=t.dn,a=first(this.value(t,this.organizationId));if(a&&this.provider instanceof r&&await this.provider.linkOrganization(a,i),a&&await this.fillOrganizationAccount(a,i).catch(t=>this.logger.error({plugin:this.name,event:"organization account",organization:a,error:String(t)})),first(this.value(t,this.sentAttribute)))return this.releaseMembers(e);this.organizationFqdn&&!first(this.value(t,this.organizationFqdn))&&await this.server.ldap.modify(e,{replace:{[this.organizationFqdn]:i}});const n=first(this.value(t,this.organizationDomain)),s=first(this.value(t,this.organizationName));await this.announce(this.config.twake_instance_b2b_exchange||"b2b",this.config.twake_instance_organization_created_key||"organization.created",{organizationId:a,workplaceFqdn:i,...s?{organization:s}:{},...n?{domain:n}:{}}),await this.server.ldap.modify(e,{replace:{[this.sentAttribute]:(new Date).toISOString()}}),await this.releaseMembers(e)}organizationOf(t){const i=this.config.twake_instance_organization_account;if(!i)return;const e=h(i).replace("\\{id\\}","(?<id>(?:[^,\\\\]|\\\\.)+)").replace(/\\\{id\\\}/g,"\\k<id>"),a=new RegExp(`^${e}$`,"i").exec(t)?.groups?.id;return a&&d(a)}async giveOrganizationAddress(t){const i=await this.findOrganization(t);if(!i||!first(this.value(i,this.sentAttribute)))return;let e=this.organizationFqdn&&first(this.value(i,this.organizationFqdn));if(!e){const a=await this.provider.find(this.organizationRequest({id:t,name:first(this.value(i,this.organizationName))||"",domain:first(this.value(i,this.organizationDomain))||""}));a?.ready&&(e=a.fqdn)}e&&await this.fillOrganizationAccount(t,e)}async fillOrganizationAccount(t,i){const e=this.config.twake_instance_organization_account;if(!e)return;const a=e.replace(/\{id\}/g,()=>u(t)),n=await this.read(a);n&&!first(this.value(n,this.fqdnAttribute))&&await this.server.ldap.modify(a,{replace:{[this.fqdnAttribute]:i}})}async releaseMembers(t){const i=await this.server.ldap.search({paged:!1,scope:"sub",filter:`(&(${this.fqdnAttribute}=*)(!(${this.sentAttribute}=*)))`},t);let e=0;for(const t of i.searchEntries)try{await this.release(t)}catch(i){e++,this.logger.error(`${this.name}: ${t.dn} not released: ${String(i)}`)}if(e)throw new Error(`${this.name}: ${e} member(s) of ${t} not released`)}async release(t){const i=t.dn,e=this.account(i,t),a=first(this.value(t,this.fqdnAttribute));if(!e||!a)return;const n=a.toLowerCase()===this.provider.address(e)?{fqdn:a,ready:!0}:await this.provider.find(e,a);if(n?.ready)return this.settle(i,n.fqdn);this.logger.warn(`${this.name}: ${i} stays held, its address ${a} is not confirmed; ensureInstance sends it once it is`)}async serialized(t,i){const e=t.toLowerCase(),a=(this.queues.get(e)??Promise.resolve()).catch(()=>{}).then(i),n=a.catch(()=>{});return this.queues.set(e,n),n.then(()=>{this.queues.get(e)===n&&this.queues.delete(e)}),a}get organizationId(){return this.config.twake_instance_organization_id_attribute||"ou"}get organizationDomain(){return this.config.twake_instance_organization_domain_attribute||"twakeDomain"}get organizationName(){return this.config.twake_instance_organization_name_attribute||"description"}get organizationFqdn(){return this.config.twake_instance_organization_fqdn_attribute||""}organizationMail(t,i){return`${t}@${i}`.toLowerCase()}isOrganization(t){const i=this.config.twake_instance_organization_base;if(!i)return!1;return t.slice(t.indexOf(",")+1).toLowerCase()===i.toLowerCase()}async findOrganization(t){const i=this.config.twake_instance_organization_base;if(!i)return;return(await this.server.ldap.search({paged:!1,scope:"one",filter:`(${this.organizationId}=${l(t)})`},i)).searchEntries[0]}async read(t,i){try{return(await this.server.ldap.search({paged:!1,scope:"base"},t,i)).searchEntries[0]}catch(i){const a=s(i);if(32===a)return;if(34===a)throw new e(`Invalid DN: ${t}`);throw i}}async announce(t,i,e){const a=this.requirePlugin("rabbitmq");if(!a||!await a.getRawClient())throw new Error(`${this.name}: RabbitMQ unreachable, ${i} not sent`);await a.publish(t,i,e),this.logger.info({plugin:this.name,event:i,exchange:t})}account(t,i){const e=this.match(t);if(!e)return;if(w(i,this.lifecycle))return;const a=this.config.twake_instance_skip_attribute;if(a&&first(this.value(i,a))?.toLowerCase()===(this.config.twake_instance_skip_value||"").toLowerCase())return;const r=first(this.value(i,"mail")),s=first(this.value(i,"uid"));if(!r||!s)return void this.logger.warn(`${this.name}: ${t} has no mail or uid, no instance`);const o=this.provider instanceof n,c=e.groups?.org||(o?this.config.twake_instance_cozy_org_id:"")||void 0,h=e.groups?.org?r.split("@")[1]:o&&this.config.twake_instance_cozy_org_domain||void 0;return{id:this.id(s,e),...o?{oidc:s}:{},twakeId:s,email:r,publicName:first(this.value(i,"displayName"))||first(this.value(i,"cn"))||s,locale:first(this.value(i,"preferredLanguage"))||this.locale,phone:first(this.value(i,"mobile")),...c?{orgId:c}:{},...h?{orgDomain:h}:{}}}match(t){return this.patterns.map(i=>i.exec(t)).find(t=>null!==t)}id(t,i){const e={uid:t,...i.groups};return(this.config.twake_instance_id||"{uid}").replace(/\{(\w+)\}/g,(t,i)=>e[i]??"").replace(/\./g,"").toLowerCase()}value(t,i){const e=i.toLowerCase(),a=Object.keys(t).find(t=>t.toLowerCase()===e);return a?t[a]:void 0}async findByMail(t,i){return(await this.server.ldap.search({paged:!1,scope:"sub",filter:`(mail=${l(t)})`},this.config.ldap_base,i)).searchEntries.find(t=>this.patterns.some(i=>i.test(t.dn)))}}export{TwakeInstances as default};
1
+ import t from"../../abstract/plugin.js";import{NotFoundError as e,BadRequestError as i}from"../../lib/errors.js";import{jsonBody as a}from"../../lib/expressFormatedResponses.js";import{CozyStackProvider as n,ClouderyProvider as r}from"../../lib/instanceProviders.js";import{extractLdapCode as s}from"../../lib/ldapCodes.js";import{rdnValue as o,asyncHandler as c,escapeLdapFilter as h}from"../../lib/utils.js";import{lifecycleAttributes as d,isTombstone as l}from"./lifecycleAttributes.js";const first=t=>{const e=Array.isArray(t)?t[0]:t;return(null==e?"":e.toString())||void 0},u=/(^|\/)twake\/(cozy|cloudery)Provision(\.js)?$/;class TwakeInstances extends t{constructor(t){super(t),this.name="twakeInstances",this.roles=["consistency","api"],this.dependencies={rabbitmq:"core/rabbitmq"},this.offers=[],this.queues=new Map,this.hooks={ldapaddrequest:([t,e,i])=>{if(this.patterns.some(e=>e.test(t))||this.isOrganization(t)){const t=[this.fqdnAttribute,this.sentAttribute,this.organizationFqdn].map(t=>t.toLowerCase());for(const i of Object.keys(e))t.includes(i.toLowerCase())&&delete e[i]}return[t,e,i]},ldapaddafter:async([t,e])=>{const i=this.account(t,e);i&&await this.serialized(t,async()=>{try{await this.provide(t,e,i)}catch(e){this.logger.error({plugin:this.name,event:"create",dn:t,error:String(e)})}})},ldapdeletedone:async t=>{const e=this.provider;if(e instanceof n)for(const i of Array.isArray(t)?t:[t]){const t=this.match(i),a=/^uid=/i.test(i)?o(i):"";t&&a&&await this.serialized(i,async()=>{try{await e.destroy(this.id(a,t),a)}catch(t){this.logger.error({plugin:this.name,event:"destroy",dn:i,error:String(t)})}})}}};const e=this.config,i=(e.plugin||[]).find(t=>u.test(t));if(i)throw new Error(`${this.name}: ${i} gives accounts their instance too; load one of them`);this.patterns=(e.twake_instance_dn||[]).map(t=>new RegExp(t,"i"));const a=new Set(["uid"]);for(const t of this.patterns)for(const[,e]of t.source.matchAll(/\(\?<(\w+)>/g))a.add(e);for(const[,t]of(e.twake_instance_id||"{uid}").matchAll(/\{(\w+)\}/g))if(!a.has(t))throw new Error(`${this.name}: --twake-instance-id names {${t}}, which no --twake-instance-dn group captures`);this.lifecycle=d(e),this.fqdnAttribute=e.twake_instance_fqdn_attribute||"twakeWorkspaceUrl",this.sentAttribute=e.twake_instance_sent_attribute||"twakeCreatedEventAt";const s=e.twake_instance_timeout||3e4;if("cozy-stack"===e.twake_instance_provider){if(!e.twake_instance_cozy_url||!e.twake_instance_cozy_domain)throw new Error(`${this.name}: --twake-instance-cozy-url and --twake-instance-cozy-domain are required`);this.provider=new n(e.twake_instance_cozy_url.replace(/\/$/,""),e.twake_instance_cozy_user||"admin",e.twake_instance_cozy_passphrase||"",e.twake_instance_cozy_domain,e.twake_instance_cozy_context||"",e.twake_instance_cozy_apps||"",s),this.locale=e.twake_instance_locale||"fr"}else{if("cloudery"!==e.twake_instance_provider)throw new Error(`${this.name}: --twake-instance-provider must be cloudery or cozy-stack`);{if(!e.twake_instance_cloudery_url||!e.twake_instance_cloudery_domain)throw new Error(`${this.name}: --twake-instance-cloudery-url and --twake-instance-cloudery-domain are required`);const t=(e.twake_instance_cloudery_offer||"").split(";").map(t=>t.trim());if(t.length>1&&""===t[t.length-1]&&t.pop(),t.length>1&&t.length>this.patterns.length)throw new Error(`${this.name}: --twake-instance-cloudery-offer has more offers than --twake-instance-dn has expressions`);t.length>1&&(this.offers=t),this.provider=new r(e.twake_instance_cloudery_url.replace(/\/$/,""),e.twake_instance_cloudery_token||"",e.twake_instance_cloudery_domain,t.length>1?"":t[0],s),this.locale=e.twake_instance_locale||"en"}}}async api(t){const n=this.requirePlugin("rabbitmq");if(!n||!await n.getRawClient())throw new Error(`${this.name}: RabbitMQ is required and unreachable`);const s=await this.server.ldap.schemaIndex(),o=[this.fqdnAttribute,this.sentAttribute,...this.config.twake_instance_organization_base?[this.organizationDomain,this.organizationFqdn,this.organizationName]:[]].filter(t=>t&&!s.getAttributeType(t));if(o.length)throw new Error(`${this.name}: the directory schema defines no ${o.join(", ")}; load a schema that does, or name other attributes`);if(this.organizationFqdn&&this.organizationFqdn.toLowerCase()===this.organizationName.toLowerCase())throw new Error(`${this.name}: an organization's name and address cannot share the attribute ${this.organizationName}`);t.post(`${this.config.api_prefix}/v1/twake/instances/ensure`,c(async(t,n)=>{const r=a(t,n);if(!1===r)return;let s=r?.dn;if(void 0===s&&"string"==typeof r?.mail&&r.mail){const i=await this.findByMail(r.mail,t);if(!i)throw new e(`No account for ${r.mail}`);s=i.dn}if("string"!=typeof s||!s)throw new i("Give the account as dn or mail");n.json({state:await this.ensureInstance(s,t)})})),this.provider instanceof r&&await n.subscribe(this.config.twake_instance_auth_exchange||"auth",this.config.twake_instance_workplace_created_key||"workplace.created",this.config.twake_instance_queue||"workplace.created.ldap-rest",t=>this.onWorkplaceCreated(t))}async ensureInstance(t,a){return this.serialized(t,async()=>{const n=await this.read(t,a);if(!n)throw new e(`No entry at ${t}`);const r=this.account(t,n);if(!r)throw new i(`${t} gets no instance`);if(first(this.value(n,this.sentAttribute)))return"ready";a&&await this.server.ldap.modify(t,{replace:{[this.sentAttribute]:[]}},a);const s=first(this.value(n,this.fqdnAttribute));if(s){const e=s.toLowerCase()===this.provider.address(r)?{fqdn:s,ready:!0}:await this.provider.find(r,s);if(e?.ready)return await this.ready(t,n,r,e.fqdn,a),"ready"}return this.provide(t,n,r,a)})}async ensureOrganization(t){return this.serialized(`organization:${t.id}`,async()=>{const i=await this.findOrganization(t.id);if(!i)throw new e(`No organization ${t.id}`);t.name&&!first(this.value(i,this.organizationName))&&(await this.server.ldap.modify(i.dn,{replace:{[this.organizationName]:t.name}}),i[this.organizationName]=t.name);const a=this.organizationFqdn&&first(this.value(i,this.organizationFqdn)),n=this.organizationRequest(t),s=a?{fqdn:a,ready:!0}:await this.provider.find(n);if(first(this.value(i,this.sentAttribute)))return s?.ready?await this.organizationReady(i,s.fqdn):await this.releaseMembers(i.dn),"ready";let o=s?.ready&&s.fqdn;return s||(this.provider instanceof r&&await this.provider.createOrganization(t),o=await this.provider.create(n)),o?(await this.organizationReady(i,o),"ready"):"pending"})}organizationRequest(t){return{id:t.id,email:this.organizationMail(t.id,t.domain),publicName:t.name,locale:this.locale,orgId:t.id,orgDomain:t.domain,offer:this.config.twake_instance_cloudery_organization_offer}}async requestOrganization(t){if(!(this.provider instanceof r))return;const e=await this.findOrganization(t);if(!e||first(this.value(e,this.sentAttribute)))return;const i=first(this.value(e,this.organizationName)),a=first(this.value(e,this.organizationDomain));if(!i||!a)return void this.logger.warn(`${this.name}: organization ${t} has no ${this.organizationName} or ${this.organizationDomain}, so its instance cannot be asked for`);const n=this.organizationRequest({id:t,name:i,domain:a});if(!await this.provider.find(n)){await this.provider.createOrganization({id:t,name:i,domain:a});try{await this.provider.create(n)}catch(t){if(!await this.provider.find(n))throw t}}}async onWorkplaceCreated(t){const{internalEmail:e,workplaceFqdn:i}=t;if(!e||!i)return;const a=t.twakeId&&await this.findOrganization(t.twakeId);if(a){const n=t.twakeId.toLowerCase(),r=first(this.value(a,this.organizationDomain)),s=e.toLowerCase();if(r?this.organizationMail(n,r)===s:s.startsWith(`${n}@`))return this.serialized(`organization:${t.twakeId}`,async()=>{const t=await this.read(a.dn);t&&await this.organizationReady(t,i)})}const n=await this.findByMail(e);n?await this.settle(n.dn,i):this.logger.debug(`${this.name}: no account here for ${e}`)}async settle(t,e){await this.serialized(t,async()=>{const i=await this.read(t),a=i&&this.account(t,i);i&&a&&(first(this.value(i,this.sentAttribute))||await this.ready(t,i,a,e))})}async provide(t,e,i,a){i.orgId&&await this.requestOrganization(i.orgId);const n=await this.provider.find(i),r=n?n.ready&&n.fqdn:await this.provider.create(i);return r?(await this.ready(t,e,i,r,a),"ready"):"pending"}async ready(t,e,i,a,n){if(first(this.value(e,this.fqdnAttribute))!==a&&await this.server.ldap.modify(t,{replace:{[this.fqdnAttribute]:a}},n),first(this.value(e,this.sentAttribute)))return;const s=this.provider instanceof r&&i.orgId&&await this.findOrganization(i.orgId);if(s&&!first(this.value(s,this.sentAttribute))){if(t.toLowerCase().endsWith(`,${s.dn.toLowerCase()}`))return void this.logger.info(`${this.name}: ${t} waits for organization ${i.orgId}`);this.logger.warn(`${this.name}: ${t} is not below its organization entry, so it is announced before organization ${i.orgId} is linked; clear its ${this.sentAttribute} and send it again once the organization is announced`)}await this.announce(this.config.twake_instance_auth_exchange||"auth",this.config.twake_instance_user_created_key||"user.created",{twakeId:i.twakeId,internalEmail:i.email,workplaceFqdn:a,...i.orgId?{organizationId:i.orgId}:{},...i.orgDomain?{domain:i.orgDomain,organizationDomain:i.orgDomain}:{},...i.phone?{mobile:i.phone}:{}}),await this.server.ldap.modify(t,{replace:{[this.sentAttribute]:(new Date).toISOString()}},n)}async organizationReady(t,e){const i=t.dn,a=first(this.value(t,this.organizationId));if(a&&this.provider instanceof r&&await this.provider.linkOrganization(a,e),first(this.value(t,this.sentAttribute)))return this.releaseMembers(i);this.organizationFqdn&&!first(this.value(t,this.organizationFqdn))&&await this.server.ldap.modify(i,{replace:{[this.organizationFqdn]:e}});const n=first(this.value(t,this.organizationDomain)),s=first(this.value(t,this.organizationName));await this.announce(this.config.twake_instance_b2b_exchange||"b2b",this.config.twake_instance_organization_created_key||"organization.created",{organizationId:a,workplaceFqdn:e,...s?{organization:s}:{},...n?{domain:n}:{}}),await this.server.ldap.modify(i,{replace:{[this.sentAttribute]:(new Date).toISOString()}}),await this.releaseMembers(i)}async releaseMembers(t){const e=await this.server.ldap.search({paged:!1,scope:"sub",filter:`(&(${this.fqdnAttribute}=*)(!(${this.sentAttribute}=*)))`},t);let i=0;for(const t of e.searchEntries)try{await this.release(t)}catch(e){i++,this.logger.error(`${this.name}: ${t.dn} not released: ${String(e)}`)}if(i)throw new Error(`${this.name}: ${i} member(s) of ${t} not released`)}async release(t){const e=t.dn,i=this.account(e,t),a=first(this.value(t,this.fqdnAttribute));if(!i||!a)return;const n=a.toLowerCase()===this.provider.address(i)?{fqdn:a,ready:!0}:await this.provider.find(i,a);if(n?.ready)return this.settle(e,n.fqdn);this.logger.warn(`${this.name}: ${e} stays held, its address ${a} is not confirmed; ensureInstance sends it once it is`)}async serialized(t,e){const i=t.toLowerCase(),a=(this.queues.get(i)??Promise.resolve()).catch(()=>{}).then(e),n=a.catch(()=>{});return this.queues.set(i,n),n.then(()=>{this.queues.get(i)===n&&this.queues.delete(i)}),a}get organizationId(){return this.config.twake_instance_organization_id_attribute||"ou"}get organizationDomain(){return this.config.twake_instance_organization_domain_attribute||"twakeDomain"}get organizationName(){return this.config.twake_instance_organization_name_attribute||"description"}get organizationFqdn(){return this.config.twake_instance_organization_fqdn_attribute||""}organizationMail(t,e){return`${t}@${e}`.toLowerCase()}isOrganization(t){const e=this.config.twake_instance_organization_base;if(!e)return!1;return t.slice(t.indexOf(",")+1).toLowerCase()===e.toLowerCase()}async findOrganization(t){const e=this.config.twake_instance_organization_base;if(!e)return;return(await this.server.ldap.search({paged:!1,scope:"one",filter:`(${this.organizationId}=${h(t)})`},e)).searchEntries[0]}async read(t,e){try{return(await this.server.ldap.search({paged:!1,scope:"base"},t,e)).searchEntries[0]}catch(e){const a=s(e);if(32===a)return;if(34===a)throw new i(`Invalid DN: ${t}`);throw e}}async announce(t,e,i){const a=this.requirePlugin("rabbitmq");if(!a||!await a.getRawClient())throw new Error(`${this.name}: RabbitMQ unreachable, ${e} not sent`);await a.publish(t,e,i),this.logger.info({plugin:this.name,event:e,exchange:t})}account(t,e){const i=this.match(t);if(!i)return;if(l(e,this.lifecycle))return;const a=this.config.twake_instance_skip_attribute;if(a&&first(this.value(e,a))?.toLowerCase()===(this.config.twake_instance_skip_value||"").toLowerCase())return;const r=first(this.value(e,"mail")),s=first(this.value(e,"uid"));if(!r||!s)return void this.logger.warn(`${this.name}: ${t} has no mail or uid, no instance`);const o=this.provider instanceof n,c=i.groups?.org||(o?this.config.twake_instance_cozy_org_id:"")||void 0,h=i.groups?.org?r.split("@")[1]:o&&this.config.twake_instance_cozy_org_domain||void 0,d=this.offers[this.patterns.findIndex(e=>e.test(t))];return{id:this.id(s,i),...o?{oidc:s}:{},twakeId:s,email:r,publicName:first(this.value(e,"displayName"))||first(this.value(e,"cn"))||s,locale:first(this.value(e,"preferredLanguage"))||this.locale,phone:first(this.value(e,"mobile")),...c?{orgId:c}:{},...h?{orgDomain:h}:{},...d?{offer:d}:{}}}match(t){return this.patterns.map(e=>e.exec(t)).find(t=>null!==t)}id(t,e){const i={uid:t,...e.groups};return(this.config.twake_instance_id||"{uid}").replace(/\{(\w+)\}/g,(t,e)=>i[e]??"").replace(/\./g,"").toLowerCase()}value(t,e){const i=e.toLowerCase(),a=Object.keys(t).find(t=>t.toLowerCase()===i);return a?t[a]:void 0}async findByMail(t,e){return(await this.server.ldap.search({paged:!1,scope:"sub",filter:`(mail=${h(t)})`},this.config.ldap_base,e)).searchEntries.find(t=>this.patterns.some(e=>e.test(t.dn)))}}export{TwakeInstances as default};
@@ -52,6 +52,8 @@ export declare class DM {
52
52
  */
53
53
  private _authState;
54
54
  private _errorMiddlewareSetup;
55
+ /** The parsers plugins mount ahead of the global ones, see registerBodyParser */
56
+ private readonly bodyParsers;
55
57
  /**
56
58
  * Whether the plugins of the configuration are loaded and their
57
59
  * composition checked. A plugin registered after that — a server
@@ -111,6 +113,20 @@ export declare class DM {
111
113
  * @param plugin plugin whose `authenticate` guards the paths it claims
112
114
  */
113
115
  registerAuthenticator(plugin: AuthBase): void;
116
+ /**
117
+ * Parse the bodies under `path` with `parser`, ahead of the global JSON and
118
+ * form parsers, which then leave them alone: for another media type or
119
+ * size limit there. Authentication runs before the routes, so a parser
120
+ * mounted by a route would come too late for core/auth/hmac.
121
+ *
122
+ * `jsonBodyParser` builds a JSON one that keeps the bytes it reads; any
123
+ * other must pass `keepRawBody` as `verify`: core/auth/hmac refuses a body
124
+ * whose bytes were not kept.
125
+ *
126
+ * @param path the prefix whose bodies `parser` reads
127
+ * @param parser a body-parser middleware
128
+ */
129
+ registerBodyParser(path: string, parser: express.RequestHandler): void;
114
130
  /**
115
131
  * Pick the authentication a request must satisfy, and run it.
116
132
  *
@@ -262,7 +262,6 @@ export interface Config {
262
262
  twake_instance_organization_domain_attribute?: string;
263
263
  twake_instance_organization_name_attribute?: string;
264
264
  twake_instance_organization_fqdn_attribute?: string;
265
- twake_instance_organization_account?: string;
266
265
  twake_lifecycle_reason_attribute?: string;
267
266
  twake_tombstone_dn?: string[];
268
267
  twake_tombstone_default_reason?: string;
@@ -55,10 +55,27 @@ export interface Hooks {
55
55
  ldapaddafter?: (args: [string, AttributesList], context?: ChangeContext) => MaybePromise<void>;
56
56
  ldapmodifyrequest?: ChainedHook<[string, ModifyRequest, number, Request?]>;
57
57
  ldapmodifydone?: (args: [string, ModifyRequest, number], context?: ChangeContext) => MaybePromise<void>;
58
+ /**
59
+ * Once a modify is over, whatever became of it, after its `ldapmodifydone`
60
+ * subscribers have returned; with the operation number
61
+ */
62
+ ldapmodifyend?: (op: number) => MaybePromise<void>;
58
63
  ldapdeleterequest?: ChainedHook<[string | string[], Request?]>;
59
64
  ldapdeletedone?: (dn: string | string[], context?: ChangeContext) => MaybePromise<void>;
65
+ /**
66
+ * Once a delete is over, whatever became of it, after its `ldapdeletedone`
67
+ * subscribers have returned; with the operation number its request and
68
+ * done hooks read from `currentOperation()`
69
+ */
70
+ ldapdeleteend?: (op: number) => MaybePromise<void>;
60
71
  ldaprenamerequest?: ChainedHook<[string, string, Request?]>;
61
72
  ldaprenamedone?: (args: [string, string], context?: ChangeContext) => MaybePromise<void>;
73
+ /**
74
+ * Once a rename is over, whatever became of it, after its `ldaprenamedone`
75
+ * subscribers have returned; with the operation number its request and
76
+ * done hooks read from `currentOperation()`
77
+ */
78
+ ldaprenameend?: (op: number) => MaybePromise<void>;
62
79
  /**
63
80
  * Plugins
64
81
  */
@@ -242,7 +242,9 @@ declare class ldapActions {
242
242
  private wrapPaginatedSearch;
243
243
  add(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
244
244
  modify(dn: string, changes: ModifyRequest, req?: Request, options?: ModifyOptions): Promise<boolean>;
245
+ private applyModify;
245
246
  rename(dn: string, newRdn: string, req?: Request): Promise<boolean>;
247
+ private applyRename;
246
248
  /**
247
249
  * Move an entry to a new location (different parent)
248
250
  * Uses LDAP modifyDN with full DN to change both RDN and parent
@@ -259,7 +261,16 @@ declare class ldapActions {
259
261
  */
260
262
  move(dn: string, newDn: string, req?: Request): Promise<boolean>;
261
263
  delete(dn: string | string[], req?: Request): Promise<boolean>;
264
+ private applyDelete;
262
265
  private setDn;
266
+ /**
267
+ * Launch the "end" hooks of a write once its "done" hooks have returned,
268
+ * whatever became of it: written, refused by a request hook, taken out of
269
+ * the request by one, or failed in the directory, with its operation
270
+ * number. A plugin keeping state from a request hook to a done one, keyed
271
+ * by `currentOperation()`, drops there what no done hook took.
272
+ */
273
+ private endOperation;
263
274
  opNumber(): number;
264
275
  }
265
276
  export default ldapActions;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Run a write as operation `op`.
3
+ *
4
+ * @param op the operation number
5
+ * @param write the write, its request and "done" hooks included
6
+ * @returns what the write returns
7
+ */
8
+ export declare function runOperation<T>(op: number, write: () => Promise<T>): Promise<T>;
9
+ /**
10
+ * Run something outside any operation, such as a move a request hook makes:
11
+ * its hooks belong to no request.
12
+ *
13
+ * @param fn what to run
14
+ * @returns what it returns
15
+ */
16
+ export declare function outsideOperation<T>(fn: () => T): T;
17
+ /**
18
+ * The operation a hook is called for.
19
+ *
20
+ * Inside the request and "done" hooks of a modify, delete or rename, it is
21
+ * that write. Elsewhere it is whatever encloses the call: an add or a search
22
+ * made from a delete's request hook (the trash, a tombstone) sees the
23
+ * delete's number, and the "end" hooks run in their caller's context, so an
24
+ * end subscriber must use the number it is given, not this.
25
+ *
26
+ * @returns the number of the modify, delete or rename the call runs inside,
27
+ * or undefined outside any
28
+ */
29
+ export declare function currentOperation(): number | undefined;
@@ -1,21 +1,40 @@
1
1
  /**
2
- * The bytes of a request body, as the global parsers read them
2
+ * The bytes of a request body, as the parsers ahead of the routes read them
3
3
  * @module lib/rawBody
4
4
  */
5
5
  import type { IncomingMessage } from 'http';
6
+ import bodyParser from 'body-parser';
7
+ import type { RequestHandler } from 'express';
8
+ /** A body as such a parser read it */
9
+ export interface RawBody {
10
+ /** The bytes received, once Content-Encoding is removed */
11
+ bytes: Buffer;
12
+ /** The charset the parser decodes them with, lowercased */
13
+ encoding: string;
14
+ }
6
15
  /**
7
- * body-parser `verify` callback keeping the bytes it read, for
8
- * core/auth/hmac to hash what the client signed
16
+ * body-parser `verify` callback keeping the bytes it read, and the charset it
17
+ * decodes them with, for core/auth/hmac to hash what the client signed
9
18
  *
10
19
  * @param req the request
11
20
  * @param _res the response
12
- * @param buf the body read
21
+ * @param bytes the body read
22
+ * @param encoding the charset body-parser decodes it with: its own reading
23
+ * of the Content-Type, or its default
13
24
  */
14
- export declare function keepRawBody(req: IncomingMessage, _res: unknown, buf: Buffer): void;
25
+ export declare function keepRawBody(req: IncomingMessage, _res: unknown, bytes: Buffer, encoding: string): void;
15
26
  /**
16
- * The bytes a global parser read from a request body.
27
+ * The body a parser kept from a request, with `keepRawBody`.
17
28
  *
18
29
  * @param req the request
19
- * @returns them, or undefined when no global parser read the body
30
+ * @returns it, or undefined when no parser kept it
20
31
  */
21
- export declare function rawBodyOf(req: IncomingMessage): Buffer | undefined;
32
+ export declare function rawBodyOf(req: IncomingMessage): RawBody | undefined;
33
+ /**
34
+ * A JSON parser that keeps the bytes it reads, for `DM.registerBodyParser`:
35
+ * core/auth/hmac refuses a body whose bytes were not kept
36
+ *
37
+ * @param options body-parser JSON options
38
+ * @returns the parser
39
+ */
40
+ export declare function jsonBodyParser(options?: Omit<bodyParser.OptionsJson, 'verify'>): RequestHandler;
@@ -17,7 +17,7 @@ export default class AuthHmac extends AuthBase {
17
17
  /**
18
18
  * Calculate SHA256 hash of request body
19
19
  * Returns empty string for GET/DELETE/HEAD methods, and undefined for a
20
- * body no global parser read, which cannot be checked
20
+ * body no parser kept, which cannot be checked
21
21
  */
22
22
  private calculateBodyHash;
23
23
  /**
@@ -24,8 +24,11 @@ declare class OnLdapChange extends DmPlugin {
24
24
  name: string;
25
25
  roles: Role[];
26
26
  stack: Record<number, Entry>;
27
- pendingDeletions: Map<string, Entry>;
28
- pendingRenames: Map<string, Entry>;
27
+ pendingDeletions: Map<number, Map<string, Entry>>;
28
+ pendingRenames: Map<number, {
29
+ dn: string;
30
+ entry: Entry;
31
+ }>;
29
32
  hooks: Hooks;
30
33
  read(dn: string): Promise<Entry | undefined>;
31
34
  publish(dn: string, before: Entry | null, after: Entry | null, context?: ChangeContext): void;
@@ -1,7 +1,8 @@
1
- import type { Express } from 'express';
1
+ import type { Express, RequestHandler } from 'express';
2
2
  import DmPlugin, { type Role } from '../../abstract/plugin';
3
3
  import type { DM } from '../../bin';
4
4
  import type ldapActions from '../../lib/ldapActions';
5
+ import type { Config } from '../../config/args';
5
6
  import { writeScimErrorFromException } from './errors';
6
7
  /**
7
8
  * Reusable SCIM 2.0 schemas surfaced by this plugin.
@@ -392,6 +393,15 @@ import { writeScimErrorFromException } from './errors';
392
393
  * resourceType: { type: string, example: Schema }
393
394
  * location: { type: string }
394
395
  */
396
+ /**
397
+ * The parser core/scim registers under its prefix: application/json too, so
398
+ * that a SCIM request sent as such gets the SCIM limit rather than the global
399
+ * parser's 100kb
400
+ *
401
+ * @param config the configuration holding the SCIM limit
402
+ * @returns the parser
403
+ */
404
+ export declare function scimBodyParser(config: Config): RequestHandler;
395
405
  export default class Scim extends DmPlugin {
396
406
  name: string;
397
407
  roles: Role[];
@@ -35,6 +35,8 @@ export default class TwakeInstances extends DmPlugin {
35
35
  protected readonly sentAttribute: string;
36
36
  private readonly locale;
37
37
  private readonly lifecycle;
38
+ /** The Cloudery offer of each `--twake-instance-dn` expression, when several */
39
+ private readonly offers;
38
40
  /** Work on one account, or organization, waits for the work before it */
39
41
  private readonly queues;
40
42
  constructor(server: DM);
@@ -74,14 +76,6 @@ export default class TwakeInstances extends DmPlugin {
74
76
  private provide;
75
77
  protected ready(dn: string, entry: AttributesList, account: Account, fqdn: string, req?: Request): Promise<void>;
76
78
  private organizationReady;
77
- /** The organization whose account `dn` is, if it is one */
78
- private organizationOf;
79
- /**
80
- * An organization account added once its organization is announced. One
81
- * added before is filled by organizationReady
82
- */
83
- private giveOrganizationAddress;
84
- private fillOrganizationAccount;
85
79
  /** Send the user.created held while the organization at `dn` was pending */
86
80
  private releaseMembers;
87
81
  /** Announce a held member, with the address rule ensureInstance applies */
@@ -690,6 +690,27 @@ api(app: Express): void {
690
690
  }
691
691
  ```
692
692
 
693
+ #### Parsing request bodies
694
+
695
+ The server parses `application/json` (up to 100 kB) and
696
+ `application/x-www-form-urlencoded` bodies before authentication runs, so
697
+ that `core/auth/hmac` can check them. A plugin that needs another media type
698
+ or limit under its prefix registers its own parser, which runs ahead of
699
+ those:
700
+
701
+ ```typescript
702
+ import { jsonBodyParser } from '../../lib/rawBody';
703
+
704
+ api(app: Express): void {
705
+ this.server.registerBodyParser('/myplugin', jsonBodyParser({ limit: '1mb' }));
706
+ }
707
+ ```
708
+
709
+ `jsonBodyParser` keeps the bytes it reads, which `core/auth/hmac` hashes. Any
710
+ other parser must pass `keepRawBody` as `verify`, or `core/auth/hmac` refuses
711
+ its bodies with `401`. A parser mounted with `app.use()` in `api()` runs
712
+ after authentication, too late for that check.
713
+
693
714
  #### Error Handling Best Practices
694
715
 
695
716
  The `asyncHandler` wrapper automatically catches errors and passes them to the global error middleware. This ensures:
@@ -19,6 +19,9 @@ Typescript definitions into [hooks.ts](./src/hooks.ts)
19
19
  - **ldapmodifyrequest**: called before any ldapmodify
20
20
  - **ldapdeleterequest**: called before any ldapdelete
21
21
  - **ldaprenamerequest**: called before any ldap rename/modifyDN operation
22
+ - **ldapmodifyend**, **ldapdeleteend**, **ldaprenameend**: launched once a
23
+ modify, delete or rename is over, whatever became of it, see
24
+ [below](#ldapmodifyend-ldapdeleteend-ldaprenameend)
22
25
 
23
26
  ### ldapaddafter
24
27
 
@@ -41,6 +44,28 @@ the account the entry describes.
41
44
  no set order with it: an add event may or may not include what a
42
45
  subscriber here wrote.
43
46
 
47
+ ### ldapmodifyend, ldapdeleteend, ldaprenameend
48
+
49
+ For a plugin that keeps something from a request hook to the matching "done"
50
+ one, as core/ldap/onChange keeps the entry it read. A write that is refused
51
+ by a request hook, that one of them takes out of the request (core/ldap/trash
52
+ and core/twake/tombstone do with a delete), or that the directory refuses,
53
+ never reaches its "done" hook; its "end" hook fires all the same.
54
+
55
+ - Each gets the number of its operation. A modify's request and "done" hooks
56
+ are given it in their arguments; for every modify, delete and rename,
57
+ `currentOperation()` (`lib/operation`) returns it inside the request and
58
+ "done" hooks. Key what you keep by it rather than by DN: two writes of one
59
+ DN may be under way, and only the operation tells them apart.
60
+ Outside those hooks it is whatever encloses the call (an add made from a
61
+ delete's request hook sees the delete's number): in an end hook, use the
62
+ number it is given.
63
+ - It is launched once the "done" subscribers of the operation have returned,
64
+ and is not awaited by the write.
65
+ - A move (`ldapActions.move`) launches no request hook and no end hook, and
66
+ its `ldaprenamedone` runs outside any operation, even when a request hook
67
+ made it.
68
+
44
69
  ## [onChange](../usage/plugins/ldap/on-change.md) hooks
45
70
 
46
71
  - **onLdapEntryChange**: called after any write, with the entry before and
@@ -479,7 +479,7 @@ See [lifecycle events](plugins/integrations/lifecycle-events.md).
479
479
  | `--twake-instance-cloudery-url` | `DM_TWAKE_INSTANCE_CLOUDERY_URL` | | Cloudery API |
480
480
  | `--twake-instance-cloudery-token` | `DM_TWAKE_INSTANCE_CLOUDERY_TOKEN` | | Its token |
481
481
  | `--twake-instance-cloudery-domain` | `DM_TWAKE_INSTANCE_CLOUDERY_DOMAIN` | | Domain of the instances |
482
- | `--twake-instance-cloudery-offer` | `DM_TWAKE_INSTANCE_CLOUDERY_OFFER` | `b2b_twake_default` | Offer of an account's instance |
482
+ | `--twake-instance-cloudery-offer` | `DM_TWAKE_INSTANCE_CLOUDERY_OFFER` | `b2b_twake_default` | Offer of an account's instance, or one per DN expression |
483
483
  | `--twake-instance-cloudery-organization-offer` | `DM_TWAKE_INSTANCE_CLOUDERY_ORGANIZATION_OFFER` | | Offer of an organization's instance |
484
484
  | `--twake-instance-cozy-url` | `DM_TWAKE_INSTANCE_COZY_URL` | | cozy-stack admin API |
485
485
  | `--twake-instance-cozy-user` | `DM_TWAKE_INSTANCE_COZY_USER` | `admin` | Its user |
@@ -500,7 +500,6 @@ See [lifecycle events](plugins/integrations/lifecycle-events.md).
500
500
  | `--twake-instance-organization-domain-attribute` | `DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE` | `twakeDomain` | Attribute holding an organization's domain |
501
501
  | `--twake-instance-organization-name-attribute` | `DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE` | `description` | Attribute holding an organization's name |
502
502
  | `--twake-instance-organization-fqdn-attribute` | `DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE` | | Address attribute of an organization (empty: not written) |
503
- | `--twake-instance-organization-account` | `DM_TWAKE_INSTANCE_ORGANIZATION_ACCOUNT` | | Organization account DN, `{id}` its id (empty: none) |
504
503
 
505
504
  See [instances](plugins/integrations/instances.md).
506
505
 
@@ -60,8 +60,13 @@ DM_AUTH_HMAC_WINDOW=120000 # Default: 120000ms (2 minutes)
60
60
 
61
61
  Only a body the server parses before checking the signature can be signed:
62
62
  `application/json`, `application/x-www-form-urlencoded` and, under the
63
- SCIM prefix, `application/scim+json`. A POST, PATCH or PUT carrying any other body,
63
+ prefix of a loaded `core/scim`, `application/scim+json`. A POST, PATCH or PUT carrying any other body,
64
64
  such as a `multipart/form-data` bulk import upload, is refused with `401`.
65
+ So is a GET, DELETE or HEAD carrying a body, since they are signed without
66
+ one.
67
+
68
+ The `Content-Type` is not part of the signature. A signed body is read as
69
+ UTF-8 only: one sent under another `charset` is refused with `401`.
65
70
 
66
71
  2. **Client sends Authorization header:**
67
72
 
@@ -75,16 +75,6 @@ from `--twake-instance-organization-name-attribute`, which
75
75
  `ensureOrganization` fills from `name` when the entry has none. The mark, not
76
76
  the address, says the organization is done.
77
77
 
78
- `--twake-instance-organization-account` names an account of the
79
- organization by a DN template, `{id}` standing for the organization id, for
80
- example `uid={id},ou=users,ou={id},ou=organizations,dc=example,dc=com`. It
81
- is usually a technical account, kept from an instance of its own by
82
- `--twake-instance-skip-attribute`. When the organization is announced, and on
83
- any replay, the account gets the organization's address in
84
- `--twake-instance-fqdn-attribute`, when it exists and has none. An account
85
- added once the organization is marked gets it at creation, read from the
86
- organization entry or else from the provider.
87
-
88
78
  With the Cloudery, a member's `user.created` waits for its organization's: a
89
79
  member under an organization entry that is not marked yet gets its address
90
80
  but no event and no mark. A member outside its organization entry is not
@@ -149,6 +139,15 @@ and no such group, `--twake-instance-cozy-org-id` and
149
139
  ```
150
140
 
151
141
  A DN holds commas, so in `DM_TWAKE_INSTANCE_DN` end each expression with `;`.
142
+
143
+ With the Cloudery, `--twake-instance-cloudery-offer` is the offer of every
144
+ account's instance, or one offer per `--twake-instance-dn` expression,
145
+ separated by `;` in the same order: `personal;business` gives the accounts
146
+ the first expression matches `personal`, those of the second `business`. A
147
+ final `;` is ignored. An expression with an empty item, or none, leaves the
148
+ choice to the Cloudery. More offers than expressions stop the server at
149
+ startup. An organization's own instance keeps
150
+ `--twake-instance-cloudery-organization-offer`.
152
151
  Every option and its default is listed in the
153
152
  [configuration reference](../../configuration.md). The plugin needs
154
153
  `--rabbitmq-url`.
@@ -4,7 +4,34 @@ What to check before deploying, newest first. Only releases that need a
4
4
  decision or a configuration change appear here; see the
5
5
  [CHANGELOG](../../CHANGELOG.md) for everything else.
6
6
 
7
- ## Unreleased
7
+ ## To 0.14.0
8
+
9
+ ### `--twake-instance-organization-account` is removed
10
+
11
+ **Who is affected:** a deployment that sets
12
+ `--twake-instance-organization-account` or
13
+ `DM_TWAKE_INSTANCE_ORGANIZATION_ACCOUNT`.
14
+
15
+ The option is ignored, and no account gets the organization's address any
16
+ more. An account that already got it keeps it. To copy the address to
17
+ accounts yourself, set `--twake-instance-organization-fqdn-attribute`. The
18
+ organization entry then keeps the address, but only for organizations
19
+ announced from then on. An organization already announced does not get it:
20
+ take its address from its account or from the Cloudery.
21
+
22
+ ### HMAC refuses a body it does not sign
23
+
24
+ **Who is affected:** a client of `core/auth/hmac` that sends a body with a
25
+ GET, DELETE or HEAD, or a signed body under a `charset` other than UTF-8.
26
+
27
+ These methods are signed without a body, so one they carried reached the
28
+ route unsigned; and the signature covers the bytes, not the charset that says
29
+ how to read them. Both are now answered `401`: send GET, DELETE and HEAD
30
+ without a body (and without `Transfer-Encoding`, which announces one), and
31
+ bodies in UTF-8
32
+ ([#257](https://github.com/linagora/ldap-rest/issues/257)).
33
+
34
+ ## To 0.13.3
8
35
 
9
36
  ### HMAC hashes the body bytes received
10
37
 
@@ -20,6 +47,13 @@ bundled browser client does, sees no change. One that hashed a
20
47
  hash the bytes sent instead, before compressing them if the request carries
21
48
  a `Content-Encoding`.
22
49
 
50
+ A request without a body is signed with an empty `body-hash`, as documented.
51
+ 0.13.2 hashed the `{}` the parser left for a request that carries
52
+ `Content-Type: application/json` without a body, and required
53
+ `sha256("{}")`. A client that signs this way is now refused with `401`. The
54
+ bundled browser client sends no `Content-Type` without a body and is not
55
+ affected.
56
+
23
57
  ## To 0.13.2
24
58
 
25
59
  ### HMAC refuses a body it cannot check
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -120,6 +120,11 @@ class HmacAuthClient {
120
120
  return undefined;
121
121
  }
122
122
  if (typeof body === 'string') {
123
+ // fetch would send it as text/plain, which the server does not read:
124
+ // a string here is JSON already serialized, unless the caller says
125
+ // otherwise
126
+ if (!headers.has('Content-Type'))
127
+ headers.set('Content-Type', 'application/json');
123
128
  return body;
124
129
  }
125
130
  if (body instanceof FormData) {
@@ -1 +1 @@
1
- {"version":3,"file":"hmac.js","sources":["../../../../../src/browser/shared/utils/hmac.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hmac.js","sources":["../../../../../src/browser/shared/utils/hmac.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAA;;;;AAIG;AAUH;;AAEG;AACH,eAAe,MAAM,CAAC,IAAY,EAAA;AAChC,IAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE;IACjC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;AACvC,IAAA,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC;AACpE,IAAA,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IACxD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AACrE;AAEA;;AAEG;AACH,eAAe,UAAU,CAAC,MAAc,EAAE,OAAe,EAAA;AACvD,IAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE;IACjC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;IACtC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;;AAG3C,IAAA,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAC7C,KAAK,EACL,OAAO,EACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT;;AAGD,IAAA,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC;AAC1E,IAAA,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IAC5D,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1E;AAEA;;;;AAIG;AACI,eAAe,qBAAqB,CACzC,MAAc,EACd,MAAc,EACd,IAAY,EACZ,SAAiB,EACjB,IAAc,EAAA;;IAGd,IAAI,QAAQ,GAAG,EAAE;AACjB,IAAA,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE;AAExC,IAAA,IACE,IAAI;AACJ,SAAC,WAAW,KAAK,MAAM,IAAI,WAAW,KAAK,OAAO,IAAI,WAAW,KAAK,KAAK,CAAC,EAC5E;AACA,QAAA,MAAM,UAAU,GAAG,OAAO,IAAI,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;AACzE,QAAA,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC;IACrC;;IAGA,MAAM,aAAa,GAAG,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAE;;AAGvE,IAAA,OAAO,MAAM,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC;AAChD;AAEA;;;AAGG;MACU,cAAc,CAAA;AAGzB,IAAA,WAAA,CAAY,MAAkB,EAAA;AAC5B,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;AAEpB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;AAC1B,YAAA,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC;QAC3C;AAEA,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE;AACzD,YAAA,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC;QAC/D;IACF;AAEA;;;;AAIG;AACH,IAAA,MAAM,aAAa,CACjB,MAAc,EACd,IAAY,EACZ,IAAc,EAAA;AAEd,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;AAC5B,QAAA,MAAM,SAAS,GAAG,MAAM,qBAAqB,CAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,EAClB,MAAM,EACN,IAAI,EACJ,SAAS,EACT,IAAI,CACL;QAED,OAAO,CAAA,YAAA,EAAe,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE;IACzE;AAEA;;AAEG;AACK,IAAA,WAAW,CAAC,GAAW,EAAA;AAC7B,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;AACnD,YAAA,OAAO,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM;QACxC;AAAE,QAAA,MAAM;;AAEN,YAAA,OAAO,GAAG;QACZ;IACF;AAEA;;AAEG;AACH,IAAA,MAAM,KAAK,CAAC,GAAW,EAAE,UAAuB,EAAE,EAAA;AAChD,QAAA,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;;QAGlC,IAAI,IAAI,GAAY,SAAS;AAC7B,QAAA,IAAI,OAAO,CAAC,IAAI,EAAE;AAChB,YAAA,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE;AACpC,gBAAA,IAAI,GAAG,OAAO,CAAC,IAAI;YACrB;AAAO,iBAAA,IAAI,OAAO,CAAC,IAAI,YAAY,QAAQ,EAAE;;;AAG3C,gBAAA,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC;YAC/D;iBAAO;AACL,gBAAA,IAAI,GAAG,OAAO,CAAC,IAAI;YACrB;QACF;AAEA,QAAA,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;QAE/D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;AAC5C,QAAA,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC;QAExC,OAAO,KAAK,CAAC,GAAG,EAAE;AAChB,YAAA,GAAG,OAAO;YACV,OAAO;AACR,SAAA,CAAC;IACJ;AAEA;;AAEG;IACK,kBAAkB,CACxB,IAAa,EACb,OAAgB,EAAA;QAEhB,IAAI,CAAC,IAAI,EAAE;AACT,YAAA,OAAO,SAAS;QAClB;AAEA,QAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;;;;AAI5B,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;AAC9B,gBAAA,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC;AACjD,YAAA,OAAO,IAAI;QACb;AAEA,QAAA,IAAI,IAAI,YAAY,QAAQ,EAAE;AAC5B,YAAA,OAAO,IAAI;QACb;AAEA,QAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AAC5B,YAAA,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC;AAC/C,YAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC7B;AAEA,QAAA,OAAO,SAAS;IAClB;AAEA;;AAEG;AACH,IAAA,MAAM,GAAG,CAAC,GAAW,EAAE,UAAuB,EAAE,EAAA;AAC9C,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACvD;AAEA;;AAEG;IACH,MAAM,IAAI,CACR,GAAW,EACX,IAAc,EACd,UAAuB,EAAE,EAAA;QAEzB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC;AAE1D,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;AACrB,YAAA,GAAG,OAAO;AACV,YAAA,MAAM,EAAE,MAAM;YACd,OAAO;AACP,YAAA,IAAI,EAAE,WAAW;AAClB,SAAA,CAAC;IACJ;AAEA;;AAEG;IACH,MAAM,GAAG,CACP,GAAW,EACX,IAAc,EACd,UAAuB,EAAE,EAAA;QAEzB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC;AAE1D,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;AACrB,YAAA,GAAG,OAAO;AACV,YAAA,MAAM,EAAE,KAAK;YACb,OAAO;AACP,YAAA,IAAI,EAAE,WAAW;AAClB,SAAA,CAAC;IACJ;AAEA;;AAEG;AACH,IAAA,MAAM,MAAM,CAAC,GAAW,EAAE,UAAuB,EAAE,EAAA;AACjD,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC1D;AAEA;;AAEG;IACH,MAAM,KAAK,CACT,GAAW,EACX,IAAc,EACd,UAAuB,EAAE,EAAA;QAEzB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC;AAE1D,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;AACrB,YAAA,GAAG,OAAO;AACV,YAAA,MAAM,EAAE,OAAO;YACf,OAAO;AACP,YAAA,IAAI,EAAE,WAAW;AAClB,SAAA,CAAC;IACJ;AACD;AAED;;AAEG;AACG,SAAU,cAAc,CAAC,MAAc,EAAA;AAC3C,IAAA,OAAO,MAAM,CAAC,MAAM,IAAI,EAAE;AAC5B;AAEA;;;AAGG;AACI,eAAe,oBAAoB,CACxC,SAAiB,EAAE,EAAA;AAEnB,IAAA,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC;AACpC,IAAA,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC;AAC7B,IAAA,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/E;;;;"}