ldap-rest 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/authz/base.js +1 -1
- package/dist/lib/errors.js +1 -1
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/authzScope.js +1 -1
- package/dist/plugins/ldap/bulkImport.js +1 -1
- package/dist/plugins/ldap/enterpriseRules.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/passwordPolicy.js +1 -1
- package/dist/src/config/args.d.ts +6 -0
- package/dist/src/config/schema.d.ts +5 -3
- package/dist/src/lib/authz/base.d.ts +39 -0
- package/dist/src/lib/errors.d.ts +35 -2
- package/dist/src/lib/utils.d.ts +12 -0
- package/dist/src/plugins/auth/authzScope.d.ts +19 -0
- package/dist/src/plugins/ldap/bulkImport.d.ts +10 -0
- package/dist/src/plugins/ldap/enterpriseRules.d.ts +25 -7
- package/dist/src/plugins/ldap/groups.d.ts +29 -5
- package/dist/src/plugins/ldap/passwordPolicy.d.ts +13 -1
- package/docs/client-development/api/rest-api.md +52 -0
- package/docs/usage/configuration.md +7 -6
- package/docs/usage/plugins/auth/authz-linid1.md +36 -1
- package/docs/usage/plugins/ldap/bulk-import.md +15 -0
- package/docs/usage/plugins/ldap/enterprise-rules.md +41 -11
- package/docs/usage/plugins/ldap/password-policy.md +13 -1
- package/docs/usage/plugins/ldap/trash.md +9 -0
- package/docs/usage/upgrading.md +37 -0
- package/package.json +1 -1
- package/static/schemas/example/groups.json +2 -1
- package/static/schemas/example/organizations.json +2 -0
- package/static/schemas/standard/devices.json +4 -2
- package/static/schemas/twake/groups.json +2 -1
- package/static/schemas/twake/organizations.json +2 -0
- package/static/schemas/twake/teamMailboxes.json +2 -1
- package/static/schemas/twake/users.json +1 -0
package/dist/bin/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as e}from"url";import{join as t,dirname as r}from"path";import i from"express";import s from"body-parser";import{parseConfig as o}from"../lib/parseConfig.js";import{keepRawBody as n}from"../lib/rawBody.js";import a from"../config/args.js";import h from"../lib/ldapActions.js";import{buildLogger as l}from"../logger/winston.js";import{setLogger as
|
|
1
|
+
import{fileURLToPath as e}from"url";import{join as t,dirname as r}from"path";import i from"express";import s from"body-parser";import{parseConfig as o}from"../lib/parseConfig.js";import{keepRawBody as n}from"../lib/rawBody.js";import a from"../config/args.js";import h from"../lib/ldapActions.js";import{buildLogger as l}from"../logger/winston.js";import{setLogger as d}from"../lib/expressFormatedResponses.js";import{prefixCoversPath as u}from"../lib/auth/base.js";import{assertAuthzComposition as g}from"../lib/authz/composition.js";import{errorBody as p,AUTHZ_REFUSED as c}from"../lib/errors.js";export{BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UriTooLongError}from"../lib/errors.js";import{recordHookOwner as f}from"../lib/utils.js";export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDummyMemberDn,launchHooks,launchHooksChained,movedToTrash,normalizeDn,organizationLink,parseDn,rdnValue,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue}from"../lib/utils.js";import m from"../plugins/priority.json.js";class DM{constructor(){this.hooks={},this.loadedPlugins={},this.authenticators=[],this._authState={dispatcherMounted:!1},this._errorMiddlewareSetup=!1,this.bodyParsers=i.Router(),this._composed=!1,this.pluginLoads=new Map,this.config=o(a),this.app=i(),this.app.use(this.bodyParsers),this.app.use(s.json({verify:n})),this.app.use(s.urlencoded({extended:!0,verify:n})),this.logger=l(this.config),this.ldap=new h(this),d(this.logger),this.operationSequence=0;const e=[];if(this.config.plugin){const t=this.config.plugin.find(e=>e.includes("configApi"));let r=this.config.plugin.filter(e=>!e.includes("configApi"));const i=this.logger,s=(async()=>{for(const e of m){const t=this.resolveModule(DM.moduleOf(e)),s=r.filter(e=>this.resolveModule(DM.moduleOf(e))===t);if(0!==s.length){r=r.filter(e=>!s.includes(e));for(const e of s)i.debug(`Loading ${e} in the priority pass`),await this.loadPlugin(e)}}})();e.push(s),e.push(s.then(async()=>{const e=r.map(e=>(i.debug(`Loading ${e} in the parallel batch`),this.loadPlugin(e)));await Promise.all(e)})),t&&e.push(Promise.all(e).then(()=>this.loadPlugin(t)))}this.ready=new Promise((t,r)=>{e.length>0?Promise.all(e).then(async()=>{this.setupErrorMiddleware(),this.warnUnauthenticatedRoutes(),await g(this),this._composed=!0,this.afterLoad(),t()}).catch(e=>r(new Error("Error loading plugins: "+e))):(this.setupErrorMiddleware(),this._composed=!0,t())})}routerStack(){const e=this.app;return(e.router??e._router)?.stack}withConfig(e){return Object.assign(Object.create(Object.getPrototypeOf(this)),this,{config:e})}mountAuthDispatcher(e){const t=e.roles||[];t.includes("protect")||t.includes("logging")||this.mountAuthDispatcherNow()}mountAuthDispatcherNow(){this._authState.dispatcherMounted||(this._authState.dispatcherMounted=!0,this.app.use((e,t,r)=>{this.dispatchAuth(e,t,r)}))}registerAuthenticator(e){this.mountAuthDispatcherNow(),this.authenticators.includes(e)||this.authenticators.push(e)}registerBodyParser(e,t){this.bodyParsers.use(e,t)}dispatchAuth(e,t,r){let i=-1,s=[];for(const t of this.authenticators)for(const r of t.pathPrefixes)u(r,e.path)&&(r.length>i?(i=r.length,s=[t]):r.length!==i||s.includes(t)||s.push(t));if(i<0&&(s=this.authenticators.filter(e=>0===e.pathPrefixes.length)),0===s.length)return r();const finish=i=>{if(i>=s.length)return r();const o=s[i];if(!o.afterChain)return finish(i+1);o.afterChain(e,t,()=>finish(i+1))},o=[];e.authenticators=o;const run=r=>{if(r>=s.length)return finish(0);const i=s[r];i.authenticate(e,t,()=>{i.vouchedFor(e)&&o.push(i.name),run(r+1)})};run(0)}afterLoad(){for(const e of Object.values(this.loadedPlugins))try{e.afterLoad?.()}catch(t){this.logger.error(`Plugin ${e.name}: afterLoad failed: ${String(t)}`)}}warnUnauthenticatedRoutes(){const e=Object.values(this.loadedPlugins).filter(e=>e.roles?.includes("auth"));if(0===e.length)return[];const t=[];for(const r of e){if(!r.pathPrefixes||0===r.pathPrefixes.length)return[];t.push(...r.pathPrefixes)}const r=this.routerStack(),i=new Set;for(const e of r||[]){const t=e.route?.path;"string"==typeof t?i.add(t):Array.isArray(t)&&t.forEach(e=>"string"==typeof e&&i.add(e))}const s=[...i].filter(e=>!t.some(t=>e===t||e.startsWith(`${t}/`)));return s.length>0&&this.logger.warn(`Authentication is restricted to ${t.join(", ")}, so these routes are served without authentication: ${s.sort().join(", ")}`),s.sort()}setupErrorMiddleware(){if(this._errorMiddlewareSetup&&this._errorMiddleware){const e=this.routerStack();if(e){const t=e.findIndex(e=>e.handle===this._errorMiddleware);-1!==t&&e.splice(t,1)}}this._errorMiddleware=(e,t,r,i)=>{let s="statusCode"in e?e.statusCode:500,o=e.message;if(500===s&&/\[authz-forbidden\]/.test(e.message)?(s=403,o=c):403===s&&/\[authz-forbidden\]/.test(e.message)&&(o=c),s>=400&&s<500)return this.logger.warn(`Client error ${s} in request ${t.method} ${t.path}: ${o}`),void(r.headersSent||r.status(s).json(p(e,o)));this.logger.error(`Server error ${s} in request ${t.method} ${t.path}: ${e.message}`,{stack:e.stack,url:t.url,method:t.method}),r.headersSent||r.status(s).json({error:"Internal Server Error",message:this.config.debug?e.message:"An error occurred"})},this.app.use(this._errorMiddleware),this._errorMiddlewareSetup=!0}run(){return process.on("uncaughtException",e=>{this.logger.error(`Uncaught exception: ${e.message}`,{stack:e.stack})}),process.on("unhandledRejection",(e,t)=>{this.logger.error(`Unhandled promise rejection: ${e}`,{reason:e,promise:t})}),new Promise((e,t)=>{this.server=this.app.listen(this.config.port,r=>{r?(this.logger.error(`Error starting server: ${r}`),t(r)):(this.logger.debug(`Server started on port ${this.config.port}`),e())})})}stop(){this.app.removeAllListeners(),this.server?.close(),this.logger.debug("Server stopped")}static moduleOf(e){const t=e.indexOf(":");return-1===t?e:e.substring(0,t)}resolveModule(i){return i.startsWith("core/")?i.replace("core/",t(r(e(import.meta.url)),"..","plugins")+"/").replace(/$/,".js"):i.startsWith(".")?e(new URL(i,import.meta.url)):i}loadPlugin(e){let t,r;if(/:/.test(e)){let i=e.substring(e.indexOf(":")+1);if(e=e.substring(0,e.indexOf(":")),/:/.test(i)){t=i.substring(0,i.indexOf(":")),t||(t=void 0),i=i.substring(i.indexOf(":")+1);try{r=JSON.parse(i),"object"!=typeof r?(this.logger.error(`Overrides for plugin ${e} are not valid: ${i}`),r=void 0):this.logger.debug(`Overrides for plugin ${t||e}: ${i}`)}catch(t){this.logger.error(`Failed to parse overrides for plugin ${e}: ${t}, using ${i}`),r=void 0}}else t=i,t||(t=void 0)}else t=void 0;const i=r?void 0:`${this.resolveModule(e)}\0${t??""}`,s=void 0===i?void 0:this.pluginLoads.get(i);if(s)return this.logger.debug(`Plugin ${e} already loaded, skipping`),s;const o=this.importPlugin(e,t,r);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,r){return this.logger.debug(`Loading plugin ${e}`),e.startsWith("core/")&&(e=this.resolveModule(e)),new Promise((i,s)=>{import(e).then(async o=>{let n;if(o&&o.default&&(o=o.default),r){const e={...this.config,...r};n=new o(this.withConfig(e))}else n=new o(this);if(!n)return s(new Error(`Unable to load ${e}`));i(await this.registerPlugin(e,n,t)),this.logger.debug(`Plugin ${n.name} loaded`)}).catch(t=>s(new Error(`Failed to load plugin ${e}: ${t}`)))})}async admitToComposition(e){this.loadedPlugins[e.name]=e;try{await g(this,e)}finally{delete this.loadedPlugins[e.name]}}async registerPlugin(e,t,r){if(t.name||(t.name=e),r&&(t.name=r),this.loadedPlugins[t.name])return this.logger.warn(`Plugin ${e} not registered: the name "${t.name}" is already taken. This instance is dropped and its routes will not exist. To load the same plugin twice, give each one a name: --plugin '${e}:${t.name}2:{…}'`),!1;if(this.logger.debug(`Registering plugin ${e} as ${t.name}`),t.dependencies)for(const e in t.dependencies)this.loadedPlugins[e]||(this.logger.debug(`Plugin ${t.name} depends on ${e}, loading it first`),await this.loadPlugin(t.dependencies[e]));if(this._composed&&await this.admitToComposition(t),t.api&&(this.mountAuthDispatcher(t),this.logger.debug(`Plugin ${t.name} has API, registering it`),await t.api(this.app),this._errorMiddlewareSetup&&this.setupErrorMiddleware()),t.hooks)for(const e in t.hooks){this.logger.debug(`Plugin ${t.name} has hook ${e}, registering it`);const r=t.hooks[e];if(this.hooks[e]||(this.hooks[e]=[]),!r||"function"!=typeof r)throw new Error(`Plugin ${t.name}: hook ${e} is invalid`);this.hooks[e].push(r),f(r,t.name,e)}return this.loadedPlugins[t.name]=t,!0}}export{DM,f as recordHookOwner};
|
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=[["--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"],["--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"],["--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-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 default};
|
|
1
|
+
import{fileURLToPath as e}from"url";import{join as _,dirname as a}from"path";const t=[["--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"],["--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"],["--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 default};
|
package/dist/lib/authz/base.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"../../abstract/plugin.js";import{roleAttribute as e}from"../../config/schema.js";import{assertIdentityMode as r,identityFor as s}from"../auth/base.js";import{AmbiguousIdentityError as i,ForbiddenError as n}from"../errors.js";import{isDnInBranch as a,organizationLink as o,getParentDn as h,normalizeDn as c,escapeLdapFilter as u}from"../utils.js";import{authzFor as l,servesRequest as d}from"./composition.js";class AuthzBase extends t{constructor(t){super(t),this.roles=["authz"],this.resolutionCache=new Map,this.resolutionCacheFullWarned=!1,this.fallbackWarned=!1,this.hooks={ldaprenamedone:()=>{this.resolutionCache.clear()},ldapdeletedone:()=>{this.resolutionCache.clear()},ldapmodifyrequest:async([t,e,r,s])=>{if(this.shouldSkipAuthorization(s))return[t,e,r,s];const i=await this.resolveCaller(s);if(!i)return[t,e,r,s];const n=this.config.ldap_organization_link_attribute;if(n&&e.replace?.[n]){let r,a=this.extractBranchDn(t),h=!1;try{const e=(await this.server.ldap.search({paged:!1,scope:"base",attributes:[n]},t)).searchEntries[0];e&&(h=!0,r=o(e[n]),r&&(a=r))}catch{}const c=e.replace[n],u=Array.isArray(c)?String(c[0]):String(c),l=this.filtersAttachedEntries(),d=l&&h&&this.inTransit(r),f=l&&this.inTransit(u);if(!d){const t=f?"write":this.sourcePermission(a);if(!(await this.getUserPermissions(i,a))[t])throw new Error(`[authz-forbidden] User ${s.user} does not have ${t} permission for source branch ${a}`)}if(f){if(d){if(0===(await this.getAuthorizedBranches(i)).length)throw new Error(`[authz-forbidden] User ${s.user} administers no branch`)}}else{if(!(await this.getUserPermissions(i,u)).write)throw new Error(`[authz-forbidden] User ${s.user} does not have write permission for destination branch ${u}`)}}else{const e=await this.effectiveBranch(t);if(!(await this.getUserPermissions(i,e)).write)throw new Error(`[authz-forbidden] User ${s.user} does not have write permission for branch ${e}`)}const a=this.domainLinkAttribute();return a&&await this.guardDomainLinks(i,s,t,[...AuthzBase.valuesOf(e.add,a),...AuthzBase.valuesOf(e.replace,a)],a,!1),[t,e,r,s]},ldapaddrequest:async([t,e,r])=>{if(this.shouldSkipAuthorization(r))return[t,e,r];const s=await this.resolveCaller(r);if(!s)return[t,e,r];let i;const n=this.config.ldap_organization_link_attribute;if(n&&e[n]){const t=e[n];i=Array.isArray(t)?String(t[0]):String(t)}else i=this.extractBranchDn(t);if(!(await this.getUserPermissions(s,i)).write)throw new Error(`[authz-forbidden] User ${r.user} does not have write permission for branch ${i}`);const a=this.domainLinkAttribute();return a&&await this.guardDomainLinks(s,r,t,AuthzBase.valuesOf(e,a),a,!0),[t,e,r]},ldapsearchrequest:async([t,e,r])=>{if(this.shouldSkipAuthorization(r))return[t,e,r];const s=await this.resolveCaller(r);if(!s)return[t,e,r];if(t===this.config.ldap_top_organization&&"base"===e.scope)return[t,e,r];if(this.filtersAttachedEntries()){if(0===(await this.getAuthorizedBranches(s)).length)throw new Error(`[authz-forbidden] User ${r.user} administers no branch`);if(this.globalBranches().some(e=>a(t,e))&&!(await this.getUserPermissions(s,t)).read)throw new Error(`[authz-forbidden] User ${r.user} does not have read permission for branch ${t}`);const i=this.config.ldap_organization_link_attribute;if(i&&!AuthzBase.wantsAttribute(e,i)){const t=e.attributes,r=Array.isArray(t)?t.map(String):[String(t)];e={...e,attributes:[...r,i]},AuthzBase.widenedForLink.add(e)}return[t,e,r]}if(!(await this.getUserPermissions(s,t)).read)throw new Error(`[authz-forbidden] User ${r.user} does not have read permission for branch ${t}`);return[t,e,r]},ldapsearchfilter:async([t,e,r])=>{const s=[t,e,r];if(!this.filtersAttachedEntries())return s;if(this.shouldSkipAuthorization(e))return s;if(!t?.searchEntries?.length)return s;const i=await this.resolveCaller(e);if(!i)return s;const n=this.globalBranches();if(n.length){const r=new Map,s=[];for(const o of t.searchEntries){const t=n.find(t=>a(o.dn,t));void 0===t||(r.has(t)||r.set(t,!0===(await this.getUserPermissions(i,t)).read),r.get(t))?s.push(o):this.logger.debug(`${this.name}: hiding ${String(o.dn)} from ${e.user}, in the global branch ${t}`)}t.searchEntries=s}const o=this.config.ldap_organization_link_attribute;if(!o)return s;const h=await this.getAuthorizedBranches(i);if(0===h.length)return s;const within=t=>h.some(e=>a(t,e)),c=this.transitKey();if(t.searchEntries=t.searchEntries.filter(t=>{const r=t[o];if(null==r)return!0;const s=(Array.isArray(r)?r:[r]).map(String);return 0===s.length||(!!s.some(within)||(!!s.some(t=>this.inTransit(t,c))||(this.logger.debug(`${this.name}: hiding ${String(t.dn)} from ${e.user}, attached to ${s[0]}`),!1)))}),r&&AuthzBase.widenedForLink.has(r))for(const e of t.searchEntries)delete e[o];return[t,e,r]},getOrganisationTop:async([t,e])=>{if(this.shouldSkipAuthorization(t))return[t,e];const r=await this.resolveCaller(t);if(!r)return[t,e];const s=await this.getAuthorizedBranches(r);if(s.length>0){const e=[];for(const r of s)try{const s=await this.server.ldap.search({paged:!1,scope:"base"},r,t);1===s.searchEntries.length&&e.push(s.searchEntries[0])}catch(t){this.logger.warn(`Failed to fetch authorized branch ${r}: ${t}`)}if(1===e.length)return[t,e[0]];if(e.length>1)return[t,e[0]]}return[t,e]},ldaprenamerequest:async([t,e,r])=>{if(this.shouldSkipAuthorization(r))return[t,e,r];const s=await this.resolveCaller(r);if(!s)return[t,e,r];const i=this.extractBranchDn(t),n=this.extractBranchDn(e),a=await this.getUserPermissions(s,i),o=this.sourcePermission(i);if(!a[o])throw new Error(`[authz-forbidden] User ${r.user} does not have ${o} permission for source branch ${i}`);if(!(await this.getUserPermissions(s,n)).write)throw new Error(`[authz-forbidden] User ${r.user} does not have write permission for destination branch ${n}`);return[t,e,r]},ldapdeleterequest:async([t,e])=>{if(this.shouldSkipAuthorization(e))return[t,e];const r=await this.resolveCaller(e);if(!r)return[t,e];const s=Array.isArray(t)?t:[t];for(const t of s){const s=await this.effectiveBranch(t);if(!(await this.getUserPermissions(r,s)).delete)throw new Error(`[authz-forbidden] User ${e.user} does not have delete permission for branch ${s}`)}return[t,e]}},r(this.config,this.constructor.name),l(this.config,this.constructor.name);const e=this.config.authz_unresolved_user??"deny";if(!AuthzBase.UNRESOLVED_POLICIES.includes(e))throw new Error(`${this.constructor.name}: unknown --authz-unresolved-user "${e}". Known: ${AuthzBase.UNRESOLVED_POLICIES.join(", ")}.`);this.config.authz_transit_branch&&!this.filtersAttachedEntries()&&this.logger.warn(`${this.constructor.name}: --authz-transit-branch is ignored, entries are not judged by their attachment here (--authz-filter-attached-entries)`)}static wantsAttribute(t,e){const r=t?.attributes;if(void 0===r)return!0;const s=(Array.isArray(r)?r:[r]).map(t=>String(t).toLowerCase());return 0===s.length||(!!s.includes("*")||s.includes(e.toLowerCase()))}extractBranchDn(t){return h(t)}filtersAttachedEntries(){return Boolean(this.config.authz_filter_attached_entries)}globalBranches(){return[]}sourcePermission(t){return"read"}async effectiveBranch(t){const e=this.config.ldap_organization_link_attribute;if(!e||!this.filtersAttachedEntries())return this.extractBranchDn(t);try{const r=await this.server.ldap.search({paged:!1,scope:"base",attributes:[e]},t),s=o(r.searchEntries?.[0]?.[e]);if(s)return s}catch{}return this.extractBranchDn(t)}transitBranch(){if(!this.filtersAttachedEntries())return;const t=String(this.config.authz_transit_branch??"").trim();return t.length>0?t:void 0}inTransit(t,e=this.transitKey()){return void 0===t||void 0!==e&&c(t)===e}transitKey(){const t=this.transitBranch();return void 0===t?void 0:c(t)}async findUserDn(t){const e=`(${this.config.ldap_user_main_attribute||"uid"}=${u(t)})`,r=(await this.server.ldap.search({paged:!1,filter:e,attributes:["dn"],scope:"sub"},this.server.ldap.base)).searchEntries??[];if(r.length>1)throw new i(t,r.length);if(0===r.length)return null;const s=r[0].dn;return"string"==typeof s?s:String(s)}shouldSkipAuthorization(t){return!t?.user||!d(t,this.config)}async resolveCaller(t){const{value:e,fellBack:r}=s(t,this.config);r&&!this.fallbackWarned&&(this.fallbackWarned=!0,this.logger.warn(`${this.name}: --authz-identity asks for req.userName and the authenticator published none, so permissions are read for req.user instead. Rules written on logins will not match`));const i=e,a=Date.now(),o=this.resolutionCache.get(i);let h;if(o&&a-o.at<this.cacheTTL)h=o.user;else{if(h=await this.resolveUser(i),this.resolutionCache.size>=AuthzBase.RESOLUTION_CACHE_MAX)for(const[t,e]of this.resolutionCache)a-e.at>=this.cacheTTL&&this.resolutionCache.delete(t);this.resolutionCache.size<AuthzBase.RESOLUTION_CACHE_MAX?this.resolutionCache.set(i,{user:h,at:a}):this.resolutionCacheFullWarned||(this.resolutionCacheFullWarned=!0,this.logger.warn(`${this.name}: more than ${AuthzBase.RESOLUTION_CACHE_MAX} identities resolved within one cache window, so resolutions are no longer cached and each request costs a lookup`))}if(h)return h;if("allow"===this.config.authz_unresolved_user)return this.logger.warn(`User ${i} could not be resolved by ${this.name}; allowed by --authz-unresolved-user allow`),null;throw new n(`[authz-forbidden] User ${i} could not be resolved by ${this.name}, so no permission can be read for them`)}domainLinkAttribute(){const t=this.server.loadedPlugins.ldapOrganizations;return e(t?.schema,"domainLink")||this.config.enterprise_domain_link_attribute||void 0}static valuesOf(t,e){if(!t)return[];const r=e.toLowerCase(),s=[];for(const[e,i]of Object.entries(t))if(e.split(";")[0].toLowerCase()===r&&null!=i)for(const t of Array.isArray(i)?i:[i])s.push(String(t));return s}async storedValues(t,e){try{const r=await this.server.ldap.search({paged:!1,scope:"base",attributes:[e]},t);return AuthzBase.valuesOf(r.searchEntries?.[0],e)}catch{return[]}}async guardDomainLinks(t,e,r,s,i,n){if(0===s.length)return;const o=new Set((n?[]:await this.storedValues(r,i)).map(c)),u=s.filter(t=>!o.has(c(t)));if(0===u.length)return;const l=this.config.ldap_top_organization,d=c(l||this.server.ldap.base),f=new Set;let g=h(r);const w=new Set;for(;g&&!w.has(c(g));){const t=c(g);if(w.add(t),t!==d&&!a(g,d))break;for(const t of await this.storedValues(g,i))f.add(c(t));if(t===d)break;const e=h(g);if(e===g)break;g=e}const m=u.filter(t=>!f.has(c(t)));if(0!==m.length&&!await this.mayLinkAnyDomain(t))throw new Error(`[authz-forbidden] User ${e.user} may not link ${r} to domain ${m[0]}: no organization above it is linked to it`)}async mayLinkAnyDomain(t){const e=this.config.ldap_top_organization;return!!e&&!0===(await this.getUserPermissions(t,e)).write}clearResolutionCache(){this.resolutionCache.clear()}}AuthzBase.widenedForLink=new WeakSet,AuthzBase.UNRESOLVED_POLICIES=["deny","allow"],AuthzBase.RESOLUTION_CACHE_MAX=1e4;export{AuthzBase as default};
|
|
1
|
+
import t from"../../abstract/plugin.js";import{roleAttribute as e}from"../../config/schema.js";import{assertIdentityMode as r,identityFor as i}from"../auth/base.js";import{AmbiguousIdentityError as s,ForbiddenError as n}from"../errors.js";import{isDnInBranch as a,organizationLink as o,getParentDn as h,normalizeDn as c,escapeLdapFilter as u}from"../utils.js";import{authzFor as l,servesRequest as d}from"./composition.js";class AuthzBase extends t{constructor(t){super(t),this.roles=["authz"],this.resolutionCache=new Map,this.resolutionCacheFullWarned=!1,this.fallbackWarned=!1,this.hooks={ldaprenamedone:()=>{this.resolutionCache.clear()},ldapdeletedone:()=>{this.resolutionCache.clear()},ldapmodifyrequest:async([t,e,r,i])=>{if(this.shouldSkipAuthorization(i))return[t,await this.recordTransitOrigin(t,e),r,i];const s=await this.resolveCaller(i);if(!s)return[t,await this.recordTransitOrigin(t,e),r,i];const n=this.config.ldap_organization_link_attribute;if(n&&e.replace?.[n]){let r,a=this.extractBranchDn(t),h=!1;try{const e=(await this.server.ldap.search({paged:!1,scope:"base",attributes:[n]},t)).searchEntries[0];e&&(h=!0,r=o(e[n]),r&&(a=r))}catch{}const c=e.replace[n],u=Array.isArray(c)?String(c[0]):String(c),l=this.filtersAttachedEntries(),d=l&&h&&this.inTransit(r),f=l&&this.inTransit(u);if(!d){const t=f?"write":this.sourcePermission(a);if(!(await this.getUserPermissions(s,a))[t])throw new Error(`[authz-forbidden] User ${i.user} does not have ${t} permission for source branch ${a}`)}if(f){if(d){if(0===(await this.getAuthorizedBranches(s)).length)throw new Error(`[authz-forbidden] User ${i.user} administers no branch`)}}else{if(!(await this.getUserPermissions(s,u)).write)throw new Error(`[authz-forbidden] User ${i.user} does not have write permission for destination branch ${u}`)}}else{const e=await this.effectiveBranch(t);if(!(await this.getUserPermissions(s,e)).write)throw new Error(`[authz-forbidden] User ${i.user} does not have write permission for branch ${e}`)}const a=this.domainLinkAttribute();return a&&await this.guardDomainLinks(s,i,t,[...AuthzBase.valuesOf(e.add,a),...AuthzBase.valuesOf(e.replace,a)],a,!1),[t,await this.recordTransitOrigin(t,e),r,i]},ldapaddrequest:async([t,e,r])=>{if(this.shouldSkipAuthorization(r))return[t,e,r];const i=await this.resolveCaller(r);if(!i)return[t,e,r];let s;const n=this.config.ldap_organization_link_attribute;if(n&&e[n]){const t=e[n];s=Array.isArray(t)?String(t[0]):String(t)}else s=this.extractBranchDn(t);if(!(await this.getUserPermissions(i,s)).write)throw new Error(`[authz-forbidden] User ${r.user} does not have write permission for branch ${s}`);const a=this.domainLinkAttribute();return a&&await this.guardDomainLinks(i,r,t,AuthzBase.valuesOf(e,a),a,!0),[t,e,r]},ldapsearchrequest:async([t,e,r])=>{if(this.shouldSkipAuthorization(r))return[t,e,r];const i=await this.resolveCaller(r);if(!i)return[t,e,r];if(t===this.config.ldap_top_organization&&"base"===e.scope)return[t,e,r];const s=await this.searchRefusal(i,t);if(s)throw new Error(`[authz-forbidden] User ${r.user} ${s}`);if(this.filtersAttachedEntries()){const i=this.config.ldap_organization_link_attribute;if(i&&!AuthzBase.wantsAttribute(e,i)){const t=e.attributes,r=Array.isArray(t)?t.map(String):[String(t)];e={...e,attributes:[...r,i]},AuthzBase.widenedForLink.add(e)}return[t,e,r]}return[t,e,r]},ldapsearchfilter:async([t,e,r])=>{const i=[t,e,r];if(!this.filtersAttachedEntries())return i;if(this.shouldSkipAuthorization(e))return i;if(!t?.searchEntries?.length)return i;const s=await this.resolveCaller(e);if(!s)return i;const n=this.globalBranches();if(n.length){const r=new Map,i=[];for(const o of t.searchEntries){const t=n.find(t=>a(o.dn,t));void 0===t||(r.has(t)||r.set(t,!0===(await this.getUserPermissions(s,t)).read),r.get(t))?i.push(o):this.logger.debug(`${this.name}: hiding ${String(o.dn)} from ${e.user}, in the global branch ${t}`)}t.searchEntries=i}const o=this.config.ldap_organization_link_attribute;if(!o)return i;const h=await this.getAuthorizedBranches(s);if(0===h.length)return i;const within=t=>h.some(e=>a(t,e)),c=this.transitKey();if(t.searchEntries=t.searchEntries.filter(t=>{const r=t[o];if(null==r)return!0;const i=(Array.isArray(r)?r:[r]).map(String);return 0===i.length||(!!i.some(within)||(!!i.some(t=>this.inTransit(t,c))||(this.logger.debug(`${this.name}: hiding ${String(t.dn)} from ${e.user}, attached to ${i[0]}`),!1)))}),r&&AuthzBase.widenedForLink.has(r))for(const e of t.searchEntries)delete e[o];return[t,e,r]},getOrganisationTop:async([t,e])=>{if(this.shouldSkipAuthorization(t))return[t,e];const r=await this.resolveCaller(t);if(!r)return[t,e];const i=await this.getAuthorizedBranches(r);if(i.length>0){const e=[];for(const r of i)try{const i=await this.server.ldap.search({paged:!1,scope:"base"},r,t);1===i.searchEntries.length&&e.push(i.searchEntries[0])}catch(t){this.logger.warn(`Failed to fetch authorized branch ${r}: ${t}`)}if(1===e.length)return[t,e[0]];if(e.length>1)return[t,e[0]]}return[t,e]},ldaprenamerequest:async([t,e,r])=>{if(this.shouldSkipAuthorization(r))return[t,e,r];const i=await this.resolveCaller(r);if(!i)return[t,e,r];const s=this.extractBranchDn(t),n=this.extractBranchDn(e),a=await this.getUserPermissions(i,s),o=this.sourcePermission(s);if(!a[o])throw new Error(`[authz-forbidden] User ${r.user} does not have ${o} permission for source branch ${s}`);if(!(await this.getUserPermissions(i,n)).write)throw new Error(`[authz-forbidden] User ${r.user} does not have write permission for destination branch ${n}`);return[t,e,r]},ldapdeleterequest:async([t,e])=>{if(this.shouldSkipAuthorization(e))return[t,e];const r=await this.resolveCaller(e);if(!r)return[t,e];const i=Array.isArray(t)?t:[t];for(const t of i){const i=await this.effectiveBranch(t);if(!(await this.getUserPermissions(r,i)).delete)throw new Error(`[authz-forbidden] User ${e.user} does not have delete permission for branch ${i}`)}return[t,e]}},r(this.config,this.constructor.name),l(this.config,this.constructor.name);const e=this.config.authz_unresolved_user??"deny";if(!AuthzBase.UNRESOLVED_POLICIES.includes(e))throw new Error(`${this.constructor.name}: unknown --authz-unresolved-user "${e}". Known: ${AuthzBase.UNRESOLVED_POLICIES.join(", ")}.`);this.config.authz_transit_branch&&!this.filtersAttachedEntries()&&this.logger.warn(`${this.constructor.name}: --authz-transit-branch is ignored, entries are not judged by their attachment here (--authz-filter-attached-entries)`)}static wantsAttribute(t,e){const r=t?.attributes;if(void 0===r)return!0;const i=(Array.isArray(r)?r:[r]).map(t=>String(t).toLowerCase());return 0===i.length||(!!i.includes("*")||i.includes(e.toLowerCase()))}extractBranchDn(t){return h(t)}filtersAttachedEntries(){return Boolean(this.config.authz_filter_attached_entries)}globalBranches(){return[]}async searchRefusal(t,e){if(this.filtersAttachedEntries()){if(0===(await this.getAuthorizedBranches(t)).length)return"administers no branch";if(!this.globalBranches().some(t=>a(e,t)))return}return(await this.getUserPermissions(t,e)).read?void 0:`does not have read permission for branch ${e}`}sourcePermission(t){return"read"}async effectiveBranch(t){const e=this.config.ldap_organization_link_attribute;if(!e||!this.filtersAttachedEntries())return this.extractBranchDn(t);try{const r=await this.server.ldap.search({paged:!1,scope:"base",attributes:[e]},t),i=o(r.searchEntries?.[0]?.[e]);if(i)return i}catch{}return this.extractBranchDn(t)}transitBranch(){if(!this.filtersAttachedEntries())return;const t=String(this.config.authz_transit_branch??"").trim();return t.length>0?t:void 0}inTransit(t,e=this.transitKey()){return void 0===t||void 0!==e&&c(t)===e}transitOriginAttribute(){if(void 0===this.transitBranch())return;const t=String(this.config.authz_transit_origin_attribute??"").trim();return t.length>0?t:void 0}async recordTransitOrigin(t,e){const r=this.transitOriginAttribute(),i=this.config.ldap_organization_link_attribute;if(!r||!i)return e;const same=(t,e)=>t.split(";")[0].toLowerCase()===e.toLowerCase(),named=(t,e)=>void 0!==t&&Object.keys(t).some(t=>same(t,e)),deleted=t=>Array.isArray(e.delete)?e.delete.some(e=>same(e,t)):named(e.delete,t);let s,n;if(named(e.replace,i))s=o(AuthzBase.valuesOf(e.replace,i));else if(named(e.add,i))s=o(AuthzBase.valuesOf(e.add,i));else if(!deleted(i))return e;if(named(e.replace,r)||named(e.add,r)||deleted(r))return e;try{n=(await this.server.ldap.search({paged:!1,scope:"base",attributes:[i,r]},t)).searchEntries[0]}catch{}if(!n)return e;const a=o(AuthzBase.valuesOf(n,i)),h=this.inTransit(a),c=this.inTransit(s);return!h&&c&&a?{...e,replace:{...e.replace,[r]:a}}:h&&!c&&AuthzBase.valuesOf(n,r).length>0?{...e,delete:Array.isArray(e.delete)?[...e.delete,r]:{...e.delete,[r]:[]}}:e}transitKey(){const t=this.transitBranch();return void 0===t?void 0:c(t)}async findUserDn(t){const e=`(${this.config.ldap_user_main_attribute||"uid"}=${u(t)})`,r=(await this.server.ldap.search({paged:!1,filter:e,attributes:["dn"],scope:"sub"},this.server.ldap.base)).searchEntries??[];if(r.length>1)throw new s(t,r.length);if(0===r.length)return null;const i=r[0].dn;return"string"==typeof i?i:String(i)}shouldSkipAuthorization(t){return!t?.user||!d(t,this.config)}async resolveCaller(t){const{value:e,fellBack:r}=i(t,this.config);r&&!this.fallbackWarned&&(this.fallbackWarned=!0,this.logger.warn(`${this.name}: --authz-identity asks for req.userName and the authenticator published none, so permissions are read for req.user instead. Rules written on logins will not match`));const s=e,a=Date.now(),o=this.resolutionCache.get(s);let h;if(o&&a-o.at<this.cacheTTL)h=o.user;else{if(h=await this.resolveUser(s),this.resolutionCache.size>=AuthzBase.RESOLUTION_CACHE_MAX)for(const[t,e]of this.resolutionCache)a-e.at>=this.cacheTTL&&this.resolutionCache.delete(t);this.resolutionCache.size<AuthzBase.RESOLUTION_CACHE_MAX?this.resolutionCache.set(s,{user:h,at:a}):this.resolutionCacheFullWarned||(this.resolutionCacheFullWarned=!0,this.logger.warn(`${this.name}: more than ${AuthzBase.RESOLUTION_CACHE_MAX} identities resolved within one cache window, so resolutions are no longer cached and each request costs a lookup`))}if(h)return h;if("allow"===this.config.authz_unresolved_user)return this.logger.warn(`User ${s} could not be resolved by ${this.name}; allowed by --authz-unresolved-user allow`),null;throw new n(`[authz-forbidden] User ${s} could not be resolved by ${this.name}, so no permission can be read for them`)}domainLinkAttribute(){const t=this.server.loadedPlugins.ldapOrganizations;return e(t?.schema,"domainLink")||this.config.enterprise_domain_link_attribute||void 0}static valuesOf(t,e){if(!t)return[];const r=e.toLowerCase(),i=[];for(const[e,s]of Object.entries(t))if(e.split(";")[0].toLowerCase()===r&&null!=s)for(const t of Array.isArray(s)?s:[s])i.push(String(t));return i}async storedValues(t,e){try{const r=await this.server.ldap.search({paged:!1,scope:"base",attributes:[e]},t);return AuthzBase.valuesOf(r.searchEntries?.[0],e)}catch{return[]}}async guardDomainLinks(t,e,r,i,s,n){if(0===i.length)return;const o=new Set((n?[]:await this.storedValues(r,s)).map(c)),u=i.filter(t=>!o.has(c(t)));if(0===u.length)return;const l=this.config.ldap_top_organization,d=c(l||this.server.ldap.base),f=new Set;let g=h(r);const w=new Set;for(;g&&!w.has(c(g));){const t=c(g);if(w.add(t),t!==d&&!a(g,d))break;for(const t of await this.storedValues(g,s))f.add(c(t));if(t===d)break;const e=h(g);if(e===g)break;g=e}const p=u.filter(t=>!f.has(c(t)));if(0!==p.length&&!await this.mayLinkAnyDomain(t))throw new Error(`[authz-forbidden] User ${e.user} may not link ${r} to domain ${p[0]}: no organization above it is linked to it`)}async mayLinkAnyDomain(t){const e=this.config.ldap_top_organization;return!!e&&!0===(await this.getUserPermissions(t,e)).write}clearResolutionCache(){this.resolutionCache.clear()}}AuthzBase.widenedForLink=new WeakSet,AuthzBase.UNRESOLVED_POLICIES=["deny","allow"],AuthzBase.RESOLUTION_CACHE_MAX=1e4;export{AuthzBase as default};
|
package/dist/lib/errors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class HttpError extends Error{constructor(r,o=500){super(r),this.statusCode=o,this.name="HttpError"}}class BadRequestError extends HttpError{constructor(r="Bad request"){super(r,400),this.name="BadRequestError"}}class UnauthorizedError extends HttpError{constructor(r="Unauthorized"){super(r,401),this.name="UnauthorizedError"}}const r="Token does not have permission on this branch";class ForbiddenError extends HttpError{constructor(r="Forbidden"){super(r,403),this.name="ForbiddenError"}}class AmbiguousIdentityError extends ForbiddenError{constructor(r,o){super(`[authz-forbidden] ${r} names ${o} directory entries, so no single one can be taken for the caller`),this.identity=r,this.count=o,this.name="AmbiguousIdentityError"}}class NotFoundError extends HttpError{constructor(r="Not found"){super(r,404),this.name="NotFoundError"}}class ConflictError extends HttpError{constructor(r="Conflict"){super(r,409),this.name="ConflictError"}}class UriTooLongError extends HttpError{constructor(r="URI Too Long"){super(r,414),this.name="UriTooLongError"}}class UnprocessableEntityError extends HttpError{constructor(r="Unprocessable Entity"){super(r,422),this.name="UnprocessableEntityError"}}class TooManyRequestsError extends HttpError{constructor(r="Too Many Requests"){super(r,429),this.name="TooManyRequestsError"}}class BadGatewayError extends HttpError{constructor(r="Bad Gateway"){super(r,502),this.name="BadGatewayError"}}class ServiceUnavailableError extends HttpError{constructor(r="Service Unavailable"){super(r,503),this.name="ServiceUnavailableError"}}class GatewayTimeoutError extends HttpError{constructor(r="Gateway Timeout"){super(r,504),this.name="GatewayTimeoutError"}}export{r as AUTHZ_REFUSED,AmbiguousIdentityError,BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UnprocessableEntityError,UriTooLongError};
|
|
1
|
+
class HttpError extends Error{constructor(r,o=500,t){super(r),this.statusCode=o,this.refusal=t,this.name="HttpError"}}class BadRequestError extends HttpError{constructor(r="Bad request"){super(r,400),this.name="BadRequestError"}}class UnauthorizedError extends HttpError{constructor(r="Unauthorized"){super(r,401),this.name="UnauthorizedError"}}const r="Token does not have permission on this branch",errorBody=(o,t)=>{if(t===r)return{error:t,code:"forbidden"};const e=o instanceof HttpError?o.refusal:void 0;return e?{error:t,...e}:{error:t}};class ForbiddenError extends HttpError{constructor(r="Forbidden"){super(r,403),this.name="ForbiddenError"}}class AmbiguousIdentityError extends ForbiddenError{constructor(r,o){super(`[authz-forbidden] ${r} names ${o} directory entries, so no single one can be taken for the caller`),this.identity=r,this.count=o,this.name="AmbiguousIdentityError"}}class NotFoundError extends HttpError{constructor(r="Not found"){super(r,404),this.name="NotFoundError"}}class ConflictError extends HttpError{constructor(r="Conflict",o){super(r,409,o),this.name="ConflictError"}}class UriTooLongError extends HttpError{constructor(r="URI Too Long"){super(r,414),this.name="UriTooLongError"}}class UnprocessableEntityError extends HttpError{constructor(r="Unprocessable Entity"){super(r,422),this.name="UnprocessableEntityError"}}class TooManyRequestsError extends HttpError{constructor(r="Too Many Requests"){super(r,429),this.name="TooManyRequestsError"}}class BadGatewayError extends HttpError{constructor(r="Bad Gateway"){super(r,502),this.name="BadGatewayError"}}class ServiceUnavailableError extends HttpError{constructor(r="Service Unavailable"){super(r,503),this.name="ServiceUnavailableError"}}class GatewayTimeoutError extends HttpError{constructor(r="Gateway Timeout"){super(r,504),this.name="GatewayTimeoutError"}}export{r as AUTHZ_REFUSED,AmbiguousIdentityError,BadGatewayError,BadRequestError,ConflictError,ForbiddenError,GatewayTimeoutError,HttpError,NotFoundError,ServiceUnavailableError,TooManyRequestsError,UnauthorizedError,UnprocessableEntityError,UriTooLongError,errorBody};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AUTHZ_REFUSED as
|
|
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({error:"check logs"})},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,setLogger,tryMethod,tryMethodData,unauthorized,wantJson};
|
package/dist/lib/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BadRequestError as e}from"./errors.js";import{getLogger as n}from"./expressFormatedResponses.js";const r=new Map;function getCompiledRegex(e,n){const t=n?`${e}:${n}`:e;let i=r.get(t);return i||(i=new RegExp(e,n),r.set(t,i)),i}function escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const describeThrown=e=>{try{return JSON.stringify(e)??String(e)}catch{return String(e)}},hookLabel=e=>{const n=hookOwner(e);return n?` in ${n.plugin} (${n.hook})`:e.name?` in ${e.name}`:""},launchHooks=async(e,...r)=>{if(e)for(const t of e)if(t)try{await t(...r)}catch(e){const r=hookLabel(t);e instanceof Error?n()?.error(`Hook error${r}`,e):n()?.error(`Hook error${r}: ${describeThrown(e)}`)}},t=new WeakMap,recordHookOwner=(e,n,r)=>{t.set(e,{plugin:n,hook:r})},hookOwner=e=>t.get(e),isAuthzRefusal=e=>/\[authz-forbidden\]/.test(String(e?.message??"")),isRefusal=e=>403===e?.statusCode||isAuthzRefusal(e),launchHooksChained=async(e,r)=>{if(e)for(const t of e)if(t)try{r=await t(r)}catch(e){if(isRefusal(e)){const r=hookOwner(t);n()?.warn(`${r?`${r.plugin} refused ${r.hook}`:`a hook no plugin registered (${t.name||"anonymous"}) refused`}: ${e.message}`)}throw e}return r},transformSchemas=(e,n)=>e.toString().replace(/__(\S+)__/g,(e,r)=>{if(!r||"string"!=typeof r)return e;const t=r.trim().toLowerCase();return n[t]?"object"!=typeof n[t]?n[t]:JSON.stringify(n[t]):e});function escapeLdapFilter(e){return e.replace(/\\/g,"\\5c").replace(/\*/g,"\\2a").replace(/\(/g,"\\28").replace(/\)/g,"\\29").replace(/\0/g,"\\00")}function substringSearchFilter(n,r){const t=/^[a-zA-Z][a-zA-Z0-9-]*$/,i=r.split(",").map(e=>e.trim()).filter(Boolean);if(0===i.length||!i.every(e=>t.test(e)))throw new e("Invalid LDAP attribute name");const o=escapeLdapFilter(n),a=i.map(e=>`(${e}=*${o}*)`);return 1===a.length?a[0]:`(|${a.join("")})`}function escapeDnValue(e){return e.replace(/\\/g,"\\\\").replace(/,/g,"\\,").replace(/\+/g,"\\+").replace(/"/g,'\\"').replace(/</g,"\\<").replace(/>/g,"\\>").replace(/;/g,"\\;").replace(/=/g,"\\=").replace(/\0/g,"\\00").replace(/^\s/,"\\ ").replace(/\s$/,"\\ ").replace(/^#/,"\\#")}function unescapeDnValue(n){if("string"!=typeof n)throw new e("DN value must be a string");let r="",t=0;for(;t<n.length;)if("\\"===n[t]&&t+1<n.length){const e=n[t+1];if(/[0-9a-fA-F]/.test(e)&&t+2<n.length){const e=n.substring(t+1,t+3);if(/^[0-9a-fA-F]{2}$/.test(e)){r+=String.fromCharCode(parseInt(e,16)),t+=3;continue}}r+=e,t+=2}else r+=n[t],t++;return r}function validateDnValue(e,n){if(null==e||"string"!=typeof e)throw new Error(`${n} must be a string`);if(0===e.trim().length)throw new Error(`${n} must be a non-empty string`);if(/[\x00-\x1F\x7F]/.test(e))throw new Error(`${n} contains invalid control characters`);if(/[\u200B-\u200F\uFEFF]/.test(e))throw new Error(`${n} contains invalid invisible characters`)}function parseDn(n){if("string"!=typeof n)throw new e("DN must be a string");const r=[];let t="",i=!1;for(let e=0;e<n.length;e++){const o=n[e];i?(t+=o,i=!1):"\\"===o?(t+=o,i=!0):","===o?(r.push(t.trim()),t=""):t+=o}return t&&r.push(t.trim()),r}function getParentDn(e){const n=parseDn(e);return n.length<=1?e:n.slice(1).join(",")}function getRdn(e){return parseDn(e)[0]||""}function rdnValue(e){const n=/^[^=]+=((?:\\.|[^,])*)/.exec(e);return n?n[1].replace(/\\(.)/g,"$1"):""}function isChildOf(e,n){const r=e.toLowerCase(),t=n.toLowerCase();if(!r.endsWith(t))return!1;if(r.length===t.length)return!1;return r.substring(0,r.length-t.length).endsWith(",")}function normalizeDn(e){return parseDn(e).map(e=>e.split("+").map(e=>{const n=e.indexOf("=");return(-1===n?e.trim():`${e.slice(0,n).trim()}=${e.slice(n+1).trim()}`).toLowerCase()}).sort().join("+")).filter(e=>e.length>0).join(",")}function isDummyMemberDn(e,n){if(!n||"string"!=typeof e)return!1;if(e===n)return!0;try{return normalizeDn(e)===normalizeDn(n)}catch{return!1}}function organizationLink(e){const n=Array.isArray(e)?e[0]:e;if(null==n)return;const r=String(n);return r.length>0?r:void 0}function isDnInBranch(e,n){const r=normalizeDn(n).split(",").filter(Boolean);if(0===r.length)return!1;const t=normalizeDn(e).split(",").filter(Boolean);if(t.length<r.length)return!1;return t.slice(t.length-r.length).every((e,n)=>e===r[n])}const asyncHandler=e=>(n,r,t)=>{Promise.resolve(e(n,r,t)).catch(t)};export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDummyMemberDn,launchHooks,launchHooksChained,normalizeDn,organizationLink,parseDn,rdnValue,recordHookOwner,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue};
|
|
1
|
+
import{BadRequestError as e}from"./errors.js";import{getLogger as n}from"./expressFormatedResponses.js";const r=new Map;function getCompiledRegex(e,n){const t=n?`${e}:${n}`:e;let i=r.get(t);return i||(i=new RegExp(e,n),r.set(t,i)),i}function escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const describeThrown=e=>{try{return JSON.stringify(e)??String(e)}catch{return String(e)}},hookLabel=e=>{const n=hookOwner(e);return n?` in ${n.plugin} (${n.hook})`:e.name?` in ${e.name}`:""},launchHooks=async(e,...r)=>{if(e)for(const t of e)if(t)try{await t(...r)}catch(e){const r=hookLabel(t);e instanceof Error?n()?.error(`Hook error${r}`,e):n()?.error(`Hook error${r}: ${describeThrown(e)}`)}},t=new WeakMap,recordHookOwner=(e,n,r)=>{t.set(e,{plugin:n,hook:r})},hookOwner=e=>t.get(e),isAuthzRefusal=e=>/\[authz-forbidden\]/.test(String(e?.message??"")),isRefusal=e=>403===e?.statusCode||isAuthzRefusal(e),launchHooksChained=async(e,r)=>{if(e)for(const t of e)if(t)try{r=await t(r)}catch(e){if(isRefusal(e)){const r=hookOwner(t);n()?.warn(`${r?`${r.plugin} refused ${r.hook}`:`a hook no plugin registered (${t.name||"anonymous"}) refused`}: ${e.message}`)}throw e}return r},transformSchemas=(e,n)=>e.toString().replace(/__(\S+)__/g,(e,r)=>{if(!r||"string"!=typeof r)return e;const t=r.trim().toLowerCase();return n[t]?"object"!=typeof n[t]?n[t]:JSON.stringify(n[t]):e});function escapeLdapFilter(e){return e.replace(/\\/g,"\\5c").replace(/\*/g,"\\2a").replace(/\(/g,"\\28").replace(/\)/g,"\\29").replace(/\0/g,"\\00")}function substringSearchFilter(n,r){const t=/^[a-zA-Z][a-zA-Z0-9-]*$/,i=r.split(",").map(e=>e.trim()).filter(Boolean);if(0===i.length||!i.every(e=>t.test(e)))throw new e("Invalid LDAP attribute name");const o=escapeLdapFilter(n),a=i.map(e=>`(${e}=*${o}*)`);return 1===a.length?a[0]:`(|${a.join("")})`}function escapeDnValue(e){return e.replace(/\\/g,"\\\\").replace(/,/g,"\\,").replace(/\+/g,"\\+").replace(/"/g,'\\"').replace(/</g,"\\<").replace(/>/g,"\\>").replace(/;/g,"\\;").replace(/=/g,"\\=").replace(/\0/g,"\\00").replace(/^\s/,"\\ ").replace(/\s$/,"\\ ").replace(/^#/,"\\#")}function unescapeDnValue(n){if("string"!=typeof n)throw new e("DN value must be a string");let r="",t=0;for(;t<n.length;)if("\\"===n[t]&&t+1<n.length){const e=n[t+1];if(/[0-9a-fA-F]/.test(e)&&t+2<n.length){const e=n.substring(t+1,t+3);if(/^[0-9a-fA-F]{2}$/.test(e)){r+=String.fromCharCode(parseInt(e,16)),t+=3;continue}}r+=e,t+=2}else r+=n[t],t++;return r}function validateDnValue(e,n){if(null==e||"string"!=typeof e)throw new Error(`${n} must be a string`);if(0===e.trim().length)throw new Error(`${n} must be a non-empty string`);if(/[\x00-\x1F\x7F]/.test(e))throw new Error(`${n} contains invalid control characters`);if(/[\u200B-\u200F\uFEFF]/.test(e))throw new Error(`${n} contains invalid invisible characters`)}function parseDn(n){if("string"!=typeof n)throw new e("DN must be a string");const r=[];let t="",i=!1;for(let e=0;e<n.length;e++){const o=n[e];i?(t+=o,i=!1):"\\"===o?(t+=o,i=!0):","===o?(r.push(t.trim()),t=""):t+=o}return t&&r.push(t.trim()),r}function getParentDn(e){const n=parseDn(e);return n.length<=1?e:n.slice(1).join(",")}function getRdn(e){return parseDn(e)[0]||""}function rdnValue(e){const n=/^[^=]+=((?:\\.|[^,])*)/.exec(e);return n?n[1].replace(/\\(.)/g,"$1"):""}function isChildOf(e,n){const r=e.toLowerCase(),t=n.toLowerCase();if(!r.endsWith(t))return!1;if(r.length===t.length)return!1;return r.substring(0,r.length-t.length).endsWith(",")}function normalizeDn(e){return parseDn(e).map(e=>e.split("+").map(e=>{const n=e.indexOf("=");return(-1===n?e.trim():`${e.slice(0,n).trim()}=${e.slice(n+1).trim()}`).toLowerCase()}).sort().join("+")).filter(e=>e.length>0).join(",")}function isDummyMemberDn(e,n){if(!n||"string"!=typeof e)return!1;if(e===n)return!0;try{return normalizeDn(e)===normalizeDn(n)}catch{return!1}}function organizationLink(e){const n=Array.isArray(e)?e[0]:e;if(null==n)return;const r=String(n);return r.length>0?r:void 0}function isDnInBranch(e,n){const r=normalizeDn(n).split(",").filter(Boolean);if(0===r.length)return!1;const t=normalizeDn(e).split(",").filter(Boolean);if(t.length<r.length)return!1;return t.slice(t.length-r.length).every((e,n)=>e===r[n])}function movedToTrash(e,n,r){return!!e&&isDnInBranch(r,e)&&!isDnInBranch(n,e)}const asyncHandler=e=>(n,r,t)=>{Promise.resolve(e(n,r,t)).catch(t)};export{asyncHandler,escapeDnValue,escapeLdapFilter,escapeRegex,getCompiledRegex,getParentDn,getRdn,hookOwner,isAuthzRefusal,isChildOf,isDnInBranch,isDummyMemberDn,launchHooks,launchHooksChained,movedToTrash,normalizeDn,organizationLink,parseDn,rdnValue,recordHookOwner,substringSearchFilter,transformSchemas,unescapeDnValue,validateDnValue};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../../abstract/plugin.js";import{identityFor as s}from"../../lib/auth/base.js";import{servesRequest as t,overlap as
|
|
1
|
+
import e from"../../abstract/plugin.js";import{identityFor as s}from"../../lib/auth/base.js";import{servesRequest as t,overlap as i,EVERYONE as a,authzFor as r}from"../../lib/authz/composition.js";import{asyncHandler as o}from"../../lib/utils.js";import{UnauthorizedError as n}from"../../lib/errors.js";import{roleAttribute as c}from"../../config/schema.js";class AuthzScope extends e{constructor(){super(...arguments),this.name="authzScope",this.roles=["api"]}api(e){e.get(`${this.config.api_prefix}/v1/authz/scope`,o(async(e,s)=>this.scope(e,s)))}static canDescribe(e){if(!e.roles?.includes("authz"))return!1;const s=e;return"function"==typeof s.resolveUser&&"function"==typeof s.getAuthorizedBranches&&"function"==typeof s.getUserPermissions}describers(){return Object.values(this.server.loadedPlugins).filter(e=>AuthzScope.canDescribe(e))}static judges(e,s){return e.roles?.includes("auth")?Boolean(s.authenticators?.includes(e.name)):Boolean(s.user)&&t(s,e.config)}assertComposition(){const e=this.config.authz_scope_source;if(!e)return;const s=this.describers().map(e=>e.name);if(!s.includes(e))throw new Error(`${this.name}: --authz-scope-source names ${e}, which is not a loaded plugin able to describe a scope`+(s.length>0?` (loaded: ${s.join(", ")})`:""))}afterLoad(){if(this.config.authz_scope_source)return;const e=this.describers(),s=this.server.authenticators,population=e=>r(e.config,e.name)??a;for(let t=0;t<e.length;t++)for(let a=t+1;a<e.length;a++){const r=e[t],o=e[a];if(i(population(r),population(o),s))return void this.logger.warn(`${this.name}: ${r.name} and ${o.name} can both describe the scope of one caller, and ${r.name} answers because it loaded first. Set --authz-scope-source to choose; the answer lists both under \`sources\``)}}entities(){const e=(this.config.ldap_organization_link_attribute||"").toLowerCase(),linkable=s=>Boolean(e)&&(!s?.attributes||Object.keys(s.attributes).some(s=>s.toLowerCase()===e)),s=[],t=this.server.loadedPlugins.ldapFlatGeneric;for(const e of t?.instances||[])s.push({name:e.pluralName,base:e.base,linkable:linkable(e.schema)});const i=this.server.loadedPlugins.ldapGroups;return i?.base&&s.push({name:"groups",base:i.base,linkable:linkable(i.schema)}),this.server.loadedPlugins.ldapOrganizations&&this.config.ldap_top_organization&&s.push({name:"organizations",base:this.config.ldap_top_organization,linkable:!1}),s}async scope(e,t){const i=this.describers();if(Object.values(this.server.loadedPlugins).some(e=>e.roles?.includes("authz"))&&!e.user)throw new n("No authenticated user");const a=Object.values(this.server.loadedPlugins).filter(s=>s.roles?.includes("authz")&&AuthzScope.judges(s,e)),r=i.filter(e=>a.includes(e)),o=this.config.authz_scope_source,c=r.find(e=>e.name===o)??r[0],u=a.map(e=>e.name);if(!c)return 0===a.length?void t.json({user:e.user??null,unrestricted:!0,described:!0,source:null,sources:[],branches:[],linksAnyDomain:!0,...this.passwordPolicies(!0),entities:this.entities().map(({name:e,base:s})=>({name:e,base:s,create:!0}))}):void t.json({user:e.user??null,unrestricted:!1,described:!1,source:null,sources:u,branches:[],linksAnyDomain:!1,...this.passwordPolicies(!1),entities:[]});const l=s(e,c.config).value,h=await c.resolveUser(l);if(!h)throw new n(`Unknown user ${l}`);const d=await c.getAuthorizedBranches(h),p=[],f=new Map,permissionsOn=async e=>{let s=f.get(e);return s||(s=await c.getUserPermissions(h,e),f.set(e,s)),s};for(const e of d){const s=await permissionsOn(e);p.push({dn:e,...s,...await this.branchLabel(e)})}const g=d.some(e=>!0===f.get(e)?.write),m=this.config.ldap_top_organization,b=[];for(const{name:e,base:s,linkable:t}of this.entities()){const i=m&&s===m?!0===(await permissionsOn(m)).write:t?g:!0===(await permissionsOn(s)).write;b.push({name:e,base:s,create:i})}t.json({user:h,unrestricted:!1,described:!0,source:c.name,sources:u,branches:p,transit:c.transitBranch?.()??null,linksAnyDomain:!0===await(c.mayLinkAnyDomain?.(h)),...this.passwordPolicies(await this.readsPolicies(c,h)),entities:b})}policyPlugin(){return this.server.loadedPlugins.ldapPasswordPolicy}passwordPolicies(e){return this.policyPlugin()?{passwordPolicies:e}:{}}async readsPolicies(e,s){const t=this.policyPlugin();return!(!t||!e.searchRefusal)&&void 0===await e.searchRefusal(s,t.policyBase())}async branchLabel(e){const s=this.server.loadedPlugins.ldapOrganizations,t=c(s?.schema,"organizationPath")||this.config.ldap_organization_path_attribute;try{const s=await this.server.ldap.search({paged:!1,scope:"base",attributes:["ou","o","cn",...t?[t]:[]]},e),i=s?.searchEntries?.[0];if(!i)return{};const first=e=>{const s=Array.isArray(e)?e[0]:e;return"string"==typeof s?s||void 0:Buffer.isBuffer(s)&&s.toString("utf8")||void 0};return{name:first(i.ou)||first(i.o)||first(i.cn),path:t?first(i[t]):void 0}}catch(e){return{}}}}export{AuthzScope as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"multer";import{parse as e}from"csv-parse/sync";import i from"../../abstract/plugin.js";import{badRequest as r,serverError as s}from"../../lib/expressFormatedResponses.js";import{
|
|
1
|
+
import t from"multer";import{parse as e}from"csv-parse/sync";import i from"../../abstract/plugin.js";import{badRequest as r,serverError as s}from"../../lib/expressFormatedResponses.js";import{HttpError as o}from"../../lib/errors.js";import{loadSchemaFile as n}from"../../lib/schemaFile.js";import{validateDnValue as a,escapeDnValue as c}from"../../lib/utils.js";const l=/^([A-Za-z][A-Za-z0-9-]*|\d+(\.\d+)+)(;[A-Za-z0-9-]+)*$/;class LdapBulkImport extends i{constructor(e){super(e),this.name="ldapBulkImport",this.roles=["configurable"],this.resources=new Map;const i=parseInt(this.config.bulk_import_max_file_size,10)||10485760;this.upload=t({storage:t.memoryStorage(),limits:{fileSize:i},fileFilter:(t,e,i)=>{"text/csv"===e.mimetype||e.originalname.endsWith(".csv")?i(null,!0):i(new Error("Only CSV files are allowed"))}});const r=this.config.bulk_import_schemas;if(!r)return void this.logger.warn("No bulk import schemas configured");const s=r.split(",").map(t=>t.trim());for(const t of s){const[e,i]=t.split(":").map(t=>t.trim());if(e&&i)try{const t=n(i,{config:this.config});this.resources.set(e,{name:e,schema:t,base:t.base||this.config.ldap_base,mainAttribute:t.mainAttribute||"cn"}),this.logger.info(`Loaded bulk import resource: ${e} from ${i}`)}catch(t){this.logger.error(`Failed to load schema for ${e}: ${t.message}`)}else this.logger.warn(`Invalid schema entry: ${t}`)}}api(t){for(const[e,i]of this.resources)t.get(`${this.config.api_prefix}/v1/ldap/bulk-import/${e}/template.csv`,(t,e)=>this.getTemplate(t,e,i)),t.post(`${this.config.api_prefix}/v1/ldap/bulk-import/${e}`,this.upload.single("file"),async(t,e)=>this.bulkImport(t,e,i))}getTemplate(t,e,i){const r=this.getEditableAttributes(i.schema);r.push("organizationDn"),e.setHeader("Content-Type","text/csv; charset=utf-8"),e.setHeader("Content-Disposition",`attachment; filename="${i.name}-template.csv"`),e.send(r.join(",")+"\n")}async bulkImport(t,i,n){if(!t.file)return r(i,"No file uploaded");const a="true"===t.body.dryRun||!0===t.body.dryRun,c="true"===t.body.updateExisting||!0===t.body.updateExisting,l="false"!==t.body.continueOnError&&!1!==t.body.continueOnError,p=Date.now(),d={success:!0,total:0,created:0,updated:0,skipped:0,failed:0,errors:[],details:{duration:"",linesProcessed:0}};try{const r=t.file.buffer.toString("utf-8"),s=e(r,{columns:!0,skip_empty_lines:!0,trim:!0});d.total=s.length;for(let e=0;e<s.length;e++){const i=e+2,r=s[e];try{const{dn:e,entry:i}=await this.processRecord(r,n),s=await this.entryExists(e);if(s&&!c){d.skipped++;continue}if(a){d.created++;continue}s&&c?(await this.server.ldap.modify(e,{replace:i},t),d.updated++):(await this.server.ldap.add(e,i,t),d.created++)}catch(t){if(d.failed++,d.errors.push({line:i,identifier:r[n.mainAttribute],error:t.message,...t instanceof o?t.refusal:void 0}),!l)throw t}}d.details.linesProcessed=s.length,d.details.duration=`${((Date.now()-p)/1e3).toFixed(1)}s`,i.json(d)}catch(t){return s(i,t)}}async processRecord(t,e){const i={},r=e.schema,s=t.organizationDn;delete t.organizationDn;for(const[t,e]of Object.entries(r.properties||{}))if(!0===e.fixed){const r=e.default||e.const;void 0!==r&&(i[t]=r)}for(const[e,r]of Object.entries(t))if(r&&""!==r.trim()){if(!l.test(e))throw new Error(`Invalid column name: ${e}`);i[e]=r.includes(";")?r.split(";").map(t=>t.trim()):r}if(s){const t=this.findAttributeByRole(r,"organizationLink");t&&(i[t]=s);const e=this.findAttributeByRole(r,"organizationPath");if(e){const t=(await this.fetchOrganization(s))[e];i[e]=Array.isArray(t)?t[0]:t}}for(const[t,e]of Object.entries(r.properties||{}))if(!0===e.required&&!i[t])throw new Error(`Missing required attribute: ${t}`);const o=e.mainAttribute,n=i[o];if(!n)throw new Error(`Missing main attribute: ${o}`);const p=Array.isArray(n)?String(n[0]):String(n);a(p,o);return{dn:`${o}=${c(p)},${e.base}`,entry:i}}getEditableAttributes(t){const e=[];for(const[i,r]of Object.entries(t.properties||{}))!0!==r.fixed&&(r.role?.includes("organizationLink")||r.role?.includes("organizationPath")||e.push(i));return e}findAttributeByRole(t,e){for(const[i,r]of Object.entries(t.properties||{}))if(r.role?.includes(e))return i;return null}async fetchOrganization(t){const e=await this.server.ldap.search({paged:!1,scope:"base"},t);if(0===e.searchEntries.length)throw new Error(`Organization not found: ${t}`);return e.searchEntries[0]}async entryExists(t){try{return(await this.server.ldap.search({paged:!1,scope:"base"},t)).searchEntries.length>0}catch(t){return!1}}getConfigApiData(){const t=this.config.api_prefix||"/api",e=[];return this.resources.forEach((i,r)=>{e.push({name:r,mainAttribute:i.mainAttribute,base:i.base,maxFileSize:parseInt(this.config.bulk_import_max_file_size,10)||10485760,batchSize:parseInt(this.config.bulk_import_batch_size,10)||100,endpoints:{template:`${t}/v1/ldap/bulk-import/${r}/template.csv`,import:`${t}/v1/ldap/bulk-import/${r}`}})}),{enabled:!0,resources:e}}}export{LdapBulkImport as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../../abstract/plugin.js";import{roleAttribute as t,hasRole as n,invalidValueMessage as r}from"../../config/schema.js";import{normalizeDn as i,isDnInBranch as a,getParentDn as s,rdnValue as o,escapeLdapFilter as c,isDummyMemberDn as u}from"../../lib/utils.js";import{BadRequestError as l,ConflictError as d}from"../../lib/errors.js";function valueList(e){return null==e?[]:Array.isArray(e)?e.map(e=>String(e)):[String(e)]}function entryValues(e,t){const n=t.toLowerCase();for(const[t,r]of Object.entries(e))if(t.split(";")[0].toLowerCase()===n)return valueList(r);return[]}function asDn(e){try{return i(e)}catch{return e}}const f={B:1,KB:1e3,MB:1e6,GB:1e9,TB:1e12};function parseByteSize(e){if("number"==typeof e)return Math.round(e);const t=String(e).trim(),n=/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)?$/i.exec(t);if(!n)throw new l(`"${t}" is not a valid size`);const r=(n[2]||"B").toUpperCase();return Math.round(parseFloat(n[1])*f[r])}function fillsGeneratedAttribute(e,n,r){return!(void 0===n.default||!n.generated&&!n.normalize)||t(r,"organizationPath")===e}function parseDirectoryDate(e){const t=String(e).trim(),n=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})?(\d{2})?(?:[.,](\d+))?(Z|[+-]\d{2}(?::?\d{2})?)?$/.exec(t);if(!n){const e=new Date(t);return isNaN(e.getTime())?null:e}const[,r,i,a,s,o,c,u,l]=n,[d,f,h,m,p,g]=[r,i,a,s,o,c].map(e=>void 0===e?0:Number(e));if(f<1||f>12||m>23||p>59||g>60)return null;let b=Date.UTC(d,f-1,h,m,p,g);const y=new Date(b);if(y.getUTCMonth()!==f-1||y.getUTCDate()!==h)return null;if(void 0!==u){const e=void 0!==c?1e3:void 0!==o?6e4:36e5;b+=Math.round(Number(`0.${u}`)*e)}if(l&&"Z"!==l){const e=l.slice(1).replace(":",""),t=Number(e.slice(0,2)),n=e.length>2?Number(e.slice(2,4)):0;if(t>23||n>59)return null;b-=("-"===l[0]?-1:1)*(60*t+n)*6e4}return new Date(b)}function startOfUtcDay(e=new Date){return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate())}function mailInDomains(e,t,n=!1){if(0===t.length)return!0;if(t.includes("*"))return!0;const r=e.lastIndexOf("@");if(r<0)return!1;const i=e.slice(r+1).toLowerCase();return t.some(e=>{const t=e.toLowerCase();return i===t||n&&i.endsWith(`.${t}`)})}class LdapEnterpriseRules extends e{constructor(e){super(e),this.name="ldapEnterpriseRules",this.roles=["consistency"],this.hooks={ldapaddrequest:async([e,t,n])=>{const r=this.resolveEntity(e);return r&&(await this.runChecks(r,e,t,null),await this.runTransformations(r,e,t,!0)),void 0!==n?[e,t,n]:[e,t]},ldapmodifyrequest:async([e,n,r,i])=>{const a=this.resolveEntity(e);if(!a)return[e,n,r,i];const s={...n.add||{},...n.replace||{}};if(0===Object.keys(s).length)return[e,n,r,i];const o=await this.readEntry(e),c=t(a.schema,"organizationLink"),u={...s};if(c&&c in s&&o)for(const[e,t]of Object.entries(a.schema.attributes))t.mailDomainScope&&(e in u||void 0!==o[e]&&(u[e]=o[e]));await this.runChecks(a,e,u,o),await this.runTransformations(a,e,s,!1);for(const[e,t]of Object.entries(s))n.replace&&e in n.replace?n.replace[e]=t:n.add&&e in n.add?n.add[e]=t:(n.replace=n.replace||{},n.replace[e]=t);return[e,n,r,i]},ldapdeleterequest:async([e,t])=>{for(const t of Array.isArray(e)?e:[e])await this.guardDelete(t);return[e,t]}},this.dummyMember=this.config.group_dummy_user,this.topOrganization=this.config.ldap_top_organization,this.logger.info("Enterprise directory rules enabled")}fillsGeneratedAttribute(e,t,n){return fillsGeneratedAttribute(e,t,n)}bindings(){const e=[],t=this.server.loadedPlugins.ldapFlatGeneric;for(const n of t?.instances||[])n.schema&&e.push({base:n.base,subtree:!1,schema:n.schema,label:n.singularName});const n=this.server.loadedPlugins.ldapGroups;n?.base&&n.schema&&e.push({base:n.base,subtree:!0,schema:n.schema,label:"group"});const r=this.server.loadedPlugins.ldapOrganizations;return r?.schema&&this.topOrganization&&e.push({base:this.topOrganization,subtree:!0,schema:r.schema,label:"organization"}),e}resolveEntity(e){const t=i(e);let n,r=-1;for(const o of this.bindings()){const c=i(o.base);if(t===c)continue;if(!a(e,o.base))continue;if(!o.subtree&&i(s(e))!==c)continue;const u=c.split(",").length;u>r&&(n=o,r=u)}return n}async runChecks(e,t,r,i){for(const[a,s]of Object.entries(r)){const o=e.schema.attributes[a];if(!o)continue;const c=valueList(s);0!==c.length&&(o.unique&&await this.checkUnique(e,a,o,c,i?t:null),o.mailDomainScope&&await this.checkMailDomains(e,o,c,r,i),n(o,"accountExpiry")&&this.checkNotInThePast(a,o,c))}}async runTransformations(e,n,i,a){for(const[t,n]of Object.entries(e.schema.attributes)){if(a&&void 0===i[t]&&void 0!==n.default&&(n.generated||n.normalize)&&(i[t]=Array.isArray(n.default)?n.default.map(e=>String(e)):String(n.default),"pointer"===n.type||"pointer"===n.items?.type))for(const e of valueList(i[t]))if(!await this.readEntry(e))throw new l(`Schema default for "${t}" points to a non-existent entry: ${e}`);if("byteSize"===n.normalize&&void 0!==i[t]){const e=valueList(i[t]);try{i[t]=Array.isArray(i[t])?e.map(e=>String(parseByteSize(e))):String(parseByteSize(e[0]))}catch{throw new l(r(t,n))}}}const c=t(e.schema,"organizationPath");if(!c)return;if(!e.schema.attributes[c]?.generated&&void 0!==i[c])return;const u=t(e.schema,"organizationLink");if(u){const e=valueList(i[u])[0];if(!e)return;const t=await this.organizationPath(e);if(void 0===t)throw new l(`Organization ${e} has no ${c}, so "${c}" cannot be computed`);return void(i[c]=t)}if(!a)return;const d=valueList(i[t(e.schema,"identifier")||""])[0]||o(n);if(!d)return;const f=s(n),h=(this.topOrganization||this.config.ldap_base||"").toLowerCase(),m=!h||f.toLowerCase().endsWith(`,${h}`)?await this.organizationPath(f):void 0,p=this.config.ldap_organization_path_separator||" / ";i[c]=m?`${m}${p}${d}`:d}async checkUnique(e,t,n,r,i){const a="object"==typeof n.unique?n.unique:{},s=[t,...a.attributes||[]],o=function(e,t){const contains=(e,t)=>t===e||t.endsWith(`,${e}`),n=[];for(const r of[e,...t||[]]){const e=r.toLowerCase();if(!n.some(t=>contains(t.toLowerCase(),e))){for(let t=n.length-1;t>=0;t--)contains(e,n[t].toLowerCase())&&n.splice(t,1);n.push(r)}}return n}(e.base,a.branches);for(const n of r){if(void 0!==a.sentinel&&n===a.sentinel)continue;const r=s.map(e=>`(${e}=${c(n)})`).join("");let u=s.length>1?`(|${r})`:r;a.filter&&(u=`(&${a.filter}${u})`);for(const r of o){const a=await this.server.ldap.search({paged:!1,scope:"sub",filter:u,attributes:["dn"]},r);for(const r of a.searchEntries||[]){const a=String(r.dn);if(!i||a.toLowerCase()!==i.toLowerCase())throw this.logger.warn(`${t}="${n}" refused for ${i||"a new entry"}: already held by ${a}`),new d(`${e.label}: "${n}" is already used (${t} must be unique)`)}}}}async checkMailDomains(e,t,n,r,i){const a="directory"===t.mailDomainScope?await this.directoryDomains():await this.organizationDomains(e,r,i);if(0!==a.length)for(const e of n)if(!mailInDomains(e,a,t.allowSubdomains))throw new d(`The mail domain of "${e}" is not one of the authorised domains (${a.join(", ")})`)}async organizationDomains(e,n,r){const i=t(e.schema,"organizationLink");if(!i)return[];const a=valueList(n[i])[0]||valueList(r?.[i])[0]||void 0;if(!a)return[];const o=this.bindings().find(e=>"organization"===e.label)?.schema,c=t(o,"domainLink")||this.config.enterprise_domain_link_attribute;if(!c)return[];const u=[],l=new Set;let d=a;const f=(this.topOrganization||this.config.ldap_base||"").toLowerCase(),h=this.domainNameAttribute();for(;d&&!l.has(d.toLowerCase());){l.add(d.toLowerCase());const e=await this.readEntry(d);for(const t of valueList(e?.[c])){const e=await this.readEntry(t),n=valueList(e?.[h]);0===n.length&&this.logger.warn(`Organization ${d} links to domain ${t}, which has no "${h}": no mail domain restriction comes from it`),u.push(...n)}if(d.toLowerCase()===f)break;const t=s(d);d=t===d?void 0:t}return u}domainNameAttribute(){for(const e of this.bindings()){const n=t(e.schema,"domainName");if(n)return n}return this.config.enterprise_domain_name_attribute||"associatedDomain"}async directoryDomains(){const e=this.config.ldap_base;if(!e)return[];const t=this.domainNameAttribute(),n=await this.server.ldap.search({paged:!1,scope:"sub",filter:`(${t}=*)`,attributes:[t]},e),r=[];for(const e of n.searchEntries||[])r.push(...valueList(e[t]));return r}checkNotInThePast(e,t,n){const i=startOfUtcDay();for(const a of n){const n=parseDirectoryDate(a);if(!n)throw new l(r(e,t));if(n.getTime()<i)throw new l(`Attribute "${e}" must not be earlier than today`)}}async organizationPath(e){const n=this.bindings().find(e=>"organization"===e.label)?.schema,r=t(n,"organizationPath")||this.config.ldap_organization_path_attribute;if(!r)return;const i=await this.readEntry(e);return i?valueList(i[r])[0]:void 0}async guardDelete(e){const t=this.resolveEntity(e);if(t)for(const[n,r]of Object.entries(t.schema.attributes)){if("nonEmpty"!==r.deleteGuard)continue;const i=await this.readEntry(e),a=valueList(i?.[n]).filter(e=>!u(e,this.dummyMember));if(a.length>0)throw new d(`Cannot delete ${t.label} ${e}: it still has ${a.length} member(s)`)}await this.guardReferences(e)}async guardReferences(e){for(const t of this.referringAttributes(e)){if(!t.pointer)continue;if("restrict"!==t.attr.referentialIntegrity)continue;const n=(await this.searchReferrers(t,e))[0];if(n)throw this.logger.warn(`Delete of ${e} refused: still referenced by ${String(n.dn)} (${t.name})`),new d(`Cannot delete ${e}: still referenced by another entry (${t.name})`)}}referringAttributes(e){const t=s(e),r=[];for(const e of this.bindings())for(const[i,s]of Object.entries(e.schema.attributes)){const o="pointer"===s.type?s:s.items,c=n(s,"members")||n(s,"owners");if(!o&&!c)continue;const u=o?.branch||[];u.length>0&&!u.some(e=>a(t,e))||r.push({binding:e,name:i,attr:s,pointer:o,multiValued:"array"===s.type})}return r}async searchReferrers(e,t){return(await this.server.ldap.search({paged:!1,scope:"sub",filter:`(${e.name}=${c(t)})`,attributes:["dn",e.name]},e.binding.base)).searchEntries||[]}async findReferences(e){const t=[],n=new Set;return await Promise.all(this.referringAttributes(e).map(r=>this.server.ldap.queryLimit(async()=>{for(const i of await this.searchReferrers(r,e)){const e=String(i.dn),a=`${asDn(e)} ${r.name.toLowerCase()}`;n.has(a)||(n.add(a),t.push({dn:e,attribute:r.name,values:entryValues(i,r.name),multiValued:r.multiValued}))}}))),t}static referenceKey(e){return`${asDn(e.dn)} ${e.attribute.toLowerCase()}`}async rewriteReferences(e,t,n,r){const i=asDn(t),a=asDn(n);if(i===a)return{updated:0,failed:[]};const s=r?this.server.ldap.forRequest(r):{modify:(e,t)=>this.server.ldap.modify(e,t)},o=new Map;for(const e of await this.findReferences(t))o.set(LdapEnterpriseRules.referenceKey(e),e);let c=e.filter(e=>!o.has(LdapEnterpriseRules.referenceKey(e))).length;const u=new Map;return await Promise.all([...o.values()].map(e=>this.server.ldap.queryLimit(async()=>{try{await this.applyRewrite(s,e,i,a,n),c++}catch(r){this.logger.error(`Rename ${t} -> ${n}: could not rewrite ${e.attribute} of ${e.dn}: ${String(r)}`),u.set(e.attribute,(u.get(e.attribute)||0)+1)}}))),{updated:c,failed:[...u].map(([e,t])=>({attribute:e,count:t}))}}async applyRewrite(e,t,n,r,i){const a=this.rewrite(t,n,r,i);if(!a)return;try{return void await e.modify(t.dn,a)}catch(e){const n=e.code;if(20!==n&&16!==n)throw e;this.logger.debug(`Rewrite of ${t.attribute} on ${t.dn} met a concurrent one (code ${n}); reading it back`)}const s={...t,values:await this.freshValues(t.dn,t.attribute)},o=this.rewrite(s,n,r,i);o&&await e.modify(s.dn,o)}async freshValues(e,t){this.server.ldap.invalidateCache(e);const n=await this.server.ldap.search({paged:!1,scope:"base",attributes:[t]},e),r=n.searchEntries?.[0];return r?entryValues(r,t):[]}rewrite(e,t,n,r){const i=e.attribute;if(t===n)return;const a=e.values.filter(e=>asDn(e)===t);if(0===a.length)return;const s=e.values.some(e=>asDn(e)===n);if(!e.multiValued){const n=[];for(const i of e.values){const e=asDn(i)===t?r:i;n.some(t=>asDn(t)===asDn(e))||n.push(e)}return{replace:{[i]:n}}}return s?{delete:{[i]:a}}:{delete:{[i]:a},add:{[i]:r}}}async readEntry(e){try{const t=await this.server.ldap.search({paged:!1,scope:"base"},e);return t?.searchEntries?.[0]||null}catch(e){return null}}}export{LdapEnterpriseRules as default,fillsGeneratedAttribute,mailInDomains,parseByteSize,parseDirectoryDate,startOfUtcDay};
|
|
1
|
+
import e from"../../abstract/plugin.js";import{roleAttribute as t,hasRole as r,invalidValueMessage as n}from"../../config/schema.js";import{movedToTrash as i,normalizeDn as a,isDnInBranch as s,getParentDn as o,rdnValue as c,escapeLdapFilter as u,isDummyMemberDn as l}from"../../lib/utils.js";import{BadRequestError as f,ConflictError as d}from"../../lib/errors.js";import{extractLdapCode as h}from"../../lib/ldapCodes.js";function valueList(e){return null==e?[]:Array.isArray(e)?e.map(e=>String(e)):[String(e)]}function entryValues(e,t){const r=t.toLowerCase();for(const[t,n]of Object.entries(e))if(t.split(";")[0].toLowerCase()===r)return valueList(n);return[]}function asDn(e){try{return a(e)}catch{return e}}const m={B:1,KB:1e3,MB:1e6,GB:1e9,TB:1e12};function parseByteSize(e){if("number"==typeof e)return Math.round(e);const t=String(e).trim(),r=/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)?$/i.exec(t);if(!r)throw new f(`"${t}" is not a valid size`);const n=(r[2]||"B").toUpperCase();return Math.round(parseFloat(r[1])*m[n])}function fillsGeneratedAttribute(e,r,n){return!(void 0===r.default||!r.generated&&!r.normalize)||t(n,"organizationPath")===e}function parseDirectoryDate(e){const t=String(e).trim(),r=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})?(\d{2})?(?:[.,](\d+))?(Z|[+-]\d{2}(?::?\d{2})?)?$/.exec(t);if(!r){const e=new Date(t);return isNaN(e.getTime())?null:e}const[,n,i,a,s,o,c,u,l]=r,[f,d,h,m,p,g]=[n,i,a,s,o,c].map(e=>void 0===e?0:Number(e));if(d<1||d>12||m>23||p>59||g>60)return null;let b=Date.UTC(f,d-1,h,m,p,g);const y=new Date(b);if(y.getUTCMonth()!==d-1||y.getUTCDate()!==h)return null;if(void 0!==u){const e=void 0!==c?1e3:void 0!==o?6e4:36e5;b+=Math.round(Number(`0.${u}`)*e)}if(l&&"Z"!==l){const e=l.slice(1).replace(":",""),t=Number(e.slice(0,2)),r=e.length>2?Number(e.slice(2,4)):0;if(t>23||r>59)return null;b-=("-"===l[0]?-1:1)*(60*t+r)*6e4}return new Date(b)}function startOfUtcDay(e=new Date){return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate())}function mailInDomains(e,t,r=!1){if(0===t.length)return!0;if(t.includes("*"))return!0;const n=e.lastIndexOf("@");if(n<0)return!1;const i=e.slice(n+1).toLowerCase();return t.some(e=>{const t=e.toLowerCase();return i===t||r&&i.endsWith(`.${t}`)})}class LdapEnterpriseRules extends e{constructor(e){super(e),this.name="ldapEnterpriseRules",this.roles=["consistency"],this.hooks={ldapaddrequest:async([e,t,r])=>{const n=this.resolveEntity(e);return n&&(await this.runChecks(n,e,t,null),await this.runTransformations(n,e,t,!0)),void 0!==r?[e,t,r]:[e,t]},ldapmodifyrequest:async([e,r,n,i])=>{const a=this.resolveEntity(e);if(!a)return[e,r,n,i];const s={...r.add||{},...r.replace||{}};if(0===Object.keys(s).length)return[e,r,n,i];const o=await this.readEntry(e),c=t(a.schema,"organizationLink"),u={...s};if(c&&c in s&&o)for(const[e,t]of Object.entries(a.schema.attributes))t.mailDomainScope&&(e in u||void 0!==o[e]&&(u[e]=o[e]));await this.runChecks(a,e,u,o),await this.runTransformations(a,e,s,!1);for(const[e,t]of Object.entries(s))r.replace&&e in r.replace?r.replace[e]=t:r.add&&e in r.add?r.add[e]=t:(r.replace=r.replace||{},r.replace[e]=t);return[e,r,n,i]},ldapdeleterequest:async([e,t])=>{for(const t of Array.isArray(e)?e:[e])await this.guardDelete(t);return[e,t]},ldapdeletedone:async(e,t)=>{for(const r of Array.isArray(e)?e:[e])await this.removeReferences(r,t)},ldaprenamedone:async([e,t],r)=>{i(this.config.trash_base,e,t)&&await this.removeReferences(e,r)}},this.dummyMember=this.config.group_dummy_user,this.topOrganization=this.config.ldap_top_organization,this.logger.info("Enterprise directory rules enabled")}fillsGeneratedAttribute(e,t,r){return fillsGeneratedAttribute(e,t,r)}bindings(){const e=[],t=this.server.loadedPlugins.ldapFlatGeneric;for(const r of t?.instances||[])r.schema&&e.push({base:r.base,subtree:!1,schema:r.schema,label:r.singularName});const r=this.server.loadedPlugins.ldapGroups;r?.base&&r.schema&&e.push({base:r.base,subtree:!0,schema:r.schema,label:"group"});const n=this.server.loadedPlugins.ldapOrganizations;return n?.schema&&this.topOrganization&&e.push({base:this.topOrganization,subtree:!0,schema:n.schema,label:"organization"}),e}resolveEntity(e){const t=a(e);let r,n=-1;for(const i of this.bindings()){const c=a(i.base);if(t===c)continue;if(!s(e,i.base))continue;if(!i.subtree&&a(o(e))!==c)continue;const u=c.split(",").length;u>n&&(r=i,n=u)}return r}async runChecks(e,t,n,i){for(const[a,s]of Object.entries(n)){const o=e.schema.attributes[a];if(!o)continue;const c=valueList(s);0!==c.length&&(o.unique&&await this.checkUnique(e,a,o,c,i?t:null),o.mailDomainScope&&await this.checkMailDomains(e,a,o,c,n,i),r(o,"accountExpiry")&&this.checkNotInThePast(a,o,c))}}async runTransformations(e,r,i,a){for(const[t,r]of Object.entries(e.schema.attributes)){if(a&&void 0===i[t]&&void 0!==r.default&&(r.generated||r.normalize)&&(i[t]=Array.isArray(r.default)?r.default.map(e=>String(e)):String(r.default),"pointer"===r.type||"pointer"===r.items?.type))for(const e of valueList(i[t]))if(!await this.readEntry(e))throw new f(`Schema default for "${t}" points to a non-existent entry: ${e}`);if("byteSize"===r.normalize&&void 0!==i[t]){const e=valueList(i[t]);try{i[t]=Array.isArray(i[t])?e.map(e=>String(parseByteSize(e))):String(parseByteSize(e[0]))}catch{throw new f(n(t,r))}}}const s=t(e.schema,"organizationPath");if(!s)return;if(!e.schema.attributes[s]?.generated&&void 0!==i[s])return;const u=t(e.schema,"organizationLink");if(u){const e=valueList(i[u])[0];if(!e)return;const t=await this.organizationPath(e);if(void 0===t)throw new f(`Organization ${e} has no ${s}, so "${s}" cannot be computed`);return void(i[s]=t)}if(!a)return;const l=valueList(i[t(e.schema,"identifier")||""])[0]||c(r);if(!l)return;const d=o(r),h=(this.topOrganization||this.config.ldap_base||"").toLowerCase(),m=!h||d.toLowerCase().endsWith(`,${h}`)?await this.organizationPath(d):void 0,p=this.config.ldap_organization_path_separator||" / ";i[s]=m?`${m}${p}${l}`:l}async checkUnique(e,t,r,n,i){const a="object"==typeof r.unique?r.unique:{},s=[t,...a.attributes||[]],o=function(e,t){const contains=(e,t)=>t===e||t.endsWith(`,${e}`),r=[];for(const n of[e,...t||[]]){const e=n.toLowerCase();if(!r.some(t=>contains(t.toLowerCase(),e))){for(let t=r.length-1;t>=0;t--)contains(e,r[t].toLowerCase())&&r.splice(t,1);r.push(n)}}return r}(e.base,a.branches);for(const r of n){if(void 0!==a.sentinel&&r===a.sentinel)continue;const n=s.map(e=>`(${e}=${u(r)})`).join("");let c=s.length>1?`(|${n})`:n;a.filter&&(c=`(&${a.filter}${c})`);for(const n of o){const a=await this.server.ldap.search({paged:!1,scope:"sub",filter:c,attributes:["dn"]},n);for(const n of a.searchEntries||[]){const a=String(n.dn);if(!i||a.toLowerCase()!==i.toLowerCase())throw this.logger.warn(`${t}="${r}" refused for ${i||"a new entry"}: already held by ${a}`),new d(`${e.label}: "${r}" is already used (${t} must be unique)`,{code:"unique",attribute:t,value:r})}}}}async checkMailDomains(e,t,r,n,i,a){const s="directory"===r.mailDomainScope?await this.directoryDomains():await this.organizationDomains(e,i,a);if(0!==s.length)for(const e of n)if(!mailInDomains(e,s,r.allowSubdomains))throw new d(`The mail domain of "${e}" is not one of the authorised domains (${s.join(", ")})`,{code:"mailDomain",attribute:t,value:e,domains:s})}async organizationDomains(e,r,n){const i=t(e.schema,"organizationLink");if(!i)return[];const a=valueList(r[i])[0]||valueList(n?.[i])[0]||void 0;if(!a)return[];const s=this.bindings().find(e=>"organization"===e.label)?.schema,c=t(s,"domainLink")||this.config.enterprise_domain_link_attribute;if(!c)return[];const u=[],l=new Set;let f=a;const d=(this.topOrganization||this.config.ldap_base||"").toLowerCase(),h=this.domainNameAttribute();for(;f&&!l.has(f.toLowerCase());){l.add(f.toLowerCase());const e=await this.readEntry(f);for(const t of valueList(e?.[c])){const e=await this.readEntry(t),r=valueList(e?.[h]);0===r.length&&this.logger.warn(`Organization ${f} links to domain ${t}, which has no "${h}": no mail domain restriction comes from it`),u.push(...r)}if(f.toLowerCase()===d)break;const t=o(f);f=t===f?void 0:t}return u.filter((e,t)=>u.findIndex(t=>t.toLowerCase()===e.toLowerCase())===t)}domainNameAttribute(){for(const e of this.bindings()){const r=t(e.schema,"domainName");if(r)return r}return this.config.enterprise_domain_name_attribute||"associatedDomain"}async directoryDomains(){const e=this.config.ldap_base;if(!e)return[];const t=this.domainNameAttribute(),r=await this.server.ldap.search({paged:!1,scope:"sub",filter:`(${t}=*)`,attributes:[t]},e),n=[];for(const e of r.searchEntries||[])n.push(...valueList(e[t]));return n}checkNotInThePast(e,t,r){const i=startOfUtcDay();for(const a of r){const r=parseDirectoryDate(a);if(!r)throw new f(n(e,t));if(r.getTime()<i)throw new f(`Attribute "${e}" must not be earlier than today`)}}async organizationPath(e){const r=this.bindings().find(e=>"organization"===e.label)?.schema,n=t(r,"organizationPath")||this.config.ldap_organization_path_attribute;if(!n)return;const i=await this.readEntry(e);return i?valueList(i[n])[0]:void 0}async guardDelete(e){const t=this.resolveEntity(e);if(t)for(const[r,n]of Object.entries(t.schema.attributes)){if("nonEmpty"!==n.deleteGuard)continue;const i=await this.readEntry(e),a=valueList(i?.[r]).filter(e=>!l(e,this.dummyMember));if(a.length>0)throw new d(`Cannot delete ${t.label} ${e}: it still has ${a.length} member(s)`)}await this.guardReferences(e)}async guardReferences(e){for(const t of this.referringAttributes(e)){if(!t.pointer)continue;if("restrict"!==t.attr.referentialIntegrity)continue;const r=(await this.searchReferrers(t,e))[0];if(r)throw this.logger.warn(`Delete of ${e} refused: still referenced by ${String(r.dn)} (${t.name})`),new d(`Cannot delete ${e}: still referenced by another entry (${t.name})`)}}async removeReferences(e,t){const r=asDn(e),n=new Set;for(const i of this.referringAttributes(e)){if("remove"!==i.attr.referentialIntegrity)continue;let a;try{a=await this.searchReferrers(i,e)}catch(t){32!==h(t)&&this.logger.error(`Delete of ${e}: could not look for ${i.name} references: ${String(t)}`);continue}for(const s of a){const a=String(s.dn),o=`${asDn(a)} ${i.name.toLowerCase()}`;if(n.has(o))continue;n.add(o);const c=entryValues(s,i.name).filter(e=>asDn(e)===r);if(0!==c.length)try{await this.server.ldap.modify(a,{delete:{[i.name]:c}},void 0,{context:t})}catch(t){if(16===h(t))continue;this.logger.error(`Delete of ${e}: could not remove it from ${i.name} of ${a}: ${String(t)}`)}}}}referringAttributes(e){const t=o(e),n=[];for(const e of this.bindings())for(const[i,a]of Object.entries(e.schema.attributes)){const o="pointer"===a.type?a:a.items,c=r(a,"members")||r(a,"owners");if(!o&&!c)continue;const u=o?.branch||[];u.length>0&&!u.some(e=>s(t,e))||n.push({binding:e,name:i,attr:a,pointer:o,multiValued:"array"===a.type})}return n}async searchReferrers(e,t){return(await this.server.ldap.search({paged:!1,scope:"sub",filter:`(${e.name}=${u(t)})`,attributes:["dn",e.name]},e.binding.base)).searchEntries||[]}async findReferences(e){const t=[],r=new Set;return await Promise.all(this.referringAttributes(e).map(n=>this.server.ldap.queryLimit(async()=>{for(const i of await this.searchReferrers(n,e)){const e=String(i.dn),a=`${asDn(e)} ${n.name.toLowerCase()}`;r.has(a)||(r.add(a),t.push({dn:e,attribute:n.name,values:entryValues(i,n.name),multiValued:n.multiValued}))}}))),t}static referenceKey(e){return`${asDn(e.dn)} ${e.attribute.toLowerCase()}`}async rewriteReferences(e,t,r,n){const i=asDn(t),a=asDn(r);if(i===a)return{updated:0,failed:[]};const s=n?this.server.ldap.forRequest(n):{modify:(e,t)=>this.server.ldap.modify(e,t)},o=new Map;for(const e of await this.findReferences(t))o.set(LdapEnterpriseRules.referenceKey(e),e);let c=e.filter(e=>!o.has(LdapEnterpriseRules.referenceKey(e))).length;const u=new Map;return await Promise.all([...o.values()].map(e=>this.server.ldap.queryLimit(async()=>{try{await this.applyRewrite(s,e,i,a,r),c++}catch(n){this.logger.error(`Rename ${t} -> ${r}: could not rewrite ${e.attribute} of ${e.dn}: ${String(n)}`),u.set(e.attribute,(u.get(e.attribute)||0)+1)}}))),{updated:c,failed:[...u].map(([e,t])=>({attribute:e,count:t}))}}async applyRewrite(e,t,r,n,i){const a=this.rewrite(t,r,n,i);if(!a)return;try{return void await e.modify(t.dn,a)}catch(e){const r=e.code;if(20!==r&&16!==r)throw e;this.logger.debug(`Rewrite of ${t.attribute} on ${t.dn} met a concurrent one (code ${r}); reading it back`)}const s={...t,values:await this.freshValues(t.dn,t.attribute)},o=this.rewrite(s,r,n,i);o&&await e.modify(s.dn,o)}async freshValues(e,t){this.server.ldap.invalidateCache(e);const r=await this.server.ldap.search({paged:!1,scope:"base",attributes:[t]},e),n=r.searchEntries?.[0];return n?entryValues(n,t):[]}rewrite(e,t,r,n){const i=e.attribute;if(t===r)return;const a=e.values.filter(e=>asDn(e)===t);if(0===a.length)return;const s=e.values.some(e=>asDn(e)===r);if(!e.multiValued){const r=[];for(const i of e.values){const e=asDn(i)===t?n:i;r.some(t=>asDn(t)===asDn(e))||r.push(e)}return{replace:{[i]:r}}}return s?{delete:{[i]:a}}:{delete:{[i]:a},add:{[i]:n}}}async readEntry(e){try{const t=await this.server.ldap.search({paged:!1,scope:"base"},e);return t?.searchEntries?.[0]||null}catch(e){return null}}}export{LdapEnterpriseRules as default,fillsGeneratedAttribute,mailInDomains,parseByteSize,parseDirectoryDate,startOfUtcDay};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"../../abstract/plugin.js";import{assertModifyRequest as t}from"../../lib/ldapActions.js";import{wantJson as r,jsonBody as i,tryMethod as s,ok as a}from"../../lib/expressFormatedResponses.js";import{extractLdapCode as o}from"../../lib/ldapCodes.js";import{loadSchemaFileAsync as n,schemaUrl as h}from"../../lib/schemaFile.js";import{asyncHandler as c,isDummyMemberDn as d,escapeDnValue as p,validateDnValue as m,launchHooksChained as u,launchHooks as l,escapeLdapFilter as f,organizationLink as g,isAuthzRefusal as w,substringSearchFilter as b}from"../../lib/utils.js";import{NotFoundError as y,BadRequestError as $,HttpError as v}from"../../lib/errors.js";import{assertClientMaySet as _,modifiedAttributeNames as A,toDirectoryEntry as G,invalidValueMessage as q,missingRequiredAttribute as C,toDirectoryChanges as E,checkDnValues as j,matchesPattern as k}from"../../config/schema.js";class LdapGroups extends e{constructor(e){if(super(e),this.name="ldapGroups",this.roles=["api","configurable"],this.hooks={ldapdeletedone:async e=>{const t=Array.isArray(e)?e:[e];this.logger.debug(`User deletion detected, removing from groups: ${t.join(", ")}`),await Promise.all(t.map(e=>this.deleteMemberFromAll(e))).catch(e=>{this.logger.error("Failed to process user deletion in groups:",e)})}},this.ldap=e.ldap,this.base=this.config.ldap_group_base,this.cn=this.config.ldap_groups_main_attribute,this.base||(this.base=this.config.ldap_base,this.logger.warn(`LDAP group base is not defined, using "${this.base}"`)),!this.base)throw new Error("LDAP base is not defined, please set --ldap-group-base");if(this.config.ldap_group_base&&e.ldap.declareObjectClasses(this.base,this.config.group_class||[]),this.config.group_schema){const e=this.config.group_schema;n(e,{config:this.config}).then(e=>{this.schema=this.adaptSchema(e),this.logger.debug("Group schema loaded")}).catch(t=>this.logger.error(`Failed to load group schema from ${e}: ${t}`))}}adaptSchema(e){return e}api(e){e.get(`${this.config.api_prefix}/v1/ldap/groups`,c(async(e,t)=>{if(!r(e,t))return;const i={};if(e.query.match){if("string"!=typeof e.query.match)throw new $("Invalid match query");if("string"==typeof e.query.attribute&&e.query.attribute)i.filter=b(e.query.match,e.query.attribute);else if(/=/.test(e.query.match)){if(!/^[\w*=()&|, .@'-]+$/.test(e.query.match))throw new $("Invalid match query: contains forbidden characters");i.filter=e.query.match}else{const t=f(e.query.match);i.filter=`(${this.cn}=${t})`}}e.query.attributes&&"string"==typeof e.query.attributes&&(i.attributes=e.query.attributes.split(","));const s=await this.listGroups({...i,req:e});return a(t,this.hideNeverReturn(s))})),e.get(`${this.config.api_prefix}/v1/ldap/groups/:cn`,c(async(e,t)=>this.apiGet(e,t))),e.post(`${this.config.api_prefix}/v1/ldap/groups`,c(async(e,t)=>this.apiAdd(e,t,this.cn))),e.delete(`${this.config.api_prefix}/v1/ldap/groups/:cn`,c(async(e,t)=>this.apiDelete(e,t))),e.put(`${this.config.api_prefix}/v1/ldap/groups/:cn`,c(async(e,t)=>this.apiModify(e,t))),e.post(`${this.config.api_prefix}/v1/ldap/groups/:cn/members`,c(async(e,t)=>{const r=decodeURIComponent(e.params.cn);if(!r)throw new $("cn is required");const a=i(e,t,"member");if(!a)return;if((Array.isArray(a.member)?a.member:[a.member]).some(e=>"string"!=typeof e))throw new $("member must be a string or string array");await s(t,this.addMember.bind(this),r,a.member,e)})),e.delete(`${this.config.api_prefix}/v1/ldap/groups/:cn/members/:member`,c(async(e,t)=>{if(!r(e,t))return;const i=decodeURIComponent(e.params.cn),a=decodeURIComponent(e.params.member);if(!i||!a)throw new $("cn and member are required");await s(t,this.deleteMember.bind(this),i,a,e)})),this.config.ldap_organization_link_attribute&&this.config.ldap_organization_path_attribute&&e.post(`${this.config.api_prefix}/v1/ldap/groups/:cn/move`,c(async(e,t)=>{if(!r(e,t))return;const a=decodeURIComponent(e.params.cn);if(!a)throw new $("cn is required");const o=i(e,t,"targetOrgDn");if(o){if("string"!=typeof o.targetOrgDn)throw new $("targetOrgDn must be a string");await s(t,this.moveGroup.bind(this),a,o.targetOrgDn,e)}})),e.post(`${this.config.api_prefix}/v1/ldap/groups/:cn/rename`,c(async(e,t)=>{if(!r(e,t))return;const a=decodeURIComponent(e.params.cn);if(!a)throw new $("cn is required");const o=i(e,t,"newCn");if(o){if("string"!=typeof o.newCn)throw new $("newCn must be a string");await s(t,this.renameGroup.bind(this),a,o.newCn,e)}}))}isDummyMember(e){return d(e,this.config.group_dummy_user)}withoutDummyMember(e){const t=e.member;if(void 0===t)return e;const r=Array.isArray(t)?t:[t];return{...e,member:r.filter(e=>!this.isDummyMember(e))}}async apiGet(e,t){if(!r(e,t))return;const i=decodeURIComponent(e.params.cn);try{const r=/,/.test(i)?i:`${this.cn}=${p(i)},${this.base}`,s=await this.ldap.search({paged:!1,scope:"base"},r,e);if(0===s.searchEntries.length)throw new y("Group not found");t.json(this.hideNeverReturn(this.withoutDummyMember(s.searchEntries[0])))}catch(e){if(e.code&&32===e.code)throw new y("Group not found");throw e}}async apiAdd(e,t,...r){const a=i(e,t,...r);if(!a)return;_(this.schema,Object.keys(a),this.cn);const o=a[this.cn],n=a.member?a.member:[],h=Object.fromEntries(Object.entries(a).filter(([e,t])=>e!==this.cn&&"member"!==e&&void 0!==t&&!(!0===this.schema?.attributes[e]?.fixed)));await s(t,this.addGroup.bind(this),o,n,h,e)}async apiDelete(e,t){if(!r(e,t))return;const i=decodeURIComponent(e.params.cn);if(!i)throw new $("cn is required");await s(t,this.deleteGroup.bind(this),i,e)}async apiModify(e,r){const a=i(e,r);if(!a)return;t(a);const o=this.fixDn(decodeURIComponent(e.params.cn));if(!o)throw new $("cn is required");_(this.schema,A(a));const n=Object.fromEntries(Object.entries(a).filter(([e,t])=>!(!0===this.schema?.attributes[e]?.fixed)));await s(r,this.modifyGroup.bind(this),o,n,e)}async addGroup(e,t=[],r={},i){let s;new RegExp(`^${this.cn}=`).test(e)?(s=e,e=e.replace(new RegExp(`^${this.cn}=((?:\\\\.|[^,])+)(?:,.*)?$`),"$1")):(m(e,this.cn),s=`${this.cn}=${p(e)},${this.base}`),await this.validateMembers(s,t,i),await this.validateNewGroup(s,{objectClass:this.config.group_class,cn:e,member:t,...r});const a=[...this.config.group_class];!(r.mail||r.twakeMailboxType||r.twakeDepartmentLink||r.twakeDepartmentPath)||a.includes("twakeGroup")||a.includes("twakeStaticGroup")||a.push("twakeGroup");let o,n={objectClass:a,...this.config.group_default_attributes,[this.cn]:e,member:[this.config.group_dummy_user],...r};t.length&&n.member.push(...t),G(this.schema,n),[s,n]=await u(this.registeredHooks.ldapgroupadd,[s,n]);try{o=await this.ldap.add(s,n,i)}catch(e){if(e instanceof v)throw e;throw new Error(`Failed to add group ${s}: ${e}`)}return l(this.registeredHooks.ldapgroupadddone,[s,n]),o}async modifyGroup(e,t,r){let i=/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`;const s=this.opNumber();if([i,t]=await u(this.registeredHooks.ldapgroupmodify,[i,t,s]),t.add){if(t.add.member)throw new $("Use dedicated API to add members");if(t.add[this.cn])throw new $(`${this.cn} attribute is unique, cannot add`)}if(t.delete){if(t.delete instanceof Object){if(t.delete.member)throw new $("Use dedicated API to delete members");if(t.delete[this.cn])throw new $(`Cannot delete ${this.cn} attribute`)}if(Array.isArray(t.delete)){if(t.delete.includes("member"))throw new $("Use dedicated API to delete members");if(t.delete.includes(this.cn))throw new $(`Cannot delete ${this.cn} attribute`)}}if(t.replace){if(t.replace.member)throw new $("Use dedicated API to replace members");if(t.replace[this.cn])throw new $(`Use dedicated API to change ${this.cn} attribute`)}await this.validateChanges(i,t);const a=await this.ldap.modify(i,t,r);return l(this.registeredHooks.ldapgroupmodifydone,[i,t,s]),a}async renameGroup(e,t,r){/,/.test(e)||m(e,this.cn),/,/.test(t)||m(t,this.cn);let i=/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`,s=/,/.test(t)?t:`${this.cn}=${p(t)},${this.base}`;[i,s]=await u(this.registeredHooks.ldapgrouprename,[i,s]);const a=await this.ldap.rename(i,s,r);return l(this.registeredHooks.ldapgrouprenamedone,[i,s]),a}async deleteGroup(e,t){let r=/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`;r=await u(this.registeredHooks.ldapgroupdelete,r);const i=await this.ldap.delete(r,t);return l(this.registeredHooks.ldapgroupdeletedone,r),i}async addMember(e,t,r){const i=/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`;return Array.isArray(t)||(t=[t]),[e,t]=await u(this.registeredHooks.ldapgroupaddmember,[e,t]),await this.validateMembers(i,t,r),await this.ldap.modify(i,{add:{member:t}},r).catch(e=>{if(e instanceof v)throw e;throw new Error(`Failed to add member(s) to ${i}: ${e}`)})}async deleteMember(e,t,r){const i=/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`;if([e,t]=await u(this.registeredHooks.ldapgroupdeletemember,[e,t]),this.isDummyMember(t))throw new $("Cannot delete dummy member from group");return await this.ldap.modify(i,{delete:{member:t}},r).catch(e=>{if(16===o(e))throw new y(`${t} is not a member of ${i}`);if(e instanceof v)throw e;throw new Error(`Failed to delete member ${t} from ${i}: ${e}`)})}async deleteMemberFromAll(e){const t=await this.ldap.search({filter:`member=${f(e)}`,paged:!1,attributes:[this.cn]},this.base).catch(e=>{throw new Error(`Failed to search groups from ${this.base}: ${e}`)});await Promise.all(t.searchEntries.map(t=>this.ldap.modify(t.dn,{delete:{member:e}}).catch(r=>{if(65===o(r)&&this.config.group_dummy_user)return this.ldap.modify(t.dn,{add:{member:this.config.group_dummy_user},delete:{member:e}});throw r}).catch(r=>{const i=o(r);16!==i&&32!==i&&this.logger.error(`Failed to remove ${e} from group ${t.dn}:`,r)})))}async moveGroup(e,t,r){const i=this.config.ldap_organization_link_attribute,s=this.config.ldap_organization_path_attribute,a=/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`;let n;try{n=await this.ldap.search({paged:!1,scope:"base"},a)}catch(e){if(32===o(e))throw new y(`Group ${a} not found`);throw e}if(0===n.searchEntries.length)throw new y(`Group ${a} not found`);const h=n.searchEntries[0],c=g(h[i]);if(!c)throw new $(`Group ${a} does not have ${i} attribute and cannot be moved`);if(c===t)throw new $("Group is already in the target organization");let d;try{d=await this.ldap.search({paged:!1,scope:"base",attributes:[s,"ou","o"]},t)}catch(e){const r=o(e);if(32===r)throw new y(`Target organization ${t} not found`);if(34===r)throw new $(`${t} is not a valid DN`);throw new Error(`Target organization ${t} not found: ${e instanceof Error&&e.message?e.message:String(e)}`)}if(0===d.searchEntries.length)throw new y(`Target organization ${t} not found`);const m=d.searchEntries[0][s];if(!m)throw new Error(`Target organization ${t} does not have ${s} attribute`);return await this.ldap.modify(a,{replace:{[i]:t,[s]:m}},r),this.logger.info(`Group ${a} moved from ${c} to ${t}`),{success:!0}}async listGroups({filter:e,attributes:t,req:r}={}){const i=await this.ldap.search({filter:e||"(objectClass=*)",attributes:t||[this.cn,"member"],paged:!0},this.base,r).catch(e=>{throw new Error(`Failed to list groups from ${this.base}: ${e}`)});let s={};for await(const e of i)for(const t of e.searchEntries){const e=t[this.cn];if(Array.isArray(e)&&0===e.length)continue;e&&(s[e]=this.withoutDummyMember(t))}return s=await u(this.registeredHooks._ldapgrouplist,s),s}async searchGroupsByName(e,t=!1,r=[this.cn,"member"],i){const s=f(e),a=t?`(${this.cn}=*${s}*)`:`(${this.cn}=${s})`;return await this.listGroups({filter:a,attributes:r,req:i})}fixDn(e){return!!e&&(/,/.test(e)?e:`${this.cn}=${p(e)},${this.base}`)}async validateMembers(e,t,r){[e,t]=await u(this.server.hooks.ldapgroupvalidatemembers,[e,t]);const i=Boolean(this.config.groups_allow_unexistent_members);if((!i||r)&&t&&t.length)try{await Promise.all(t.map(e=>this.server.ldap.queryLimit(async()=>{let t;try{t=await this.ldap.search({paged:!1,scope:"base"},e,r)}catch(t){if(w(t))throw t;if(i)return;const r=o(t);if(32===r)throw new $(`Member ${e} not found`);if(34===r)throw new $(`Member ${e} is not a valid DN`);throw new Error(`Member ${e} not found: ${t}`)}if(r&&!t.searchEntries?.length)throw new $(`Member ${e} not found`)})))}catch(e){if(e instanceof v||w(e))throw e;throw new Error(`Failed to find member(s): ${e}`)}}async validateNewGroup(e,t){if(!this.schema)return G(void 0,t),!0;[e,t]=await u(this.server.hooks.ldapgroupvalidatenew,[e,t]);for(const[e,r]of Object.entries(t))if(!await this._validateOneChange(e,r))throw new $(q(e,this.schema.attributes[e]));const r=C(this.schema,t,this.server.loadedPlugins);if(r)throw new $(`Attribute "${r}" is required`);return G(this.schema,t),!0}async validateChanges(e,t){if(!this.schema)return E(void 0,t),!0;if([e,t]=await u(this.server.hooks.ldapgroupvalidatechanges,[e,t]),t.add)for(const[e,r]of Object.entries(t.add))if(!await this._validateOneChange(e,r))throw new $(q(e,this.schema.attributes[e]));if(t.replace)for(const[e,r]of Object.entries(t.replace))if(!await this._validateOneChange(e,r))throw new $(q(e,this.schema.attributes[e]));if(t.delete&&t.delete instanceof Object)for(const e of Array.isArray(t.delete)?t.delete:Object.keys(t.delete))await this._validateOneChange(e,null);return E(this.schema,t),!0}async _validateOneChange(e,t){if(!this.schema)return!0;const r=this.schema.attributes[e];if(!r){if(this.schema.strict)throw new $(`Attribute "${e}" is not allowed`);return!0}if(null==t||Array.isArray(t)&&0===t.length){if(r.required)throw new $(`Attribute "${e}" is required`);return!0}if("array"===r.type){if(!Array.isArray(t))throw new $(`Attribute "${e}" must be an array`);if(!r.items)throw new Error(`Schema error: no item for array ${e}`);if("array"===r.items.type)throw new Error(`Schema error: array of array not supported for ${e}`);if(r.items.test&&"pointer"!==r.items.type)for(const i of t)if(typeof i!==r.items.type)throw new $(`Attribute "${e}" must be of type ${r.items.type}`)}else if("pointer"!==r.type&&typeof t!==r.type)return!1;if(await j(e,r,t,async e=>(await this.ldap.search({paged:!1,scope:"base",attributes:["dn"]},e)).searchEntries.length>0),!k(r,t))throw new $(q(e,r));return!0}getConfigApiData(){const e=this.config.api_prefix||"/api",t=this.server.loadedPlugins.static&&this.config.group_schema?h(this.config,this.config.group_schema):void 0;return{enabled:!0,base:this.base||"",mainAttribute:this.cn||"cn",objectClass:this.config.group_class||["top","groupOfNames"],schema:this.schema,schemaUrl:t,endpoints:{list:`${e}/v1/ldap/groups`,get:`${e}/v1/ldap/groups/:id`,create:`${e}/v1/ldap/groups`,update:`${e}/v1/ldap/groups/:id`,delete:`${e}/v1/ldap/groups/:id`,addMember:`${e}/v1/ldap/groups/:id/members`,removeMember:`${e}/v1/ldap/groups/:id/members/:memberId`,rename:`${e}/v1/ldap/groups/:id/rename`,move:`${e}/v1/ldap/groups/:id/move`}}}}export{LdapGroups as default};
|
|
1
|
+
import e from"../../abstract/plugin.js";import{assertModifyRequest as t}from"../../lib/ldapActions.js";import{wantJson as r,jsonBody as i,tryMethod as s,ok as a}from"../../lib/expressFormatedResponses.js";import{extractLdapCode as o}from"../../lib/ldapCodes.js";import{loadSchemaFileAsync as n,schemaUrl as h}from"../../lib/schemaFile.js";import{movedToTrash as c,asyncHandler as d,isDummyMemberDn as p,escapeDnValue as m,validateDnValue as u,launchHooksChained as l,launchHooks as f,escapeLdapFilter as g,organizationLink as w,isAuthzRefusal as b,substringSearchFilter as y}from"../../lib/utils.js";import{NotFoundError as $,BadRequestError as v,HttpError as _}from"../../lib/errors.js";import{assertClientMaySet as A,modifiedAttributeNames as G,toDirectoryEntry as q,invalidValueMessage as C,missingRequiredAttribute as E,toDirectoryChanges as j,checkDnValues as k,matchesPattern as M}from"../../config/schema.js";class LdapGroups extends e{constructor(e){if(super(e),this.name="ldapGroups",this.roles=["api","configurable"],this.hooks={ldapdeletedone:async e=>{const t=Array.isArray(e)?e:[e];this.logger.debug(`User deletion detected, removing from groups: ${t.join(", ")}`),await Promise.all(t.map(e=>this.deleteMemberFromAll(e))).catch(e=>{this.logger.error("Failed to process user deletion in groups:",e)})},ldaprenamedone:async([e,t])=>{c(this.config.trash_base,e,t)&&await this.deleteMemberFromAll(e).catch(e=>{this.logger.error("Failed to process a move to the trash in groups:",e)})}},this.ldap=e.ldap,this.base=this.config.ldap_group_base,this.cn=this.config.ldap_groups_main_attribute,this.base||(this.base=this.config.ldap_base,this.logger.warn(`LDAP group base is not defined, using "${this.base}"`)),!this.base)throw new Error("LDAP base is not defined, please set --ldap-group-base");if(this.config.ldap_group_base&&e.ldap.declareObjectClasses(this.base,this.config.group_class||[]),this.config.group_schema){const e=this.config.group_schema;n(e,{config:this.config}).then(e=>{this.schema=this.adaptSchema(e),this.logger.debug("Group schema loaded")}).catch(t=>this.logger.error(`Failed to load group schema from ${e}: ${t}`))}}adaptSchema(e){return e}api(e){e.get(`${this.config.api_prefix}/v1/ldap/groups`,d(async(e,t)=>{if(!r(e,t))return;const i={};if(e.query.match){if("string"!=typeof e.query.match)throw new v("Invalid match query");if("string"==typeof e.query.attribute&&e.query.attribute)i.filter=y(e.query.match,e.query.attribute);else if(/=/.test(e.query.match)){if(!/^[\w*=()&|, .@'-]+$/.test(e.query.match))throw new v("Invalid match query: contains forbidden characters");i.filter=e.query.match}else{const t=g(e.query.match);i.filter=`(${this.cn}=${t})`}}e.query.attributes&&"string"==typeof e.query.attributes&&(i.attributes=e.query.attributes.split(","));const s=await this.listGroups({...i,req:e});return a(t,this.hideNeverReturn(s))})),e.get(`${this.config.api_prefix}/v1/ldap/groups/:cn`,d(async(e,t)=>this.apiGet(e,t))),e.post(`${this.config.api_prefix}/v1/ldap/groups`,d(async(e,t)=>this.apiAdd(e,t,this.cn))),e.delete(`${this.config.api_prefix}/v1/ldap/groups/:cn`,d(async(e,t)=>this.apiDelete(e,t))),e.put(`${this.config.api_prefix}/v1/ldap/groups/:cn`,d(async(e,t)=>this.apiModify(e,t))),e.post(`${this.config.api_prefix}/v1/ldap/groups/:cn/members`,d(async(e,t)=>{const r=decodeURIComponent(e.params.cn);if(!r)throw new v("cn is required");const a=i(e,t,"member");if(!a)return;if((Array.isArray(a.member)?a.member:[a.member]).some(e=>"string"!=typeof e))throw new v("member must be a string or string array");await s(t,this.addMember.bind(this),r,a.member,e)})),e.delete(`${this.config.api_prefix}/v1/ldap/groups/:cn/members/:member`,d(async(e,t)=>{if(!r(e,t))return;const i=decodeURIComponent(e.params.cn),a=decodeURIComponent(e.params.member);if(!i||!a)throw new v("cn and member are required");await s(t,this.deleteMember.bind(this),i,a,e)})),this.config.ldap_organization_link_attribute&&this.config.ldap_organization_path_attribute&&e.post(`${this.config.api_prefix}/v1/ldap/groups/:cn/move`,d(async(e,t)=>{if(!r(e,t))return;const a=decodeURIComponent(e.params.cn);if(!a)throw new v("cn is required");const o=i(e,t,"targetOrgDn");if(o){if("string"!=typeof o.targetOrgDn)throw new v("targetOrgDn must be a string");await s(t,this.moveGroup.bind(this),a,o.targetOrgDn,e)}})),e.post(`${this.config.api_prefix}/v1/ldap/groups/:cn/rename`,d(async(e,t)=>{if(!r(e,t))return;const a=decodeURIComponent(e.params.cn);if(!a)throw new v("cn is required");const o=i(e,t,"newCn");if(o){if("string"!=typeof o.newCn)throw new v("newCn must be a string");await s(t,this.renameGroup.bind(this),a,o.newCn,e)}}))}isDummyMember(e){return p(e,this.config.group_dummy_user)}withoutDummyMember(e){const t=e.member;if(void 0===t)return e;const r=Array.isArray(t)?t:[t];return{...e,member:r.filter(e=>!this.isDummyMember(e))}}async apiGet(e,t){if(!r(e,t))return;const i=decodeURIComponent(e.params.cn);try{const r=/,/.test(i)?i:`${this.cn}=${m(i)},${this.base}`,s=await this.ldap.search({paged:!1,scope:"base"},r,e);if(0===s.searchEntries.length)throw new $("Group not found");t.json(this.hideNeverReturn(this.withoutDummyMember(s.searchEntries[0])))}catch(e){if(e.code&&32===e.code)throw new $("Group not found");throw e}}async apiAdd(e,t,...r){const a=i(e,t,...r);if(!a)return;A(this.schema,Object.keys(a),this.cn);const o=a[this.cn],n=a.member?a.member:[],h=Object.fromEntries(Object.entries(a).filter(([e,t])=>e!==this.cn&&"member"!==e&&void 0!==t&&!(!0===this.schema?.attributes[e]?.fixed)));await s(t,this.addGroup.bind(this),o,n,h,e)}async apiDelete(e,t){if(!r(e,t))return;const i=decodeURIComponent(e.params.cn);if(!i)throw new v("cn is required");await s(t,this.deleteGroup.bind(this),i,e)}async apiModify(e,r){const a=i(e,r);if(!a)return;t(a);const o=this.fixDn(decodeURIComponent(e.params.cn));if(!o)throw new v("cn is required");A(this.schema,G(a));const n=Object.fromEntries(Object.entries(a).filter(([e,t])=>!(!0===this.schema?.attributes[e]?.fixed)));await s(r,this.modifyGroup.bind(this),o,n,e)}async addGroup(e,t=[],r={},i){let s;new RegExp(`^${this.cn}=`).test(e)?(s=e,e=e.replace(new RegExp(`^${this.cn}=((?:\\\\.|[^,])+)(?:,.*)?$`),"$1")):(u(e,this.cn),s=`${this.cn}=${m(e)},${this.base}`),await this.validateMembers(s,t,i),await this.validateNewGroup(s,{objectClass:this.config.group_class,cn:e,member:t,...r});const a=[...this.config.group_class];!(r.mail||r.twakeMailboxType||r.twakeDepartmentLink||r.twakeDepartmentPath)||a.includes("twakeGroup")||a.includes("twakeStaticGroup")||a.push("twakeGroup");let o,n={objectClass:a,...this.config.group_default_attributes,[this.cn]:e,member:[this.config.group_dummy_user],...r};t.length&&n.member.push(...t),q(this.schema,n),[s,n]=await l(this.registeredHooks.ldapgroupadd,[s,n]);try{o=await this.ldap.add(s,n,i)}catch(e){if(e instanceof _)throw e;throw new Error(`Failed to add group ${s}: ${e}`)}return f(this.registeredHooks.ldapgroupadddone,[s,n]),o}async modifyGroup(e,t,r){let i=/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`;const s=this.opNumber();if([i,t]=await l(this.registeredHooks.ldapgroupmodify,[i,t,s]),t.add){if(t.add.member)throw new v("Use dedicated API to add members");if(t.add[this.cn])throw new v(`${this.cn} attribute is unique, cannot add`)}if(t.delete){if(t.delete instanceof Object){if(t.delete.member)throw new v("Use dedicated API to delete members");if(t.delete[this.cn])throw new v(`Cannot delete ${this.cn} attribute`)}if(Array.isArray(t.delete)){if(t.delete.includes("member"))throw new v("Use dedicated API to delete members");if(t.delete.includes(this.cn))throw new v(`Cannot delete ${this.cn} attribute`)}}if(t.replace){if(t.replace.member)throw new v("Use dedicated API to replace members");if(t.replace[this.cn])throw new v(`Use dedicated API to change ${this.cn} attribute`)}await this.validateChanges(i,t);const a=await this.ldap.modify(i,t,r);return f(this.registeredHooks.ldapgroupmodifydone,[i,t,s]),a}async renameGroup(e,t,r){/,/.test(e)||u(e,this.cn),/,/.test(t)||u(t,this.cn);let i=/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`,s=/,/.test(t)?t:`${this.cn}=${m(t)},${this.base}`;[i,s]=await l(this.registeredHooks.ldapgrouprename,[i,s]);const a=await this.ldap.rename(i,s,r);return f(this.registeredHooks.ldapgrouprenamedone,[i,s]),a}async deleteGroup(e,t){let r=/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`;r=await l(this.registeredHooks.ldapgroupdelete,r);const i=await this.ldap.delete(r,t);return f(this.registeredHooks.ldapgroupdeletedone,r),i}async addMember(e,t,r){const i=/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`;return Array.isArray(t)||(t=[t]),[e,t]=await l(this.registeredHooks.ldapgroupaddmember,[e,t]),await this.validateMembers(i,t,r),await this.ldap.modify(i,{add:{member:t}},r).catch(e=>{if(e instanceof _)throw e;throw new Error(`Failed to add member(s) to ${i}: ${e}`)})}async deleteMember(e,t,r){const i=/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`;if([e,t]=await l(this.registeredHooks.ldapgroupdeletemember,[e,t]),this.isDummyMember(t))throw new v("Cannot delete dummy member from group");return await this.ldap.modify(i,{delete:{member:t}},r).catch(e=>{if(16===o(e))throw new $(`${t} is not a member of ${i}`);if(e instanceof _)throw e;throw new Error(`Failed to delete member ${t} from ${i}: ${e}`)})}async deleteMemberFromAll(e){const t=await this.ldap.search({filter:`member=${g(e)}`,paged:!1,attributes:[this.cn]},this.base).catch(e=>{throw new Error(`Failed to search groups from ${this.base}: ${e}`)});await Promise.all(t.searchEntries.map(t=>this.ldap.modify(t.dn,{delete:{member:e}}).catch(r=>{if(65===o(r)&&this.config.group_dummy_user)return this.ldap.modify(t.dn,{add:{member:this.config.group_dummy_user},delete:{member:e}});throw r}).catch(r=>{const i=o(r);16!==i&&32!==i&&this.logger.error(`Failed to remove ${e} from group ${t.dn}:`,r)})))}async moveGroup(e,t,r){const i=this.config.ldap_organization_link_attribute,s=this.config.ldap_organization_path_attribute,a=/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`;let n;try{n=await this.ldap.search({paged:!1,scope:"base"},a)}catch(e){if(32===o(e))throw new $(`Group ${a} not found`);throw e}if(0===n.searchEntries.length)throw new $(`Group ${a} not found`);const h=n.searchEntries[0],c=w(h[i]);if(!c)throw new v(`Group ${a} does not have ${i} attribute and cannot be moved`);if(c===t)throw new v("Group is already in the target organization");let d;try{d=await this.ldap.search({paged:!1,scope:"base",attributes:[s,"ou","o"]},t)}catch(e){const r=o(e);if(32===r)throw new $(`Target organization ${t} not found`);if(34===r)throw new v(`${t} is not a valid DN`);throw new Error(`Target organization ${t} not found: ${e instanceof Error&&e.message?e.message:String(e)}`)}if(0===d.searchEntries.length)throw new $(`Target organization ${t} not found`);const p=d.searchEntries[0][s];if(!p)throw new Error(`Target organization ${t} does not have ${s} attribute`);return await this.ldap.modify(a,{replace:{[i]:t,[s]:p}},r),this.logger.info(`Group ${a} moved from ${c} to ${t}`),{success:!0}}async listGroups({filter:e,attributes:t,req:r}={}){const i=await this.ldap.search({filter:e||"(objectClass=*)",attributes:t||[this.cn,"member"],paged:!0},this.base,r).catch(e=>{throw new Error(`Failed to list groups from ${this.base}: ${e}`)});let s={};for await(const e of i)for(const t of e.searchEntries){const e=t[this.cn];if(Array.isArray(e)&&0===e.length)continue;e&&(s[e]=this.withoutDummyMember(t))}return s=await l(this.registeredHooks._ldapgrouplist,s),s}async searchGroupsByName(e,t=!1,r=[this.cn,"member"],i){const s=g(e),a=t?`(${this.cn}=*${s}*)`:`(${this.cn}=${s})`;return await this.listGroups({filter:a,attributes:r,req:i})}fixDn(e){return!!e&&(/,/.test(e)?e:`${this.cn}=${m(e)},${this.base}`)}async validateMembers(e,t,r){[e,t]=await l(this.server.hooks.ldapgroupvalidatemembers,[e,t]);const i=Boolean(this.config.groups_allow_unexistent_members);if((!i||r)&&t&&t.length)try{await Promise.all(t.map(e=>this.server.ldap.queryLimit(async()=>{let t;try{t=await this.ldap.search({paged:!1,scope:"base"},e,r)}catch(t){if(b(t))throw t;if(i)return;const r=o(t);if(32===r)throw new v(`Member ${e} not found`);if(34===r)throw new v(`Member ${e} is not a valid DN`);throw new Error(`Member ${e} not found: ${t}`)}if(r&&!t.searchEntries?.length)throw new v(`Member ${e} not found`)})))}catch(e){if(e instanceof _||b(e))throw e;throw new Error(`Failed to find member(s): ${e}`)}}async validateNewGroup(e,t){if(!this.schema)return q(void 0,t),!0;[e,t]=await l(this.server.hooks.ldapgroupvalidatenew,[e,t]);for(const[e,r]of Object.entries(t))if(!await this._validateOneChange(e,r))throw new v(C(e,this.schema.attributes[e]));const r=E(this.schema,t,this.server.loadedPlugins);if(r)throw new v(`Attribute "${r}" is required`);return q(this.schema,t),!0}async validateChanges(e,t){if(!this.schema)return j(void 0,t),!0;if([e,t]=await l(this.server.hooks.ldapgroupvalidatechanges,[e,t]),t.add)for(const[e,r]of Object.entries(t.add))if(!await this._validateOneChange(e,r))throw new v(C(e,this.schema.attributes[e]));if(t.replace)for(const[e,r]of Object.entries(t.replace))if(!await this._validateOneChange(e,r))throw new v(C(e,this.schema.attributes[e]));if(t.delete&&t.delete instanceof Object)for(const e of Array.isArray(t.delete)?t.delete:Object.keys(t.delete))await this._validateOneChange(e,null);return j(this.schema,t),!0}async _validateOneChange(e,t){if(!this.schema)return!0;const r=this.schema.attributes[e];if(!r){if(this.schema.strict)throw new v(`Attribute "${e}" is not allowed`);return!0}if(null==t||Array.isArray(t)&&0===t.length){if(r.required)throw new v(`Attribute "${e}" is required`);return!0}if("array"===r.type){if(!Array.isArray(t))throw new v(`Attribute "${e}" must be an array`);if(!r.items)throw new Error(`Schema error: no item for array ${e}`);if("array"===r.items.type)throw new Error(`Schema error: array of array not supported for ${e}`);if(r.items.test&&"pointer"!==r.items.type)for(const i of t)if(typeof i!==r.items.type)throw new v(`Attribute "${e}" must be of type ${r.items.type}`)}else if("pointer"!==r.type&&typeof t!==r.type)return!1;if(await k(e,r,t,async e=>(await this.ldap.search({paged:!1,scope:"base",attributes:["dn"]},e)).searchEntries.length>0),!M(r,t))throw new v(C(e,r));return!0}getConfigApiData(){const e=this.config.api_prefix||"/api",t=this.server.loadedPlugins.static&&this.config.group_schema?h(this.config,this.config.group_schema):void 0;return{enabled:!0,base:this.base||"",mainAttribute:this.cn||"cn",objectClass:this.config.group_class||["top","groupOfNames"],schema:this.schema,schemaUrl:t,endpoints:{list:`${e}/v1/ldap/groups`,get:`${e}/v1/ldap/groups/:id`,create:`${e}/v1/ldap/groups`,update:`${e}/v1/ldap/groups/:id`,delete:`${e}/v1/ldap/groups/:id`,addMember:`${e}/v1/ldap/groups/:id/members`,removeMember:`${e}/v1/ldap/groups/:id/members/:memberId`,rename:`${e}/v1/ldap/groups/:id/rename`,move:`${e}/v1/ldap/groups/:id/move`}}}}export{LdapGroups as default};
|