ldap-rest 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # LDAP-Rest
4
4
 
5
- Lite LinID directory manager is a lightweight directory manager that provides LDAP integration through a plugin-based architecture.
5
+ **ldap-rest** is a lightweight directory manager that provides LDAP integration through a plugin-based architecture.
6
6
  This system enables directory management operations with configurable authentication, extensible functionality through events/hooks,
7
7
  and extensible REST API.
8
8
 
@@ -1 +1 @@
1
- import{fileURLToPath as a}from"url";import{join as _,dirname as e}from"path";const t=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls"],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",300,"number"],["--ldap-pool-size","DM_LDAP_POOL_SIZE",5,"number"],["--ldap-connection-ttl","DM_LDAP_CONNECTION_TTL",60,"number"],["--schemas-path","DM_SCHEMAS_PATH",_(e(a(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuota"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",_(e(a(import.meta.url)),"..","..","static","schemas","twake","groups.json")],["--external-members-branch","DM_EXTERNAL_MEMBERS_BRANCH","ou=contacts,dc=example,dc=com"],["--external-branch-class","DM_EXTERNAL_BRANCH_CLASSES",["top","inetOrgPerson"],"array","--external-branch-classes"],["--static-path","DM_STATIC_PATH",_(e(a(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--ldap-flat-schema","DM_LDAP_FLAT_SCHEMA",[],"array","--ldap-flat-schemas"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-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-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-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-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
1
+ import{fileURLToPath as _}from"url";import{join as a,dirname as e}from"path";const t=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls"],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",300,"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","mailQuota"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",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"],["--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"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--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-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-route","DM_AUTHZ_PER_ROUTES",[],"array","--authz-per-routes"],["--authz-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-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-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-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
@@ -1 +1 @@
1
- let r;const setLogger=t=>{r=t},getLogger=()=>r,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=(t,e,n)=>{r?.warn(`Client error ${t}: ${n}`),e.status(t).json({error:n})},ok=(r,t={success:!0})=>{r.status(200).json(t)},created=(r,t)=>{r.status(201).json(t)},badRequest=(r,t="Bad request")=>_rejectResponse(400,r,t),unauthorized=(r,t="Unauthorized")=>_rejectResponse(401,r,t),notFound=(r,t="Not found")=>_rejectResponse(404,r,t),serverError=(t,e)=>{const n=e instanceof Error?e.message:String(e);let s=e instanceof Error&&"statusCode"in e?e.statusCode:500;if(500===s&&/\[authz-forbidden\]/.test(n)&&(s=403),s>=400&&s<500){const e=403===s&&/\[authz-forbidden\]/.test(n)?"Token does not have permission on this branch":n;return r.warn(`Client error ${s}: ${e}`),void t.status(s).json({error:e})}e instanceof Error?r.error(e.message,e):"string"==typeof e?r.error(e):r.error("Server error",e),t.status(s).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(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,getLogger,jsonBody,notFound,ok,serverError,setLogger,tryMethod,tryMethodData,unauthorized,wantJson};
1
+ let r;const setLogger=t=>{r=t},getLogger=()=>r,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=(t,e,n)=>{r?.warn(`Client error ${t}: ${n}`),e.status(t).json({error:n})},ok=(r,t={success:!0})=>{r.status(200).json(t)},created=(r,t)=>{r.status(201).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=(t,e)=>{const n=e instanceof Error?e.message:String(e);let o=e instanceof Error&&"statusCode"in e?e.statusCode:500;if(500===o&&/\[authz-forbidden\]/.test(n)&&(o=403),o>=400&&o<500){const e=403===o&&/\[authz-forbidden\]/.test(n)?"Token does not have permission on this branch":n;return r.warn(`Client error ${o}: ${e}`),void t.status(o).json({error:e})}e instanceof Error?r.error(e.message,e):"string"==typeof e?r.error(e):r.error("Server error",e),t.status(o).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(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,notFound,ok,serverError,setLogger,tryMethod,tryMethodData,unauthorized,wantJson};
@@ -0,0 +1 @@
1
+ import e from"../../abstract/plugin.js";import{forbidden as t}from"../../lib/expressFormatedResponses.js";const r=/^[\w/.\-+*]*$/;function globToRegex(e){if(!r.test(e))throw new Error(`Invalid glob pattern: "${e}" — only [a-zA-Z0-9_/.-+*] are allowed`);const t=e.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/\\\*\\\*/g,".*").replace(/\\\*/g,"[^/]*");return new RegExp(`^${t}$`)}class AuthzPerRoute extends e{constructor(...e){super(...e),this.name="authzPerRoute",this.roles=["authz"],this.rules=new Map;const t=this.config.authz_per_route??[];for(const e of t)this.parseEntry(e);const r=[...this.rules.entries()].map(([e,t])=>`${e}: ${t.some(e=>"wildcard"===e.kind)?"full access":`${t.length} rule${1!==t.length?"s":""}`}`).join(", ");this.logger.info(`authzPerRoute: ${this.rules.size} user${1!==this.rules.size?"s":""} configured${this.rules.size>0?` (${r})`:""}`)}parseEntry(e){const t=e.split(":").map(e=>e.trim());if(t.length<2)return void this.logger.warn(`authzPerRoute: ignoring invalid rule entry: ${e}`);const r=t[0];if(r)if(2!==t.length||"*"!==t[1]){if(t.length>=3){const i=t[1].toUpperCase(),o=new Set(["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS","*"]);if(!i||!o.has(i))return void this.logger.warn(`authzPerRoute: ignoring rule with invalid method "${t[1]}" in entry: ${e}`);const n=t.slice(2).join(":").trim();if(!n)return void this.logger.warn(`authzPerRoute: ignoring rule with empty path pattern in entry: ${e}`);let s;try{s=globToRegex(n)}catch{return void this.logger.warn(`authzPerRoute: ignoring entry with invalid glob "${n}" in rule: ${e}`)}return void this.addRule(r,{kind:"method-path",method:i,pathRe:s})}this.logger.warn(`authzPerRoute: ignoring invalid rule entry: ${e}`)}else this.addRule(r,{kind:"wildcard"});else this.logger.warn(`authzPerRoute: ignoring rule with empty user: ${e}`)}addRule(e,t){const r=this.rules.get(e);r?r.push(t):this.rules.set(e,[t])}matches(e,t,r){for(const i of e){if("wildcard"===i.kind)return!0;if(("*"===i.method||i.method===t.toUpperCase())&&i.pathRe.test(r))return!0}return!1}api(e){e.use((e,r,i)=>{const o=e.user;if(!o)return i();const n=this.rules.get(o);return n?this.matches(n,e.method,e.path)?i():(this.logger.warn(`authzPerRoute: user '${o}' denied for ${e.method} ${e.path}`),t(r)):(this.logger.warn(`authzPerRoute: user '${o}' denied for ${e.method} ${e.path} (no rules configured)`),t(r))})}}export{AuthzPerRoute as default,globToRegex};
@@ -1 +1 @@
1
- import t from"fs";import i from"../../abstract/plugin.js";import{tryMethodData as a,jsonBody as e,tryMethod as n,wantJson as r}from"../../lib/expressFormatedResponses.js";import{transformSchemas as s,asyncHandler as o,validateDnValue as h,escapeDnValue as c,launchHooksChained as d,escapeLdapFilter as g}from"../../lib/utils.js";import{BadRequestError as l,NotFoundError as p,ConflictError as f}from"../../lib/errors.js";class LdapOrganizations extends i{constructor(i){if(super(i),this.name="ldapOrganizations",this.roles=["api","consistency","configurable"],this.hooks={ldapaddrequest:async([t,i,a])=>(this.isOu(i)||await this.checkDeptLink(i),this.isOu(i)&&await this.checkDeptPath(i),void 0!==a?[t,i,a]:[t,i]),ldapmodifyrequest:async([t,i,a])=>{let e,n={},r={};const checkIsOu=async()=>{if(void 0!==e)return e;if(i.replace?.objectClass)e=this.isOu({objectClass:i.replace.objectClass});else if(i.add?.objectClass)e=this.isOu({objectClass:i.add.objectClass});else{const i=await this.server.ldap.search({paged:!1,scope:"base"},t);e=i.searchEntries.length>0&&this.isOu(i.searchEntries[0])}return e};if(i.delete){const t=Array.isArray(i.delete)?i.delete.includes(this.linkAttr):i.delete[this.linkAttr],a=Array.isArray(i.delete)?i.delete.includes(this.pathAttr):i.delete[this.pathAttr];if(t||a){if(!await checkIsOu()&&t)throw new l("An organization link cannot be deleted");if(a)throw new l("An organization path cannot be deleted")}}if(i.replace&&(i.replace[this.linkAttr]&&(n={...i.replace}),i.replace[this.pathAttr]&&(r={...i.replace})),i.add&&(i.add[this.linkAttr]&&(n={...n,...i.add}),i.add[this.pathAttr]&&(r={...r,...i.add})),Object.keys(n).length>0){await checkIsOu()||await this.checkDeptLink(n)}if(Object.keys(r).length>0){await checkIsOu()&&await this.checkDeptPath(r)}return[t,i,a]},ldapdeleterequest:async([t])=>(/^ou=/.test(t)&&await this.isEmptyOrganization(t),[t]),ldaprenamerequest:([t,i])=>[t,i]},!this.config.ldap_top_organization)throw new Error("Missing --ldap-top-organization");this.pathAttr=this.config.ldap_organization_path_attribute,this.linkAttr=this.config.ldap_organization_link_attribute,this.config.organization_schema&&t.readFile(this.config.organization_schema,(t,i)=>{if(t)this.logger.error(`Failed to load organization schema from ${this.config.organization_schema}: ${t}`);else try{this.schema=JSON.parse(s(i.toString(),this.config)),this.logger.debug("Organization schema loaded")}catch(t){this.logger.error(`Failed to parse organization schema: ${t}`)}})}api(t){t.get(`${this.config.api_prefix}/v1/ldap/organizations/top`,async(t,i)=>{await a(i,this.getOrganisationTop.bind(this),t)}),t.get(`${this.config.api_prefix}/v1/ldap/organizations/:dn`,async(t,i)=>{const e=decodeURIComponent(t.params.dn);await a(i,this.getOrganisationByDn.bind(this),e,t)}),t.get(`${this.config.api_prefix}/v1/ldap/organizations/:dn/subnodes`,async(t,i)=>{const e=decodeURIComponent(t.params.dn);await a(i,this.getOrganisationSubnodes.bind(this),e,t)}),t.get(`${this.config.api_prefix}/v1/ldap/organizations/:dn/subnodes/search`,o(async(t,i)=>{const e=decodeURIComponent(t.params.dn),n=t.query.q;if(!n)throw new l('query parameter "q" is required');await a(i,this.searchOrganisationSubnodes.bind(this),e,n)})),t.post(`${this.config.api_prefix}/v1/ldap/organizations`,o(async(t,i)=>this.apiAdd(t,i))),t.put(`${this.config.api_prefix}/v1/ldap/organizations/:dn`,o(async(t,i)=>this.apiModify(t,i))),t.delete(`${this.config.api_prefix}/v1/ldap/organizations/:dn`,o(async(t,i)=>this.apiDelete(t,i))),t.post(`${this.config.api_prefix}/v1/ldap/organizations/:dn/move`,o(async(t,i)=>this.apiMove(t,i)))}async apiAdd(t,i){const a=e(t,i,"ou");if(!a)return;h(a.ou,"ou");const r=a.parentDn||this.config.ldap_top_organization,s=`ou=${c(a.ou)},${r}`,o={objectClass:this.config.ldap_organization_class,ou:a.ou,...Object.fromEntries(Object.entries(a).filter(([t])=>"ou"!==t&&"parentDn"!==t))};await n(i,this.addOrganization.bind(this),s,o,t)}async apiModify(t,i){const a=e(t,i);if(!a)return;const r=decodeURIComponent(t.params.dn);if(!r)throw new l("dn is required");await n(i,this.modifyOrganization.bind(this),r,a)}async apiDelete(t,i){if(!r(t,i))return;const a=decodeURIComponent(t.params.dn);if(!a)throw new l("dn is required");await n(i,this.deleteOrganization.bind(this),a)}async apiMove(t,i){if(!r(t,i))return;const n=e(t,i,"targetOrgDn");if(!n)return;const s=decodeURIComponent(t.params.dn);if(!s)throw new l("dn is required");const{targetOrgDn:o}=n;if(!o||"string"!=typeof o)throw new l("Missing or invalid targetOrgDn in request body");await a(i,this.moveOrganization.bind(this),s,o,t)}async checkDeptLink(t){if(t[this.linkAttr]){const i=t[this.linkAttr],a=Array.isArray(i)?i[0]:i,e=await this.server.ldap.search({paged:!1,scope:"base"},a);if(0===e.searchEntries.length)throw new p(`Organization ${a} does not exist`);if(!new RegExp(`(.*,)?${this.config.ldap_top_organization}`).test(e.searchEntries[0].dn))throw new l(`Entry ${a} isn't in top organization branch`)}}async checkDeptPath(t){if(t[this.pathAttr]){const i=t[this.pathAttr],a=Array.isArray(i)?i[0]:i,e=this.config.ldap_organization_path_separator||" / ";let n=a;if(this.isOu(t)){const i=t.ou,r=Array.isArray(i)?i[0]:i;if(!a.startsWith(r+e))throw new l(`Organization path must start with its own name followed by separator "${e}"`);n=a.slice(r.length+e.length)}const[r,s]=n.split(e,2),o=await this.server.ldap.search({paged:!1,filter:`(ou=${r})`,scope:"sub"},this.config.ldap_top_organization);if(0===o.searchEntries.length)throw new l(`Invalid organization path ${a}`);if(s){let t=!1;for(const i of o.searchEntries){const a=i[this.pathAttr],e=Array.isArray(a)?a[0]:a;if(e&&e===s){t=!0;break}}if(!t)throw new l(`Invalid organization path ${a}: no matching entry for ${r} with path ${s}`)}else{let t=!1;for(const i of o.searchEntries){const a=i.dn,n=this.config.ldap_top_organization;if(a.toLowerCase()===n.toLowerCase()||a.toLowerCase().endsWith(`,${n.toLowerCase()}`)){const a=i[this.pathAttr],n=Array.isArray(a)?a[0]:a;if(!n||n===r||!n.includes(e)){t=!0;break}}}if(!t)throw new l(`Invalid organization path ${a}: no matching top-level entry for ${r}`)}}}async isEmptyOrganization(t){if((await this.server.ldap.search({paged:!1,filter:`(${this.config.ldap_organization_link_attribute}=${t})`})).searchEntries.length>0)throw new f(`Organization ${t} is not empty`)}isOu(t){if(!t.objectClass)return!1;const i=t.objectClass.map(t=>t.toLowerCase());return this.config.ldap_organization_class.filter(t=>"top"!==t.toLowerCase()).some(t=>i.includes(t.toLowerCase()))}async getOrganisationTop(t){if(!this.config.ldap_top_organization)throw new l("No top organization configured");const i=await this.server.ldap.search({paged:!1,scope:"base"},this.config.ldap_top_organization,t);if(1!==i.searchEntries.length)throw new p("Top organization not found");const[,a]=await d(this.registeredHooks.getOrganisationTop,[t,i.searchEntries[0]]);return a}async getOrganisationByDn(t,i){const a=await this.server.ldap.search({paged:!1,scope:"base",filter:"(objectClass=organizationalUnit)"},t,i);if(1!==a.searchEntries.length)throw new p(`Organization ${t} not found`);return a.searchEntries[0]}async getOrganisationSubnodes(t,i){const a=[],e=this.config.ldap_organization_max_subnodes||50,n=i?.query?.objectClass;if(!n||"organizationalUnit"===n)try{const e=await this.server.ldap.search({paged:!1,scope:"one",filter:"(objectClass=organizationalUnit)"},t,i);this.server.logger.debug(`Found ${e.searchEntries.length} sub-OUs for ${t}`),a.push(...e.searchEntries)}catch(i){this.server.logger.debug(`No sub-OUs found for ${t}: ${i}`)}const r=this.config.ldap_top_organization.replace(/^ou=[^,]+,/,"");let s=`(${this.config.ldap_organization_link_attribute}=${t})`;n&&(s=`(&${s}(objectClass=${n}))`),this.server.logger.debug(`Searching for linked entities with filter: ${s} in ${r}`);const o=await this.server.ldap.search({paged:!0,filter:s},r,i);let h=0,c=0;for await(const t of o){c+=t.searchEntries.length;const i=e-h;if(i>0){const e=t.searchEntries.slice(0,i);a.push(...e),h+=e.length}}return this.server.logger.debug(`Found ${c} linked entities for ${t}, returning ${h}`),c>e&&a.push({dn:`more-${t}`,cn:[`... ${c-e} more elements`],objectClass:["moreIndicator"],_isMoreIndicator:"true",_totalCount:c.toString(),_displayedCount:e.toString()}),a}async addOrganization(t,i,a){return this.validateNewOrganization(t,i),await this.server.ldap.add(t,i,a)}async modifyOrganization(t,i){return this.validateChanges(t,i),await this.server.ldap.modify(t,i)}validateNewOrganization(t,i){if(!this.schema)return!0;for(const[t,a]of Object.entries(i))if(!this._validateOneChange(t,a))throw new l(`Invalid value for field ${t}`);for(const[t,a]of Object.entries(this.schema.attributes))if(a.required&&null==i[t])throw new l(`Missing required field ${t}`);return!0}validateChanges(t,i){if(!this.schema)return!0;if(i.add)for(const[t,a]of Object.entries(i.add))if(!this._validateOneChange(t,a))throw new l(`Invalid value for field ${t}`);if(i.replace)for(const[t,a]of Object.entries(i.replace))if(!this._validateOneChange(t,a))throw new l(`Invalid value for field ${t}`);return!0}_validateOneChange(t,i){if(!this.schema)return!0;const a=this.schema.attributes[t];if(!a){if(this.schema.strict)throw new Error(`Field ${t} is not allowed`);return!0}if(null==i){if(a.required)throw new Error(`Field ${t} is required`);return!0}if(a.type)switch(a.type){case"string":if(Array.isArray(i))throw new Error(`Field ${t} must be a single value`);break;case"array":if(!Array.isArray(i))throw new Error(`Field ${t} must be an array`)}if(a.test){const e=Array.isArray(i)?i[0]:i;if(!("string"==typeof a.test?new RegExp(a.test):a.test).test(String(e)))throw new Error(`Field ${t} does not match required pattern`)}return!0}async moveOrganization(t,i,a){try{const t=await this.server.ldap.search({paged:!1,scope:"base"},i);if(!t.searchEntries||0===t.searchEntries.length)throw new Error(`Target organization ${i} not found`);if(!this.isOu(t.searchEntries[0]))throw new Error(`Target ${i} is not an organizational unit`)}catch(t){throw new Error(`Invalid target organization: ${t}`)}const e=`${t.split(",")[0]},${i}`;try{const i=await this.server.ldap.search({paged:!1,scope:"base"},t);if(!i.searchEntries||0===i.searchEntries.length)throw new Error(`Source organization ${t} not found`)}catch(t){throw new Error(`Source organization not found: ${t}`)}if(e===t)throw new Error("Cannot move organization to its current location");if(i===t||i.endsWith(`,${t}`))throw new Error("Cannot move organization into itself or its descendant");try{await this.server.ldap.rename(t,e,a),this.logger.info(`Moved organization from ${t} to ${e}`)}catch(t){throw new Error(`Failed to move organization: ${t}`)}return{newDn:e}}async deleteOrganization(t){return await this.server.ldap.delete(t)}async searchOrganisationSubnodes(t,i){const a=[],e=g(i);try{const n=await this.server.ldap.search({paged:!1,scope:"one",filter:`(&(objectClass=organizationalUnit)(|(ou=*${e}*)(description=*${e}*)))`},t);this.server.logger.debug(`Found ${n.searchEntries.length} sub-OUs matching "${i}" for ${t}`),a.push(...n.searchEntries)}catch(a){this.server.logger.debug(`No sub-OUs found matching "${i}" for ${t}: ${a}`)}const n=this.config.ldap_top_organization.replace(/^ou=[^,]+,/,""),r=g(t),s=`(&(${this.config.ldap_organization_link_attribute}=${r})(|(uid=*${e}*)(cn=*${e}*)(mail=*${e}*)(sn=*${e}*)(givenName=*${e}*)))`;this.server.logger.debug(`Searching for linked entities with filter: ${s} in ${n}`);const o=await this.server.ldap.search({paged:!0,filter:s},n);let h=0;for await(const t of o)h+=t.searchEntries.length,a.push(...t.searchEntries);return this.server.logger.debug(`Found ${h} linked entities matching "${i}" for ${t}`),a}getConfigApiData(){const t=this.config.api_prefix||"/api";let i;if(this.server.loadedPlugins.static&&this.config.organization_schema){const t=this.config.static_name||"static",a=this.config.organization_schema.indexOf("/schemas/");if(-1!==a){i=`/${t}${this.config.organization_schema.substring(a)}`}}return{enabled:!0,topOrganization:this.config.ldap_top_organization||"",organizationClass:this.config.ldap_organization_class||["top","organizationalUnit"],linkAttribute:this.linkAttr||"",pathAttribute:this.pathAttr||"",pathSeparator:this.config.ldap_organization_path_separator||" / ",maxSubnodes:this.config.ldap_organization_max_subnodes||50,schema:this.schema,schemaUrl:i,endpoints:{getTop:`${t}/v1/ldap/organizations`,get:`${t}/v1/ldap/organizations/:dn`,getSubnodes:`${t}/v1/ldap/organizations/:dn/subnodes`,searchSubnodes:`${t}/v1/ldap/organizations/:dn/subnodes/search`,create:`${t}/v1/ldap/organizations`,update:`${t}/v1/ldap/organizations/:dn`,move:`${t}/v1/ldap/organizations/:dn/move`,delete:`${t}/v1/ldap/organizations/:dn`}}}}export{LdapOrganizations as default};
1
+ import a from"./organizations.js";console.warn("[ldap-rest] Plugin `core/ldap/organization` is deprecated; use `core/ldap/organizations` instead.");export{a as default};
@@ -0,0 +1 @@
1
+ import t from"fs";import i from"../../abstract/plugin.js";import{tryMethodData as a,jsonBody as e,tryMethod as n,wantJson as r}from"../../lib/expressFormatedResponses.js";import{transformSchemas as s,asyncHandler as o,validateDnValue as h,escapeDnValue as c,escapeLdapFilter as d,launchHooksChained as g,getParentDn as l,getRdn as p,isChildOf as f}from"../../lib/utils.js";import{BadRequestError as u,NotFoundError as w,ConflictError as $}from"../../lib/errors.js";class LdapOrganizations extends i{constructor(i){if(super(i),this.name="ldapOrganizations",this.roles=["api","consistency","configurable"],this.hooks={ldapaddrequest:async([t,i,a])=>(this.isOu(i)||await this.checkDeptLink(i),this.isOu(i)&&await this.checkDeptPath(i),void 0!==a?[t,i,a]:[t,i]),ldapmodifyrequest:async([t,i,a])=>{let e,n={},r={};const checkIsOu=async()=>{if(void 0!==e)return e;if(i.replace?.objectClass)e=this.isOu({objectClass:i.replace.objectClass});else if(i.add?.objectClass)e=this.isOu({objectClass:i.add.objectClass});else{const i=await this.server.ldap.search({paged:!1,scope:"base"},t);e=i.searchEntries.length>0&&this.isOu(i.searchEntries[0])}return e};if(i.delete){const t=Array.isArray(i.delete)?i.delete.includes(this.linkAttr):i.delete[this.linkAttr],a=Array.isArray(i.delete)?i.delete.includes(this.pathAttr):i.delete[this.pathAttr];if(t||a){if(!await checkIsOu()&&t)throw new u("An organization link cannot be deleted");if(a)throw new u("An organization path cannot be deleted")}}if(i.replace&&(i.replace[this.linkAttr]&&(n={...i.replace}),i.replace[this.pathAttr]&&(r={...i.replace})),i.add&&(i.add[this.linkAttr]&&(n={...n,...i.add}),i.add[this.pathAttr]&&(r={...r,...i.add})),Object.keys(n).length>0){await checkIsOu()||await this.checkDeptLink(n)}if(Object.keys(r).length>0){await checkIsOu()&&await this.checkDeptPath(r)}return[t,i,a]},ldapdeleterequest:async([t])=>(/^ou=/.test(t)&&await this.isEmptyOrganization(t),[t]),ldaprenamerequest:([t,i])=>[t,i]},!this.config.ldap_top_organization)throw new Error("Missing --ldap-top-organization");this.pathAttr=this.config.ldap_organization_path_attribute,this.linkAttr=this.config.ldap_organization_link_attribute,this.config.organization_schema&&t.readFile(this.config.organization_schema,(t,i)=>{if(t)this.logger.error(`Failed to load organization schema from ${this.config.organization_schema}: ${t}`);else try{this.schema=JSON.parse(s(i.toString(),this.config)),this.logger.debug("Organization schema loaded")}catch(t){this.logger.error(`Failed to parse organization schema: ${t}`)}})}api(t){t.get(`${this.config.api_prefix}/v1/ldap/organizations/top`,async(t,i)=>{await a(i,this.getOrganisationTop.bind(this),t)}),t.get(`${this.config.api_prefix}/v1/ldap/organizations/:dn`,async(t,i)=>{const e=decodeURIComponent(t.params.dn);await a(i,this.getOrganisationByDn.bind(this),e,t)}),t.get(`${this.config.api_prefix}/v1/ldap/organizations/:dn/subnodes`,async(t,i)=>{const e=decodeURIComponent(t.params.dn);await a(i,this.getOrganisationSubnodes.bind(this),e,t)}),t.get(`${this.config.api_prefix}/v1/ldap/organizations/:dn/subnodes/search`,o(async(t,i)=>{const e=decodeURIComponent(t.params.dn),n=t.query.q;if(!n)throw new u('query parameter "q" is required');await a(i,this.searchOrganisationSubnodes.bind(this),e,n)})),t.post(`${this.config.api_prefix}/v1/ldap/organizations`,o(async(t,i)=>this.apiAdd(t,i))),t.put(`${this.config.api_prefix}/v1/ldap/organizations/:dn`,o(async(t,i)=>this.apiModify(t,i))),t.delete(`${this.config.api_prefix}/v1/ldap/organizations/:dn`,o(async(t,i)=>this.apiDelete(t,i))),t.post(`${this.config.api_prefix}/v1/ldap/organizations/:dn/move`,o(async(t,i)=>this.apiMove(t,i)))}async apiAdd(t,i){const a=e(t,i,"ou");if(!a)return;h(a.ou,"ou");const r=a.parentDn||this.config.ldap_top_organization,s=`ou=${c(a.ou)},${r}`,o={objectClass:this.config.ldap_organization_class,ou:a.ou,...Object.fromEntries(Object.entries(a).filter(([t])=>"ou"!==t&&"parentDn"!==t))};await n(i,this.addOrganization.bind(this),s,o,t)}async apiModify(t,i){const a=e(t,i);if(!a)return;const r=decodeURIComponent(t.params.dn);if(!r)throw new u("dn is required");await n(i,this.modifyOrganization.bind(this),r,a)}async apiDelete(t,i){if(!r(t,i))return;const a=decodeURIComponent(t.params.dn);if(!a)throw new u("dn is required");await n(i,this.deleteOrganization.bind(this),a)}async apiMove(t,i){if(!r(t,i))return;const n=e(t,i,"targetOrgDn");if(!n)return;const s=decodeURIComponent(t.params.dn);if(!s)throw new u("dn is required");const{targetOrgDn:o}=n;if(!o||"string"!=typeof o)throw new u("Missing or invalid targetOrgDn in request body");await a(i,this.moveOrganization.bind(this),s,o,t)}async checkDeptLink(t){if(t[this.linkAttr]){const i=t[this.linkAttr],a=Array.isArray(i)?i[0]:i,e=await this.server.ldap.search({paged:!1,scope:"base"},a);if(0===e.searchEntries.length)throw new w(`Organization ${a} does not exist`);if(!new RegExp(`(.*,)?${this.config.ldap_top_organization}`).test(e.searchEntries[0].dn))throw new u(`Entry ${a} isn't in top organization branch`)}}async checkDeptPath(t){if(t[this.pathAttr]){const i=t[this.pathAttr],a=Array.isArray(i)?i[0]:i,e=this.config.ldap_organization_path_separator||" / ";let n=a;if(this.isOu(t)){const i=t.ou,r=Array.isArray(i)?i[0]:i;if(!a.startsWith(r+e))throw new u(`Organization path must start with its own name followed by separator "${e}"`);n=a.slice(r.length+e.length)}const[r,s]=n.split(e,2),o=await this.server.ldap.search({paged:!1,filter:`(ou=${d(r)})`,scope:"sub"},this.config.ldap_top_organization);if(0===o.searchEntries.length)throw new u(`Invalid organization path ${a}`);if(s){let t=!1;for(const i of o.searchEntries){const a=i[this.pathAttr],e=Array.isArray(a)?a[0]:a;if(e&&e===s){t=!0;break}}if(!t)throw new u(`Invalid organization path ${a}: no matching entry for ${r} with path ${s}`)}else{let t=!1;for(const i of o.searchEntries){const a=i.dn,n=this.config.ldap_top_organization;if(a.toLowerCase()===n.toLowerCase()||a.toLowerCase().endsWith(`,${n.toLowerCase()}`)){const a=i[this.pathAttr],n=Array.isArray(a)?a[0]:a;if(!n||n===r||!n.includes(e)){t=!0;break}}}if(!t)throw new u(`Invalid organization path ${a}: no matching top-level entry for ${r}`)}}}async isEmptyOrganization(t){if((await this.server.ldap.search({paged:!1,filter:`(${this.config.ldap_organization_link_attribute}=${t})`})).searchEntries.length>0)throw new $(`Organization ${t} is not empty`)}isOu(t){if(!t.objectClass)return!1;const i=t.objectClass.map(t=>t.toLowerCase());return this.config.ldap_organization_class.filter(t=>"top"!==t.toLowerCase()).some(t=>i.includes(t.toLowerCase()))}async getOrganisationTop(t){if(!this.config.ldap_top_organization)throw new u("No top organization configured");const i=await this.server.ldap.search({paged:!1,scope:"base"},this.config.ldap_top_organization,t);if(1!==i.searchEntries.length)throw new w("Top organization not found");const[,a]=await g(this.registeredHooks.getOrganisationTop,[t,i.searchEntries[0]]);return a}async getOrganisationByDn(t,i){const a=await this.server.ldap.search({paged:!1,scope:"base",filter:"(objectClass=organizationalUnit)"},t,i);if(1!==a.searchEntries.length)throw new w(`Organization ${t} not found`);return a.searchEntries[0]}async getOrganisationSubnodes(t,i){const a=[],e=this.config.ldap_organization_max_subnodes||50,n=i?.query?.objectClass;if(!n||"organizationalUnit"===n)try{const e=await this.server.ldap.search({paged:!1,scope:"one",filter:"(objectClass=organizationalUnit)"},t,i);this.server.logger.debug(`Found ${e.searchEntries.length} sub-OUs for ${t}`),a.push(...e.searchEntries)}catch(i){this.server.logger.debug(`No sub-OUs found for ${t}: ${i}`)}const r=this.config.ldap_top_organization,s=l(r);let o=`(${this.config.ldap_organization_link_attribute}=${d(t)})`;n&&(o=`(&${o}(objectClass=${d(n)}))`),this.server.logger.debug(`Searching for linked entities with filter: ${o} in ${s}`);const h=await this.server.ldap.search({paged:!0,filter:o},s,i);let c=0,g=0;for await(const t of h){g+=t.searchEntries.length;const i=e-c;if(i>0){const e=t.searchEntries.slice(0,i);a.push(...e),c+=e.length}}return this.server.logger.debug(`Found ${g} linked entities for ${t}, returning ${c}`),g>e&&a.push({dn:`more-${t}`,cn:[`... ${g-e} more elements`],objectClass:["moreIndicator"],_isMoreIndicator:"true",_totalCount:g.toString(),_displayedCount:e.toString()}),a}async addOrganization(t,i,a){return this.validateNewOrganization(t,i),await this.server.ldap.add(t,i,a)}async modifyOrganization(t,i){return this.validateChanges(t,i),await this.server.ldap.modify(t,i)}validateNewOrganization(t,i){if(!this.schema)return!0;for(const[t,a]of Object.entries(i))if(!this._validateOneChange(t,a))throw new u(`Invalid value for field ${t}`);for(const[t,a]of Object.entries(this.schema.attributes))if(a.required&&null==i[t])throw new u(`Missing required field ${t}`);return!0}validateChanges(t,i){if(!this.schema)return!0;if(i.add)for(const[t,a]of Object.entries(i.add))if(!this._validateOneChange(t,a))throw new u(`Invalid value for field ${t}`);if(i.replace)for(const[t,a]of Object.entries(i.replace))if(!this._validateOneChange(t,a))throw new u(`Invalid value for field ${t}`);return!0}_validateOneChange(t,i){if(!this.schema)return!0;const a=this.schema.attributes[t];if(!a){if(this.schema.strict)throw new Error(`Field ${t} is not allowed`);return!0}if(null==i){if(a.required)throw new Error(`Field ${t} is required`);return!0}if(a.type)switch(a.type){case"string":if(Array.isArray(i))throw new Error(`Field ${t} must be a single value`);break;case"array":if(!Array.isArray(i))throw new Error(`Field ${t} must be an array`)}if(a.test){const e=Array.isArray(i)?i[0]:i;if(!("string"==typeof a.test?new RegExp(a.test):a.test).test(String(e)))throw new Error(`Field ${t} does not match required pattern`)}return!0}async moveOrganization(t,i,a){let e;try{e=await this.server.ldap.search({paged:!1,scope:"base"},i)}catch(t){throw new w(`Target organization ${i} not found: ${t}`)}if(!e.searchEntries||0===e.searchEntries.length)throw new w(`Target organization ${i} not found`);if(!this.isOu(e.searchEntries[0]))throw new u(`Target ${i} is not an organizational unit`);const n=`${p(t)},${i}`;let r;try{r=await this.server.ldap.search({paged:!1,scope:"base"},t)}catch(t){throw new w(`Source organization not found: ${t}`)}if(!r.searchEntries||0===r.searchEntries.length)throw new w(`Source organization ${t} not found`);if(n===t)throw new u("Cannot move organization to its current location");if(i.toLowerCase()===t.toLowerCase()||f(i,t))throw new u("Cannot move organization into itself or its descendant");try{await this.server.ldap.rename(t,n,a),this.logger.info(`Moved organization from ${t} to ${n}`)}catch(t){throw new Error(`Failed to move organization: ${t}`)}return{newDn:n}}async deleteOrganization(t){return await this.server.ldap.delete(t)}async searchOrganisationSubnodes(t,i){const a=[],e=d(i);try{const n=await this.server.ldap.search({paged:!1,scope:"one",filter:`(&(objectClass=organizationalUnit)(|(ou=*${e}*)(description=*${e}*)))`},t);this.server.logger.debug(`Found ${n.searchEntries.length} sub-OUs matching "${i}" for ${t}`),a.push(...n.searchEntries)}catch(a){this.server.logger.debug(`No sub-OUs found matching "${i}" for ${t}: ${a}`)}const n=this.config.ldap_top_organization,r=l(n),s=d(t),o=`(&(${this.config.ldap_organization_link_attribute}=${s})(|(uid=*${e}*)(cn=*${e}*)(mail=*${e}*)(sn=*${e}*)(givenName=*${e}*)))`;this.server.logger.debug(`Searching for linked entities with filter: ${o} in ${r}`);const h=await this.server.ldap.search({paged:!0,filter:o},r);let c=0;for await(const t of h)c+=t.searchEntries.length,a.push(...t.searchEntries);return this.server.logger.debug(`Found ${c} linked entities matching "${i}" for ${t}`),a}getConfigApiData(){const t=this.config.api_prefix||"/api";let i;if(this.server.loadedPlugins.static&&this.config.organization_schema){const t=this.config.static_name||"static",a=this.config.organization_schema.indexOf("/schemas/");if(-1!==a){i=`/${t}${this.config.organization_schema.substring(a)}`}}return{enabled:!0,topOrganization:this.config.ldap_top_organization||"",organizationClass:this.config.ldap_organization_class||["top","organizationalUnit"],linkAttribute:this.linkAttr||"",pathAttribute:this.pathAttr||"",pathSeparator:this.config.ldap_organization_path_separator||" / ",maxSubnodes:this.config.ldap_organization_max_subnodes||50,schema:this.schema,schemaUrl:i,endpoints:{getTop:`${t}/v1/ldap/organizations/top`,get:`${t}/v1/ldap/organizations/:dn`,getSubnodes:`${t}/v1/ldap/organizations/:dn/subnodes`,searchSubnodes:`${t}/v1/ldap/organizations/:dn/subnodes/search`,create:`${t}/v1/ldap/organizations`,update:`${t}/v1/ldap/organizations/:dn`,move:`${t}/v1/ldap/organizations/:dn/move`,delete:`${t}/v1/ldap/organizations/:dn`}}}}export{LdapOrganizations as default};
@@ -1 +1 @@
1
- var e=["core/auth/trustedProxy","core/weblogs","core/auth/crowdsec","core/auth/rateLimit","core/auth/token","core/auth/llng","core/auth/openidconnect"];export{e as default};
1
+ var e=["core/auth/trustedProxy","core/weblogs","core/auth/crowdsec","core/auth/rateLimit","core/auth/token","core/auth/llng","core/auth/openidconnect","core/auth/authzPerRoute"];export{e as default};
@@ -0,0 +1 @@
1
+ import{Buffer as t}from"buffer";import e from"node-fetch";import i from"../../abstract/plugin.js";class CozyProvision extends i{constructor(t){super(t),this.name="cozyProvision",this.roles=["consistency"],this.publisher=null,this.publisherInit=null,this.hooks={scimusercreatedone:async t=>{await this.createCozyInstance(t)&&await this.publishUserCreated(t)},scimuserdeletedone:async t=>{await this.publishUserDeleted(t)}},this.cozyAdminUrl=(this.config.cozy_admin_url||"").replace(/\/$/,""),this.cozyAdminUser=this.config.cozy_admin_user||"admin",this.cozyAdminPassphrase=this.config.cozy_admin_passphrase||"",this.cozyOrgId=this.config.cozy_org_id||"",this.cozyOrgDomain=this.config.cozy_org_domain||"",this.cozyDefaultLocale=this.config.cozy_default_locale||"fr",this.rabbitmqUrl=this.config.rabbitmq_url||"",this.authExchange=this.config.cozy_auth_exchange||"auth",this.b2bExchange=this.config.cozy_b2b_exchange||"b2b",this.cozyAdminUrl||this.logger.warn(`${this.name}: cozy_admin_url is empty — Cozy instance creation will be skipped`),this.cozyOrgDomain||this.logger.warn(`${this.name}: cozy_org_domain is empty — workplaceFqdn cannot be composed`),this.rabbitmqUrl||this.logger.warn(`${this.name}: rabbitmq_url is empty — AMQP publishes will be skipped`),this.registerShutdown()}async createCozyInstance(i){if(!this.cozyAdminUrl)return!1;const s=this.extractId(i);if(!s)return this.logger.warn({plugin:this.name,event:"createCozyInstance",message:"user has no userName/id — skipping"}),!1;if(!this.cozyOrgDomain)return this.logger.error({plugin:this.name,event:"createCozyInstance",id:s,message:"cozy_org_domain not configured — cannot compose Domain"}),!1;const r=`${s}.${this.cozyOrgDomain}`,n=new URLSearchParams;n.set("Domain",r),n.set("Locale",this.extractLocale(i));const a=this.extractPrimaryEmail(i);a&&n.set("Email",a),this.cozyOrgId&&n.set("OrgID",this.cozyOrgId),this.cozyOrgDomain&&n.set("OrgDomain",this.cozyOrgDomain);const o=`${this.cozyAdminUrl}/instances?${n.toString()}`,c=t.from(`${this.cozyAdminUser}:${this.cozyAdminPassphrase}`).toString("base64"),h={plugin:this.name,event:"createCozyInstance",id:s,domain:r,email:a||void 0};try{const t=await e(o,{method:"POST",headers:{Authorization:`Basic ${c}`,Accept:"application/json"}});return t.ok?(this.logger.info({...h,result:"success",http_status:t.status}),!0):409===t.status?(this.logger.info({...h,result:"already_exists",http_status:t.status}),!0):(this.logger.error({...h,result:"error",http_status:t.status,http_status_text:t.statusText}),!1)}catch(t){return this.logger.error({...h,result:"error",error:`${t}`}),!1}}async publishUserCreated(t){const e=await this.getPublisher();if(!e)return;const i=this.extractId(t);if(!i)return;const s=this.extractPrimaryEmail(t),r={sub:i,organizationDomain:this.cozyOrgDomain,workplaceFqdn:`${i}.${this.cozyOrgDomain}`};s&&(r.email=s);try{await e.publish(this.authExchange,"user.created",r),this.logger.info({plugin:this.name,event:"publishUserCreated",result:"success",exchange:this.authExchange,routingKey:"user.created",sub:i})}catch(t){this.logger.error({plugin:this.name,event:"publishUserCreated",sub:i,error:`${t}`})}}async publishUserDeleted(t){const e=await this.getPublisher();if(!e)return;if(!this.cozyOrgDomain)return void this.logger.error({plugin:this.name,event:"publishUserDeleted",id:t,message:"cozy_org_domain not configured — cannot compose workplaceFqdn"});const i={workplaceFqdn:`${t}.${this.cozyOrgDomain}`,domain:this.cozyOrgDomain};try{await e.publish(this.b2bExchange,"domain.user.deleted",i),this.logger.info({plugin:this.name,event:"publishUserDeleted",result:"success",exchange:this.b2bExchange,routingKey:"domain.user.deleted",workplaceFqdn:i.workplaceFqdn})}catch(e){this.logger.error({plugin:this.name,event:"publishUserDeleted",id:t,error:`${e}`})}}async getPublisher(){return this.publisher?this.publisher:this.rabbitmqUrl?(this.publisherInit||(this.publisherInit=(async()=>{try{const t=new((await import("@linagora/rabbitmq-client")).RabbitMQClient)({url:this.rabbitmqUrl});return await t.init(),this.publisher=t,this.logger.info(`${this.name}: connected to RabbitMQ at ${function(t){try{const e=new URL(t);return e.username="",e.password="",e.toString()}catch{return"amqp://[broker]"}}(this.rabbitmqUrl)}`),t}catch(t){return this.logger.warn({plugin:this.name,event:"rabbitmq_init",message:"@linagora/rabbitmq-client unavailable or broker unreachable — AMQP publishes disabled",error:`${t}`}),null}})()),this.publisherInit):null}extractId(t){return"string"==typeof t.userName&&t.userName.length>0?t.userName:"string"==typeof t.id&&t.id.length>0?t.id:null}extractPrimaryEmail(t){const e=t.emails;if(!e||0===e.length)return null;const i=(e.find(t=>t.primary)||e[0]).value;return"string"==typeof i&&i.length>0?i:null}extractLocale(t){return"string"==typeof t.locale&&t.locale.length>0?t.locale:"string"==typeof t.preferredLanguage&&t.preferredLanguage.length>0?t.preferredLanguage:this.cozyDefaultLocale}registerShutdown(){!function(t){if(s.push(t),r)return;r=!0;const fanOut=()=>{for(const t of s.splice(0))try{t()}catch{}};process.once("SIGTERM",fanOut),process.once("SIGINT",fanOut)}(()=>{const t=this.publisher;this.publisher=null,this.publisherInit=null,t&&t.close().catch(t=>{this.logger.warn({plugin:this.name,event:"shutdown",error:`${t}`})})})}}const s=[];let r=!1;export{CozyProvision as default};
@@ -38,6 +38,113 @@ export interface LdapFlatConfig {
38
38
  */
39
39
  hookPrefix: string;
40
40
  }
41
+ /**
42
+ * Generic OpenAPI schemas for the flat LDAP-entry CRUD surface.
43
+ * Every concrete plugin that extends LdapFlat (users, mailgroups, …)
44
+ * exposes these shapes, with `mainAttribute` and concrete attribute
45
+ * names varying per instance.
46
+ *
47
+ * @openapi-component
48
+ * FlatEntry:
49
+ * type: object
50
+ * description: |
51
+ * A single LDAP entry from a flat branch. The `dn` field is always
52
+ * present; all other fields depend on the concrete plugin's schema
53
+ * (e.g. `uid`, `cn`, `mail`, `sn` for users).
54
+ * required: [dn]
55
+ * properties:
56
+ * dn:
57
+ * type: string
58
+ * description: Fully-qualified distinguished name of the entry.
59
+ * example: uid=alice,ou=users,dc=example,dc=com
60
+ * additionalProperties:
61
+ * oneOf:
62
+ * - type: string
63
+ * - type: array
64
+ * items: { type: string }
65
+ * example:
66
+ * dn: uid=alice,ou=users,dc=example,dc=com
67
+ * uid: alice
68
+ * cn: Alice Smith
69
+ * sn: Smith
70
+ * mail: alice@example.com
71
+ * FlatList:
72
+ * type: object
73
+ * description: |
74
+ * A map of entries returned by the LIST endpoint, keyed by the
75
+ * entry's `mainAttribute` value (e.g. the `uid` for users). This
76
+ * is the `LdapList` shape (`Record<string, FlatEntry>`).
77
+ * additionalProperties:
78
+ * $ref: '#/components/schemas/FlatEntry'
79
+ * example:
80
+ * alice:
81
+ * dn: uid=alice,ou=users,dc=example,dc=com
82
+ * uid: alice
83
+ * cn: Alice Smith
84
+ * mail: alice@example.com
85
+ * bob:
86
+ * dn: uid=bob,ou=users,dc=example,dc=com
87
+ * uid: bob
88
+ * cn: Bob Jones
89
+ * mail: bob@example.com
90
+ * FlatCreate:
91
+ * type: object
92
+ * description: |
93
+ * Body for creating a new entry. The plugin's `mainAttribute` field
94
+ * (e.g. `uid`) is required and used to build the DN. All other
95
+ * attributes are plugin-specific; unknown attributes are accepted
96
+ * unless the plugin's schema is in strict mode.
97
+ * additionalProperties: true
98
+ * example:
99
+ * uid: carol
100
+ * cn: Carol White
101
+ * sn: White
102
+ * mail: carol@example.com
103
+ * FlatModify:
104
+ * type: object
105
+ * description: |
106
+ * Partial-update body. Supports `add`, `replace`, and `delete`
107
+ * maps matching the LDAP modify operation. The `mainAttribute`
108
+ * (e.g. `uid`) cannot be changed through this endpoint — use the
109
+ * rename API instead.
110
+ * properties:
111
+ * add:
112
+ * type: object
113
+ * description: Attributes whose values are appended.
114
+ * additionalProperties: true
115
+ * replace:
116
+ * type: object
117
+ * description: Attributes whose values are replaced wholesale.
118
+ * additionalProperties: true
119
+ * delete:
120
+ * description: |
121
+ * Attributes (or specific values) to remove. Either an array
122
+ * of attribute names or a map of attribute → value(s).
123
+ * oneOf:
124
+ * - type: array
125
+ * items: { type: string }
126
+ * - type: object
127
+ * additionalProperties: true
128
+ * example:
129
+ * replace:
130
+ * mail: carol.white@example.com
131
+ * cn: Carol White-Smith
132
+ * FlatMoveRequest:
133
+ * type: object
134
+ * description: |
135
+ * Body for moving an entry to a different organisational unit.
136
+ * Requires `ldap_organization_link_attribute` and
137
+ * `ldap_organization_path_attribute` to be defined in the plugin's
138
+ * schema.
139
+ * required: [targetOrgDn]
140
+ * properties:
141
+ * targetOrgDn:
142
+ * type: string
143
+ * description: DN of the destination organisational unit.
144
+ * example: ou=engineering,ou=departments,dc=example,dc=com
145
+ * example:
146
+ * targetOrgDn: ou=engineering,ou=departments,dc=example,dc=com
147
+ */
41
148
  export default abstract class LdapFlat extends DmPlugin {
42
149
  base: string;
43
150
  ldap: ldapActions;
@@ -76,6 +76,7 @@ export interface Config {
76
76
  oidc_client_id?: string;
77
77
  oidc_client_secret?: string;
78
78
  base_url?: string;
79
+ authz_per_route?: string[];
79
80
  authz_per_branch_config?: AuthConfig;
80
81
  authz_per_branch_cache_ttl?: number;
81
82
  authz_dynamic_base?: string;
@@ -119,6 +120,15 @@ export interface Config {
119
120
  calendar_resource_objectclass?: string;
120
121
  calendar_resource_creator?: string;
121
122
  calendar_resource_domain?: string;
123
+ cozy_admin_url?: string;
124
+ cozy_admin_user?: string;
125
+ cozy_admin_passphrase?: string;
126
+ cozy_org_id?: string;
127
+ cozy_org_domain?: string;
128
+ cozy_default_locale?: string;
129
+ cozy_auth_exchange?: string;
130
+ cozy_b2b_exchange?: string;
131
+ rabbitmq_url?: string;
122
132
  applicative_account_base?: string;
123
133
  max_app_accounts?: number;
124
134
  ldap_operational_attributes?: string[];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @module core/auth/authzPerRoute
3
+ * Route-level authorization plugin.
4
+ *
5
+ * Restricts access by HTTP method + path based on the authenticated user name
6
+ * (req.user, set by an auth plugin loaded before this one).
7
+ *
8
+ * @author Xavier Guimard <xguimard@linagora.com>
9
+ */
10
+ import type { Express } from 'express';
11
+ import DmPlugin, { type Role } from '../../abstract/plugin';
12
+ export declare function globToRegex(glob: string): RegExp;
13
+ export default class AuthzPerRoute extends DmPlugin {
14
+ name: string;
15
+ roles: Role[];
16
+ private rules;
17
+ constructor(...args: ConstructorParameters<typeof DmPlugin>);
18
+ private parseEntry;
19
+ private addRule;
20
+ private matches;
21
+ api(app: Express): void;
22
+ }
@@ -10,6 +10,57 @@
10
10
  */
11
11
  import type { Express } from 'express';
12
12
  import DmPlugin, { type Role } from '../abstract/plugin';
13
+ /**
14
+ * @openapi-component
15
+ * Config:
16
+ * type: object
17
+ * description: |
18
+ * Aggregated server configuration. The top-level fields are always
19
+ * present; `features` is a dynamic map whose keys are the names of
20
+ * loaded plugins that declare the `configurable` role. Each plugin
21
+ * contributes an arbitrary sub-object — consult the individual plugin
22
+ * documentation for its exact shape.
23
+ * additionalProperties: true
24
+ * properties:
25
+ * apiPrefix:
26
+ * type: string
27
+ * description: The API base path configured on this server.
28
+ * example: /api
29
+ * ldapBase:
30
+ * type: string
31
+ * description: The root LDAP suffix used by this server.
32
+ * example: dc=example,dc=com
33
+ * features:
34
+ * type: object
35
+ * description: |
36
+ * Per-plugin configuration blocks. Keys match the plugin's
37
+ * `name` field; values are plugin-specific objects.
38
+ * additionalProperties: true
39
+ * example:
40
+ * ldapGroups:
41
+ * enabled: true
42
+ * base: ou=groups,dc=example,dc=com
43
+ * mainAttribute: cn
44
+ * objectClass: [top, groupOfNames]
45
+ * static:
46
+ * enabled: true
47
+ * staticPath: /static
48
+ * appAccountsApi:
49
+ * enabled: true
50
+ * base: ou=app-accounts,dc=example,dc=com
51
+ * maxAccounts: 5
52
+ * example:
53
+ * apiPrefix: /api
54
+ * ldapBase: dc=example,dc=com
55
+ * features:
56
+ * ldapGroups:
57
+ * enabled: true
58
+ * base: ou=groups,dc=example,dc=com
59
+ * mainAttribute: cn
60
+ * static:
61
+ * enabled: true
62
+ * staticPath: /static
63
+ */
13
64
  export default class ConfigApi extends DmPlugin {
14
65
  name: string;
15
66
  roles: Role[];
@@ -5,6 +5,27 @@
5
5
  */
6
6
  import type { Express } from 'express';
7
7
  import DmPlugin, { type Role } from '../../abstract/plugin';
8
+ /**
9
+ * @openapi-component
10
+ * Hello:
11
+ * type: object
12
+ * description: Response returned by the hello-world demo endpoint.
13
+ * properties:
14
+ * message:
15
+ * type: string
16
+ * description: Static greeting string.
17
+ * example: Hello
18
+ * hookResults:
19
+ * type: array
20
+ * description: |
21
+ * Results collected from every function registered on the `hello`
22
+ * hook. Empty when no hook listeners are installed.
23
+ * items: {}
24
+ * example: []
25
+ * example:
26
+ * message: Hello
27
+ * hookResults: []
28
+ */
8
29
  export default class HelloWorld extends DmPlugin {
9
30
  name: string;
10
31
  roles: Role[];
@@ -6,6 +6,71 @@
6
6
  import type { Express } from 'express';
7
7
  import DmPlugin, { type Role } from '../../abstract/plugin';
8
8
  import type { DM } from '../../bin';
9
+ /**
10
+ * Shared OpenAPI schemas surfaced by this plugin. Picked up by
11
+ * scripts/generate-openapi.ts and merged into `components.schemas`.
12
+ *
13
+ * @openapi-component
14
+ * BulkImportResult:
15
+ * type: object
16
+ * description: Summary returned after a bulk import operation.
17
+ * required: [success, total, created, updated, skipped, failed, errors, details]
18
+ * properties:
19
+ * success:
20
+ * type: boolean
21
+ * description: True when no fatal error aborted the import early.
22
+ * total:
23
+ * type: integer
24
+ * description: Total number of CSV data rows (excluding header).
25
+ * example: 42
26
+ * created:
27
+ * type: integer
28
+ * description: Entries successfully created (or counted in dry-run).
29
+ * example: 38
30
+ * updated:
31
+ * type: integer
32
+ * description: Existing entries updated.
33
+ * example: 2
34
+ * skipped:
35
+ * type: integer
36
+ * description: Existing entries skipped because `updateExisting` was false.
37
+ * example: 1
38
+ * failed:
39
+ * type: integer
40
+ * description: Rows that caused an error.
41
+ * example: 1
42
+ * errors:
43
+ * type: array
44
+ * items:
45
+ * type: object
46
+ * required: [line, error]
47
+ * properties:
48
+ * line:
49
+ * type: integer
50
+ * description: 1-based CSV line number (2 = first data row).
51
+ * identifier:
52
+ * type: string
53
+ * description: Value of the main attribute for the failing row (if known).
54
+ * error:
55
+ * type: string
56
+ * description: Error message.
57
+ * example:
58
+ * - line: 5
59
+ * identifier: bob
60
+ * error: 'Missing required attribute: mail'
61
+ * details:
62
+ * type: object
63
+ * required: [duration, linesProcessed]
64
+ * properties:
65
+ * duration:
66
+ * type: string
67
+ * description: Wall-clock time for the import (e.g. `1.2s`).
68
+ * example: 1.2s
69
+ * linesProcessed:
70
+ * type: integer
71
+ * description: Total data rows attempted.
72
+ * example: 42
73
+ */
9
74
  export default class LdapBulkImport extends DmPlugin {
10
75
  name: string;
11
76
  roles: Role[];
@@ -10,6 +10,74 @@ export interface postAdd {
10
10
  [key: string]: AttributeValue | undefined;
11
11
  }
12
12
  export type postModify = ModifyRequest;
13
+ /**
14
+ * Shared OpenAPI schemas surfaced by this plugin. Picked up by
15
+ * scripts/generate-openapi.ts and merged into `components.schemas`.
16
+ *
17
+ * @openapi-component
18
+ * Group:
19
+ * type: object
20
+ * description: An LDAP group entry (groupOfNames / groupOfUniqueNames).
21
+ * required: [dn, cn]
22
+ * properties:
23
+ * dn:
24
+ * type: string
25
+ * description: Fully-qualified distinguished name of the entry.
26
+ * example: cn=admins,ou=groups,dc=example,dc=com
27
+ * cn:
28
+ * type: string
29
+ * description: Common name (RDN attribute) of the group.
30
+ * example: admins
31
+ * description:
32
+ * type: string
33
+ * example: Server administrators
34
+ * member:
35
+ * type: array
36
+ * items: { type: string }
37
+ * description: DNs of the group's members.
38
+ * example:
39
+ * - uid=alice,ou=users,dc=example,dc=com
40
+ * - uid=bob,ou=users,dc=example,dc=com
41
+ * GroupCreate:
42
+ * type: object
43
+ * required: [cn]
44
+ * properties:
45
+ * cn:
46
+ * type: string
47
+ * description: Common name of the new group.
48
+ * description:
49
+ * type: string
50
+ * member:
51
+ * type: array
52
+ * items: { type: string }
53
+ * description: Initial members (DNs).
54
+ * example:
55
+ * cn: admins
56
+ * description: Server administrators
57
+ * member:
58
+ * - uid=alice,ou=users,dc=example,dc=com
59
+ * GroupModify:
60
+ * type: object
61
+ * description: |
62
+ * Partial update. Each provided attribute is replaced wholesale. Use
63
+ * the dedicated `/members` endpoints to add/remove members one at a
64
+ * time without supplying the full list.
65
+ * properties:
66
+ * description: { type: string }
67
+ * member:
68
+ * type: array
69
+ * items: { type: string }
70
+ * example:
71
+ * description: Updated description
72
+ * Error:
73
+ * type: object
74
+ * properties:
75
+ * error: { type: string }
76
+ * code: { type: integer }
77
+ * example:
78
+ * error: Group not found
79
+ * code: 404
80
+ */
13
81
  export default class LdapGroups extends DmPlugin {
14
82
  name: string;
15
83
  roles: Role[];
@@ -1,65 +1,8 @@
1
- import type { Express, Request, Response } from 'express';
2
- import DmPlugin, { type Role } from '../../abstract/plugin';
3
- import { DM } from '../../bin';
4
- import { Hooks } from '../../hooks';
5
- import { AttributesList, AttributeValue, ModifyRequest } from '../../lib/ldapActions';
6
- import type { Schema } from '../../config/schema';
7
- export default class LdapOrganizations extends DmPlugin {
8
- name: string;
9
- roles: Role[];
10
- pathAttr: string;
11
- linkAttr: string;
12
- schema?: Schema;
13
- constructor(dm: DM);
14
- /**
15
- * API routes for LDAP organizations
16
- * @param app Express application
17
- */
18
- api(app: Express): void;
19
- apiAdd(req: Request, res: Response): Promise<void>;
20
- apiModify(req: Request, res: Response): Promise<void>;
21
- apiDelete(req: Request, res: Response): Promise<void>;
22
- apiMove(req: Request, res: Response): Promise<void>;
23
- /**
24
- * Consistency checks on any entry
25
- */
26
- hooks: Hooks;
27
- /**
28
- * Check if the department link is valid
29
- * @param entry LDAP entry to check
30
- */
31
- checkDeptLink(entry: AttributesList): Promise<void>;
32
- /**
33
- * Check if the department path is valid
34
- * @param entry LDAP entry to check
35
- */
36
- checkDeptPath(entry: AttributesList): Promise<void>;
37
- isEmptyOrganization(dn: string): Promise<void>;
38
- /**
39
- * Check if entry is an organisation
40
- * @param entry LDAP entry to check
41
- * @returns True if entry is an organisation, false otherwise
42
- */
43
- isOu(entry: AttributesList): boolean;
44
- getOrganisationTop(req?: Request): Promise<AttributesList | AttributesList[]>;
45
- getOrganisationByDn(dn: string, req?: Request): Promise<AttributesList>;
46
- getOrganisationSubnodes(dn: string, req?: Request): Promise<AttributesList[]>;
47
- addOrganization(dn: string, entry: AttributesList, req?: Request): Promise<boolean>;
48
- modifyOrganization(dn: string, changes: ModifyRequest): Promise<boolean>;
49
- validateNewOrganization(dn: string, entry: AttributesList): boolean;
50
- validateChanges(dn: string, changes: ModifyRequest): boolean;
51
- _validateOneChange(field: string, value: AttributeValue | null): boolean;
52
- /**
53
- * Move an organization to a different parent organization
54
- * Uses LDAP modifyDN to change the DN hierarchy
55
- */
56
- moveOrganization(dn: string, targetOrgDn: string, req?: Request): Promise<{
57
- newDn: string;
58
- }>;
59
- deleteOrganization(dn: string): Promise<boolean>;
60
- searchOrganisationSubnodes(dn: string, query: string): Promise<AttributesList[]>;
61
- /**
62
- * Provide configuration for config API
63
- */
64
- getConfigApiData(): Record<string, unknown>;
65
- }
1
+ /**
2
+ * @deprecated Use `core/ldap/organizations` instead. This file is a
3
+ * backwards-compatibility shim for setups that still load the plugin under
4
+ * the historical (singular) filename. It will be removed in a future major
5
+ * release.
6
+ */
7
+ import LdapOrganizations from './organizations';
8
+ export default LdapOrganizations;