ldap-rest 1.1.0 → 1.2.1
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/bin/index.mjs +8 -7
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/abstract/twakePlugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/config/schema.js +1 -1
- package/dist/lib/changeContext.js +1 -1
- package/dist/lib/entitySchema/validator.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/health.js +1 -0
- package/dist/lib/instanceProviders.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/ldapSchema.js +1 -1
- package/dist/lib/metrics.js +1 -0
- package/dist/lib/requestContext.js +1 -1
- package/dist/lib/shutdown.js +1 -0
- package/dist/lib/staticFiles.js +1 -0
- package/dist/lib/storage/store.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/logger/winston.js +1 -1
- package/dist/plugins/auth/crowdsec.js +1 -1
- package/dist/plugins/auth/openidconnect.js +1 -1
- package/dist/plugins/demo/helloworld.js +1 -1
- package/dist/plugins/ldap/bulkImport.js +1 -1
- package/dist/plugins/ldap/departmentSync.js +1 -1
- package/dist/plugins/ldap/flatGeneric.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/metrics.js +1 -0
- package/dist/plugins/rabbitmq.js +1 -1
- package/dist/plugins/scim/baseResolver.js +1 -1
- package/dist/plugins/scim/bulk.js +1 -1
- package/dist/plugins/scim/errors.js +1 -1
- package/dist/plugins/scim/filter.js +1 -1
- package/dist/plugins/scim/groups.js +1 -1
- package/dist/plugins/scim/mapping.js +1 -1
- package/dist/plugins/scim/patch.js +1 -1
- package/dist/plugins/scim/projection.js +1 -1
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/scim/users.js +1 -1
- package/dist/plugins/static.js +1 -1
- package/dist/plugins/storage/file.js +1 -1
- package/dist/plugins/storage/postgres.js +1 -1
- package/dist/plugins/storage/valkey.js +1 -1
- package/dist/plugins/storage.js +1 -1
- package/dist/plugins/twake/calendar.js +1 -1
- package/dist/plugins/twake/clouderyProvision.js +1 -1
- package/dist/plugins/twake/cozyProvision.js +1 -1
- package/dist/plugins/twake/drive.js +1 -1
- package/dist/plugins/twake/groups.js +1 -1
- package/dist/plugins/twake/james.js +1 -1
- package/dist/plugins/twake/migrationProxy.js +1 -0
- package/dist/plugins/twake/spaces.js +1 -1
- package/dist/plugins/weblogs.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +3 -13
- package/dist/src/abstract/plugin.d.ts +20 -0
- package/dist/src/abstract/twakePlugin.d.ts +23 -1
- package/dist/src/bin/index.d.ts +46 -1
- package/dist/src/config/args.d.ts +12 -1
- package/dist/src/lib/changeContext.d.ts +11 -0
- package/dist/src/lib/entitySchema/validator.d.ts +64 -0
- package/dist/src/lib/expressFormatedResponses.d.ts +19 -0
- package/dist/src/lib/health.d.ts +63 -0
- package/dist/src/lib/instanceProviders.d.ts +4 -0
- package/dist/src/lib/ldapActions.d.ts +32 -2
- package/dist/src/lib/metrics.d.ts +44 -0
- package/dist/src/lib/requestContext.d.ts +40 -0
- package/dist/src/lib/shutdown.d.ts +20 -0
- package/dist/src/lib/staticFiles.d.ts +33 -0
- package/dist/src/lib/storage/store.d.ts +8 -0
- package/dist/src/lib/utils.d.ts +15 -0
- package/dist/src/logger/winston.d.ts +4 -0
- package/dist/src/plugins/ldap/groups.d.ts +18 -3
- package/dist/src/plugins/ldap/organizations.d.ts +3 -5
- package/dist/src/plugins/metrics.d.ts +17 -0
- package/dist/src/plugins/rabbitmq.d.ts +37 -1
- package/dist/src/plugins/scim/errors.d.ts +2 -0
- package/dist/src/plugins/scim/types.d.ts +2 -1
- package/dist/src/plugins/static.d.ts +12 -0
- package/dist/src/plugins/storage/file.d.ts +4 -0
- package/dist/src/plugins/storage/postgres.d.ts +7 -14
- package/dist/src/plugins/storage/valkey.d.ts +2 -15
- package/dist/src/plugins/storage.d.ts +8 -0
- package/dist/src/plugins/twake/drive.d.ts +8 -0
- package/dist/src/plugins/twake/groups.d.ts +1 -2
- package/dist/src/plugins/twake/james.d.ts +2 -0
- package/dist/src/plugins/twake/migrationProxy.d.ts +71 -0
- package/docs/client-development/api/reference.md +5 -9
- package/docs/client-development/api/rest-api.md +4 -10
- package/docs/client-development/schemas/README.md +60 -21
- package/docs/plugin-development/README.md +15 -13
- package/docs/plugin-development/dependencies.md +8 -0
- package/docs/plugin-development/hooks.md +1 -1
- package/docs/usage/README.md +1 -0
- package/docs/usage/configuration.md +62 -24
- package/docs/usage/operations.md +193 -0
- package/docs/usage/plugins/README.md +2 -0
- package/docs/usage/plugins/auth/authz-per-branch.md +2 -1
- package/docs/usage/plugins/integrations/README.md +8 -4
- package/docs/usage/plugins/integrations/groups.md +2 -2
- package/docs/usage/plugins/integrations/mail-migration.md +102 -0
- package/docs/usage/plugins/integrations/scim.md +16 -3
- package/docs/usage/plugins/integrations/spaces.md +1 -1
- package/docs/usage/plugins/ldap/bulk-import.md +3 -1
- package/docs/usage/plugins/ldap/flat-generic.md +5 -2
- package/docs/usage/plugins/ldap/on-change.md +2 -1
- package/docs/usage/plugins/ldap/organizations.md +7 -7
- package/docs/usage/plugins/utilities/metrics.md +21 -0
- package/docs/usage/plugins/utilities/static.md +16 -8
- package/docs/usage/plugins/utilities/storage.md +7 -0
- package/docs/usage/plugins/utilities/weblogs.md +51 -30
- package/docs/usage/upgrading.md +84 -0
- package/package.json +11 -2
- package/static/schemas/example/README.md +1 -0
package/bin/index.mjs
CHANGED
|
@@ -21,8 +21,8 @@ Main options:
|
|
|
21
21
|
--help, -h Show this help message
|
|
22
22
|
--port PORT Server port (default: 8080)
|
|
23
23
|
--ldap-url URL LDAP server URL
|
|
24
|
-
--ldap-dn DN LDAP bind DN
|
|
25
|
-
--ldap-pwd PASSWORD LDAP bind password
|
|
24
|
+
--ldap-dn DN LDAP bind DN (required)
|
|
25
|
+
--ldap-pwd PASSWORD LDAP bind password (required)
|
|
26
26
|
--ldap-base BASE LDAP base DN (required)
|
|
27
27
|
--plugin PLUGIN Load plugin (can be specified multiple times)
|
|
28
28
|
--api-prefix PREFIX API prefix (default: /api)
|
|
@@ -36,11 +36,10 @@ Environment Variables:
|
|
|
36
36
|
Example: --ldap-url becomes DM_LDAP_URL
|
|
37
37
|
|
|
38
38
|
Examples:
|
|
39
|
-
# Start
|
|
40
|
-
npx ldap-rest
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
npx ldap-rest --ldap-url ldap://localhost:389
|
|
39
|
+
# Start against a directory
|
|
40
|
+
npx ldap-rest --ldap-url ldap://localhost:389 \\
|
|
41
|
+
--ldap-base dc=example,dc=com \\
|
|
42
|
+
--ldap-dn cn=admin,dc=example,dc=com --ldap-pwd secret
|
|
44
43
|
|
|
45
44
|
# Load specific plugins
|
|
46
45
|
npx ldap-rest --plugin ldapGroups --plugin ldapOrganization
|
|
@@ -52,3 +51,5 @@ const server = new DM();
|
|
|
52
51
|
|
|
53
52
|
await server.ready;
|
|
54
53
|
await server.run();
|
|
54
|
+
server.shutdownOnSignals();
|
|
55
|
+
server.exitOnCrash();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{assertModifyRequest as t}from"../lib/ldapActions.js";import{wantJson as e,jsonBody as i,created as s,tryMethod as r,multiStatus as a}from"../lib/expressFormatedResponses.js";import{getCompiledRegex as n,escapeDnValue as o,asyncHandler as h,escapeRegex as d,validateDnValue as c,isDummyMemberDn as u,normalizeDn as l,getParentDn as f,launchHooksChained as m,launchHooks as b,escapeLdapFilter as p,isDnInBranch as w,substringSearchFilter as g}from"../lib/utils.js";import{neverReturnAttributes as y,schemaAttribute as $,invalidValueMessage as A,modifiedAttributeNames as v,toDirectoryEntry as E,missingRequiredAttribute as j,toDirectoryChanges as x,checkDnValues as N}from"../config/schema.js";import{BadRequestError as C,ConflictError as k,NotFoundError as D,HttpError as O,UnprocessableEntityError as q}from"../lib/errors.js";import{extractLdapCode as P}from"../lib/ldapCodes.js";import{loadSchemaFile as _}from"../lib/schemaFile.js";import L from"./plugin.js";class LdapFlat extends L{constructor(t,e){if(super(t),this.ldap=t.ldap,this.base=e.base,this.mainAttribute=e.mainAttribute,this.objectClass=e.objectClass,this.defaultAttributes=e.defaultAttributes||{},this.singularName=e.singularName,this.pluralName=e.pluralName,this.hookPrefix=e.hookPrefix,!this.base)throw new Error(`LDAP base is not defined for ${this.singularName}`);if(this.ldap.declareObjectClasses(this.base,this.objectClass),e.schemaPath)try{this.schema=_(e.schemaPath,{config:this.config}),this.ldap.knowAttributeNames(Object.keys(this.schema.attributes)),this.logger.info(`${this.singularName} schema loaded from ${e.schemaPath}`)}catch(t){this.logger.error(`Failed to load ${this.singularName} schema from ${e.schemaPath}: ${t}`)}}clientForbiddenAttributes(){const t=new Set;if(!this.schema)return t;for(const[e,i]of Object.entries(this.schema.attributes))(e!==this.mainAttribute||i.generatedFrom)&&(i.generated||i.readOnly)&&t.add(e.toLowerCase());return t}hiddenAttributes(){return y([this.schema])}entryAttributes(){return["*",...Object.keys(this.schema?.attributes??{})]}project(t){const e=this.hiddenAttributes();if(0===e.size)return t;const i={};for(const[s,r]of Object.entries(t))e.has(s.split(";")[0].toLowerCase())||(i[s]=r);return i}projectList(t){if(0===this.hiddenAttributes().size)return t;const e={};for(const[i,s]of Object.entries(t))e[i]=this.project(s);return e}rejectForbiddenInput(t){const e=this.clientForbiddenAttributes();if(0!==e.size)for(const i of t){if("dn"===i)continue;if(!e.has(i.split(";")[0].toLowerCase()))continue;const t=$(this.schema,i)?.[1];throw new C(t?.readOnly?`Attribute "${i}" is read-only and cannot be set`:`Attribute "${i}" is computed by the server and cannot be set`)}}async generateMainAttribute(t){const e=this.deriveMainAttribute(t),i=this.schema?.attributes[this.mainAttribute]?.generatedFrom;if(void 0!==e&&i){if(!await this.rdnExists(e))return e;if("suffix"!==i.onCollision)throw new k(`${this.singularName} "${e}" already exists (generated from ${i.attribute})`);for(let t=2;t<1e3;t++){const i=`${e}-${t}`;if(!await this.rdnExists(i))return i}throw new k(`Cannot generate a free "${this.mainAttribute}" from "${i.attribute}"`)}}deriveMainAttribute(t){const e=this.schema?.attributes[this.mainAttribute],i=e?.generatedFrom;if(!i||!e.generated)return;const s=t[i.attribute],r=Array.isArray(s)?s[0]:s;if(null==r||""===r)throw new C(`Attribute "${i.attribute}" is required to generate "${this.mainAttribute}"`);let a=String(r);if(i.extract){const t=n(i.extract).exec(a);t&&(a=void 0!==t[1]?t[1]:t[0])}if(i.lowercase&&(a=a.toLowerCase()),i.strip&&(a=a.replace(n(i.strip,"g"),"")),!a)throw new C(`Cannot generate "${this.mainAttribute}" from "${i.attribute}"`);return a}candidateEntry(t,e){const i={objectClass:this.objectClass,...this.defaultAttributes,[this.mainAttribute]:t,...e};for(const[t,e]of Object.entries(this.schema?.attributes??{}))e.fixed&&void 0!==e.default&&"objectClass"!==t&&(i[t]=e.default);return i}async rdnExists(t){try{const e=await this.ldap.system.search({paged:!1,scope:"base"},`${this.mainAttribute}=${o(t)},${this.base}`);return(e?.searchEntries?.length??0)>0}catch(t){return!1}}invalidValueMessage(t){return A(t,$(this.schema,t)?.[1])}api(t){t.get(`${this.config.api_prefix}/v1/ldap/${this.pluralName}`,h(async(t,i)=>{if(!e(t,i))return;const s={};let r,a;if(t.query.match&&"string"==typeof t.query.match&&t.query.attribute&&"string"==typeof t.query.attribute&&(s.filter=g(t.query.match,t.query.attribute)),t.query.attributes&&"string"==typeof t.query.attributes&&(s.attributes=t.query.attributes.split(",")),void 0!==t.query.limit){const e=t.query.limit;if(r="string"==typeof e&&/^\d+$/.test(e)?+e:NaN,!Number.isSafeInteger(r)||r<1)throw new C('Query parameter "limit" must be a positive integer')}try{a=await this.listEntriesLimited({...s,limit:r,req:t})}catch(t){if(void 0!==r||4!==P(t))throw t;throw new q("Too many entries to list at once: the directory's size limit was exceeded. Narrow the search, or pass `limit`.")}a.truncated&&i.set("X-Result-Truncated","true"),i.json(this.projectList(a.entries))})),t.get(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id`,h(async(t,e)=>this.apiGet(t,e))),t.post(`${this.config.api_prefix}/v1/ldap/${this.pluralName}`,h(async(t,e)=>this.apiAdd(t,e))),t.delete(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id`,h(async(t,e)=>this.apiDelete(t,e))),t.put(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id`,h(async(t,e)=>this.apiModify(t,e))),t.post(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id/move`,h(async(t,e)=>this.apiMove(t,e))),t.post(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id/rename`,h(async(t,e)=>this.apiRename(t,e)))}async apiGet(t,i){if(!e(t,i))return;const s=decodeURIComponent(t.params.id);try{const e=this.resolveDn(s),r=await this.ldap.search({paged:!1,scope:"base",attributes:this.entryAttributes()},e,t);if(0===r.searchEntries.length)throw new D(`${this.singularName} not found`);i.json(this.project(r.searchEntries[0]))}catch(t){if(t.code&&32===t.code)throw new D(`${this.singularName} not found`);throw t}}async apiAdd(t,e){const r=this.schema?.attributes[this.mainAttribute],a=Boolean(r?.generated&&r.generatedFrom),n=a?i(t,e):i(t,e,this.mainAttribute);if(!n)return;this.rejectForbiddenInput(Object.keys(n));const o={...n};delete o[this.mainAttribute],delete o.dn;const h=a?this.deriveMainAttribute(n):void 0;void 0!==h&&await this.ldap.authorize("ldapaddrequest",[this.resolveDn(h),this.candidateEntry(h,o),t]);const d=a?await this.generateMainAttribute(n):n[this.mainAttribute];await this.addEntry(d,o,t);const c=await this.ldap.system.search({paged:!1,scope:"base",attributes:[this.mainAttribute]},this.resolveDn(d));return s(e,this.project(c.searchEntries[0]))}async apiDelete(t,i){if(!e(t,i))return;const s=decodeURIComponent(t.params.id);await r(i,this.deleteEntry.bind(this),s,t)}async apiModify(e,s){const a=i(e,s);if(!a)return;t(a),this.rejectForbiddenInput(v(a));const n=decodeURIComponent(e.params.id);await r(s,this.modifyEntry.bind(this),n,a,e)}async apiMove(t,s){if(!e(t,s))return;const r=i(t,s,"targetOrgDn");if(!r)return;const a=decodeURIComponent(t.params.id),{targetOrgDn:n}=r;if(!n||"string"!=typeof n)throw new C("Missing or invalid targetOrgDn in request body");const o=await this.moveEntry(a,n,t);s.json({success:!0,...o})}async apiRename(t,s){if(!e(t,s))return;const r=i(t,s,"newId");if(!r)return;const n=r.newId;if("string"!=typeof n)throw new C("Missing or invalid newId in request body");if(this.rejectForbiddenInput(Object.keys(r).filter(t=>"newId"!==t)),new RegExp(`^${d(this.mainAttribute)}=`,"i").test(n))throw new C(`newId must be a ${this.mainAttribute} value, not a DN`);try{c(n,this.mainAttribute)}catch(t){throw new C(t instanceof Error?t.message:"Invalid newId")}if(!await this._validateOneChange(this.mainAttribute,n))throw new C(this.invalidValueMessage(this.mainAttribute));const o=decodeURIComponent(t.params.id),h=this.resolveDn(o),f=this.resolveDn(n),m=this.config.group_dummy_user;if(m&&(u(h,m)||u(f,m)))throw new k(`${m} is the configured placeholder member and cannot be renamed`);await this.ldap.authorize("ldaprenamerequest",[h,f,t]);const b=l(h)===l(f),p=await this.entryExists(h),w=b?p:await this.entryExists(f),g=this.requirePlugin("ldapEnterpriseRules");let y=[];if(b){if(!p)throw new D(`${this.singularName} not found`)}else if(p){if(w)throw new k(`${this.singularName} "${n}" already exists`);y=await(g?.findReferences(h))||[],await this.renameEntry(h,f,t)}else{if(!w)throw new D(`${this.singularName} not found`);y=await(g?.findReferences(h))||[]}const $=g&&y.length>0?await g.rewriteReferences(y,h,f,t):{updated:0,failed:[]},A={success:0===$.failed.length,dn:f,referencesUpdated:$.updated};0!==$.failed.length?a(s,{...A,referencesFailed:$.failed}):s.json(A)}async entryExists(t){try{const e=await this.ldap.system.search({paged:!1,scope:"base",attributes:["dn"]},t);return(e?.searchEntries?.length??0)>0}catch(t){return!1}}resolveDn(t){if(new RegExp(`^${d(this.mainAttribute)}=`,"i").test(t)){const e=f(t),i=e!==t;if(!i||e.toLowerCase()!==this.base.toLowerCase())throw new C(`DN must be a direct child of "${this.base}". Provided DN "${t}" has parent "${i?e:"<none>"}"`);return t}return`${this.mainAttribute}=${o(t)},${this.base}`}async addEntry(t,e={},i){let s;await this.ldap.canonicalAttributes(e),new RegExp(`^${this.mainAttribute}=`,"i").test(t)?(s=this.resolveDn(t),t=t.replace(new RegExp(`^${this.mainAttribute}=((?:\\\\.|[^,])+)(?:,.*)?$`,"i"),"$1")):(c(t,this.mainAttribute),s=this.resolveDn(t));let r,a=this.candidateEntry(t,e);await this.ldap.authorize("ldapaddrequest",[s,{...a},i]),await this.validateNewEntry(s,{objectClass:this.objectClass,[this.mainAttribute]:t,...e}),E(this.schema,a),this.schema?this.logger.debug("Schema loaded, attributes:",Object.keys(this.schema.attributes)):this.logger.warn("No schema available"),[s,a]=await m(this.registeredHooks[`${this.hookPrefix}add`],[s,a]),this.logger.debug("Adding LDAP entry:",{dn:s,entry:JSON.stringify(a,null,2)}),this.logger.debug("Entry attributes breakdown:");for(const[t,e]of Object.entries(a))this.logger.debug(` ${t}: ${typeof e} = ${JSON.stringify(e)}`);try{r=await this.ldap.add(s,a,i)}catch(t){const e=t instanceof O&&t.statusCode<500;if(this.logger[e?"debug":"error"]("LDAP add failed:",{dn:s,entry:JSON.stringify(a,null,2),error:t}),t instanceof O)throw t;throw new Error(`Failed to add ${this.singularName} ${s}: ${t}`)}return b(this.registeredHooks[`${this.hookPrefix}adddone`],[s,a]),r}async modifyEntry(t,e,i){await this.ldap.canonicalChanges(e);let s=this.resolveDn(t);const r=this.opNumber();if([s,e]=await m(this.registeredHooks[`${this.hookPrefix}modify`],[s,e,r]),e.add&&e.add[this.mainAttribute])throw new k(`${this.mainAttribute} attribute is unique, cannot add`);if(e.delete){if(e.delete instanceof Object&&e.delete[this.mainAttribute])throw new C(`Cannot delete ${this.mainAttribute} attribute`);if(Array.isArray(e.delete)&&e.delete.includes(this.mainAttribute))throw new C(`Cannot delete ${this.mainAttribute} attribute`)}if(e.replace&&e.replace[this.mainAttribute])throw new C(`Use dedicated API to change ${this.mainAttribute} attribute`);await this.ldap.authorize("ldapmodifyrequest",[s,e,r,i]),await this.validateChanges(s,e);const a=await this.ldap.modify(s,e,i);return b(this.registeredHooks[`${this.hookPrefix}modifydone`],[s,e,r]),a}async renameEntry(t,e,i){/,/.test(t)||c(t,this.mainAttribute),/,/.test(e)||c(e,this.mainAttribute);let s=this.resolveDn(t),r=this.resolveDn(e);[s,r]=await m(this.registeredHooks[`${this.hookPrefix}rename`],[s,r]);const a=await this.ldap.rename(s,r,i);return b(this.registeredHooks[`${this.hookPrefix}renamedone`],[s,r]),a}async deleteEntry(t,e){let i=this.resolveDn(t);i=await m(this.registeredHooks[`${this.hookPrefix}delete`],i);const s=await this.ldap.delete(i,e);return b(this.registeredHooks[`${this.hookPrefix}deletedone`],i),s}async moveEntry(t,e,i){const s=this.resolveDn(t),r=this.config.ldap_organization_link_attribute||"twakeDepartmentLink",a=this.config.ldap_organization_path_attribute||"twakeDepartmentPath";if(!this.schema?.attributes[r]||!this.schema?.attributes[a])throw new C(`Schema for ${this.singularName} does not support move operation (missing ${r} or ${a})`);let n;try{n=await this.ldap.search({paged:!1,scope:"base",attributes:[r]},s,i)}catch(t){if(32===P(t))throw new D(`Entry ${s} not found`);throw t}if(!n.searchEntries||0===n.searchEntries.length)throw new D(`Entry ${s} not found`);const o=await this.getDepartmentPath(e,i);[,e]=await m(this.registeredHooks[`${this.hookPrefix}move`],[s,e,i]);const h={replace:{[r]:e,[a]:o}};return await this.modifyEntry(t,h,i),{departmentPath:o,departmentLink:e}}async getDepartmentPath(t,e){const i=this.config.ldap_organization_path_attribute||"twakeDepartmentPath";try{const s=await this.ldap.search({paged:!1,scope:"base",attributes:[i,"ou","o"]},t,e);if(!s.searchEntries||0===s.searchEntries.length)throw new D(`Organization ${t} not found`);const r=s.searchEntries[0];if(r[i]){const t=r[i];return Array.isArray(t)?String(t[0]):String(t)}const a=r.ou||r.o;if(a){return`/${Array.isArray(a)?String(a[0]):String(a)}`}return this.logger.warn(`Organization ${t} has no ${i} attribute, using DN`),t}catch(e){if(e instanceof D)throw e;const i=P(e);if(32===i)throw new D(`Organization ${t} not found`);if(34===i)throw new C(`${t} is not a valid DN`);throw new Error(`Failed to fetch organization ${t}: ${e}`)}}async listEntries(t){return(await this.listEntriesLimited(t)).entries}async listEntriesLimited({filter:t,attributes:e,req:i,limit:s}){const r={paged:void 0===s||{pageSize:Math.min(s+2,100)},filter:t||"(objectClass=*)"};if(e&&e.length>0){const t=this.mainAttribute.toLowerCase();r.attributes=e.some(e=>e.toLowerCase()===t)?e:[...e,this.mainAttribute]}const a=function(t,e){const i={},s=new Set;return{entries:i,add(r){for(const a of r){const r=a[t],n=Array.isArray(r)?r[0]:r;if(null==n||""===n)continue;const o=String(n);if(!s.has(o)){if(void 0!==e&&s.size>=e)return!1;s.add(o)}i[o]=a}return!0}}}(this.mainAttribute,s);try{const t=await this.ldap.search(r,this.base,i);for await(const e of t)if(!a.add(e.searchEntries))return{entries:a.entries,truncated:!0};return{entries:a.entries,truncated:!1}}catch(t){if(void 0===s||4!==P(t))throw t}try{const t=await this.ldap.search({...r,paged:!1,sizeLimit:Math.min(s+1,2147483647)},this.base,i);a.add(t.searchEntries)}catch(t){if(4!==P(t))throw t}return{entries:a.entries,truncated:!0}}async searchEntriesByName(t,e=!1,i=[this.mainAttribute]){const s=p(t),r=e?`(${this.mainAttribute}=*${s}*)`:`(${this.mainAttribute}=${s})`;return await this.listEntries({filter:r,attributes:i})}async validateNewEntry(t,e){if(E(this.schema,e),!this.schema)return!0;for(const[t,i]of Object.entries(this.schema.attributes))i.fixed&&void 0!==i.default&&(e[t]=i.default);for(const[t,i]of Object.entries(e)){const e=$(this.schema,t)?.[1];if(e){if(e.fixed&&void 0!==e.default){const s=JSON.stringify(e.default);if(s!==JSON.stringify(i))throw new C(`Attribute "${t}" is fixed and cannot be modified. Expected: ${s}`)}if(!await this._validateOneChange(t,i))throw new C(this.invalidValueMessage(t));if(e.required&&!i&&!e.generated)throw new C(`Attribute "${t}" is required`)}else if(this.schema.strict)throw new C(`Unknown attribute "${t}" for ${this.singularName}`)}const i=j(this.schema,e,this.server.loadedPlugins);if(i)throw new C(`Attribute "${i}" is required`);return!0}async validateChanges(t,e){if(x(this.schema,e),!this.schema)return!0;const checkFixed=(t,e)=>{const i=$(this.schema,t)?.[1];if(i?.fixed)throw new C(`Attribute "${t}" is fixed and cannot be modified`)};if(e.add)for(const[t,i]of Object.entries(e.add))if(checkFixed(t),!await this._validateOneChange(t,i))throw new C(this.invalidValueMessage(t));if(e.replace)for(const[t,i]of Object.entries(e.replace))if(checkFixed(t),!await this._validateOneChange(t,i))throw new C(this.invalidValueMessage(t));if(e.delete){const t=Array.isArray(e.delete)?e.delete:Object.keys(e.delete);for(const e of t){const t=$(this.schema,e)?.[1];if(t?.fixed)throw new C(`Attribute "${e}" is fixed and cannot be deleted`)}}return!0}async _validateOneChange(t,e){if(!this.schema)return!0;const i=$(this.schema,t)?.[1];if(!i)return!this.schema.strict;if(!e)return!0;if(Array.isArray(e)&&0===e.length){if(i.required)throw new C(`Attribute "${t}" is required`);return!0}if("pointer"===i.type){if("string"!=typeof e)throw new C(`Attribute "${t}" must be a string (DN pointer)`);const s=e;if(i.branch&&i.branch.length>0){if(!i.branch.some(t=>w(s,t)))throw new C(`Attribute "${t}" must point to a DN within allowed branches: ${i.branch.join(", ")}`)}try{const e=await this.ldap.system.search({paged:!1,scope:"base"},s);if(!e||!e.searchEntries||0===e.searchEntries.length)throw new C(`Attribute "${t}" points to non-existent DN: ${s}`)}catch(e){throw new C(`Attribute "${t}" points to invalid or non-existent DN: ${s}`)}}"pointer"!==i.type&&await N(t,i,e,async t=>(await this.ldap.system.search({paged:!1,scope:"base",attributes:["dn"]},t)).searchEntries.length>0);const s=i.test??i.items?.test;if(s){const t="string"==typeof s?n(s):s;return function(t){return(Array.isArray(t)?t:[t]).map(t=>String(t))}(e).every(e=>t.test(e))}return!0}}export{LdapFlat as default};
|
|
1
|
+
import{assertModifyRequest as t}from"../lib/ldapActions.js";import{wantJson as e,jsonBody as i,created as s,tryMethod as r,multiStatus as a}from"../lib/expressFormatedResponses.js";import{getCompiledRegex as n,escapeDnValue as o,asyncHandler as h,escapeRegex as d,validateDnValue as c,isDummyMemberDn as u,normalizeDn as l,getParentDn as m,launchHooksChained as f,launchHooks as p,escapeLdapFilter as b,substringSearchFilter as g}from"../lib/utils.js";import{neverReturnAttributes as w,assertClientMaySet as y,modifiedAttributeNames as $,toDirectoryEntry as A}from"../config/schema.js";import{applyFixedDefaults as v,EntityValidator as E}from"../lib/entitySchema/validator.js";import{ConflictError as N,BadRequestError as j,NotFoundError as x,HttpError as k,UnprocessableEntityError as C}from"../lib/errors.js";import{extractLdapCode as D}from"../lib/ldapCodes.js";import{loadSchemaFile as P}from"../lib/schemaFile.js";import q from"./plugin.js";class LdapFlat extends q{constructor(t,e){if(super(t),this.ldap=t.ldap,this.base=e.base,this.mainAttribute=e.mainAttribute,this.objectClass=e.objectClass,this.defaultAttributes=e.defaultAttributes||{},this.singularName=e.singularName,this.pluralName=e.pluralName,this.hookPrefix=e.hookPrefix,!this.base)throw new Error(`LDAP base is not defined for ${this.singularName}`);if(this.ldap.declareObjectClasses(this.base,this.objectClass),e.schemaPath)try{this.schema=P(e.schemaPath,{config:this.config}),this.ldap.knowAttributeNames(Object.keys(this.schema.attributes)),this.logger.info(`${this.singularName} schema loaded from ${e.schemaPath}`)}catch(t){this.logger.error(`Failed to load ${this.singularName} schema from ${e.schemaPath}: ${t}`)}}hiddenAttributes(){return w([this.schema])}entryAttributes(){return["*",...Object.keys(this.schema?.attributes??{})]}project(t){const e=this.hiddenAttributes();if(0===e.size)return t;const i={};for(const[s,r]of Object.entries(t))e.has(s.split(";")[0].toLowerCase())||(i[s]=r);return i}projectList(t){if(0===this.hiddenAttributes().size)return t;const e={};for(const[i,s]of Object.entries(t))e[i]=this.project(s);return e}rejectForbiddenInput(t){y(this.schema,t,this.mainAttribute)}async generateMainAttribute(t){const e=this.deriveMainAttribute(t),i=this.schema?.attributes[this.mainAttribute]?.generatedFrom;if(void 0!==e&&i){if(!await this.rdnExists(e))return e;if("suffix"!==i.onCollision)throw new N(`${this.singularName} "${e}" already exists (generated from ${i.attribute})`);for(let t=2;t<1e3;t++){const i=`${e}-${t}`;if(!await this.rdnExists(i))return i}throw new N(`Cannot generate a free "${this.mainAttribute}" from "${i.attribute}"`)}}deriveMainAttribute(t){const e=this.schema?.attributes[this.mainAttribute],i=e?.generatedFrom;if(!i||!e.generated)return;const s=t[i.attribute],r=Array.isArray(s)?s[0]:s;if(null==r||""===r)throw new j(`Attribute "${i.attribute}" is required to generate "${this.mainAttribute}"`);let a=String(r);if(i.extract){const t=n(i.extract).exec(a);t&&(a=void 0!==t[1]?t[1]:t[0])}if(i.lowercase&&(a=a.toLowerCase()),i.strip&&(a=a.replace(n(i.strip,"g"),"")),!a)throw new j(`Cannot generate "${this.mainAttribute}" from "${i.attribute}"`);return a}candidateEntry(t,e){const i={...this.defaultAttributes,[this.mainAttribute]:t,...e,objectClass:this.objectClass};return v(this.schema,i),i}async rdnExists(t){try{const e=await this.ldap.system.search({paged:!1,scope:"base"},`${this.mainAttribute}=${o(t)},${this.base}`);return(e?.searchEntries?.length??0)>0}catch(t){return!1}}validator(){return new E(this.schema,{entryExists:async t=>{try{return(await this.ldap.system.search({paged:!1,scope:"base",attributes:["dn"]},t)).searchEntries.length>0}catch{return!1}},loadedPlugins:this.server.loadedPlugins,singularName:this.singularName})}api(t){t.get(`${this.config.api_prefix}/v1/ldap/${this.pluralName}`,h(async(t,i)=>{if(!e(t,i))return;const s={};let r,a;if(t.query.match&&"string"==typeof t.query.match&&t.query.attribute&&"string"==typeof t.query.attribute&&(s.filter=g(t.query.match,t.query.attribute)),t.query.attributes&&"string"==typeof t.query.attributes&&(s.attributes=t.query.attributes.split(",")),void 0!==t.query.limit){const e=t.query.limit;if(r="string"==typeof e&&/^\d+$/.test(e)?+e:NaN,!Number.isSafeInteger(r)||r<1)throw new j('Query parameter "limit" must be a positive integer')}try{a=await this.listEntriesLimited({...s,limit:r,req:t})}catch(t){if(void 0!==r||4!==D(t))throw t;throw new C("Too many entries to list at once: the directory's size limit was exceeded. Narrow the search, or pass `limit`.")}a.truncated&&i.set("X-Result-Truncated","true"),i.json(this.projectList(a.entries))})),t.get(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id`,h(async(t,e)=>this.apiGet(t,e))),t.post(`${this.config.api_prefix}/v1/ldap/${this.pluralName}`,h(async(t,e)=>this.apiAdd(t,e))),t.delete(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id`,h(async(t,e)=>this.apiDelete(t,e))),t.put(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id`,h(async(t,e)=>this.apiModify(t,e))),t.post(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id/move`,h(async(t,e)=>this.apiMove(t,e))),t.post(`${this.config.api_prefix}/v1/ldap/${this.pluralName}/:id/rename`,h(async(t,e)=>this.apiRename(t,e)))}async apiGet(t,i){if(!e(t,i))return;const s=decodeURIComponent(t.params.id);try{const e=this.resolveDn(s),r=await this.ldap.search({paged:!1,scope:"base",attributes:this.entryAttributes()},e,t);if(0===r.searchEntries.length)throw new x(`${this.singularName} not found`);i.json(this.project(r.searchEntries[0]))}catch(t){if(t.code&&32===t.code)throw new x(`${this.singularName} not found`);throw t}}async apiAdd(t,e){const r=this.schema?.attributes[this.mainAttribute],a=Boolean(r?.generated&&r.generatedFrom),n=a?i(t,e):i(t,e,this.mainAttribute);if(!n)return;this.rejectForbiddenInput(Object.keys(n));const o={...n};delete o[this.mainAttribute],delete o.dn;const h=a?this.deriveMainAttribute(n):void 0;void 0!==h&&await this.ldap.authorize("ldapaddrequest",[this.resolveDn(h),this.candidateEntry(h,o),t]);const d=a?await this.generateMainAttribute(n):n[this.mainAttribute];await this.addEntry(d,o,t);const c=await this.ldap.system.search({paged:!1,scope:"base",attributes:[this.mainAttribute]},this.resolveDn(d));return s(e,this.project(c.searchEntries[0]))}async apiDelete(t,i){if(!e(t,i))return;const s=decodeURIComponent(t.params.id);await r(i,this.deleteEntry.bind(this),s,t)}async apiModify(e,s){const a=i(e,s);if(!a)return;t(a),this.rejectForbiddenInput($(a));const n=decodeURIComponent(e.params.id);await r(s,this.modifyEntry.bind(this),n,a,e)}async apiMove(t,s){if(!e(t,s))return;const r=i(t,s,"targetOrgDn");if(!r)return;const a=decodeURIComponent(t.params.id),{targetOrgDn:n}=r;if(!n||"string"!=typeof n)throw new j("Missing or invalid targetOrgDn in request body");const o=await this.moveEntry(a,n,t);s.json({success:!0,...o})}async apiRename(t,s){if(!e(t,s))return;const r=i(t,s,"newId");if(!r)return;const n=r.newId;if("string"!=typeof n)throw new j("Missing or invalid newId in request body");if(this.rejectForbiddenInput(Object.keys(r).filter(t=>"newId"!==t)),new RegExp(`^${d(this.mainAttribute)}=`,"i").test(n))throw new j(`newId must be a ${this.mainAttribute} value, not a DN`);try{c(n,this.mainAttribute)}catch(t){throw new j(t instanceof Error?t.message:"Invalid newId")}await this.validator().validateValue(this.mainAttribute,n);const o=decodeURIComponent(t.params.id),h=this.resolveDn(o),m=this.resolveDn(n),f=this.config.group_dummy_user;if(f&&(u(h,f)||u(m,f)))throw new N(`${f} is the configured placeholder member and cannot be renamed`);await this.ldap.authorize("ldaprenamerequest",[h,m,t]);const p=l(h)===l(m),b=await this.entryExists(h),g=p?b:await this.entryExists(m),w=this.requirePlugin("ldapEnterpriseRules");let y=[];if(p){if(!b)throw new x(`${this.singularName} not found`)}else if(b){if(g)throw new N(`${this.singularName} "${n}" already exists`);y=await(w?.findReferences(h))||[],await this.renameEntry(h,m,t)}else{if(!g)throw new x(`${this.singularName} not found`);y=await(w?.findReferences(h))||[]}const $=w&&y.length>0?await w.rewriteReferences(y,h,m,t):{updated:0,failed:[]},A={success:0===$.failed.length,dn:m,referencesUpdated:$.updated};0!==$.failed.length?a(s,{...A,referencesFailed:$.failed}):s.json(A)}async entryExists(t){try{const e=await this.ldap.system.search({paged:!1,scope:"base",attributes:["dn"]},t);return(e?.searchEntries?.length??0)>0}catch(t){return!1}}resolveDn(t){if(new RegExp(`^${d(this.mainAttribute)}=`,"i").test(t)){const e=m(t),i=e!==t;if(!i||e.toLowerCase()!==this.base.toLowerCase())throw new j(`DN must be a direct child of "${this.base}". Provided DN "${t}" has parent "${i?e:"<none>"}"`);return t}return`${this.mainAttribute}=${o(t)},${this.base}`}async addEntry(t,e={},i){let s;await this.ldap.canonicalAttributes(e),new RegExp(`^${this.mainAttribute}=`,"i").test(t)?(s=this.resolveDn(t),t=t.replace(new RegExp(`^${this.mainAttribute}=((?:\\\\.|[^,])+)(?:,.*)?$`,"i"),"$1")):(c(t,this.mainAttribute),s=this.resolveDn(t));let r=this.candidateEntry(t,e);await this.ldap.authorize("ldapaddrequest",[s,{...r},i]);const a={[this.mainAttribute]:t,...e,objectClass:this.objectClass};await this.validateNewEntry(s,a),Object.assign(r,a),A(this.schema,r),this.schema?this.logger.debug("Schema loaded, attributes:",Object.keys(this.schema.attributes)):this.logger.warn("No schema available"),[s,r]=await f(this.registeredHooks[`${this.hookPrefix}add`],[s,r]),this.logger.debug("Adding LDAP entry:",{dn:s,entry:JSON.stringify(r,null,2)}),this.logger.debug("Entry attributes breakdown:");for(const[t,e]of Object.entries(r))this.logger.debug(` ${t}: ${typeof e} = ${JSON.stringify(e)}`);let n;try{n=await this.ldap.add(s,r,i)}catch(t){const e=t instanceof k&&t.statusCode<500;if(this.logger[e?"debug":"error"]("LDAP add failed:",{dn:s,entry:JSON.stringify(r,null,2),error:t}),t instanceof k)throw t;throw new Error(`Failed to add ${this.singularName} ${s}: ${t}`)}return p(this.registeredHooks[`${this.hookPrefix}adddone`],[s,r]),n}async modifyEntry(t,e,i){await this.ldap.canonicalChanges(e);let s=this.resolveDn(t);const r=this.opNumber();if([s,e]=await f(this.registeredHooks[`${this.hookPrefix}modify`],[s,e,r]),e.add&&e.add[this.mainAttribute])throw new N(`${this.mainAttribute} attribute is unique, cannot add`);if(e.delete){if(e.delete instanceof Object&&e.delete[this.mainAttribute])throw new j(`Cannot delete ${this.mainAttribute} attribute`);if(Array.isArray(e.delete)&&e.delete.includes(this.mainAttribute))throw new j(`Cannot delete ${this.mainAttribute} attribute`)}if(e.replace&&e.replace[this.mainAttribute])throw new j(`Use dedicated API to change ${this.mainAttribute} attribute`);await this.ldap.authorize("ldapmodifyrequest",[s,e,r,i]),await this.validateChanges(s,e);const a=await this.ldap.modify(s,e,i);return p(this.registeredHooks[`${this.hookPrefix}modifydone`],[s,e,r]),a}async renameEntry(t,e,i){/,/.test(t)||c(t,this.mainAttribute),/,/.test(e)||c(e,this.mainAttribute);let s=this.resolveDn(t),r=this.resolveDn(e);[s,r]=await f(this.registeredHooks[`${this.hookPrefix}rename`],[s,r]);const a=await this.ldap.rename(s,r,i);return p(this.registeredHooks[`${this.hookPrefix}renamedone`],[s,r]),a}async deleteEntry(t,e){let i=this.resolveDn(t);i=await f(this.registeredHooks[`${this.hookPrefix}delete`],i);const s=await this.ldap.delete(i,e);return p(this.registeredHooks[`${this.hookPrefix}deletedone`],i),s}async moveEntry(t,e,i){const s=this.resolveDn(t),r=this.config.ldap_organization_link_attribute||"twakeDepartmentLink",a=this.config.ldap_organization_path_attribute||"twakeDepartmentPath";if(!this.schema?.attributes[r]||!this.schema?.attributes[a])throw new j(`Schema for ${this.singularName} does not support move operation (missing ${r} or ${a})`);let n;try{n=await this.ldap.search({paged:!1,scope:"base",attributes:[r]},s,i)}catch(t){if(32===D(t))throw new x(`Entry ${s} not found`);throw t}if(!n.searchEntries||0===n.searchEntries.length)throw new x(`Entry ${s} not found`);const o=await this.getDepartmentPath(e,i);[,e]=await f(this.registeredHooks[`${this.hookPrefix}move`],[s,e,i]);const h={replace:{[r]:e,[a]:o}};return await this.modifyEntry(t,h,i),{departmentPath:o,departmentLink:e}}async getDepartmentPath(t,e){const i=this.config.ldap_organization_path_attribute||"twakeDepartmentPath";try{const s=await this.ldap.search({paged:!1,scope:"base",attributes:[i,"ou","o"]},t,e);if(!s.searchEntries||0===s.searchEntries.length)throw new x(`Organization ${t} not found`);const r=s.searchEntries[0];if(r[i]){const t=r[i];return Array.isArray(t)?String(t[0]):String(t)}const a=r.ou||r.o;if(a){return`/${Array.isArray(a)?String(a[0]):String(a)}`}return this.logger.warn(`Organization ${t} has no ${i} attribute, using DN`),t}catch(e){if(e instanceof x)throw e;const i=D(e);if(32===i)throw new x(`Organization ${t} not found`);if(34===i)throw new j(`${t} is not a valid DN`);throw new Error(`Failed to fetch organization ${t}: ${e}`)}}async listEntries(t){return(await this.listEntriesLimited(t)).entries}async listEntriesLimited({filter:t,attributes:e,req:i,limit:s}){const r={paged:void 0===s||{pageSize:Math.min(s+2,100)},filter:t||"(objectClass=*)"};if(e&&e.length>0){const t=this.mainAttribute.toLowerCase();r.attributes=e.some(e=>e.toLowerCase()===t)?e:[...e,this.mainAttribute]}const a=function(t,e){const i={},s=new Set;return{entries:i,add(r){for(const a of r){const r=a[t],n=Array.isArray(r)?r[0]:r;if(null==n||""===n)continue;const o=String(n);if(!s.has(o)){if(void 0!==e&&s.size>=e)return!1;s.add(o)}i[o]=a}return!0}}}(this.mainAttribute,s);try{const t=await this.ldap.search(r,this.base,i);for await(const e of t)if(!a.add(e.searchEntries))return{entries:a.entries,truncated:!0};return{entries:a.entries,truncated:!1}}catch(t){if(void 0===s||4!==D(t))throw t}try{const t=await this.ldap.search({...r,paged:!1,sizeLimit:Math.min(s+1,2147483647)},this.base,i);a.add(t.searchEntries)}catch(t){if(4!==D(t))throw t}return{entries:a.entries,truncated:!0}}async searchEntriesByName(t,e=!1,i=[this.mainAttribute]){const s=b(t),r=e?`(${this.mainAttribute}=*${s}*)`:`(${this.mainAttribute}=${s})`;return await this.listEntries({filter:r,attributes:i})}async validateNewEntry(t,e){return await this.validator().validateNew(e),!0}async validateChanges(t,e){return await this.validator().validateChanges(e),!0}}export{LdapFlat as default};
|
package/dist/abstract/plugin.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{neverReturnAttributes as
|
|
1
|
+
import r from"node-fetch";import{timedOutbound as e}from"../lib/metrics.js";import{neverReturnAttributes as s,withoutAttributes as t}from"../config/schema.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,unescapeDnValue,validateDnValue}from"../lib/utils.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";class DmPlugin{fetch(s,t){return e(this.name,()=>r(s,t))}constructor(r){this.registeredHooks={},this.server=r,this.config=r.config,this.logger=r.logger,this.registeredHooks=r.hooks}opNumber(){return this.server.operationSequence++}hideNeverReturn(r){const e=[],isSchema=r=>"object"==typeof r?.attributes;for(const r of Object.values(this.server.loadedPlugins)){const s=r;if(isSchema(s.schema)&&e.push(s.schema),Array.isArray(s.instances))for(const r of s.instances)isSchema(r.schema)&&e.push(r.schema)}const i=s(e);if(0===i.size||!r||"object"!=typeof r)return r;const project=r=>r&&"object"==typeof r&&!Array.isArray(r)?t(r,i):r;return Array.isArray(r)?r.map(project):"dn"in r?t(r,i):Object.fromEntries(Object.entries(r).map(([r,e])=>[r,project(e)]))}requirePlugin(r){const e=this.server.loadedPlugins[r];return e||(this.missingPluginsWarned||(this.missingPluginsWarned=new Set),this.missingPluginsWarned.has(r)||(this.missingPluginsWarned.add(r),this.logger.warn(`${this.name}: required plugin '${r}' is not loaded — its features will be skipped`)),null)}}export{DmPlugin as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"
|
|
1
|
+
import t from"p-limit";import{PROBE_TIMEOUT_MS as e}from"../lib/health.js";import{escapeLdapFilter as r}from"../lib/utils.js";import s from"./plugin.js";class TwakePlugin extends s{constructor(e,r,s,i){super(e),this.retryAttempts=1,this.retryDelay=0;let a=this.config[r]||"";for(;a.endsWith("/");)a=a.slice(0,-1);if(this.webadminUrl=a,this.webadminToken=this.config[s]||"",!this.webadminUrl)throw new Error(`Twake plugin: ${r} is required`);this.webadminToken||this.logger.warn(`Twake plugin: No authentication token configured (${s}) - requests will be unauthenticated`);const n="number"==typeof this.config[i]?this.config[i]:10;this.requestLimit=t(n),this.logger.info(`Twake plugin HTTP request concurrency limit set to ${n}`),this.mailAttr=this.config.mail_attribute||"mail",this.displayNameAttr=this.config.display_name_attribute||"displayName"}createHeaders(t){const e={};return this.webadminToken&&(e.Authorization=`Bearer ${this.webadminToken}`),t&&(e["Content-Type"]=t),e}async callWebAdminApi(t,e,r,s,i,a,n=[]){const o={plugin:this.name,event:t,result:"error",dn:s,...a},u={method:r,headers:this.createHeaders(i?"application/json":void 0)};i&&(u.body=i);for(let r=1;;r++){try{const s=await this.requestLimit(()=>this.fetch(e,u));if(s.ok)return this.logger.info({...o,result:"success",http_status:s.status,url:e}),!0;if(this.shouldRetry(s.status,n,r)){this.logger.warn({...o,result:"retry",attempt:r,http_status:s.status,http_status_text:s.statusText,url:e}),await this.waitBeforeRetry(r);continue}if(this.shouldIgnoreError(s.status,t))return this.logger.debug({...o,result:"ignored",http_status:s.status,http_status_text:s.statusText,url:e}),!0;this.logger.error({...o,...r>1?{attempts:r}:{},http_status:s.status,http_status_text:s.statusText,url:e})}catch(t){this.logger.error({...o,error:t,url:e})}return!1}}async webadminHealth(){try{return(await this.fetch(`${this.webadminUrl}/healthcheck`,{headers:this.createHeaders(),signal:AbortSignal.timeout(e-500)})).ok?"ok":"degraded"}catch{return"degraded"}}async findReadableUser(t,e,s){const i=await this.server.ldap.system.search({scope:"sub",filter:`(uid=${r(t)})`,paged:!1,attributes:s},this.config.ldap_base||"");for(const t of i.searchEntries??[])if(await this.server.ldap.readableBy(String(t.dn),e))return t;return null}shouldRetry(t,e,r){return r<this.retryAttempts&&e.includes(t)}waitBeforeRetry(t){return new Promise(e=>setTimeout(e,this.retryDelay*t))}shouldIgnoreError(t,e){return 409===t&&/alias/i.test(e)}attributeToString(t){return t?Array.isArray(t)?t.length>0?String(t[0]):null:String(t):null}async ldapGetAttributes(t,e){try{const r=e&&e.length>0?e:void 0,s=await this.server.ldap.system.search({paged:!1,scope:"base",attributes:r},t);return s.searchEntries&&s.searchEntries.length>0?s.searchEntries[0]:null}catch(e){return this.logger.debug(`Could not fetch attributes from DN ${t}: ${e}`),null}}getAliases(t){if(!t)return[];return(Array.isArray(t)?t:[t]).map(t=>Buffer.isBuffer(t)?t.toString("utf-8"):String(t)).map(t=>this.normalizeAlias(t))}normalizeAlias(t){return t.toLowerCase().startsWith("smtp:")?t.substring(5):t}extractMailDomain(t){const e=t.split("@");return 2===e.length?e[1]:null}extractDomainFromDn(t){const e=t.match(/dc=([^,]+)/gi);return e?e.map(t=>t.substring(3)).join("."):"example.com"}}export{TwakePlugin,TwakePlugin as default};
|
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 s from"express";import i 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,assertEnforceable as p,LDAP_JUDGING_HOOKS as c}from"../lib/authz/composition.js";import{errorBody as f,AUTHZ_REFUSED as m}from"../lib/errors.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";import{registerAttributeTypes as w,recordHookOwner as b}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDnSyntax,isDummyMemberDn,knownAttributeName,launchHooks,launchHooksChained,movedToTrash,normalizeDn,organizationLink,parseDn,rdnTypes,rdnValue,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import{runForRequest as $}from"../lib/requestContext.js";import y from"../plugins/priority.json.js";const P=c.filter(e=>"ldapsearchfilter"!==e);class DM{constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this.bodyParsers=s.Router(),this._composed=!1,this.pluginLoads=new Map,this.authzHooks=new WeakSet,this.config=o(a),this.app=s(),this.app.disable("x-powered-by"),this.app.use((e,t,r)=>{t.setHeader("X-Content-Type-Options","nosniff"),r()}),this.app.use(this.bodyParsers),this.app.use(i.json({verify:n})),this.app.use(i.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 s=this.logger,i=(async()=>{for(const e of y){const t=this.resolveModule(DM.moduleOf(e)),i=r.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==i.length){r=r.filter(e=>!i.includes(e));for(const e of i)s.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(i),e.push(i.then(async()=>{const e=r.map(e=>(s.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),p(this),await this.learnAttributeTypes(),this._composed=!0,this.afterLoad(),t()}).catch(e=>r(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}async learnAttributeTypes(){if(Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz")))try{w((await this.ldap.schemaIndex()).schema.attributeTypes)}catch(e){this.logger.warn(`Could not read the directory schema, DNs are compared with the usual attribute type names only: ${String(e)}`)}}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 s=-1,i=[];for(const t of this.authenticators)for(const r of t.pathPrefixes)d(r,e.path)&&(r.length>s?(s=r.length,i=[t]):r.length!==s||i.includes(t)||i.push(t));if(s<0&&(i=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===i.length)return r();const finish=s=>{if(s>=i.length)return $(e,r);const o=i[s];if(!o.afterChain)return finish(s+1);o.afterChain(e,t,()=>finish(s+1))},o=[];e.authenticators=o;const run=r=>{if(r>=i.length)return finish(0);const s=i[r];s.authenticate(e,t,()=>{s.vouchedFor(e)&&o.push(s.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(),s=new Set;for(const e of r||[]){const t=e.route?.path;"string"==typeof t?s.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&s.add(e))}const i=[...s].filter(e=>!t.some(t=>e===t||e.startsWith(`${t}/`))),o=Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz"));return i.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication`+(o?" nor authorization":"")+`: ${i.sort().join(", ")}`),i.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,s)=>{let i="statusCode"in e?e.statusCode:500,o=e.message;if(500===i&&/\[authz-forbidden\]/.test(e.message)?(i=403,o=m):403===i&&/\[authz-forbidden\]/.test(e.message)&&(o=m),i>=400&&i<500)return this.logger.warn(`Client error ${i} in request ${t.method} ${t.path}: ${o}`),void(r.headersSent||r.status(i).json(f(e,o)));this.logger.error(`Server error ${i} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),r.headersSent||r.status(i).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(s){return s.startsWith("core/")?s.replace("core/",t(r(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):s.startsWith(".")?e(new URL(s,import.meta.url)):s}loadPlugin(e){let t,r;if(/:/.test(e)){let s=e.substring(e.indexOf(":")+1);if(e=e.substring(0,e.indexOf(":")),/:/.test(s)){t=s.substring(0,s.indexOf(":")),t||(t=void 0),s=s.substring(s.indexOf(":")+1);try{r=JSON.parse(s),"object"!=typeof r?(this.logger.error(`Overrides for plugin ${e} are not valid: ${s}`),r=void 0):this.logger.debug(`Overrides for plugin ${t||e}: ${s}`)}catch(t){this.logger.error(`Failed to parse overrides for plugin ${e}: ${t}, using ${s}`),r=void 0}}else t=s,t||(t=void 0)}else t=void 0;const s=r?void 0:`${this.resolveModule(e)}\0${t??""}`,i=void 0===s?void 0:this.pluginLoads.get(s);if(i)return this.logger.debug(`Plugin ${e} already loaded, skipping`),i;const o=this.importPlugin(e,t,r);if(void 0!==s){this.pluginLoads.set(s,o);const forget=()=>{this.pluginLoads.get(s)===o&&this.pluginLoads.delete(s)};o.then(e=>e||forget(),forget)}return o}importPlugin(e,t,r){return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((s,i)=>{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 i(new Error(`Unable to load ${e}`));s(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>i(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`);{const s=this.hooks[e];if(t.roles?.includes("authz")&&P.includes(e)){const e=s.findIndex(e=>!this.authzHooks.has(e));s.splice(-1===e?s.length:e,0,r),this.authzHooks.add(r)}else s.push(r);b(r,t.name,e)}}return this.loadedPlugins[t.name]=t,!0}}export{DM,b as recordHookOwner,w as registerAttributeTypes};
|
|
1
|
+
import{fileURLToPath as e}from"url";import{join as t,dirname as s}from"path";import i from"express";import r 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,setDebug as u,serverErrorBody as c}from"../lib/expressFormatedResponses.js";import{prefixCoversPath as g}from"../lib/auth/base.js";import{assertAuthzComposition as p,assertEnforceable as f,LDAP_JUDGING_HOOKS as m}from"../lib/authz/composition.js";import{errorBody as w,AUTHZ_REFUSED as b}from"../lib/errors.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";import{registerAttributeTypes as y,recordHookOwner as $}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,fillPattern,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDnSyntax,isDummyMemberDn,knownAttributeName,launchHooks,launchHooksChained,movedToTrash,normalizeDn,organizationLink,parseDn,rdnTypes,rdnValue,runAsHookOwner,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import{runWithLogContext as v,runForRequest as P}from"../lib/requestContext.js";import{requestId as j}from"../lib/changeContext.js";import{mountHealth as S,HEALTH_PATHS as x}from"../lib/health.js";import{httpRequestDuration as k,routeTemplate as M,secondsSince as E,unhandledRejections as O}from"../lib/metrics.js";import{registerProcessShutdown as C,shutdownTimeout as D}from"../lib/shutdown.js";import A from"../plugins/priority.json.js";const _=m.filter(e=>"ldapsearchfilter"!==e);class DM{get server(){return this.life.server}set server(e){this.life.server=e}get composed(){return this._composed}get stopping(){return void 0!==this.life.shutdown}get unhandledRejections(){return this.life.unhandledRejections}constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this.bodyParsers=i.Router(),this._composed=!1,this.life={unhandledRejections:0},this.inFlight=new Set,this.pluginLoads=new Map,this.authzHooks=new WeakSet,this.config=o(a),this.app=i(),this.app.disable("x-powered-by"),this.app.use((e,t,s)=>{const i=process.hrtime.bigint();t.setHeader("X-Content-Type-Options","nosniff"),this.stopping&&t.setHeader("Connection","close"),this.inFlight.add(t),t.once("finish",()=>{k.observe({method:e.method,route:M(e),status:String(t.statusCode)},E(i)),this.stopping&&this.server?.closeIdleConnections()}),t.once("close",()=>this.inFlight.delete(t)),s()}),this.app.use((e,t,s)=>{const i=j(e);t.setHeader("X-Request-Id",i),v({requestId:i},s)}),this.app.use(this.bodyParsers),this.app.use(r.json({verify:n})),this.app.use(r.urlencoded({extended:!0,verify:n})),S(this.app,this),this.logger=l(this.config),this.ldap=new h(this),d(this.logger),u(this.config.debug),this.operationSequence=0;const e=[];if(this.config.plugin){const t=this.config.plugin.find(e=>e.includes("configApi"));let s=this.config.plugin.filter(e=>!e.includes("configApi"));const i=this.logger,r=(async()=>{for(const e of A){const t=this.resolveModule(DM.moduleOf(e)),r=s.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==r.length){s=s.filter(e=>!r.includes(e));for(const e of r)i.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(r),e.push(r.then(async()=>{const e=s.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,s)=>{e.length>0?Promise.all(e).then(async()=>{this.setupErrorMiddleware(),this.warnUnauthenticatedRoutes(),await p(this),f(this),await this.learnAttributeTypes(),this._composed=!0,this.afterLoad(),t()}).catch(e=>s(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}async learnAttributeTypes(){if(Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz")))try{y((await this.ldap.schemaIndex()).schema.attributeTypes)}catch(e){this.logger.warn(`Could not read the directory schema, DNs are compared with the usual attribute type names only: ${String(e)}`)}}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,s)=>{this.dispatchAuth(e,t,s)}))}registerAuthenticator(e){this.mountAuthDispatcherNow(),this.authenticators.includes(e)||this.authenticators.push(e)}registerBodyParser(e,t){this.bodyParsers.use(e,t)}dispatchAuth(e,t,s){let i=-1,r=[];for(const t of this.authenticators)for(const s of t.pathPrefixes)g(s,e.path)&&(s.length>i?(i=s.length,r=[t]):s.length!==i||r.includes(t)||r.push(t));if(i<0&&(r=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===r.length)return s();const finish=i=>{if(i>=r.length)return P(e,s);const o=r[i];if(!o.afterChain)return finish(i+1);o.afterChain(e,t,()=>finish(i+1))},o=[];e.authenticators=o;const run=s=>{if(s>=r.length)return finish(0);const i=r[s];i.authenticate(e,t,()=>{i.vouchedFor(e)&&o.push(i.name),run(s+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 s of e){if(!s.pathPrefixes||0===s.pathPrefixes.length)return[];t.push(...s.pathPrefixes)}const s=this.routerStack(),i=new Set;for(const e of s||[]){const t=e.route?.path;"string"==typeof t?i.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&i.add(e))}const r=[...i].filter(e=>!x.includes(e)&&!t.some(t=>e===t||e.startsWith(`${t}/`))),o=Object.values(this.loadedPlugins).some(e=>e.roles?.includes("authz"));return r.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication`+(o?" nor authorization":"")+`: ${r.sort().join(", ")}`),r.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,s,i)=>{let r="statusCode"in e?e.statusCode:500,o=e.message;if(500===r&&/\[authz-forbidden\]/.test(e.message)?(r=403,o=b):403===r&&/\[authz-forbidden\]/.test(e.message)&&(o=b),r>=400&&r<500)return this.logger.warn(`Client error ${r} in request ${t.method} ${t.path}: ${o}`),void(s.headersSent||s.status(r).json(w(e,o)));this.logger.error(`Server error ${r} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),s.headersSent||s.status(r).json(c(e,this.config.debug))},this.app.use(this._errorMiddleware),this._errorMiddlewareSetup=!0}exitOnCrash(){process.on("uncaughtException",e=>{this.logger.error(`Uncaught exception: ${e.message}`,{stack:e.stack}),setTimeout(()=>process.exit(1),this.config.shutdown_timeout),this.shutdown().finally(()=>process.exit(1))}),process.on("unhandledRejection",e=>{this.life.unhandledRejections++,O.inc(),this.logger.error(`Unhandled promise rejection: ${e instanceof Error?e.message:String(e)}`,{stack:e instanceof Error?e.stack:void 0})})}run(){return new Promise((e,t)=>{this.server=this.app.listen(this.config.port,s=>{s?(this.logger.error(`Error starting server: ${s}`),t(s)):(this.logger.debug(`Server started on port ${this.config.port}`),e())})})}stop(){this.app.removeAllListeners(),this.server?.close(),this.logger.debug("Server stopped")}shutdown(){var e;return(e=this.life).shutdown??(e.shutdown=this.letGo()),this.life.shutdown}async letGo(){const e=this.config.shutdown_timeout,t=Date.now(),s=Math.max(1e3,e-5e3);this.logger.notice(`Shutting down, ${s} ms for what is in flight`);const i=this.server;if(i){const e=new Promise(e=>i.close(()=>e()));i.closeIdleConnections();for(const e of this.inFlight)e.headersSent||e.setHeader("Connection","close");await DM.within(e,s),i.closeAllConnections()}const r=new Map;for(const[e,t]of Object.entries(this.loadedPlugins))r.set(e,Promise.resolve().then(()=>t.close?.()).catch(t=>{this.logger.warn(`${e}: close failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>r.delete(e)));const o=Promise.all(r.values()).then(()=>this.ldap.close().catch(()=>{})),n=Math.max(1e3,e-(Date.now()-t)-1e3);await DM.within(o,n)||(this.logger.warn(`Shut down with a close still pending: ${[...r.keys()].join(", ")||"the directory connections"}`),this.ldap.close().catch(()=>{})),this.logger.notice("Shut down")}static async within(e,t){let s;const i=new Promise(e=>{s=setTimeout(()=>e(!1),t)});try{return await Promise.race([e.then(()=>!0),i])}finally{clearTimeout(s)}}shutdownOnSignals(){D.ms=this.config.shutdown_timeout,C(()=>this.shutdown())}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(s(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):i.startsWith(".")?e(new URL(i,import.meta.url)):i}loadPlugin(e){let t,s;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{s=JSON.parse(i),"object"!=typeof s?(this.logger.error(`Overrides for plugin ${e} are not valid: ${i}`),s=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}`),s=void 0}}else t=i,t||(t=void 0)}else t=void 0;const i=s?void 0:`${this.resolveModule(e)}\0${t??""}`,r=void 0===i?void 0:this.pluginLoads.get(i);if(r)return this.logger.debug(`Plugin ${e} already loaded, skipping`),r;const o=this.importPlugin(e,t,s);if(void 0!==i){this.pluginLoads.set(i,o);const forget=()=>{this.pluginLoads.get(i)===o&&this.pluginLoads.delete(i)};o.then(e=>e||forget(),forget)}return o}importPlugin(e,t,s){return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((i,r)=>{import(e).then(async o=>{let n;if(o&&o.default&&(o=o.default),s){const e={...this.config,...s};n=new o(this.withConfig(e))}else n=new o(this);if(!n)return r(new Error(`Unable to load ${e}`));i(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>r(new Error(`Failed to load plugin ${e}: ${t}`)))})}async admitToComposition(e){this.loadedPlugins[e.name]=e;try{await p(this,e)}finally{delete this.loadedPlugins[e.name]}}async registerPlugin(e,t,s){if(t.name||(t.name=e),s&&(t.name=s),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 s=t.hooks[e];if(this.hooks[e]||(this.hooks[e]=[]),!s||"function"!=typeof s)throw new Error(`Plugin ${t.name}: hook ${e} is invalid`);{const i=this.hooks[e];if(t.roles?.includes("authz")&&_.includes(e)){const e=i.findIndex(e=>!this.authzHooks.has(e));i.splice(-1===e?i.length:e,0,s),this.authzHooks.add(s)}else i.push(s);$(s,t.name,e)}}return this.loadedPlugins[t.name]=t,!0}}export{DM,$ as recordHookOwner,y as registerAttributeTypes};
|
package/dist/config/args.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as e}from"url";import{join as _,dirname as a}from"path";const t="frame-ancestors 'self'",E="SAMEORIGIN",r=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins","identifiers"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains","identifiers"],["--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","identifiers",!0],["--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",_(a(e(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuotaSize"],["--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","identifiers"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes","identifiers"],["--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","identifiers"],["--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",_(a(e(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","identifiers"],["--static-path","DM_STATIC_PATH",_(a(e(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--static-csp","DM_STATIC_CSP",t],["--static-frame-options","DM_STATIC_FRAME_OPTIONS",E],["--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","dns"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes","identifiers"],["--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-retry-attempts","DM_JAMES_RETRY_ATTEMPTS",5,"number"],["--james-retry-delay","DM_JAMES_RETRY_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches","dns"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--james-sync-forwards","DM_JAMES_SYNC_FORWARDS",!1,"boolean"],["--james-team-mailbox-any-domain","DM_JAMES_TEAM_MAILBOX_ANY_DOMAIN",!1,"boolean"],["--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-space-base","DM_TWAKE_SPACE_BASE",""],["--twake-space-class","DM_TWAKE_SPACE_CLASS",["top","twakeSpace"],"array",null,"identifiers"],["--twake-space-display-name-attribute","DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-space-admin-attribute","DM_TWAKE_SPACE_ADMIN_ATTRIBUTE","twakeSpaceAdmin"],["--twake-space-editor-attribute","DM_TWAKE_SPACE_EDITOR_ATTRIBUTE","twakeSpaceEditor"],["--twake-space-viewer-attribute","DM_TWAKE_SPACE_VIEWER_ATTRIBUTE","twakeSpaceViewer"],["--twake-space-user-role-attribute","DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE",""],["--twake-space-exchange","DM_TWAKE_SPACE_EXCHANGE","space"],["--twake-space-sync-queue","DM_TWAKE_SPACE_SYNC_QUEUE","twake.space.sync.requested.ldap-rest"],["--twake-tombstone-dn","DM_TWAKE_TOMBSTONE_DN",[],"array",null,"dns"],["--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",null,"identifiers"],["--twake-tombstone-clear-attributes","DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",[],"array",null,"identifiers"],["--twake-tombstone-erase-min-age","DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",2592e3,"number"],["--twake-tombstone-group-bases","DM_TWAKE_TOMBSTONE_GROUP_BASES",[],"array",null,"dns"],["--twake-instance-dn","DM_TWAKE_INSTANCE_DN",[],"array",null,"dns"],["--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"],["--app-accounts-user-attribute","DM_APP_ACCOUNTS_USER_ATTRIBUTE",""],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes","identifiers"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes","identifiers"],["--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","phrases"],["--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","phrases"],["--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",null,"identifiers"],["--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-transit-origin-attribute","DM_AUTHZ_TRANSIT_ORIGIN_ATTRIBUTE",""],["--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",null,"identifiers"],["--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"],["--authz-global-branch","DM_AUTHZ_GLOBAL_BRANCHES",[],"array","--authz-global-branches","dns"],["--authz-global-write-branch","DM_AUTHZ_GLOBAL_WRITE_BRANCHES",[],"array","--authz-global-write-branches","dns"],["--authz-global-organization","DM_AUTHZ_GLOBAL_ORGANIZATION",""],["--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","identifiers"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-base","DM_PPOLICY_BASE",""],["--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","identifiers"],["--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","identifiers"],["--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 STATIC_CSP,E as STATIC_FRAME_OPTIONS,r as default};
|
|
1
|
+
import{fileURLToPath as _}from"url";import{join as e,dirname as a}from"path";const t="frame-ancestors 'self'",r="SAMEORIGIN",E=[["--port","DM_PORT",8081,"number"],["--shutdown-timeout","DM_SHUTDOWN_TIMEOUT",2e4,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins","identifiers"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--debug","DM_DEBUG",!1,"boolean"],["--log-file","DM_LOG_FILE","error.log"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains","identifiers"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN",""],["--ldap-pwd","DM_LDAP_PWD",""],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls","identifiers",!0],["--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","mailQuotaSize"],["--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","identifiers"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes","identifiers"],["--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",""],["--metrics-port","DM_METRICS_PORT",9464,"number"],["--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","identifiers"],["--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","identifiers"],["--static-path","DM_STATIC_PATH",e(a(_(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--static-csp","DM_STATIC_CSP",t],["--static-frame-options","DM_STATIC_FRAME_OPTIONS",r],["--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","dns"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes","identifiers"],["--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-retry-attempts","DM_JAMES_RETRY_ATTEMPTS",5,"number"],["--james-retry-delay","DM_JAMES_RETRY_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches","dns"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--james-sync-forwards","DM_JAMES_SYNC_FORWARDS",!1,"boolean"],["--james-team-mailbox-any-domain","DM_JAMES_TEAM_MAILBOX_ANY_DOMAIN",!1,"boolean"],["--migration-proxy-url","DM_MIGRATION_PROXY_URL",""],["--migration-proxy-token","DM_MIGRATION_PROXY_TOKEN",""],["--migration-proxy-branch","DM_MIGRATION_PROXY_BRANCHES",[],"array","--migration-proxy-branches","dns"],["--migration-proxy-concurrency","DM_MIGRATION_PROXY_CONCURRENCY",10,"number"],["--migration-proxy-retry-attempts","DM_MIGRATION_PROXY_RETRY_ATTEMPTS",5,"number"],["--migration-proxy-retry-delay","DM_MIGRATION_PROXY_RETRY_DELAY",1e3,"number"],["--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-space-base","DM_TWAKE_SPACE_BASE",""],["--twake-space-class","DM_TWAKE_SPACE_CLASS",["top","twakeSpace"],"array",null,"identifiers"],["--twake-space-display-name-attribute","DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE","twakeDisplayName"],["--twake-space-admin-attribute","DM_TWAKE_SPACE_ADMIN_ATTRIBUTE","twakeSpaceAdmin"],["--twake-space-editor-attribute","DM_TWAKE_SPACE_EDITOR_ATTRIBUTE","twakeSpaceEditor"],["--twake-space-viewer-attribute","DM_TWAKE_SPACE_VIEWER_ATTRIBUTE","twakeSpaceViewer"],["--twake-space-user-role-attribute","DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE",""],["--twake-space-exchange","DM_TWAKE_SPACE_EXCHANGE","space"],["--twake-space-sync-queue","DM_TWAKE_SPACE_SYNC_QUEUE","twake.space.sync.requested.ldap-rest"],["--twake-tombstone-dn","DM_TWAKE_TOMBSTONE_DN",[],"array",null,"dns"],["--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",null,"identifiers"],["--twake-tombstone-clear-attributes","DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",[],"array",null,"identifiers"],["--twake-tombstone-erase-min-age","DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",2592e3,"number"],["--twake-tombstone-group-bases","DM_TWAKE_TOMBSTONE_GROUP_BASES",[],"array",null,"dns"],["--twake-instance-dn","DM_TWAKE_INSTANCE_DN",[],"array",null,"dns"],["--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"],["--app-accounts-user-attribute","DM_APP_ACCOUNTS_USER_ATTRIBUTE",""],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes","identifiers"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes","identifiers"],["--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","phrases"],["--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","phrases"],["--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",null,"identifiers"],["--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-transit-origin-attribute","DM_AUTHZ_TRANSIT_ORIGIN_ATTRIBUTE",""],["--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",null,"identifiers"],["--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"],["--authz-global-branch","DM_AUTHZ_GLOBAL_BRANCHES",[],"array","--authz-global-branches","dns"],["--authz-global-write-branch","DM_AUTHZ_GLOBAL_WRITE_BRANCHES",[],"array","--authz-global-write-branches","dns"],["--authz-global-organization","DM_AUTHZ_GLOBAL_ORGANIZATION",""],["--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","identifiers"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-base","DM_PPOLICY_BASE",""],["--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","identifiers"],["--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","identifiers"],["--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 STATIC_CSP,r as STATIC_FRAME_OPTIONS,E as default};
|
package/dist/config/schema.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BadRequestError as t}from"../lib/errors.js";import{isDnInBranch as e,getCompiledRegex as r}from"../lib/utils.js";function localizedText(t,e="en"){if(null==t)return;if("string"==typeof t)return t||void 0;const r=e.split("-")[0].toLowerCase();for(const n of[e,r,"en"])if(t[n])return t[n];return Object.values(t).find(Boolean)}function invalidValueMessage(t,e){const r=localizedText(e?.hint||e?.items?.hint);return`Invalid value for attribute "${t}"${r?`: ${r}`:""}`}function hasRole(t,e){return!!t?.role&&(Array.isArray(t.role)?t.role.includes(e):t.role===e)}function roleAttribute(t,e){if(t)for(const[r,n]of Object.entries(t.attributes))if(hasRole(n,e))return r}function schemaAttribute(t,e){if(!t)return;const r=e.split(";")[0],n=t.attributes[r];if(n)return[r,n];const i=r.toLowerCase();for(const[e,r]of Object.entries(t.attributes))if(e.toLowerCase()===i)return[e,r]}function modifiedAttributeNames(t){return[...Object.keys(t.add||{}),...Object.keys(t.replace||{}),...Array.isArray(t.delete)?t.delete.map(String):Object.keys(t.delete||{})]}function toDirectoryValue(e,r,n){if(null===n||"string"==typeof n||Buffer.isBuffer(n))return n;const i=e?.attributes[r],asText=(e,n)=>{if("string"==typeof e||Buffer.isBuffer(e))return e;if("number"==typeof e&&Number.isFinite(e)&&("number"===n||"integer"===n))return String(e);if("boolean"==typeof e&&"boolean"===n)return e?"TRUE":"FALSE";throw new t(`Attribute "${r}" must be a string or an array of strings`)};return Array.isArray(n)?n.map(t=>asText(t,i?.items?.type??i?.type)):asText(n,i?.type)}function toDirectoryEntry(t,e){for(const[r,n]of Object.entries(e))e[r]=toDirectoryValue(t,r,n)}function toDirectoryChanges(t,e){e.add&&toDirectoryEntry(t,e.add),e.replace&&toDirectoryEntry(t,e.replace),e.delete&&!Array.isArray(e.delete)&&toDirectoryEntry(t,e.delete)}function neverReturnAttributes(t){const e=new Set;for(const r of t)if(r)for(const[t,n]of Object.entries(r.attributes))n.neverReturn&&e.add(t.toLowerCase());return e}function withoutAttributes(t,e){if(0===e.size)return t;const r={};for(const[n,i]of Object.entries(t))e.has(n.split(";")[0].toLowerCase())||(r[n]=i);return r}async function checkDnValues(r,n,i,o){const s="pointer"===n.type,a=s?n.branch:n.items?.branch,u=s||"pointer"===n.items?.type;if(!u&&!a?.length)return;if(s&&"string"!=typeof i)throw new t(`Attribute "${r}" must be a string (DN pointer)`);const c=(Array.isArray(i)?i:[i]).map(t=>String(t));for(const n of c){if(a?.length&&!a.some(t=>e(n,t)))throw new t(`Attribute "${r}" must point to a DN within allowed branches: ${a.join(", ")}`);let i=!1;if(u){try{i=await o(n)}catch{i=!1}if(!i)throw new t(`Attribute "${r}" points to invalid or non-existent DN: ${n}`)}}}function matchesPattern(t,e){const n=t.test??t.items?.test;if(!n)return!0;const i="string"==typeof n?r(n):n;return(Array.isArray(e)?e:[e]).every(t=>i.test(String(t)))}function assertClientMaySet(e,r,n){if(e)for(const i of r){if("dn"===i)continue;const r=schemaAttribute(e,i);if(!r)continue;const[o,s]=r;if((!n||o.toLowerCase()!==n.toLowerCase()||s.generatedFrom)&&(s.generated||s.readOnly))throw new t(s.readOnly?`Attribute "${i}" is read-only and cannot be set`:`Attribute "${i}" is computed by the server and cannot be set`)}}function missingRequiredAttribute(t,e,r){if(!t)return;const filledByAPlugin=(e,n)=>Object.values(r).some(r=>{const i=r.fillsGeneratedAttribute;return"function"==typeof i&&i.call(r,e,n,t)});for(const[r,n]of Object.entries(t.attributes))if(n.required
|
|
1
|
+
import{BadRequestError as t}from"../lib/errors.js";import{isDnInBranch as e,getCompiledRegex as r}from"../lib/utils.js";function localizedText(t,e="en"){if(null==t)return;if("string"==typeof t)return t||void 0;const r=e.split("-")[0].toLowerCase();for(const n of[e,r,"en"])if(t[n])return t[n];return Object.values(t).find(Boolean)}function invalidValueMessage(t,e){const r=localizedText(e?.hint||e?.items?.hint);return`Invalid value for attribute "${t}"${r?`: ${r}`:""}`}function hasRole(t,e){return!!t?.role&&(Array.isArray(t.role)?t.role.includes(e):t.role===e)}function roleAttribute(t,e){if(t)for(const[r,n]of Object.entries(t.attributes))if(hasRole(n,e))return r}function schemaAttribute(t,e){if(!t)return;const r=e.split(";")[0],n=t.attributes[r];if(n)return[r,n];const i=r.toLowerCase();for(const[e,r]of Object.entries(t.attributes))if(e.toLowerCase()===i)return[e,r]}function modifiedAttributeNames(t){return[...Object.keys(t.add||{}),...Object.keys(t.replace||{}),...Array.isArray(t.delete)?t.delete.map(String):Object.keys(t.delete||{})]}function toDirectoryValue(e,r,n){if(null===n||"string"==typeof n||Buffer.isBuffer(n))return n;const i=e?.attributes[r],asText=(e,n)=>{if("string"==typeof e||Buffer.isBuffer(e))return e;if("number"==typeof e&&Number.isFinite(e)&&("number"===n||"integer"===n))return String(e);if("boolean"==typeof e&&"boolean"===n)return e?"TRUE":"FALSE";throw new t(`Attribute "${r}" must be a string or an array of strings`)};return Array.isArray(n)?n.map(t=>asText(t,i?.items?.type??i?.type)):asText(n,i?.type)}function toDirectoryEntry(t,e){for(const[r,n]of Object.entries(e))e[r]=toDirectoryValue(t,r,n)}function toDirectoryChanges(t,e){e.add&&toDirectoryEntry(t,e.add),e.replace&&toDirectoryEntry(t,e.replace),e.delete&&!Array.isArray(e.delete)&&toDirectoryEntry(t,e.delete)}function neverReturnAttributes(t){const e=new Set;for(const r of t)if(r)for(const[t,n]of Object.entries(r.attributes))n.neverReturn&&e.add(t.toLowerCase());return e}function withoutAttributes(t,e){if(0===e.size)return t;const r={};for(const[n,i]of Object.entries(t))e.has(n.split(";")[0].toLowerCase())||(r[n]=i);return r}async function checkDnValues(r,n,i,o){const s="pointer"===n.type,a=s?n.branch:n.items?.branch,u=s||"pointer"===n.items?.type;if(!u&&!a?.length)return;if(s&&"string"!=typeof i)throw new t(`Attribute "${r}" must be a string (DN pointer)`);const c=(Array.isArray(i)?i:[i]).map(t=>String(t));if(c.includes(""))throw new t(`Attribute "${r}" must point to a DN`);for(const n of c){if(a?.length&&!a.some(t=>e(n,t)))throw new t(`Attribute "${r}" must point to a DN within allowed branches: ${a.join(", ")}`);let i=!1;if(u){try{i=await o(n)}catch{i=!1}if(!i)throw new t(`Attribute "${r}" points to invalid or non-existent DN: ${n}`)}}}function matchesPattern(t,e){const n=t.test??t.items?.test;if(!n)return!0;const i="string"==typeof n?r(n):n;return(Array.isArray(e)?e:[e]).every(t=>i.test(String(t)))}function assertClientMaySet(e,r,n){if(e)for(const i of r){if("dn"===i)continue;const r=schemaAttribute(e,i);if(!r)continue;const[o,s]=r;if((!n||o.toLowerCase()!==n.toLowerCase()||s.generatedFrom)&&(s.generated||s.readOnly))throw new t(s.readOnly?`Attribute "${i}" is read-only and cannot be set`:`Attribute "${i}" is computed by the server and cannot be set`)}}function missingRequiredAttribute(t,e,r){if(!t)return;const filledByAPlugin=(e,n)=>Object.values(r).some(r=>{const i=r.fillsGeneratedAttribute;return"function"==typeof i&&i.call(r,e,n,t)});for(const[r,n]of Object.entries(t.attributes)){const t=e[r],i=null==t||""===t||Array.isArray(t)&&0===t.length;if(n.required&&i&&(!n.generated||!n.generatedFrom&&!filledByAPlugin(r,n)))return r}}export{assertClientMaySet,checkDnValues,hasRole,invalidValueMessage,localizedText,matchesPattern,missingRequiredAttribute,modifiedAttributeNames,neverReturnAttributes,roleAttribute,schemaAttribute,toDirectoryChanges,toDirectoryEntry,toDirectoryValue,withoutAttributes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"crypto";const t=new WeakMap,
|
|
1
|
+
import{randomUUID as e}from"crypto";const t=new WeakMap,r=new WeakMap;function setChangeSource(e,r){t.set(e,r)}function requestId(t){let n=r.get(t);return n||(n=e(),r.set(t,n)),n}function changeContext(e){if(!e)return{};const{user:r,userName:n}=e;return{actor:n??r,requestId:requestId(e),source:t.get(e)??"rest"}}export{changeContext,requestId,setChangeSource};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{BadRequestError as e}from"../errors.js";import{toDirectoryEntry as t,missingRequiredAttribute as r,toDirectoryChanges as i,checkDnValues as s,matchesPattern as a,invalidValueMessage as n,schemaAttribute as o}from"../../config/schema.js";class EntityValidator{constructor(e,t){this.schema=e,this.deps=t}async validateNew(i){if(!this.schema)return void t(void 0,i);applyFixedDefaults(this.schema,i);for(const[t,r]of Object.entries(i)){const s=this.attribute(t);if(s){if(s.required&&isMissing(r)&&!s.generated)throw new e(`Attribute "${t}" is required`);i[t]=asStored(await this.validateValue(t,r))}}const s=r(this.schema,i,this.deps.loadedPlugins);if(s)throw new e(`Attribute "${s}" is required`);t(this.schema,i)}async validateChanges(t){if(this.schema){for(const r of["add","replace"]){const i=t[r];if(i)for(const[t,r]of Object.entries(i)){if(this.attribute(t)?.fixed)throw new e(`Attribute "${t}" is fixed and cannot be modified`);i[t]=asStored(await this.validateValue(t,r))}}if(t.delete){const whole=e=>{if(Array.isArray(t.delete))return!0;const r=t.delete[e];return!r||isNoValue(r)},r=Array.isArray(t.delete)?t.delete:Object.keys(t.delete);for(const t of r){const r=this.attribute(t);if(r?.fixed)throw new e(`Attribute "${t}" is fixed and cannot be deleted`);if(r?.required&&whole(t))throw new e(`Attribute "${t}" is required`)}}i(this.schema,t)}else i(void 0,t)}async validateValue(t,r){if(!this.schema)return r;const i=this.attribute(t);if(!i)return r;if(isNoValue(r)){if(i.required)throw new e(`Attribute "${t}" is required`);return r}let o=r;if("array"===i.type){Array.isArray(r)||(o=[r]);for(const e of o)assertScalar(t,e,i.items?.type)}else{if(Array.isArray(r)){if(r.length>1)throw new e(`Attribute "${t}" must be a single value`);o=r[0]}i.normalize&&"string"==typeof o||assertScalar(t,o,i.type)}if(await s(t,i,o,this.deps.entryExists),!a(i,o))throw new e(n(t,i));return o}attribute(t){const r=o(this.schema,t)?.[1];if(!r&&this.schema?.strict)throw new e(`Unknown attribute "${t}" for ${this.deps.singularName}`);return r}}function applyFixedDefaults(e,t){if(e)for(const[r,i]of Object.entries(e.attributes))i.fixed&&void 0!==i.default&&"objectclass"!==r.toLowerCase()&&(t[r]=i.default)}const isNoValue=e=>null==e||Array.isArray(e)&&0===e.length,isMissing=e=>isNoValue(e)||""===e,asStored=e=>isNoValue(e)?[]:e,f=/^-?\d+(\.\d+)?$/,u=/^-?\d+$/;function assertScalar(t,r,i){const s="string"==typeof r;if(!(()=>{switch(i){case"number":return"number"==typeof r&&Number.isFinite(r)||s&&f.test(r);case"integer":return"number"==typeof r&&Number.isInteger(r)||s&&u.test(r);case"boolean":return"boolean"==typeof r||s&&("TRUE"===r||"FALSE"===r);default:return s||Buffer.isBuffer(r)}})()){throw new e(`Attribute "${t}" must be ${"number"===i||"integer"===i||"boolean"===i?`a${"integer"===i?"n":""} ${i}`:"a string"}`)}}export{EntityValidator,applyFixedDefaults};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{errorBody as r,AUTHZ_REFUSED as t}from"./errors.js";let e;const setLogger=r=>{e=r},getLogger=()=>e,tryMethod=async(r,t,...e)=>{try{return await t(...e),ok(r)}catch(t){return serverError(r,t)}},tryMethodData=async(r,t,...e)=>{try{const n=await t(...e);return ok(r,n)}catch(t){return serverError(r,t)}},_rejectResponse=(r,t,n)=>{e?.warn(`Client error ${r}: ${n}`),t.status(r).json({error:n})},ok=(r,t={success:!0})=>{r.status(200).json(t)},created=(r,t)=>{r.status(201).json(t)},multiStatus=(r,t)=>{r.status(207).json(t)},badRequest=(r,t="Bad request")=>_rejectResponse(400,r,t),unauthorized=(r,t="Unauthorized")=>_rejectResponse(401,r,t),forbidden=(r,t="Forbidden")=>_rejectResponse(403,r,t),notFound=(r,t="Not found")=>_rejectResponse(404,r,t),serverError=(n,s)=>{const o=s instanceof Error?s.message:String(s);let a=s instanceof Error&&"statusCode"in s?s.statusCode:500;if(500===a&&/\[authz-forbidden\]/.test(o)&&(a=403),a>=400&&a<500){const c=403===a&&/\[authz-forbidden\]/.test(o)?t:o;return e?.warn(`Client error ${a}: ${c}`),void n.status(a).json(r(s,c))}s instanceof Error?e?.error(s.message,s):"string"==typeof s?e?.error(s):e?.error("Server error",s),n.status(a).json(
|
|
1
|
+
import{errorBody as r,AUTHZ_REFUSED as t}from"./errors.js";let e;const setLogger=r=>{e=r},getLogger=()=>e;let n=!1;const setDebug=r=>{n=r},serverErrorBody=(r,t=n)=>({error:"Internal Server Error",message:t?r instanceof Error?r.message:String(r):"An error occurred"}),tryMethod=async(r,t,...e)=>{try{return await t(...e),ok(r)}catch(t){return serverError(r,t)}},tryMethodData=async(r,t,...e)=>{try{const n=await t(...e);return ok(r,n)}catch(t){return serverError(r,t)}},_rejectResponse=(r,t,n)=>{e?.warn(`Client error ${r}: ${n}`),t.status(r).json({error:n})},ok=(r,t={success:!0})=>{r.status(200).json(t)},created=(r,t)=>{r.status(201).json(t)},multiStatus=(r,t)=>{r.status(207).json(t)},badRequest=(r,t="Bad request")=>_rejectResponse(400,r,t),unauthorized=(r,t="Unauthorized")=>_rejectResponse(401,r,t),forbidden=(r,t="Forbidden")=>_rejectResponse(403,r,t),notFound=(r,t="Not found")=>_rejectResponse(404,r,t),serverError=(n,s)=>{const o=s instanceof Error?s.message:String(s);let a=s instanceof Error&&"statusCode"in s?s.statusCode:500;if(500===a&&/\[authz-forbidden\]/.test(o)&&(a=403),a>=400&&a<500){const c=403===a&&/\[authz-forbidden\]/.test(o)?t:o;return e?.warn(`Client error ${a}: ${c}`),void n.status(a).json(r(s,c))}s instanceof Error?e?.error(s.message,s):"string"==typeof s?e?.error(s):e?.error("Server error",s),n.status(a).json(serverErrorBody(s))},wantJson=(r,t)=>!1!==r.accepts("json")||(badRequest(t),!1),jsonBody=(r,t,...e)=>{try{if(!wantJson(r,t))return!1;const n=r.body;if(null===n||"object"!=typeof n||Array.isArray(n))return badRequest(t,"Bad content"),!1;if(e.length>0)for(let r=0;r<e.length;r++)if(null==n[e[r]])return badRequest(t,"Bad content"),!1;return n}catch(r){return badRequest(t,"Bad content"),!1}};export{badRequest,created,forbidden,getLogger,jsonBody,multiStatus,notFound,ok,serverError,serverErrorBody,setDebug,setLogger,tryMethod,tryMethodData,unauthorized,wantJson};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=["/healthz","/readyz"],t=5e3,s=2e3,a=new WeakMap,withTimeout=async(e,t)=>{let s;const a=new Promise((e,a)=>{s=setTimeout(()=>a(new Error("timed out")),t)});try{return await Promise.race([e,a])}finally{clearTimeout(s)}};function readiness(e,t=Date.now(),s=2e3){if(e.stopping)return Promise.resolve({status:"stopping"});if(!e.composed)return Promise.resolve({status:"starting"});const n=a.get(e);if(n&&t>=n.at&&t-n.at<5e3)return n.answer;const o=async function(e,t){const s=withTimeout(e.ldap.ping(t),t).then(()=>!0,()=>!1),a=Object.entries(e.loadedPlugins).filter(([,e])=>e.health).map(async([e,s])=>{try{return[e,await withTimeout(Promise.resolve(s.health()),t)]}catch{return[e,"failed"]}}),[n,o]=await Promise.all([s,Promise.all(a)]);if(!n)return{status:"unavailable",ldap:"failed"};const r={};for(const[e,t]of o)r[e]=t;return{status:Object.values(r).some(e=>"ok"!==e)?"degraded":"ok",ldap:"ok",plugins:r}}(e,s);return a.set(e,{at:t,answer:o}),o}function mountHealth(t,s){const[a,n]=e;t.get(a,(e,t)=>{t.setHeader("Cache-Control","no-store"),t.json({status:"ok"})}),t.get(n,async(e,t)=>{const a=await readiness(s);t.setHeader("Cache-Control","no-store");const n="ok"===a.status||"degraded"===a.status;t.status(n?200:503).json(a)})}export{e as HEALTH_PATHS,s as PROBE_TIMEOUT_MS,t as PROBE_TTL_MS,mountHealth,readiness};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node-fetch";import{ConflictError as e,GatewayTimeoutError as i,BadGatewayError as a}from"./errors.js";const instanceSlug=t=>t.toLowerCase().replace(/[^a-z0-9-]/g,"").replace(/^-+|-+$/g,""),matrixId=t=>{const e=t.lastIndexOf("@"),i=t.slice(0,e),a=t.slice(e+1),s=`@${i}:${a}`.toLowerCase();if(!(e<1||!a||i.includes(":")||/\s/.test(s)||Buffer.byteLength(s)>255))return s};async function refused(t,e){const i=(await e.text().catch(()=>"")).slice(0,500);return new a(`${t}: HTTP ${e.status}${i?` ${i}`:""}`)}class HttpProvider{constructor(t,e){this.domain=t,this.timeout=e}address(t){return`${t.id}.${this.domain}`.toLowerCase()}async call(e,i={}){try{return await t(e,{...i,signal:AbortSignal.timeout(this.timeout)})}catch(t){throw this.timedOut(t,e)}}async json(t){try{return await t.json()}catch(e){throw this.timedOut(e,t.url)}}timedOut(t,e){return["AbortError","TimeoutError"].includes(t.name)?new i(`${new URL(e).host} did not answer within ${this.timeout} ms`):t}}class ClouderyProvider extends HttpProvider{constructor(t,e,i,a,s){super(i,s),this.url=t,this.token=e,this.offer=a}headers(){return{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json"}}async create(t){const e=await this.call(`${this.url}/api/v1/instances`,{method:"POST",headers:this.headers(),body:JSON.stringify({email:t.email,internal_email:t.email,publicName:t.publicName,public_name:t.publicName,locale:t.locale,oidc:t.id,slug:t.id,phone:t.phone||"",offer:t.offer||this.offer,domain:this.domain,skip_email_validation:!0,...t.orgId?{org_id:t.orgId}:{},...t.orgDomain?{org_domain:t.orgDomain}:{}})});if(!e.ok)throw await refused("Cloudery refused the instance",e)}async find(t,i){const a=(i??this.address(t)).toLowerCase(),s=new URL(`${this.url}/api/v2/instances`);s.searchParams.set("fqdn",a),s.searchParams.set("limit","1");const o=await this.call(s.toString(),{headers:this.headers()});if(!o.ok)throw await refused("Cloudery instance search failed",o);const{items:r}=await this.json(o),n=r?.[0];if(n){if(n.internal_email&&n.internal_email.toLowerCase()!==t.email.toLowerCase()&&(!t.organization||!await this.organizationHolds(t.id,n._id))||n.oidc&&n.oidc!==t.id)throw new e(`${a} already exists and belongs to another user`);return{fqdn:n.fqdn,ready:Boolean(n.instantiated_at)}}}async organizationHolds(t,e){if(!e)return!1;const i=await this.call(`${this.url}/api/v2/organizations/${encodeURIComponent(t)}`,{headers:this.headers()});if(404===i.status)return!1;if(!i.ok)throw await refused("Cloudery organization lookup failed",i);return(await this.json(i)).instance_id===e}async createOrganization(t){const e=await this.call(`${this.url}/api/v2/organizations`,{method:"POST",headers:this.headers(),body:JSON.stringify({ldap_branch:t.id,name:t.name,custom_domain:t.domain})});if(!e.ok&&409!==e.status)throw await refused("Cloudery refused the organization",e)}async linkOrganization(t,e){const i=await this.call(`${this.url}/api/v2/organizations/${encodeURIComponent(t)}`,{method:"PATCH",headers:this.headers(),body:JSON.stringify({instance_fqdn:e})});if(!i.ok)throw await refused("Cloudery refused to link the organization",i)}}class CozyStackProvider extends HttpProvider{constructor(t,e,i,a,s,o,r){super(a,r),this.url=t,this.context=s,this.apps=o,this.auth=`Basic ${Buffer.from(`${e}:${i}`).toString("base64")}`}headers(){return{Authorization:this.auth,Accept:"application/json"}}async create(t){const i=this.address(t),a=new URLSearchParams({Domain:i,Email:t.email,PublicName:t.publicName,Locale:t.locale,OIDCID:t.oidc??t.id});t.phone&&a.set("Phone",t.phone),t.orgId&&a.set("OrgID",t.orgId),t.orgDomain&&a.set("OrgDomain",t.orgDomain),this.context&&a.set("ContextName",this.context),this.apps&&a.set("Apps",this.apps);const s=await this.call(`${this.url}/instances?${a.toString()}`,{method:"POST",headers:this.headers()});if(409===s.status){if(!await this.find(t))throw new e(`${i} already exists`);return i}if(!s.ok)throw await refused("cozy-stack refused the instance",s);return await this.finishOnboarding(i),i}async find(t,i){const a=(i??this.address(t)).toLowerCase(),s=await this.read(a);if(s){if(s.email?.toLowerCase()!==t.email.toLowerCase()||s.oidc_id!==(t.oidc??t.id))throw new e(`${a} already exists and belongs to another user`);return s.onboarding_finished||await this.finishOnboarding(a),{fqdn:a,ready:!0}}}async destroy(t,i){const a=this.address({id:t}),s=await this.read(a);if(!s)return;if(s.oidc_id!==i)throw new e(`${a} belongs to another user, left in place`);const o=await this.call(`${this.url}/instances/${encodeURIComponent(a)}`,{method:"DELETE",headers:this.headers()});if(!o.ok&&404!==o.status)throw await refused("cozy-stack refused to destroy the instance",o)}async read(t){const e=await this.call(`${this.url}/instances/${encodeURIComponent(t)}`,{headers:this.headers()});if(404===e.status)return;if(!e.ok)throw await refused("cozy-stack instance read failed",e);const{data:i}=await this.json(e);return i?.attributes??{}}async finishOnboarding(t){const e=await this.call(`${this.url}/instances/${encodeURIComponent(t)}?OnboardingFinished=true`,{method:"PATCH",headers:this.headers()});if(!e.ok)throw await refused("cozy-stack refused to finish onboarding",e)}}export{ClouderyProvider,CozyStackProvider,instanceSlug,matrixId};
|
|
1
|
+
import t from"node-fetch";import{ConflictError as e,GatewayTimeoutError as i,BadGatewayError as a}from"./errors.js";import{timedOutbound as s}from"./metrics.js";const instanceSlug=t=>t.toLowerCase().replace(/[^a-z0-9-]/g,"").replace(/^-+|-+$/g,""),matrixId=t=>{const e=t.lastIndexOf("@"),i=t.slice(0,e),a=t.slice(e+1),s=`@${i}:${a}`.toLowerCase();if(!(e<1||!a||i.includes(":")||/\s/.test(s)||Buffer.byteLength(s)>255))return s};async function refused(t,e){const i=(await e.text().catch(()=>"")).slice(0,500);return new a(`${t}: HTTP ${e.status}${i?` ${i}`:""}`)}class HttpProvider{constructor(t,e){this.domain=t,this.timeout=e}address(t){return`${t.id}.${this.domain}`.toLowerCase()}async call(e,i={}){try{return await s(this.target,()=>t(e,{...i,signal:AbortSignal.timeout(this.timeout)}))}catch(t){throw this.timedOut(t,e)}}async json(t){try{return await t.json()}catch(e){throw this.timedOut(e,t.url)}}timedOut(t,e){return["AbortError","TimeoutError"].includes(t.name)?new i(`${new URL(e).host} did not answer within ${this.timeout} ms`):t}}class ClouderyProvider extends HttpProvider{constructor(t,e,i,a,s){super(i,s),this.url=t,this.token=e,this.offer=a,this.target="cloudery"}headers(){return{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json"}}async create(t){const e=await this.call(`${this.url}/api/v1/instances`,{method:"POST",headers:this.headers(),body:JSON.stringify({email:t.email,internal_email:t.email,publicName:t.publicName,public_name:t.publicName,locale:t.locale,oidc:t.id,slug:t.id,phone:t.phone||"",offer:t.offer||this.offer,domain:this.domain,skip_email_validation:!0,...t.orgId?{org_id:t.orgId}:{},...t.orgDomain?{org_domain:t.orgDomain}:{}})});if(!e.ok)throw await refused("Cloudery refused the instance",e)}async find(t,i){const a=(i??this.address(t)).toLowerCase(),s=new URL(`${this.url}/api/v2/instances`);s.searchParams.set("fqdn",a),s.searchParams.set("limit","1");const o=await this.call(s.toString(),{headers:this.headers()});if(!o.ok)throw await refused("Cloudery instance search failed",o);const{items:r}=await this.json(o),n=r?.[0];if(n){if(n.internal_email&&n.internal_email.toLowerCase()!==t.email.toLowerCase()&&(!t.organization||!await this.organizationHolds(t.id,n._id))||n.oidc&&n.oidc!==t.id)throw new e(`${a} already exists and belongs to another user`);return{fqdn:n.fqdn,ready:Boolean(n.instantiated_at)}}}async organizationHolds(t,e){if(!e)return!1;const i=await this.call(`${this.url}/api/v2/organizations/${encodeURIComponent(t)}`,{headers:this.headers()});if(404===i.status)return!1;if(!i.ok)throw await refused("Cloudery organization lookup failed",i);return(await this.json(i)).instance_id===e}async createOrganization(t){const e=await this.call(`${this.url}/api/v2/organizations`,{method:"POST",headers:this.headers(),body:JSON.stringify({ldap_branch:t.id,name:t.name,custom_domain:t.domain})});if(!e.ok&&409!==e.status)throw await refused("Cloudery refused the organization",e)}async linkOrganization(t,e){const i=await this.call(`${this.url}/api/v2/organizations/${encodeURIComponent(t)}`,{method:"PATCH",headers:this.headers(),body:JSON.stringify({instance_fqdn:e})});if(!i.ok)throw await refused("Cloudery refused to link the organization",i)}}class CozyStackProvider extends HttpProvider{constructor(t,e,i,a,s,o,r){super(a,r),this.url=t,this.context=s,this.apps=o,this.target="cozy-stack",this.auth=`Basic ${Buffer.from(`${e}:${i}`).toString("base64")}`}headers(){return{Authorization:this.auth,Accept:"application/json"}}async create(t){const i=this.address(t),a=new URLSearchParams({Domain:i,Email:t.email,PublicName:t.publicName,Locale:t.locale,OIDCID:t.oidc??t.id});t.phone&&a.set("Phone",t.phone),t.orgId&&a.set("OrgID",t.orgId),t.orgDomain&&a.set("OrgDomain",t.orgDomain),this.context&&a.set("ContextName",this.context),this.apps&&a.set("Apps",this.apps);const s=await this.call(`${this.url}/instances?${a.toString()}`,{method:"POST",headers:this.headers()});if(409===s.status){if(!await this.find(t))throw new e(`${i} already exists`);return i}if(!s.ok)throw await refused("cozy-stack refused the instance",s);return await this.finishOnboarding(i),i}async find(t,i){const a=(i??this.address(t)).toLowerCase(),s=await this.read(a);if(s){if(s.email?.toLowerCase()!==t.email.toLowerCase()||s.oidc_id!==(t.oidc??t.id))throw new e(`${a} already exists and belongs to another user`);return s.onboarding_finished||await this.finishOnboarding(a),{fqdn:a,ready:!0}}}async destroy(t,i){const a=this.address({id:t}),s=await this.read(a);if(!s)return;if(s.oidc_id!==i)throw new e(`${a} belongs to another user, left in place`);const o=await this.call(`${this.url}/instances/${encodeURIComponent(a)}`,{method:"DELETE",headers:this.headers()});if(!o.ok&&404!==o.status)throw await refused("cozy-stack refused to destroy the instance",o)}async read(t){const e=await this.call(`${this.url}/instances/${encodeURIComponent(t)}`,{headers:this.headers()});if(404===e.status)return;if(!e.ok)throw await refused("cozy-stack instance read failed",e);const{data:i}=await this.json(e);return i?.attributes??{}}async finishOnboarding(t){const e=await this.call(`${this.url}/instances/${encodeURIComponent(t)}?OnboardingFinished=true`,{method:"PATCH",headers:this.headers()});if(!e.ok)throw await refused("cozy-stack refused to finish onboarding",e)}}export{ClouderyProvider,CozyStackProvider,instanceSlug,matrixId};
|
package/dist/lib/ldapActions.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e}from"node:crypto";import{Client as t,Attribute as s,Change as i,Control as a}from"ldapts";import{LRUCache as n}from"lru-cache";import r from"p-limit";import{isAuthzRefusal as o,hookOwner as c,launchHooksChained as l,normalizeDn as h,isDnInBranch as d,knownAttributeName as f,launchHooks as u,rdnTypes as p,escapeDnValue as g,getParentDn as y}from"./utils.js";import{changeContext as m}from"./changeContext.js";import{runOperation as w,outsideOperation as b}from"./operation.js";import{outsideRequest as C,currentRequest as A}from"./requestContext.js";import{BadRequestError as v,NotFoundError as $,ConflictError as j}from"./errors.js";import{extractLdapCode as L,isSchemaRefusal as D}from"./ldapCodes.js";import{SchemaIndex as S,parseSchema as P}from"./ldapSchema.js";const x={scope:"sub",filter:"(objectClass=*)",attributes:["*"],sizeLimit:0,timeLimit:10,paged:{pageSize:100}},E={ldapaddrequest:2,ldapmodifyrequest:3,ldaprenamerequest:2,ldapdeleterequest:1},O=new Set(["search","add","modify","rename","move","delete"]),isPlainObject=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function assertModifyRequest(e){const t=Object.keys(e).filter(e=>!["add","replace","delete"].includes(e));if(t.length>0)throw new v(`A modification carries add, replace or delete, not ${t.join(", ")}`);const{add:s,replace:i,delete:a}=e;for(const[e,t]of[["add",s],["replace",i]])if(void 0!==t&&!isPlainObject(t))throw new v(`"${e}" must map attributes to values`);if(!(void 0===a||isPlainObject(a)||Array.isArray(a)&&a.every(e=>"string"==typeof e)))throw new v('"delete" must list attribute names or map attributes to values');if(0===[s,i,a].reduce((e,t)=>e+(t?Object.keys(t).length:0),0))throw new v("A modification names at least one attribute in add, replace or delete")}function ldapError(e,t){const s=new Error(`${e}: ${String(t)}`),i=L(t);return void 0===i?s:withCode(s,i)}function withCode(e,t){return e.code=t,e}const digestKey=t=>e("sha256").update(t).digest("hex").slice(0,12),foldDn=e=>e.trim().toLowerCase().split(",").map(e=>e.trim()).join(","),R=new Set(["member","uniquemember"]),isMembershipAttribute=e=>R.has(e.toLowerCase()),membersNamed=e=>Object.entries(e??{}).flatMap(([e,t])=>isMembershipAttribute(e)?[t].flat().map(e=>Buffer.isBuffer(e)?e.toString():e).filter(e=>"string"==typeof e):[]),cloneSearchResult=e=>({searchEntries:e.searchEntries.map(e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Array.isArray(t)?[...t]:t]))),searchReferences:[...e.searchReferences]}),k=["*","+","1.1"],dropSelectors=e=>{for(const t of e.searchEntries)for(const e of k){const s=t[e];Array.isArray(s)&&0===s.length&&delete t[e]}return e};function ldapRefusal(e,t,s){return withCode(new v(`${e}: ${String(s)}`),t)}function assertHasValue(e,t){if(Array.isArray(t)&&0===t.length)throw new v(`Attribute "${e}" has no value`)}function ldapWriteError(e,t,s){const i=L(s);return D(i)?ldapRefusal(`The directory refused this write to ${t}`,i,s):ldapError(e,s)}function ldapRenameError(e,t,s,i){const a=L(i);return 68===a?withCode(new j(`Entry ${s} already exists`),a):D(a)?ldapRefusal(`The directory refused to rename ${t} to ${s}`,a,i):ldapError(e,i)}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.spellings=new Map,this.spelledTypes=new Map,this.classWarnings=new n({max:1e4}),this.parent=e,this.logger=e.logger,this.config=e.config;for(const[e,t]of Object.entries(this.config))/_attributes?$/.test(e)&&this.knowAttributeNames([t].flat().filter(e=>"string"==typeof e));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 s="string"==typeof this.config.ldap_cache_max?parseInt(this.config.ldap_cache_max,10)||1e3:this.config.ldap_cache_max??1e3,i=this.config.ldap_cache_ttl,a="string"==typeof i?parseInt(i,10)||0:i??0,o=1e3*Math.max(a,0);if(this.cacheEnabled=o>0,this.searchCache=new n({max:s,ttl:this.cacheEnabled?o:void 0,updateAgeOnGet:!1,updateAgeOnHas:!1}),this.logger.info(this.cacheEnabled?`LDAP search cache initialized: max=${s}, ttl=${o/1e3}s`:"LDAP search cache disabled (--ldap-cache-ttl=0)"),this.attrSignatureCache=new n({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 s=0;s<this.ldapUrls.length;s++){const i=(this.currentUrlIndex+s)%this.ldapUrls.length,a=this.ldapUrls[i];try{this.logger.debug(`Attempting connection to ${a}`);const e={...this.options,url:a,tlsOptions:a.startsWith("ldaps://")?{minVersion:"TLSv1.2"}:void 0},s=new t(e);return await s.bind(this.dn,this.pwd),i!==this.currentUrlIndex&&(this.logger.info(`LDAP failover: switched from ${this.ldapUrls[this.currentUrlIndex]} to ${a}`),this.currentUrlIndex=i),s}catch(t){this.logger.warn(`Failed to connect to ${a}: ${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 s=this.availableConnections.length-1;s>=0;s--){const i=this.availableConnections[s];if(e-i.createdAt>this.connectionTtl){t.push(i),this.availableConnections.splice(s,1);const e=this.connectionPool.indexOf(i);-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 C(()=>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 s=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(s),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 s=this.attrSignatureCache.get(t);return s||(s=[...e].sort().join(","),this.attrSignatureCache.set(t,s)),s}getCacheKey(e,t){const s=this.getAttributeSignature(t.attributes),i=t.explicitBufferAttributes?.length?[...t.explicitBufferAttributes].sort().join(","):"-",a="string"==typeof t.filter?t.filter:t.filter?t.filter.toString():"(objectClass=*)";return[e,t.scope||"sub",a,s,i,!1===t.returnAttributeValues?"novalues":"values",t.derefAliases??"-",String(t.sizeLimit??"-")].join("\0")}invalidateCache(e){if(!this.cacheEnabled)return;this.cacheGeneration++;const t=foldDn(e),s=`,${t}`,i=[];for(const e of this.searchCache.keys()){const a=foldDn(e.split("\0")[0]);(a===t||a.endsWith(s))&&i.push(e)}for(const e of i)this.searchCache.delete(e)}invalidateReferences(e){if(!this.cacheEnabled)return;this.cacheGeneration++;const t=foldDn(e),s=`,${t}`,names=e=>{if("string"!=typeof e)return!1;const i=foldDn(e);return i===t||i.endsWith(s)},i=[];for(const[e,t]of this.searchCache.entries())t.searchEntries.some(e=>Object.values(e).some(e=>Array.isArray(e)?e.some(names):names(e)))&&i.push(e);for(const e of i)this.searchCache.delete(e)}get system(){return this.systemView??(this.systemView=new Proxy(this,{get:(e,t)=>{const s=Reflect.get(e,t,e);return"function"==typeof s&&O.has(t)?(...t)=>C(()=>s.apply(e,t)):s}})),this.systemView}forRequest(e){return{search:(t,s)=>this.search(t,s??this.base,e),add:(t,s)=>this.add(t,s,e),modify:(t,s,i)=>this.modify(t,s,e,i),rename:(t,s)=>this.rename(t,s,e),delete:t=>this.delete(t,e)}}async readableBy(e,t){if(t??(t=A()),!t)return!0;try{const s=await this.search({paged:!1,scope:"base",attributes:["1.1"]},e,t);return(s.searchEntries?.length??0)>0}catch(e){if(o(e)||403===e?.statusCode||32===L(e))return!1;throw e}}async authorize(e,t){const s=[...t];"ldapaddrequest"===e&&await this.canonicalAttributes(s[1]),"ldapmodifyrequest"===e&&await this.canonicalChanges(s[1]);const i=E[e];s[i]??(s[i]=A());const[a,...n]=s,r="ldapdeleterequest"===e?[[a].flat().map(e=>this.setDn(String(e))),...n]:[this.setDn(String(a)),...n];"ldaprenamerequest"===e&&(r[1]=this.setDn(String(r[1])));const o=(this.parent.hooks[e]??[]).filter(e=>{const t=c(e);return Boolean(t&&this.parent.loadedPlugins[t.plugin]?.roles?.includes("authz"))});await l(o,r)}declareObjectClasses(e,t,s=!1){const i=(Array.isArray(t)?t:[t]).filter(e=>"string"==typeof e&&""!==e);if(!e||0===i.length)return;const a=JSON.stringify([h(e),i.map(e=>e.toLowerCase()).sort(),s]);this.declaredClasses.set(a,{base:e,classes:i,includeBase:s})}declaredFor(e){let t=0,s=[];for(const i of this.declaredClasses.values()){if(!d(e,i.base))continue;const a=h(i.base);if(!i.includeBase&&h(e)===a)continue;const n=a.split(",").length;n>t&&(t=n,s=[]),n===t&&s.push(...i.classes)}const i=new Set;return s.filter(e=>{const t=e.toLowerCase();return!i.has(t)&&(i.add(t),!0)})}warnOnce(e){this.classWarnings.has(e)||(this.classWarnings.set(e,!0),this.logger.warn(e))}async classRepair(e,t,s=[]){if(!1===this.config.ldap_flat_auto_repair)return[];const i=[...this.declaredFor(e)];for(const e of s)i.some(t=>t.toLowerCase()===e.toLowerCase())||i.push(e);if(0===i.length)return[];const a=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:s}=n.resolveAttributes(e);return new Set([...t,...s].map(attributeKey))},valued=e=>Object.entries(e||{}).filter(([,e])=>Array.isArray(e)?e.length>0:null!=e&&""!==e).map(([e])=>e),r=[...valued(t.add),...valued(t.replace)],o=i.filter(e=>{const t=n.getObjectClass(e);if("AUXILIARY"!==t?.kind)return!1;const s=allowedBy([e]);return r.some(e=>s.has(attributeKey(e)))});if(0===o.length)return[];let c;try{c=(await this.system.search({paged:!1,scope:"base",attributes:["objectClass",...n.resolveAttributes(o).must]},e)).searchEntries[0]}catch{return[]}if(!c)return[];const l=c.objectClass??c.objectclass,h=(Array.isArray(l)?l:l?[l]:[]).map(String),d=allowedBy(h);let f=r.filter(e=>n.getAttributeType(e)&&!d.has(attributeKey(e)));if(0===f.length)return"allowed";const u=new Set(a.map(attributeKey)),p=new Set([...valued(c).filter(e=>!u.has(attributeKey(e))),...r].map(attributeKey)),{auxiliary:g,structural:y}=n.missingClasses(i,h),m=[];for(const t of g){const s=t.names[0]??t.oid,i=allowedBy([s]),a=f.filter(e=>i.has(attributeKey(e)));if(0===a.length)continue;const r=n.resolveAttributes([s]).must.filter(e=>!p.has(attributeKey(e)));r.length>0?this.warnOnce(`${e} needs ${s} for ${a.join(", ")}, and cannot be given it without ${r.join(", ")}`):(m.push(s),f=f.filter(e=>!a.includes(e)))}const w=y.filter(e=>{const t=allowedBy([e]);return f.some(e=>t.has(attributeKey(e)))});return w.length>0&&this.warnOnce(`${e} cannot hold ${f.join(", ")}: its schema declares ${w.join(", ")}, a structural class an existing entry cannot be given`),m.length>0&&this.logger.info(`${e}: adding ${m.join(", ")}, which the change needs and its schema declares`),m}async subschemaDn(){try{const e=await this.system.search({paged:!1,scope:"base",filter:"(objectClass=*)",attributes:["subschemaSubentry"]},""),t=e.searchEntries[0]?.subschemaSubentry,s=Array.isArray(t)?t[0]:t;if(s)return String(s)}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 $(`Subschema entry ${e} not found`);throw t})).searchEntries[0];if(!t)throw new $(`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)},s=new S(P({objectClasses:list(t.objectClasses),attributeTypes:list(t.attributeTypes),ldapSyntaxes:list(t.ldapSyntaxes),matchingRules:list(t.matchingRules)}));return this.schemaCache={index:s,fetchedAt:Date.now()},this.logger.info(`LDAP schema loaded from ${e}: ${s.schema.objectClasses.length} object classes, ${s.schema.attributeTypes.length} attribute types`),s})();try{return await this.schemaLoading}finally{this.schemaLoading=void 0}}knowAttributeNames(e){for(const t of e){if(!t||t.includes(";"))continue;const e=t.toLowerCase();this.spellings.has(e)||this.spellings.set(e,t);const s=(f(t)??t).toLowerCase(),i=this.spelledTypes.get(s);void 0===i?this.spelledTypes.set(s,t):i!==t&&this.logger.warn(`Attribute "${t}" is also named "${i}" in the configuration or a schema: plugins comparing with one name miss the other`)}}async canonicalAttributes(e){if(!e)return;const t=Object.keys(e),s=await this.canonicalNames(t);if(t.every((e,t)=>e===s[t]))return;const i=t.map(t=>e[t]);for(const s of t)delete e[s];s.forEach((t,s)=>e[t]=i[s])}async canonicalChanges(e){e&&(await this.canonicalAttributes(e.add),await this.canonicalAttributes(e.replace),Array.isArray(e.delete)?e.delete=await this.canonicalNames(e.delete):await this.canonicalAttributes(e.delete))}async canonicalNames(e){if(0===e.length)return e;let t;try{t=await this.schemaIndex(),this.schemaUnavailable=!1}catch(e){this.schemaUnavailable||this.logger.warn(`The directory schema could not be read (${String(e)}): attribute names are resolved from the standard types only, a name given as another OID is refused, and object classes are not repaired`),this.schemaUnavailable=!0}const s=new Map;return e.map(e=>{const i=this.canonicalName(e,t),a=s.get(i.toLowerCase());if(void 0!==a)throw new v(`"${a}" and "${e}" name the same attribute`);return s.set(i.toLowerCase(),e),i})}canonicalName(e,t){const[s,...i]=e.split(";"),a=s.replace(/^oid\./i,""),n=i.find(e=>"binary"!==e.toLowerCase());if(void 0!==n)throw new v(`Attribute option "${n}" of "${e}" is not accepted`);const r=t?.getAttributeType(a),o=t?void 0:f(a);if(!t&&!o&&/^\d+(\.\d+)+$/.test(a))throw new v(`Attribute "${e}" cannot be named by its OID here`);const c=(r?[...r.names,r.oid]:[a,...o?[o]:[]]).map(e=>this.spellings.get(e.toLowerCase())).find(Boolean)??r?.names[0]??o??a;return i.length?`${c};binary`:c}filterForCaller(e,t,s){if(!this.parent.hooks.ldapsearchfilter)return e;const i=this.parent.hooks.ldapsearchfilter,one=async e=>{const[a]=await l(i,[e,t,s]);return a??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,s){s??(s=A());let i={...x,...e};i=await l(this.parent.hooks.ldapsearchopts,i),[t,i]=await l(this.parent.hooks.ldapsearchrequest,[t,i,s]);const a=this.cacheEnabled&&!i.paged&&"base"===i.scope,n=a?this.getCacheKey(t,i):"",r=this.cacheGeneration;if(a){const e=this.searchCache.get(n);if(e)return this.logger.debug(`LDAP search cache hit: ${digestKey(n)}`),this.filterForCaller(cloneSearchResult(e),s,i)}const o=await this.acquireConnection();try{let e=i.paged?async function*(e){for await(const t of e)yield dropSelectors(t)}(o.client.searchPaginated(t,i)):o.client.search(t,i).then(dropSelectors);if(e=await l(this.parent.hooks.ldapsearchresult,e),a){const t=e;return r===this.cacheGeneration?(this.searchCache.set(n,cloneSearchResult(t)),this.logger.debug(`LDAP search cached: ${digestKey(n)}`)):this.logger.debug(`LDAP search not cached, a write overtook it: ${digestKey(n)}`),this.filterForCaller(t,s,i)}return i.paged?this.filterForCaller(this.wrapPaginatedSearch(e,o),s,i):this.filterForCaller(e,s,i)}finally{i.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,i){i??(i=A()),e=this.setDn(e),await this.canonicalAttributes(t),t.objectClass&&0!==t.objectClass.length||!this.config.user_class||(t.objectClass=this.config.user_class);const a={};for(const[e,s]of Object.entries(t))Buffer.isBuffer(s)?a[e]=s.toString():Array.isArray(s)&&s.length>0&&Buffer.isBuffer(s[0])?a[e]=s.map(e=>e.toString()):a[e]=s;[e,t]=await l(this.parent.hooks.ldapaddrequest,[e,a,i]);const n=membersNamed(t),r=[];for(const[e,i]of Object.entries(t)){assertHasValue(e,i);const t=Array.isArray(i)?i:[i];r.push(new s({type:e,values:t}))}const o=await this.acquireConnection();try{this.invalidateCache(e);for(const e of n)this.invalidateCache(e);await o.client.add(e,r),this.invalidateCache(e);for(const e of n)this.invalidateCache(e);u(this.parent.hooks.ldapadddone,[e,t],m(i))}catch(t){throw function(e,t){const s=L(t);return 68===s?withCode(new j(`Entry ${e} already exists`),s):32===s?withCode(new $(`${y(e)} does not exist`),s):20===s?ldapRefusal(`The directory refused this write to ${e}`,s,t):ldapWriteError("LDAP add error",e,t)}(e,t)}finally{this.releaseConnection(o)}return await u(this.parent.hooks.ldapaddafter,[e,t],m(i)),!0}async modify(e,t,s,i={}){s??(s=A());const a=this.opNumber(),n=[];try{return await w(a,()=>this.applyModify(e,t,s,i,a,n))}finally{this.endOperation(n,this.parent.hooks.ldapmodifyend,a)}}async applyModify(e,t,n,r,o,c){e=this.setDn(e);const h=[];if(await this.canonicalChanges(t),[e,t]=await l(this.parent.hooks.ldapmodifyrequest,[e,t,o,n]),t.add)for(const[e,a]of Object.entries(t.add))assertHasValue(e,a),h.push(new i({operation:"add",modification:new s({type:e,values:Array.isArray(a)?a:[a]})}));if(t.replace)for(const[e,a]of Object.entries(t.replace))h.push(new i({operation:"replace",modification:new s({type:e,values:Array.isArray(a)?a:[a]})}));if(t.delete)if(Array.isArray(t.delete))for(const e of t.delete)e&&h.push(new i({operation:"delete",modification:new s({type:e,values:[]})}));else for(const[e,a]of Object.entries(t.delete)){const t=new i({operation:"delete",modification:new s(a?{type:e,values:Array.isArray(a)?a:[a]}:{type:e})});h.push(t)}const d=[t.add,t.replace,t.delete].flatMap(e=>Array.isArray(e)?e:Object.keys(e??{})).some(isMembershipAttribute),invalidate=()=>{if(this.invalidateCache(e),d){this.invalidateReferences(e);for(const e of[t.add,t.replace,t.delete])for(const t of membersNamed(Array.isArray(e)?{}:e))this.invalidateCache(t)}};if(0===h.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(u(this.parent.hooks.ldapmodifydone,[e,{},o],r.context??m(n))),!1}{const l=r.permissive?[new a("1.2.840.113556.1.4.1413",{critical:!1})]:void 0,send=async()=>{const t=await this.acquireConnection();try{await t.client.modify(e,h,l)}finally{this.releaseConnection(t)}};try{invalidate();try{await send()}catch(a){if(65!==L(a))throw a;const n=await this.classRepair(e,t,r.declaredClasses);if("allowed"!==n&&0===n.length)throw a;const o="allowed"===n?void 0:new i({operation:"add",modification:new s({type:"objectClass",values:n})});o&&h.push(o),this.invalidateCache(e);try{await send()}catch(e){if(!o||20!==L(e))throw e;h.splice(h.indexOf(o),1),await send()}}return invalidate(),c.push(u(this.parent.hooks.ldapmodifydone,[e,t,o],r.context??m(n))),!0}catch(t){throw this.logger.warn(`Changes that failed: ${e}, ${JSON.stringify(h)}`),function(e,t){const s=L(t);return 32===s?withCode(new $(`Entry ${e} not found`),s):16===s?withCode(new $(`Entry ${e} does not hold the value to delete`),s):65!==s&&20!==s?ldapWriteError("LDAP modify error",e,t):withCode(new j(`Entry ${e} cannot hold this change: ${String(t)}`),s)}(e,t)}}}async rename(e,t,s){s??(s=A()),e=this.setDn(e),t=this.setDn(t);const i=p(e),a=p(t);if(1!==a.length||!i.includes(a[0]))throw new v(`${t}: an entry is renamed by one value of the attribute it is named by`);const n=this.opNumber(),r=[];try{return await w(n,()=>this.applyRename(e,t,s,r))}finally{this.endOperation(r,this.parent.hooks.ldaprenameend,n)}}async applyRename(e,t,s,i){[e,t]=await l(this.parent.hooks.ldaprenamerequest,[e,t,s]);const a=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),await a.client.modifyDN(e,t),this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),i.push(u(this.parent.hooks.ldaprenamedone,[e,t],m(s))),!0}catch(s){throw ldapRenameError("LDAP rename error",e,t,s)}finally{this.releaseConnection(a)}}async move(e,t,s){s??(s=A()),e=this.setDn(e),t=this.setDn(t);const i=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),await i.client.modifyDN(e,t),this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),b(()=>u(this.parent.hooks.ldaprenamedone,[e,t],m(s))),this.logger.debug(`LDAP move: ${e} -> ${t}`),!0}catch(s){throw ldapRenameError("LDAP move error",e,t,s)}finally{this.releaseConnection(i)}}async delete(e,t,s={}){t??(t=A());const i=(Array.isArray(e)?e:[e]).map(e=>this.setDn(e)),a=this.opNumber(),n=[];try{return await w(a,()=>this.applyDelete(i,t,s,n))}finally{this.endOperation(n,this.parent?.hooks.ldapdeleteend,a)}}async applyDelete(e,t,s,i){[e]=await l(this.parent?.hooks.ldapdeleterequest,[e,t]);const a=await this.acquireConnection();try{for(const n of e){try{this.invalidateCache(n),this.invalidateReferences(n),await a.client.del(n),this.invalidateCache(n),this.invalidateReferences(n)}catch(e){const t=L(e);throw 32===t?withCode(new $(`Entry ${n} not found`),t):ldapError("LDAP delete error",e)}i.push(u(this.parent.hooks.ldapdeletedone,n,s.context??m(t)))}return!0}finally{this.releaseConnection(a)}}setDn(e){return/=/.test(e)?/,/.test(e)||(e+=`,${this.base}`):e=`${this.config.ldap_user_main_attribute}=${g(e)},${this.base}`,e}endOperation(e,t,...s){t?.length&&Promise.all(e).then(()=>u(t,...s))}opNumber(){return this.parent.operationSequence++}}export{assertModifyRequest,ldapActions as default};
|
|
1
|
+
import{createHash as e}from"node:crypto";import{Client as t,Attribute as s,Change as i,Control as n}from"ldapts";import{LRUCache as a}from"lru-cache";import r from"p-limit";import{isAuthzRefusal as o,hookOwner as c,launchHooksChained as l,normalizeDn as h,isDnInBranch as d,knownAttributeName as p,launchHooks as f,rdnTypes as u,escapeDnValue as g,getParentDn as y}from"./utils.js";import{changeContext as w}from"./changeContext.js";import{runOperation as m,outsideOperation as b}from"./operation.js";import{outsideAnyRequest as C,outsideRequest as A,currentRequest as v}from"./requestContext.js";import{timedLdap as $,ldapPoolWait as D,secondsSince as L,ldapSearchCache as j,ldapResult as S,ldapOperationDuration as P}from"./metrics.js";import{BadRequestError as E,NotFoundError as x,ConflictError as O}from"./errors.js";import{extractLdapCode as R,isSchemaRefusal as _}from"./ldapCodes.js";import{SchemaIndex as k,parseSchema as U}from"./ldapSchema.js";const q={scope:"sub",filter:"(objectClass=*)",attributes:["*"],sizeLimit:0,timeLimit:10,paged:{pageSize:100}},T={ldapaddrequest:2,ldapmodifyrequest:3,ldaprenamerequest:2,ldapdeleterequest:1},N=new Set(["search","add","modify","rename","move","delete"]),isPlainObject=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function assertModifyRequest(e){const t=Object.keys(e).filter(e=>!["add","replace","delete"].includes(e));if(t.length>0)throw new E(`A modification carries add, replace or delete, not ${t.join(", ")}`);const{add:s,replace:i,delete:n}=e;for(const[e,t]of[["add",s],["replace",i]])if(void 0!==t&&!isPlainObject(t))throw new E(`"${e}" must map attributes to values`);if(!(void 0===n||isPlainObject(n)||Array.isArray(n)&&n.every(e=>"string"==typeof e)))throw new E('"delete" must list attribute names or map attributes to values');if(0===[s,i,n].reduce((e,t)=>e+(t?Object.keys(t).length:0),0))throw new E("A modification names at least one attribute in add, replace or delete")}function ldapError(e,t){const s=new Error(`${e}: ${String(t)}`),i=R(t);return void 0===i?s:withCode(s,i)}function withCode(e,t){return e.code=t,e}const digestKey=t=>e("sha256").update(t).digest("hex").slice(0,12),foldDn=e=>e.trim().toLowerCase().split(",").map(e=>e.trim()).join(","),z=new Set(["member","uniquemember"]),isMembershipAttribute=e=>z.has(e.toLowerCase()),membersNamed=e=>Object.entries(e??{}).flatMap(([e,t])=>isMembershipAttribute(e)?[t].flat().map(e=>Buffer.isBuffer(e)?e.toString():e).filter(e=>"string"==typeof e):[]),cloneSearchResult=e=>({searchEntries:e.searchEntries.map(e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Array.isArray(t)?[...t]:t]))),searchReferences:[...e.searchReferences]}),B=["*","+","1.1"],dropSelectors=e=>{for(const t of e.searchEntries)for(const e of B){const s=t[e];Array.isArray(s)&&0===s.length&&delete t[e]}return e};function ldapRefusal(e,t,s){return withCode(new E(`${e}: ${String(s)}`),t)}function assertHasValue(e,t){if(Array.isArray(t)&&0===t.length)throw new E(`Attribute "${e}" has no value`)}function ldapWriteError(e,t,s){const i=R(s);return _(i)?ldapRefusal(`The directory refused this write to ${t}`,i,s):ldapError(e,s)}function ldapRenameError(e,t,s,i){const n=R(i);return 68===n?withCode(new O(`Entry ${s} already exists`),n):_(n)?ldapRefusal(`The directory refused to rename ${t} to ${s}`,n,i):ldapError(e,i)}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.spellings=new Map,this.spelledTypes=new Map,this.classWarnings=new a({max:1e4}),this.closed=!1,this.parent=e,this.logger=e.logger,this.config=e.config;for(const[e,t]of Object.entries(this.config))/_attributes?$/.test(e)&&this.knowAttributeNames([t].flat().filter(e=>"string"==typeof e));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 s="string"==typeof this.config.ldap_cache_max?parseInt(this.config.ldap_cache_max,10)||1e3:this.config.ldap_cache_max??1e3,i=this.config.ldap_cache_ttl,n="string"==typeof i?parseInt(i,10)||0:i??0,o=1e3*Math.max(n,0);if(this.cacheEnabled=o>0,this.searchCache=new a({max:s,ttl:this.cacheEnabled?o:void 0,updateAgeOnGet:!1,updateAgeOnHas:!1}),this.logger.info(this.cacheEnabled?`LDAP search cache initialized: max=${s}, 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 bind DN is not defined, please set --ldap-dn (or DM_LDAP_DN)");if(!e.config.ldap_pwd)throw new Error("LDAP password is not defined, please set --ldap-pwd (or DM_LDAP_PWD)");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 s=0;s<this.ldapUrls.length;s++){const i=(this.currentUrlIndex+s)%this.ldapUrls.length,n=this.ldapUrls[i];try{this.logger.debug(`Attempting connection to ${n}`);const e={...this.options,url:n,tlsOptions:n.startsWith("ldaps://")?{minVersion:"TLSv1.2"}:void 0},s=new t(e);return await $("bind",()=>s.bind(this.dn,this.pwd)),i!==this.currentUrlIndex&&(this.logger.info(`LDAP failover: switched from ${this.ldapUrls[this.currentUrlIndex]} to ${n}`),this.currentUrlIndex=i),s}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 s=this.availableConnections.length-1;s>=0;s--){const i=this.availableConnections[s];if(e-i.createdAt>this.connectionTtl){t.push(i),this.availableConnections.splice(s,1);const e=this.connectionPool.indexOf(i);-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 ping(e){if(this.closed)throw new Error("LDAP connections closed");const s=[];for(let i=0;i<this.ldapUrls.length;i++){const n=this.ldapUrls[(this.currentUrlIndex+i)%this.ldapUrls.length],a=new t({...this.options,url:n,timeout:e,connectTimeout:e,tlsOptions:n.startsWith("ldaps://")?{minVersion:"TLSv1.2"}:void 0});try{return await a.bind(this.dn,this.pwd),void await a.search("",{scope:"base",attributes:["1.1"]})}catch(e){s.push(`${n}: ${String(e)}`)}finally{await a.unbind().catch(()=>{})}}throw new Error(`No directory answered: ${s.join(", ")}`)}async close(){this.closed=!0;for(const e of this.waitingResolvers.splice(0))e(void 0);const e=this.connectionPool.splice(0);this.availableConnections.length=0,await Promise.allSettled(e.map(e=>e.client.unbind()))}async acquireConnection(){const e=process.hrtime.bigint();try{return await this.takeConnection()}finally{D.observe(L(e))}}poolStats(){return{busy:this.connectionPool.length-this.availableConnections.length,idle:this.availableConnections.length,waiting:this.waitingResolvers.length,max:this.poolSize}}async takeConnection(){if(this.closed)throw new Error("LDAP connections closed");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=await C(()=>this.createConnection());if(this.closed)throw await e.unbind().catch(()=>{}),new Error("LDAP connections closed");const t={client:e,createdAt:Date.now(),inUse:!0};return this.connectionPool.push(t),this.logger.debug(`Created new LDAP connection (pool: ${this.connectionPool.length}/${this.poolSize})`),t}return this.logger.debug("LDAP connection pool full, waiting for available connection"),new Promise((e,t)=>{const s=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=i=>{globalThis.clearTimeout(s),i?e(i):t(new Error("LDAP connections closed"))};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 s=this.attrSignatureCache.get(t);return s||(s=[...e].sort().join(","),this.attrSignatureCache.set(t,s)),s}getCacheKey(e,t){const s=this.getAttributeSignature(t.attributes),i=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,s,i,!1===t.returnAttributeValues?"novalues":"values",t.derefAliases??"-",String(t.sizeLimit??"-")].join("\0")}invalidateCache(e){if(!this.cacheEnabled)return;this.cacheGeneration++;const t=foldDn(e),s=`,${t}`,i=[];for(const e of this.searchCache.keys()){const n=foldDn(e.split("\0")[0]);(n===t||n.endsWith(s))&&i.push(e)}for(const e of i)this.searchCache.delete(e)}invalidateReferences(e){if(!this.cacheEnabled)return;this.cacheGeneration++;const t=foldDn(e),s=`,${t}`,names=e=>{if("string"!=typeof e)return!1;const i=foldDn(e);return i===t||i.endsWith(s)},i=[];for(const[e,t]of this.searchCache.entries())t.searchEntries.some(e=>Object.values(e).some(e=>Array.isArray(e)?e.some(names):names(e)))&&i.push(e);for(const e of i)this.searchCache.delete(e)}get system(){return this.systemView??(this.systemView=new Proxy(this,{get:(e,t)=>{const s=Reflect.get(e,t,e);return"function"==typeof s&&N.has(t)?(...t)=>A(()=>s.apply(e,t)):s}})),this.systemView}forRequest(e){return{search:(t,s)=>this.search(t,s??this.base,e),add:(t,s)=>this.add(t,s,e),modify:(t,s,i)=>this.modify(t,s,e,i),rename:(t,s)=>this.rename(t,s,e),delete:t=>this.delete(t,e)}}async readableBy(e,t){if(t??(t=v()),!t)return!0;try{const s=await this.search({paged:!1,scope:"base",attributes:["1.1"]},e,t);return(s.searchEntries?.length??0)>0}catch(e){if(o(e)||403===e?.statusCode||32===R(e))return!1;throw e}}async authorize(e,t){const s=[...t];"ldapaddrequest"===e&&await this.canonicalAttributes(s[1]),"ldapmodifyrequest"===e&&await this.canonicalChanges(s[1]);const i=T[e];s[i]??(s[i]=v());const[n,...a]=s,r="ldapdeleterequest"===e?[[n].flat().map(e=>this.setDn(String(e))),...a]:[this.setDn(String(n)),...a];"ldaprenamerequest"===e&&(r[1]=this.setDn(String(r[1])));const o=(this.parent.hooks[e]??[]).filter(e=>{const t=c(e);return Boolean(t&&this.parent.loadedPlugins[t.plugin]?.roles?.includes("authz"))});await l(o,r)}declareObjectClasses(e,t,s=!1){const i=(Array.isArray(t)?t:[t]).filter(e=>"string"==typeof e&&""!==e);if(!e||0===i.length)return;const n=JSON.stringify([h(e),i.map(e=>e.toLowerCase()).sort(),s]);this.declaredClasses.set(n,{base:e,classes:i,includeBase:s})}declaredFor(e){let t=0,s=[];for(const i of this.declaredClasses.values()){if(!d(e,i.base))continue;const n=h(i.base);if(!i.includeBase&&h(e)===n)continue;const a=n.split(",").length;a>t&&(t=a,s=[]),a===t&&s.push(...i.classes)}const i=new Set;return s.filter(e=>{const t=e.toLowerCase();return!i.has(t)&&(i.add(t),!0)})}warnOnce(e){this.classWarnings.has(e)||(this.classWarnings.set(e,!0),this.logger.warn(e))}async classRepair(e,t,s=[]){if(!1===this.config.ldap_flat_auto_repair)return[];const i=[...this.declaredFor(e)];for(const e of s)i.some(t=>t.toLowerCase()===e.toLowerCase())||i.push(e);if(0===i.length)return[];const n=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 a;try{a=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=>a.getAttributeType(e)?.oid??e.toLowerCase(),allowedBy=e=>{const{must:t,may:s}=a.resolveAttributes(e);return new Set([...t,...s].map(attributeKey))},valued=e=>Object.entries(e||{}).filter(([,e])=>Array.isArray(e)?e.length>0:null!=e&&""!==e).map(([e])=>e),r=[...valued(t.add),...valued(t.replace)],o=i.filter(e=>{const t=a.getObjectClass(e);if("AUXILIARY"!==t?.kind)return!1;const s=allowedBy([e]);return r.some(e=>s.has(attributeKey(e)))});if(0===o.length)return[];let c;try{c=(await this.system.search({paged:!1,scope:"base",attributes:["objectClass",...a.resolveAttributes(o).must]},e)).searchEntries[0]}catch{return[]}if(!c)return[];const l=c.objectClass??c.objectclass,h=(Array.isArray(l)?l:l?[l]:[]).map(String),d=allowedBy(h);let p=r.filter(e=>a.getAttributeType(e)&&!d.has(attributeKey(e)));if(0===p.length)return"allowed";const f=new Set(n.map(attributeKey)),u=new Set([...valued(c).filter(e=>!f.has(attributeKey(e))),...r].map(attributeKey)),{auxiliary:g,structural:y}=a.missingClasses(i,h),w=[];for(const t of g){const s=t.names[0]??t.oid,i=allowedBy([s]),n=p.filter(e=>i.has(attributeKey(e)));if(0===n.length)continue;const r=a.resolveAttributes([s]).must.filter(e=>!u.has(attributeKey(e)));r.length>0?this.warnOnce(`${e} needs ${s} for ${n.join(", ")}, and cannot be given it without ${r.join(", ")}`):(w.push(s),p=p.filter(e=>!n.includes(e)))}const m=y.filter(e=>{const t=allowedBy([e]);return p.some(e=>t.has(attributeKey(e)))});return m.length>0&&this.warnOnce(`${e} cannot hold ${p.join(", ")}: its schema declares ${m.join(", ")}, a structural class an existing entry cannot be given`),w.length>0&&this.logger.info(`${e}: adding ${w.join(", ")}, which the change needs and its schema declares`),w}async subschemaDn(){try{const e=await this.system.search({paged:!1,scope:"base",filter:"(objectClass=*)",attributes:["subschemaSubentry"]},""),t=e.searchEntries[0]?.subschemaSubentry,s=Array.isArray(t)?t[0]:t;if(s)return String(s)}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 x(`Subschema entry ${e} not found`);throw t})).searchEntries[0];if(!t)throw new x(`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)},s=new k(U({objectClasses:list(t.objectClasses),attributeTypes:list(t.attributeTypes),ldapSyntaxes:list(t.ldapSyntaxes),matchingRules:list(t.matchingRules)}));return this.schemaCache={index:s,fetchedAt:Date.now()},this.logger.info(`LDAP schema loaded from ${e}: ${s.schema.objectClasses.length} object classes, ${s.schema.attributeTypes.length} attribute types`),s})();try{return await this.schemaLoading}finally{this.schemaLoading=void 0}}knowAttributeNames(e){for(const t of e){if(!t||t.includes(";"))continue;const e=t.toLowerCase();this.spellings.has(e)||this.spellings.set(e,t);const s=(p(t)??t).toLowerCase(),i=this.spelledTypes.get(s);void 0===i?this.spelledTypes.set(s,t):i!==t&&this.logger.warn(`Attribute "${t}" is also named "${i}" in the configuration or a schema: plugins comparing with one name miss the other`)}}async canonicalAttributes(e){if(!e)return;const t=Object.keys(e),s=await this.canonicalNames(t);if(t.every((e,t)=>e===s[t]))return;const i=t.map(t=>e[t]);for(const s of t)delete e[s];s.forEach((t,s)=>e[t]=i[s])}async canonicalChanges(e){e&&(await this.canonicalAttributes(e.add),await this.canonicalAttributes(e.replace),Array.isArray(e.delete)?e.delete=await this.canonicalNames(e.delete):await this.canonicalAttributes(e.delete))}async canonicalNames(e){if(0===e.length)return e;let t;try{t=await this.schemaIndex(),this.schemaUnavailable=!1}catch(e){this.schemaUnavailable||this.logger.warn(`The directory schema could not be read (${String(e)}): attribute names are resolved from the standard types only, a name given as another OID is refused, and object classes are not repaired`),this.schemaUnavailable=!0}const s=new Map;return e.map(e=>{const i=this.canonicalName(e,t),n=s.get(i.toLowerCase());if(void 0!==n)throw new E(`"${n}" and "${e}" name the same attribute`);return s.set(i.toLowerCase(),e),i})}canonicalName(e,t){const[s,...i]=e.split(";"),n=s.replace(/^oid\./i,""),a=i.find(e=>"binary"!==e.toLowerCase());if(void 0!==a)throw new E(`Attribute option "${a}" of "${e}" is not accepted`);if(!/^(?:[A-Za-z][A-Za-z0-9-]*|\d+(?:\.\d+)+)$/.test(n))throw new E(`"${e}" is not an attribute description`);const r=t?.getAttributeType(n),o=t?void 0:p(n);if(!t&&!o&&/^\d+(\.\d+)+$/.test(n))throw new E(`Attribute "${e}" cannot be named by its OID here`);const c=(r?[...r.names,r.oid]:[n,...o?[o]:[]]).map(e=>this.spellings.get(e.toLowerCase())).find(Boolean)??r?.names[0]??o??n;return i.length?`${c};binary`:c}filterForCaller(e,t,s){if(!this.parent.hooks.ldapsearchfilter)return e;const i=this.parent.hooks.ldapsearchfilter,one=async e=>{const[n]=await l(i,[e,t,s]);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,s){s??(s=v());let i={...q,...e};i=await l(this.parent.hooks.ldapsearchopts,i),[t,i]=await l(this.parent.hooks.ldapsearchrequest,[t,i,s]);const n=this.cacheEnabled&&!i.paged&&"base"===i.scope,a=n?this.getCacheKey(t,i):"",r=this.cacheGeneration;if(n){const e=this.searchCache.get(a);if(j.inc({result:e?"hit":"miss"}),e)return this.logger.debug(`LDAP search cache hit: ${digestKey(a)}`),this.filterForCaller(cloneSearchResult(e),s,i)}const o=await this.acquireConnection();try{let e=i.paged?async function*(e){for await(const t of e)yield dropSelectors(t)}(o.client.searchPaginated(t,i)):$("search",()=>o.client.search(t,i)).then(dropSelectors);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,s,i)}return i.paged?this.filterForCaller(this.wrapPaginatedSearch(e,o),s,i):this.filterForCaller(e,s,i)}finally{i.paged||this.releaseConnection(o)}}async*wrapPaginatedSearch(e,t){let s=0,i="ok";try{for(;;){const t=process.hrtime.bigint();let n;try{n=await e.next()}catch(e){throw i=S(e),e}finally{s+=L(t)}if(n.done)return;yield n.value}}finally{try{await e.return(void 0)}catch{}finally{P.observe({operation:"search",result:i},s),this.releaseConnection(t)}}}async add(e,t,i){i??(i=v()),e=this.setDn(e),await this.canonicalAttributes(t),t.objectClass&&0!==t.objectClass.length||!this.config.user_class||(t.objectClass=this.config.user_class);const n={};for(const[e,s]of Object.entries(t))Buffer.isBuffer(s)?n[e]=s.toString():Array.isArray(s)&&s.length>0&&Buffer.isBuffer(s[0])?n[e]=s.map(e=>e.toString()):n[e]=s;[e,t]=await l(this.parent.hooks.ldapaddrequest,[e,n,i]);const a=membersNamed(t),r=[];for(const[e,i]of Object.entries(t)){assertHasValue(e,i);const t=Array.isArray(i)?i:[i];r.push(new s({type:e,values:t}))}const o=await this.acquireConnection();try{this.invalidateCache(e);for(const e of a)this.invalidateCache(e);await $("add",()=>o.client.add(e,r)),this.invalidateCache(e);for(const e of a)this.invalidateCache(e);f(this.parent.hooks.ldapadddone,[e,t],w(i))}catch(t){throw function(e,t){const s=R(t);return 68===s?withCode(new O(`Entry ${e} already exists`),s):32===s?withCode(new x(`${y(e)} does not exist`),s):20===s?ldapRefusal(`The directory refused this write to ${e}`,s,t):ldapWriteError("LDAP add error",e,t)}(e,t)}finally{this.releaseConnection(o)}return await f(this.parent.hooks.ldapaddafter,[e,t],w(i)),!0}async modify(e,t,s,i={}){s??(s=v());const n=this.opNumber(),a=[];try{return await m(n,()=>this.applyModify(e,t,s,i,n,a))}finally{this.endOperation(a,this.parent.hooks.ldapmodifyend,n)}}async applyModify(e,t,a,r,o,c){e=this.setDn(e);const h=[];if(await this.canonicalChanges(t),[e,t]=await l(this.parent.hooks.ldapmodifyrequest,[e,t,o,a]),t.add)for(const[e,n]of Object.entries(t.add))assertHasValue(e,n),h.push(new i({operation:"add",modification:new s({type:e,values:Array.isArray(n)?n:[n]})}));if(t.replace)for(const[e,n]of Object.entries(t.replace))h.push(new i({operation:"replace",modification:new s({type:e,values:Array.isArray(n)?n:[n]})}));if(t.delete)if(Array.isArray(t.delete))for(const e of t.delete)h.push(new i({operation:"delete",modification:new s({type:e,values:[]})}));else for(const[e,n]of Object.entries(t.delete)){const t=new i({operation:"delete",modification:new s(n?{type:e,values:Array.isArray(n)?n:[n]}:{type:e})});h.push(t)}const d=[t.add,t.replace,t.delete].flatMap(e=>Array.isArray(e)?e:Object.keys(e??{})).some(isMembershipAttribute),invalidate=()=>{if(this.invalidateCache(e),d){this.invalidateReferences(e);for(const e of[t.add,t.replace,t.delete])for(const t of membersNamed(Array.isArray(e)?{}:e))this.invalidateCache(t)}};if(0===h.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(f(this.parent.hooks.ldapmodifydone,[e,{},o],r.context??w(a))),!1}{const l=r.permissive?[new n("1.2.840.113556.1.4.1413",{critical:!1})]:void 0,send=async()=>{const t=await this.acquireConnection();try{await $("modify",()=>t.client.modify(e,h,l))}finally{this.releaseConnection(t)}};try{invalidate();try{await send()}catch(n){if(65!==R(n))throw n;const a=await this.classRepair(e,t,r.declaredClasses);if("allowed"!==a&&0===a.length)throw n;const o="allowed"===a?void 0:new i({operation:"add",modification:new s({type:"objectClass",values:a})});o&&h.push(o),this.invalidateCache(e);try{await send()}catch(e){if(!o||20!==R(e))throw e;h.splice(h.indexOf(o),1),await send()}}return invalidate(),c.push(f(this.parent.hooks.ldapmodifydone,[e,t,o],r.context??w(a))),!0}catch(t){throw this.logger.warn(`Changes that failed: ${e}, ${JSON.stringify(h)}`),function(e,t){const s=R(t);return 32===s?withCode(new x(`Entry ${e} not found`),s):16===s?withCode(new x(`Entry ${e} does not hold the value to delete`),s):65!==s&&20!==s?ldapWriteError("LDAP modify error",e,t):withCode(new O(`Entry ${e} cannot hold this change: ${String(t)}`),s)}(e,t)}}}async rename(e,t,s){s??(s=v()),e=this.setDn(e),t=this.setDn(t);const i=u(e),n=u(t);if(1!==n.length||!i.includes(n[0]))throw new E(`${t}: an entry is renamed by one value of the attribute it is named by`);const a=this.opNumber(),r=[];try{return await m(a,()=>this.applyRename(e,t,s,r))}finally{this.endOperation(r,this.parent.hooks.ldaprenameend,a)}}async applyRename(e,t,s,i){[e,t]=await l(this.parent.hooks.ldaprenamerequest,[e,t,s]);const n=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),await $("rename",()=>n.client.modifyDN(e,t)),this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),i.push(f(this.parent.hooks.ldaprenamedone,[e,t],w(s))),!0}catch(s){throw ldapRenameError("LDAP rename error",e,t,s)}finally{this.releaseConnection(n)}}async move(e,t,s){s??(s=v()),e=this.setDn(e),t=this.setDn(t);const i=await this.acquireConnection();try{return this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),await $("rename",()=>i.client.modifyDN(e,t)),this.invalidateCache(e),this.invalidateCache(t),this.invalidateReferences(e),b(()=>f(this.parent.hooks.ldaprenamedone,[e,t],w(s))),this.logger.debug(`LDAP move: ${e} -> ${t}`),!0}catch(s){throw ldapRenameError("LDAP move error",e,t,s)}finally{this.releaseConnection(i)}}async delete(e,t,s={}){t??(t=v());const i=(Array.isArray(e)?e:[e]).map(e=>this.setDn(e)),n=this.opNumber(),a=[];try{return await m(n,()=>this.applyDelete(i,t,s,a))}finally{this.endOperation(a,this.parent?.hooks.ldapdeleteend,n)}}async applyDelete(e,t,s,i){[e]=await l(this.parent?.hooks.ldapdeleterequest,[e,t]);const n=await this.acquireConnection();try{for(const a of e){try{this.invalidateCache(a),this.invalidateReferences(a),await $("delete",()=>n.client.del(a)),this.invalidateCache(a),this.invalidateReferences(a)}catch(e){const t=R(e);throw 32===t?withCode(new x(`Entry ${a} not found`),t):ldapError("LDAP delete error",e)}i.push(f(this.parent.hooks.ldapdeletedone,a,s.context??w(t)))}return!0}finally{this.releaseConnection(n)}}setDn(e){return/=/.test(e)?/,/.test(e)||(e+=`,${this.base}`):e=`${this.config.ldap_user_main_attribute}=${g(e)},${this.base}`,e}endOperation(e,t,...s){t?.length&&Promise.all(e).then(()=>f(t,...s))}opNumber(){return this.parent.operationSequence++}}export{assertModifyRequest,ldapActions as default};
|