ldap-rest 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
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"],["--ldap-raw-base","DM_LDAP_RAW_BASE",[],"array","--ldap-raw-bases"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes"],["--ldap-raw-show-secrets","DM_LDAP_RAW_SHOW_SECRETS",!1,"boolean"],["--ldap-raw-max-results","DM_LDAP_RAW_MAX_RESULTS",200,"number"],["--ldap-raw-schema-cache-ttl","DM_LDAP_RAW_SCHEMA_CACHE_TTL",3600,"number"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--calendar-firstname-attribute","DM_CALENDAR_FIRSTNAME_ATTRIBUTE","givenName"],["--calendar-lastname-attribute","DM_CALENDAR_LASTNAME_ATTRIBUTE","sn"],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-context-name","DM_COZY_CONTEXT_NAME","default"],["--cozy-apps","DM_COZY_APPS","home,drive,settings,notes,dataproxy"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--cozy-user-created-routing-key","DM_COZY_USER_CREATED_ROUTING_KEY","user.created"],["--cozy-user-deleted-routing-key","DM_COZY_USER_DELETED_ROUTING_KEY","domain.user.deleted"],["--cloudery-manager-url","DM_CLOUDERY_MANAGER_URL",""],["--cloudery-manager-token","DM_CLOUDERY_MANAGER_TOKEN",""],["--cloudery-offer","DM_CLOUDERY_OFFER","b2b_twake_default"],["--cloudery-domain","DM_CLOUDERY_DOMAIN",""],["--cloudery-user-branch","DM_CLOUDERY_USER_BRANCH",""],["--cloudery-org-id-header","DM_CLOUDERY_ORG_ID_HEADER","x-cloudery-org-id"],["--cloudery-org-role-header","DM_CLOUDERY_ORG_ROLE_HEADER","x-cloudery-org-role"],["--cloudery-default-org-role","DM_CLOUDERY_DEFAULT_ORG_ROLE","member"],["--cloudery-fqdn-attribute","DM_CLOUDERY_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--cloudery-org-id-attribute","DM_CLOUDERY_ORG_ID_ATTRIBUTE","twakeOrganizationId"],["--cloudery-org-role-attribute","DM_CLOUDERY_ORG_ROLE_ATTRIBUTE","twakeOrganizationRole"],["--cloudery-phones-attribute","DM_CLOUDERY_PHONES_ATTRIBUTE","twakePhones"],["--cloudery-invited-attribute","DM_CLOUDERY_INVITED_ATTRIBUTE","twakeInvited"],["--cloudery-default-locale","DM_CLOUDERY_DEFAULT_LOCALE","en"],["--cloudery-workflow-poll-interval-ms","DM_CLOUDERY_WORKFLOW_POLL_INTERVAL_MS",2e3,"number"],["--cloudery-workflow-max-attempts","DM_CLOUDERY_WORKFLOW_MAX_ATTEMPTS",60,"number"],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--auth-path-prefix","DM_AUTH_PATH_PREFIX",[],"array","--auth-path-prefixes"],["--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-base-header","DM_SCIM_USER_BASE_HEADER",""],["--scim-group-base-header","DM_SCIM_GROUP_BASE_HEADER",""],["--scim-base-header-root","DM_SCIM_BASE_HEADER_ROOT",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-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"],["--ldap-raw-base","DM_LDAP_RAW_BASE",[],"array","--ldap-raw-bases"],["--ldap-raw-hidden-attribute","DM_LDAP_RAW_HIDDEN_ATTRIBUTES",[],"array","--ldap-raw-hidden-attributes"],["--ldap-raw-show-secrets","DM_LDAP_RAW_SHOW_SECRETS",!1,"boolean"],["--ldap-raw-max-results","DM_LDAP_RAW_MAX_RESULTS",200,"number"],["--ldap-raw-schema-cache-ttl","DM_LDAP_RAW_SCHEMA_CACHE_TTL",3600,"number"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--calendar-firstname-attribute","DM_CALENDAR_FIRSTNAME_ATTRIBUTE","givenName"],["--calendar-lastname-attribute","DM_CALENDAR_LASTNAME_ATTRIBUTE","sn"],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-context-name","DM_COZY_CONTEXT_NAME","default"],["--cozy-apps","DM_COZY_APPS","home,drive,settings,notes,dataproxy"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--cozy-user-created-routing-key","DM_COZY_USER_CREATED_ROUTING_KEY","user.created"],["--cozy-user-deleted-routing-key","DM_COZY_USER_DELETED_ROUTING_KEY","domain.user.deleted"],["--cloudery-manager-url","DM_CLOUDERY_MANAGER_URL",""],["--cloudery-manager-token","DM_CLOUDERY_MANAGER_TOKEN",""],["--cloudery-offer","DM_CLOUDERY_OFFER","b2b_twake_default"],["--cloudery-domain","DM_CLOUDERY_DOMAIN",""],["--cloudery-user-branch","DM_CLOUDERY_USER_BRANCH",""],["--cloudery-org-id-header","DM_CLOUDERY_ORG_ID_HEADER","x-cloudery-org-id"],["--cloudery-org-role-header","DM_CLOUDERY_ORG_ROLE_HEADER","x-cloudery-org-role"],["--cloudery-default-org-role","DM_CLOUDERY_DEFAULT_ORG_ROLE","member"],["--cloudery-fqdn-attribute","DM_CLOUDERY_FQDN_ATTRIBUTE","twakeWorkspaceUrl"],["--cloudery-org-id-attribute","DM_CLOUDERY_ORG_ID_ATTRIBUTE","twakeOrganizationId"],["--cloudery-org-role-attribute","DM_CLOUDERY_ORG_ROLE_ATTRIBUTE","twakeOrganizationRole"],["--cloudery-phones-attribute","DM_CLOUDERY_PHONES_ATTRIBUTE","twakePhones"],["--cloudery-invited-attribute","DM_CLOUDERY_INVITED_ATTRIBUTE","twakeInvited"],["--cloudery-default-locale","DM_CLOUDERY_DEFAULT_LOCALE","en"],["--cloudery-workflow-poll-interval-ms","DM_CLOUDERY_WORKFLOW_POLL_INTERVAL_MS",2e3,"number"],["--cloudery-workflow-max-attempts","DM_CLOUDERY_WORKFLOW_MAX_ATTEMPTS",60,"number"],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--applicative-account-attribute","DM_APPLICATIVE_ACCOUNT_ATTRIBUTES",["objectClass","cn","sn","givenName","displayName","description"],"array","--applicative-account-attributes"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--auth-path-prefix","DM_AUTH_PATH_PREFIX",[],"array","--auth-path-prefixes"],["--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-base-header","DM_SCIM_USER_BASE_HEADER",""],["--scim-group-base-header","DM_SCIM_GROUP_BASE_HEADER",""],["--scim-base-header-root","DM_SCIM_BASE_HEADER_ROOT",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-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-max-scanned","DM_SCIM_MAX_SCANNED",1e4,"number"],["--scim-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
@@ -1 +1 @@
1
- import{HttpError as r}from"../../lib/errors.js";import{SCHEMA_ERROR as e}from"./types.js";const t="application/scim+json";class ScimError extends r{constructor(r,e,t){super(e,r),this.name="ScimError",this.scimType=t}}const scimInvalidFilter=(r="Invalid filter")=>new ScimError(400,r,"invalidFilter"),scimInvalidPath=(r="Invalid path")=>new ScimError(400,r,"invalidPath"),scimInvalidValue=(r="Invalid value")=>new ScimError(400,r,"invalidValue"),scimInvalidSyntax=(r="Invalid syntax")=>new ScimError(400,r,"invalidSyntax"),scimNoTarget=(r="No target")=>new ScimError(400,r,"noTarget"),scimMutability=(r="Immutable attribute")=>new ScimError(400,r,"mutability"),scimUniqueness=(r="Uniqueness violation")=>new ScimError(409,r,"uniqueness"),scimTooMany=(r="Too many results")=>new ScimError(400,r,"tooMany"),scimNotFound=(r="Resource not found")=>new ScimError(404,r);function writeScimError(r,o,i,n){const s={schemas:[e],status:String(o),...n?{scimType:n}:{},detail:i};r.status(o).type(t).json(s)}function extractLdapCode(r){if(null==r)return;if("object"==typeof r&&"code"in r){const e=r.code;if("number"==typeof e)return e}const e=r instanceof Error?r.message:"string"==typeof r?r:"";return/noSuchObject|No such object|code:?\s*(32|0x20)/i.test(e)?32:/entryAlreadyExists|Already[_ ]?Exists|code:?\s*(68|0x44)/i.test(e)?68:/noSuchAttribute|No such attribute|code:?\s*(16|0x10)/i.test(e)?16:void 0}function writeScimErrorFromException(e,t,o=500){const sanitize=r=>/\[authz-forbidden\]/.test(r)?"Token does not have permission on this branch":r;if(t instanceof ScimError)return void writeScimError(e,t.statusCode,sanitize(t.message),t.scimType);if(t instanceof r)return void writeScimError(e,t.statusCode,sanitize(t.message));const i=t instanceof Error?t.message:String(t);if(/\[authz-forbidden\]/.test(i))return void writeScimError(e,403,"Token does not have permission on this branch");const n=extractLdapCode(t);32!==n?68!==n?writeScimError(e,o,sanitize(i)||"Internal error"):writeScimError(e,409,sanitize(i),"uniqueness"):writeScimError(e,404,"Resource not found")}const scimAsyncHandler=r=>(e,t,o)=>{Promise.resolve().then(()=>r(e,t,o)).catch(r=>{t.headersSent?o(r):writeScimErrorFromException(t,r)})};export{t as SCIM_CONTENT_TYPE,ScimError,extractLdapCode,scimAsyncHandler,scimInvalidFilter,scimInvalidPath,scimInvalidSyntax,scimInvalidValue,scimMutability,scimNoTarget,scimNotFound,scimTooMany,scimUniqueness,writeScimError,writeScimErrorFromException};
1
+ import{HttpError as r}from"../../lib/errors.js";import{SCHEMA_ERROR as e}from"./types.js";const t="application/scim+json";class ScimError extends r{constructor(r,e,t){super(e,r),this.name="ScimError",this.scimType=t}}const scimInvalidFilter=(r="Invalid filter")=>new ScimError(400,r,"invalidFilter"),scimInvalidPath=(r="Invalid path")=>new ScimError(400,r,"invalidPath"),scimInvalidValue=(r="Invalid value")=>new ScimError(400,r,"invalidValue"),scimInvalidSyntax=(r="Invalid syntax")=>new ScimError(400,r,"invalidSyntax"),scimNoTarget=(r="No target")=>new ScimError(400,r,"noTarget"),scimMutability=(r="Immutable attribute")=>new ScimError(400,r,"mutability"),scimUniqueness=(r="Uniqueness violation")=>new ScimError(409,r,"uniqueness"),scimTooMany=(r="Too many results")=>new ScimError(400,r,"tooMany"),scimNotFound=(r="Resource not found")=>new ScimError(404,r);function writeScimError(r,i,o,n){const s={schemas:[e],status:String(i),...n?{scimType:n}:{},detail:o};r.status(i).type(t).json(s)}function extractLdapCode(r){if(null==r)return;if("object"==typeof r&&"code"in r){const e=r.code;if("number"==typeof e)return e}const e=r instanceof Error?r.message:"string"==typeof r?r:"";return/noSuchObject|No such object|code:?\s*(32|0x20)/i.test(e)?32:/entryAlreadyExists|Already[_ ]?Exists|code:?\s*(68|0x44)/i.test(e)?68:/noSuchAttribute|No such attribute|code:?\s*(16|0x10)/i.test(e)?16:/sizeLimitExceeded|Size Limit Exceeded/i.test(e)?4:void 0}function writeScimErrorFromException(e,t,i=500){const sanitize=r=>/\[authz-forbidden\]/.test(r)?"Token does not have permission on this branch":r;if(t instanceof ScimError)return void writeScimError(e,t.statusCode,sanitize(t.message),t.scimType);if(t instanceof r)return void writeScimError(e,t.statusCode,sanitize(t.message));const o=t instanceof Error?t.message:String(t);if(/\[authz-forbidden\]/.test(o))return void writeScimError(e,403,"Token does not have permission on this branch");const n=extractLdapCode(t);32!==n?68!==n?writeScimError(e,i,sanitize(o)||"Internal error"):writeScimError(e,409,sanitize(o),"uniqueness"):writeScimError(e,404,"Resource not found")}const scimAsyncHandler=r=>(e,t,i)=>{Promise.resolve().then(()=>r(e,t,i)).catch(r=>{t.headersSent?i(r):writeScimErrorFromException(t,r)})};export{t as SCIM_CONTENT_TYPE,ScimError,extractLdapCode,scimAsyncHandler,scimInvalidFilter,scimInvalidPath,scimInvalidSyntax,scimInvalidValue,scimMutability,scimNoTarget,scimNotFound,scimTooMany,scimUniqueness,writeScimError,writeScimErrorFromException};
@@ -1 +1 @@
1
- import{escapeDnValue as t,launchHooksChained as e,validateDnValue as s,launchHooks as r,isChildOf as i,escapeLdapFilter as a}from"../../lib/utils.js";import{SCHEMA_LIST_RESPONSE as o}from"./types.js";import{mergeMapping as n,loadMappingFile as c,DEFAULT_GROUP_MAPPING as h,requiredLdapAttributes as u,ldapToScimGroup as m,scimGroupToLdap as l}from"./mapping.js";import{scimFilterToLdap as p}from"./filter.js";import{patchToModifyRequest as d}from"./patch.js";import{extractLdapCode as g,scimNotFound as f,ScimError as b,scimTooMany as y,scimInvalidValue as w,scimUniqueness as $}from"./errors.js";class ScimGroups{constructor(t){this.ldap=t.ldap,this.config=t.config,this.logger=t.logger,this.baseResolver=t.baseResolver,this.users=t.users,this.hooks=t.hooks,this.rdnAttribute=this.config.scim_group_rdn_attribute||"cn",this.objectClass=this.config.scim_group_object_class,this.idAttribute=this.config.scim_id_attribute||"rdn",this.maxResults=this.config.scim_max_results||200,this.scimPrefix=this.config.scim_prefix||"/scim/v2";const e=this.config.scim_group_mapping||"";this.mapping=n(h,e?c(e):void 0)}ctx(t){return{idAttribute:this.idAttribute,rdnAttribute:this.rdnAttribute,resourceType:"Group",baseUrl:this.config.scim_base_url||(t?.protocol&&t.get?`${t.protocol}://${String(t.get("host")||"")}`:""),scimPrefix:this.scimPrefix}}dnForId(e,s){const r=this.baseResolver.groupBase(s);return`${this.rdnAttribute}=${t(e)},${r}`}buildMemberResolver(t){const e=this.baseResolver.userBase(t),s=this.baseResolver.groupBase(t),r=this.scimPrefix,i=this.config.scim_base_url||(t?.protocol&&t.get?`${t.protocol}://${String(t.get("host")||"")}`:""),a=this.config.scim_user_rdn_attribute||"uid",o=this.rdnAttribute,n=this.config.group_dummy_user||"cn=fakeuser";return t=>{if(!t)return;if(t.toLowerCase()===n.toLowerCase())return;const c=/^([^=]+)=((?:\\.|[^,])+)/.exec(t);if(!c)return;const h=c[2].replace(/\\(.)/g,"$1"),u=t.toLowerCase();let m;e&&u.endsWith(e.toLowerCase())?m="User":s&&u.endsWith(s.toLowerCase())?m="Group":c[1]===a?m="User":c[1]===o&&(m="Group");const l={value:h};if(m&&(l.type=m,i)){const t="User"===m?"Users":"Groups";l.$ref=`${i.replace(/\/$/,"")}${r}/${t}/${encodeURIComponent(h)}`}return l}}async get(t,e){const s=this.dnForId(e,t);let r;try{r=await this.ldap.search({paged:!1,scope:"base",attributes:[...u(this.mapping),"member"]},s)}catch(t){if(32===g(t))throw f(`Group ${e} not found`);throw t}if(!r.searchEntries||0===r.searchEntries.length)throw f(`Group ${e} not found`);return m(r.searchEntries[0],this.mapping,this.ctx(t),this.buildMemberResolver(t))}async list(t,e){const s=this.baseResolver.groupBase(t),r=Math.max(1,e.startIndex||1),i=Math.min(this.maxResults,Math.max(0,e.count??this.maxResults));let a,n=`(objectClass=${this.objectClass.find(t=>"top"!==t)||"groupOfNames"})`;if(e.filter){const t=p(e.filter,this.mapping);t.idEquals?a=t.idEquals:n=`(&${n}${t.ldapFilter})`}if(a)try{const e=await this.get(t,a);return{schemas:[o],totalResults:1,startIndex:1,itemsPerPage:1,Resources:[e]}}catch(t){if(t instanceof b&&404===t.statusCode)return{schemas:[o],totalResults:0,startIndex:1,itemsPerPage:0,Resources:[]};throw t}const c=(await this.ldap.search({filter:n,scope:"sub",paged:!1,attributes:[...u(this.mapping),"member"],sizeLimit:this.maxResults+1},s)).searchEntries||[],h=c.length;if(h>this.maxResults)throw y(`Filter matched ${h} resources, exceeds max ${this.maxResults}`);const l=c.slice(r-1,r-1+i),d=this.buildMemberResolver(t),g=l.map(e=>m(e,this.mapping,this.ctx(t),d));return{schemas:[o],totalResults:h,startIndex:r,itemsPerPage:g.length,Resources:g}}async create(i,a){if(!a.displayName)throw w("displayName is required");const o=(await e(this.hooks.scimgroupcreate,[a,i]))[0],{rdn:n,attributes:c}=l(o,this.mapping,this.objectClass);if(!n)throw w("displayName is required");s(n,this.rdnAttribute),c[this.rdnAttribute]=n;const h=[];if(o.members&&Array.isArray(o.members))for(const t of o.members){if(!t||!t.value)continue;const e=await this.users.resolveRef(i,t.value);e&&h.push(e)}if(0===h.length){const t=this.config.group_dummy_user||"cn=fakeuser";h.push(t)}c.member=h;const u=this.baseResolver.groupBase(i),m=`${this.rdnAttribute}=${t(n)},${u}`;try{await this.ldap.add(m,c,i)}catch(t){if(68===g(t))throw $(`Group ${n} already exists`);throw t}const p=await this.get(i,n);return r(this.hooks.scimgroupcreatedone,p),p}async replace(t,s,i){await this.get(t,s);const a=(await e(this.hooks.scimgroupupdate,[s,i,t]))[1],{attributes:o}=l(a,this.mapping,this.objectClass),n=[];if(a.members&&Array.isArray(a.members))for(const e of a.members){if(!e||!e.value)continue;const s=await this.users.resolveRef(t,e.value);s&&n.push(s)}if(0===n.length){const t=this.config.group_dummy_user||"cn=fakeuser";n.push(t)}const c=this.dnForId(s,t),h={replace:{}};for(const[t,e]of Object.entries(o))"objectClass"!==t&&t!==this.rdnAttribute&&(h.replace[t]=e);h.replace.member=n,await this.ldap.modify(c,h,t);const u=await this.get(t,s);return r(this.hooks.scimgroupupdatedone,s,u),u}async patch(t,e,s){await this.get(t,e);const i=await d(s,{mapping:this.mapping,memberAttribute:"member",resolveMemberRef:async e=>this.users.resolveRef(t,e)});if(!i.add&&!i.replace&&(!i.delete||Array.isArray(i.delete)&&0===i.delete.length))return this.get(t,e);const a=this.config.group_dummy_user||"cn=fakeuser",o=this.dnForId(e,t);if(i.delete&&!Array.isArray(i.delete)&&i.delete.member){const t=await this.ldap.search({paged:!1,scope:"base",attributes:["member"]},o),e=t.searchEntries[0]?.member,s=Array.isArray(e)?e:e?[String(e)]:[],r=Array.isArray(i.delete.member)?i.delete.member:[String(i.delete.member)];0===s.filter(t=>!r.includes(t)).length&&(i.add||(i.add={}),i.add.member=[a])}await this.ldap.modify(o,i,t);const n=await this.get(t,e);return r(this.hooks.scimgroupupdatedone,e,n),n}async delete(t,s){await this.get(t,s);const i=(await e(this.hooks.scimgroupdelete,[s,t]))[0],a=this.dnForId(i,t);await this.ldap.delete(a,t),r(this.hooks.scimgroupdeletedone,i)}async resolveRef(e,s){if(!s)return;const r=this.baseResolver.groupBase(e);if(s.includes("=")&&s.includes(","))return i(s,r)?s:void 0;const o=`${this.rdnAttribute}=${t(s)},${r}`;try{const t=await this.ldap.search({paged:!1,scope:"base",attributes:["dn"]},o);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch{}try{const t=await this.ldap.search({filter:`(${this.rdnAttribute}=${a(s)})`,scope:"sub",paged:!1,attributes:["dn"]},r);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch{}}get groupMapping(){return this.mapping}}export{ScimGroups};
1
+ import{escapeDnValue as t,launchHooksChained as e,validateDnValue as s,launchHooks as r,isChildOf as i,escapeLdapFilter as a}from"../../lib/utils.js";import{SCHEMA_LIST_RESPONSE as o}from"./types.js";import{mergeMapping as n,loadMappingFile as c,DEFAULT_GROUP_MAPPING as h,requiredLdapAttributes as u,ldapToScimGroup as m,scimGroupToLdap as d}from"./mapping.js";import{scimFilterToLdap as l}from"./filter.js";import{pagedSearch as p}from"./list.js";import{patchToModifyRequest as g}from"./patch.js";import{extractLdapCode as f,scimNotFound as b,ScimError as y,scimInvalidValue as w,scimUniqueness as _}from"./errors.js";class ScimGroups{constructor(t){this.ldap=t.ldap,this.config=t.config,this.logger=t.logger,this.baseResolver=t.baseResolver,this.users=t.users,this.hooks=t.hooks,this.rdnAttribute=this.config.scim_group_rdn_attribute||"cn",this.objectClass=this.config.scim_group_object_class,this.idAttribute=this.config.scim_id_attribute||"rdn",this.maxResults=this.config.scim_max_results||200,this.maxScanned=this.config.scim_max_scanned||1e4,this.scimPrefix=this.config.scim_prefix||"/scim/v2";const e=this.config.scim_group_mapping||"";this.mapping=n(h,e?c(e):void 0)}ctx(t){return{idAttribute:this.idAttribute,rdnAttribute:this.rdnAttribute,resourceType:"Group",baseUrl:this.config.scim_base_url||(t?.protocol&&t.get?`${t.protocol}://${String(t.get("host")||"")}`:""),scimPrefix:this.scimPrefix}}dnForId(e,s){const r=this.baseResolver.groupBase(s);return`${this.rdnAttribute}=${t(e)},${r}`}buildMemberResolver(t){const e=this.baseResolver.userBase(t),s=this.baseResolver.groupBase(t),r=this.scimPrefix,i=this.config.scim_base_url||(t?.protocol&&t.get?`${t.protocol}://${String(t.get("host")||"")}`:""),a=this.config.scim_user_rdn_attribute||"uid",o=this.rdnAttribute,n=this.config.group_dummy_user||"cn=fakeuser";return t=>{if(!t)return;if(t.toLowerCase()===n.toLowerCase())return;const c=/^([^=]+)=((?:\\.|[^,])+)/.exec(t);if(!c)return;const h=c[2].replace(/\\(.)/g,"$1"),u=t.toLowerCase();let m;e&&u.endsWith(e.toLowerCase())?m="User":s&&u.endsWith(s.toLowerCase())?m="Group":c[1]===a?m="User":c[1]===o&&(m="Group");const d={value:h};if(m&&(d.type=m,i)){const t="User"===m?"Users":"Groups";d.$ref=`${i.replace(/\/$/,"")}${r}/${t}/${encodeURIComponent(h)}`}return d}}async get(t,e){const s=this.dnForId(e,t);let r;try{r=await this.ldap.search({paged:!1,scope:"base",attributes:[...u(this.mapping),"member"]},s,t)}catch(t){if(32===f(t))throw b(`Group ${e} not found`);throw t}if(!r.searchEntries||0===r.searchEntries.length)throw b(`Group ${e} not found`);return m(r.searchEntries[0],this.mapping,this.ctx(t),this.buildMemberResolver(t))}async list(t,e){const s=this.baseResolver.groupBase(t),r=Math.max(1,e.startIndex||1),i=Math.min(this.maxResults,Math.max(0,e.count??this.maxResults));let a,n=`(objectClass=${this.objectClass.find(t=>"top"!==t)||"groupOfNames"})`;if(e.filter){const t=l(e.filter,this.mapping);t.idEquals?a=t.idEquals:n=`(&${n}${t.ldapFilter})`}if(a)try{const e=await this.get(t,a);return{schemas:[o],totalResults:1,startIndex:1,itemsPerPage:1,Resources:[e]}}catch(t){if(t instanceof y&&404===t.statusCode)return{schemas:[o],totalResults:0,startIndex:1,itemsPerPage:0,Resources:[]};throw t}const{entries:c,totalResults:h}=await p({ldap:this.ldap,base:s,filter:n,attributes:[...u(this.mapping),"member"],startIndex:r,count:i,maxScanned:this.maxScanned,req:t}),d=this.buildMemberResolver(t),g=c.map(e=>m(e,this.mapping,this.ctx(t),d));return{schemas:[o],totalResults:h,startIndex:r,itemsPerPage:g.length,Resources:g}}async create(i,a){if(!a.displayName)throw w("displayName is required");const o=(await e(this.hooks.scimgroupcreate,[a,i]))[0],{rdn:n,attributes:c}=d(o,this.mapping,this.objectClass);if(!n)throw w("displayName is required");s(n,this.rdnAttribute),c[this.rdnAttribute]=n;const h=[];if(o.members&&Array.isArray(o.members))for(const t of o.members){if(!t||!t.value)continue;const e=await this.users.resolveRef(i,t.value);e&&h.push(e)}if(0===h.length){const t=this.config.group_dummy_user||"cn=fakeuser";h.push(t)}c.member=h;const u=this.baseResolver.groupBase(i),m=`${this.rdnAttribute}=${t(n)},${u}`;try{await this.ldap.add(m,c,i)}catch(t){if(68===f(t))throw _(`Group ${n} already exists`);throw t}const l=await this.get(i,n);return r(this.hooks.scimgroupcreatedone,l),l}async replace(t,s,i){await this.get(t,s);const a=(await e(this.hooks.scimgroupupdate,[s,i,t]))[1],{attributes:o}=d(a,this.mapping,this.objectClass),n=[];if(a.members&&Array.isArray(a.members))for(const e of a.members){if(!e||!e.value)continue;const s=await this.users.resolveRef(t,e.value);s&&n.push(s)}if(0===n.length){const t=this.config.group_dummy_user||"cn=fakeuser";n.push(t)}const c=this.dnForId(s,t),h={replace:{}};for(const[t,e]of Object.entries(o))"objectClass"!==t&&t!==this.rdnAttribute&&(h.replace[t]=e);h.replace.member=n,await this.ldap.modify(c,h,t);const u=await this.get(t,s);return r(this.hooks.scimgroupupdatedone,s,u),u}async patch(t,e,s){await this.get(t,e);const i=await g(s,{mapping:this.mapping,memberAttribute:"member",resolveMemberRef:async e=>this.users.resolveRef(t,e)});if(!i.add&&!i.replace&&(!i.delete||Array.isArray(i.delete)&&0===i.delete.length))return this.get(t,e);const a=this.config.group_dummy_user||"cn=fakeuser",o=this.dnForId(e,t);if(i.delete&&!Array.isArray(i.delete)&&i.delete.member){const e=await this.ldap.search({paged:!1,scope:"base",attributes:["member"]},o,t),s=e.searchEntries[0]?.member,r=Array.isArray(s)?s:s?[String(s)]:[],n=Array.isArray(i.delete.member)?i.delete.member:[String(i.delete.member)];0===r.filter(t=>!n.includes(t)).length&&(i.add||(i.add={}),i.add.member=[a])}await this.ldap.modify(o,i,t);const n=await this.get(t,e);return r(this.hooks.scimgroupupdatedone,e,n),n}async delete(t,s){await this.get(t,s);const i=(await e(this.hooks.scimgroupdelete,[s,t]))[0],a=this.dnForId(i,t);await this.ldap.delete(a,t),r(this.hooks.scimgroupdeletedone,i)}async resolveRef(e,s){if(!s)return;const r=this.baseResolver.groupBase(e);if(s.includes("=")&&s.includes(","))return i(s,r)?s:void 0;const o=`${this.rdnAttribute}=${t(s)},${r}`;try{const t=await this.ldap.search({paged:!1,scope:"base",attributes:["dn"]},o,e);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch(t){if(32!==f(t))throw t}try{const t=await this.ldap.search({filter:`(${this.rdnAttribute}=${a(s)})`,scope:"sub",paged:!1,attributes:["dn"]},r,e);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch(t){if(32!==f(t))throw t}}get groupMapping(){return this.mapping}}export{ScimGroups};
@@ -0,0 +1 @@
1
+ import{scimTooMany as t,ScimError as e,extractLdapCode as r}from"./errors.js";async function pagedSearch(a){const i=Math.max(0,a.startIndex-1),s=[];let o=0;try{const e=await a.ldap.search({filter:a.filter,scope:"sub",paged:{pageSize:(n=a.count,Math.min(500,Math.max(n,100)))},attributes:a.attributes,sizeLimit:0},a.base,a.req);for await(const r of e)for(const e of r.searchEntries||[])if(o>=i&&s.length<a.count&&s.push(e),o++,o>a.maxScanned)throw t(`Query matched more than ${a.maxScanned} resources; narrow it with a filter or raise --scim-max-scanned`)}catch(a){if(a instanceof e)throw a;const i=r(a);if(32===i)return{entries:[],totalResults:0};if(4===i)throw t("The directory refused the query with sizeLimitExceeded; narrow it with a filter or raise the directory size limit");throw a}var n;return{entries:s,totalResults:o}}export{pagedSearch};
@@ -1 +1 @@
1
- import e from"fs";import{SCHEMA_GROUP as r,SCHEMA_USER as i}from"./types.js";const a=["createTimestamp","modifyTimestamp","entryUUID"],t={resourceType:"User",schemas:[i],entries:[{scim:"userName",ldap:"uid"},{scim:"externalId",ldap:"employeeNumber"},{scim:"name",sub:{familyName:"sn",givenName:"givenName",formatted:"cn",middleName:"initials"}},{scim:"displayName",ldap:"displayName"},{scim:"nickName",ldap:"displayName"},{scim:"title",ldap:"title"},{scim:"preferredLanguage",ldap:"preferredLanguage"},{scim:"emails",ldapPrimary:"mail",ldapSecondary:"mailAlternateAddress",multi:"array"},{scim:"phoneNumbers",ldapPrimary:"telephoneNumber",ldapSecondary:"mobile",multi:"array"}]},n={resourceType:"Group",schemas:[r],entries:[{scim:"displayName",ldap:"cn"},{scim:"externalId",ldap:"entryUUID",operational:!0,readOnly:!0}]};function asString(e){if(null!=e){if(Array.isArray(e)){const r=e[0];return null==r?void 0:String(r)}return Buffer.isBuffer(e)?e.toString():String(e)}}function asArray(e){return null==e?[]:Array.isArray(e)?e.map(e=>Buffer.isBuffer(e)?e.toString():String(e)):Buffer.isBuffer(e)?[e.toString()]:[String(e)]}function loadMappingFile(r){if(!r)return;const i=e.readFileSync(r,"utf8");return JSON.parse(i)}function mergeMapping(e,r){if(!r)return e;const i=new Map;for(const r of e.entries)i.set(r.scim,r);for(const e of r.entries)i.set(e.scim,e);return{...e,entries:Array.from(i.values())}}function resolveScimId(e,r){if("rdn"===r.idAttribute)return asString(e[r.rdnAttribute]);return asString(e[r.idAttribute])??asString(e[r.rdnAttribute])}function buildLocation(e,r){if(!r.baseUrl)return;const i="User"===r.resourceType?"Users":"Groups";return`${r.baseUrl.replace(/\/$/,"")}${r.scimPrefix}/${i}/${encodeURIComponent(e)}`}function ldapToScimUser(e,r,a){const t={schemas:[i]},n=resolveScimId(e,a);n&&(t.id=n);for(const i of r.entries){if(i.sub){const r={};for(const[a,t]of Object.entries(i.sub)){const i=asString(e[t]);null!=i&&(r[a]=i)}Object.keys(r).length>0&&(t[i.scim]=r);continue}if(i.ldapPrimary||i.ldapSecondary){const r=[];if(i.ldapPrimary){const a=asString(e[i.ldapPrimary]);null!=a&&r.push({value:a,primary:!0})}if(i.ldapSecondary)for(const a of asArray(e[i.ldapSecondary]))r.push({value:a});r.length>0&&(t[i.scim]=r);continue}if(i.ldap)if("array"===i.multi){const r=asArray(e[i.ldap]);r.length>0&&(t[i.scim]=r)}else{const r=asString(e[i.ldap]);null!=r&&(t[i.scim]=r)}}return t.active=null==e.pwdAccountLockedTime,n&&(t.meta={resourceType:"User",created:asString(e.createTimestamp),lastModified:asString(e.modifyTimestamp),location:buildLocation(n,a)}),t}function scimUserToLdap(e,r,i,a){const t={objectClass:a},n=e.userName||e.id||"";for(const i of r.entries){if(i.readOnly||i.operational)continue;const r=e[i.scim];if(null!=r)if(i.sub&&"object"==typeof r)for(const[e,a]of Object.entries(i.sub)){const i=r[e];"string"==typeof i&&i.length>0&&(t[a]=i)}else{if(i.ldapPrimary||i.ldapSecondary){if(!Array.isArray(r))continue;const e=r,a=e.find(e=>!0===e.primary)||e[0],n=e.filter(e=>e!==a);i.ldapPrimary&&a&&a.value&&(t[i.ldapPrimary]=a.value),i.ldapSecondary&&n.length>0&&(t[i.ldapSecondary]=n.map(e=>e.value).filter(Boolean));continue}i.ldap&&(Array.isArray(r)?t[i.ldap]=r.map(e=>String(e)):"string"!=typeof r&&"number"!=typeof r||(t[i.ldap]=String(r)))}}if(!t.cn&&n&&(t.cn=n),!t.sn){const r=e.name?.familyName||e.displayName||n;r&&(t.sn=r)}return{rdn:n,attributes:t}}function ldapToScimGroup(e,i,a,t){const n={schemas:[r]},s=resolveScimId(e,a);s&&(n.id=s);for(const r of i.entries){if(!r.ldap)continue;const i=e[r.ldap];if(null!=i)if("array"===r.multi){const e=asArray(i);e.length>0&&(n[r.scim]=e)}else{const e=asString(i);null!=e&&(n[r.scim]=e)}}const o=asArray(e.member),l=[];for(const e of o)if(e)if(t){const r=t(e);r&&l.push(r)}else l.push({value:e,type:"User"});return l.length>0&&(n.members=l),s&&(n.meta={resourceType:"Group",created:asString(e.createTimestamp),lastModified:asString(e.modifyTimestamp),location:buildLocation(s,a)}),n}function scimGroupToLdap(e,r,i){const a={objectClass:i},t=e.displayName||e.id||"";for(const i of r.entries){if(i.readOnly||i.operational)continue;if(!i.ldap)continue;const r=e[i.scim];null!=r&&(Array.isArray(r)?a[i.ldap]=r.map(e=>"string"==typeof e?e:JSON.stringify(e)):"string"!=typeof r&&"number"!=typeof r||(a[i.ldap]=String(r)))}return{rdn:t,attributes:a}}function requiredLdapAttributes(e){const r=new Set(["objectClass",...a]);for(const i of e.entries)if(i.ldap&&r.add(i.ldap),i.ldapPrimary&&r.add(i.ldapPrimary),i.ldapSecondary&&r.add(i.ldapSecondary),i.sub)for(const e of Object.values(i.sub))r.add(e);return Array.from(r)}function scimPathToLdapAttribute(e,r){const i=e.split(".")[0],a=e.includes(".")?e.split(".").slice(1).join("."):"";for(const e of r.entries)if(e.scim===i){if(e.sub&&a)return e.sub[a];if((e.ldapPrimary||e.ldapSecondary)&&("value"===a||!a))return e.ldapPrimary||e.ldapSecondary;if(e.ldap)return e.ldap}}export{n as DEFAULT_GROUP_MAPPING,t as DEFAULT_USER_MAPPING,buildLocation,ldapToScimGroup,ldapToScimUser,loadMappingFile,mergeMapping,requiredLdapAttributes,resolveScimId,scimGroupToLdap,scimPathToLdapAttribute,scimUserToLdap};
1
+ import e from"fs";import{SCHEMA_GROUP as r,SCHEMA_USER as t}from"./types.js";const i=["createTimestamp","modifyTimestamp","entryUUID"],a={resourceType:"User",schemas:[t],entries:[{scim:"userName",ldap:"uid"},{scim:"externalId",ldap:"employeeNumber"},{scim:"name",sub:{familyName:"sn",givenName:"givenName",formatted:"cn",middleName:"initials"}},{scim:"displayName",ldap:"displayName"},{scim:"nickName",ldap:"displayName"},{scim:"title",ldap:"title"},{scim:"preferredLanguage",ldap:"preferredLanguage"},{scim:"emails",ldapPrimary:"mail",ldapSecondary:"mailAlternateAddress",multi:"array"},{scim:"phoneNumbers",ldapPrimary:"telephoneNumber",ldapSecondary:"mobile",multi:"array"}]},n={resourceType:"Group",schemas:[r],entries:[{scim:"displayName",ldap:"cn"},{scim:"externalId",ldap:"entryUUID",operational:!0,readOnly:!0}]};function asString(e){if(null!=e){if(Array.isArray(e)){const r=e[0];return null==r?void 0:String(r)}return Buffer.isBuffer(e)?e.toString():String(e)}}function ldapTimeToIso(e){if(!e)return;const r=/^(\d{4})(\d{2})(\d{2})(\d{2})(?:(\d{2})(?:(\d{2}))?)?(?:[.,](\d+))?(Z|[+-]\d{2}(?:\d{2})?)?$/.exec(e);if(!r)return e;const[,t,i,a,n,s,o,l,c]=r;let d=0;if(l){const e=o?1e3:s?6e4:36e5;d=Math.round(Number(`0.${l}`)*e)}let p=1e3*(60*(60*Number(n)+Number(s||0))+Number(o||0))+d;const m=Math.floor(p/36e5);p-=36e5*m;const u=Math.floor(p/6e4);p-=6e4*u;const f=Math.floor(p/1e3),y=p-1e3*f;let S="Z";c&&"Z"!==c&&(S=`${c[0]}${c.slice(1,3)}:${c.slice(3,5)||"00"}`);const pad=e=>String(e).padStart(2,"0"),g=`${t}-${i}-${a}T${pad(m)}:${pad(u)}:${pad(f)}${y?`.${String(y).padStart(3,"0")}`:""}${S}`;return Number.isNaN(Date.parse(g))?e:g}function asArray(e){return null==e?[]:Array.isArray(e)?e.map(e=>Buffer.isBuffer(e)?e.toString():String(e)):Buffer.isBuffer(e)?[e.toString()]:[String(e)]}function loadMappingFile(r){if(!r)return;const t=e.readFileSync(r,"utf8");return JSON.parse(t)}function mergeMapping(e,r){if(!r)return e;const t=new Map;for(const r of e.entries)t.set(r.scim,r);for(const e of r.entries)t.set(e.scim,e);return{...e,entries:Array.from(t.values())}}function resolveScimId(e,r){if("rdn"===r.idAttribute)return asString(e[r.rdnAttribute]);return asString(e[r.idAttribute])??asString(e[r.rdnAttribute])}function buildLocation(e,r){if(!r.baseUrl)return;const t="User"===r.resourceType?"Users":"Groups";return`${r.baseUrl.replace(/\/$/,"")}${r.scimPrefix}/${t}/${encodeURIComponent(e)}`}function ldapToScimUser(e,r,i){const a={schemas:[t]},n=resolveScimId(e,i);n&&(a.id=n);for(const t of r.entries){if(t.sub){const r={};for(const[i,a]of Object.entries(t.sub)){const t=asString(e[a]);null!=t&&(r[i]=t)}Object.keys(r).length>0&&(a[t.scim]=r);continue}if(t.ldapPrimary||t.ldapSecondary){const r=[];if(t.ldapPrimary){const i=asString(e[t.ldapPrimary]);null!=i&&r.push({value:i,primary:!0})}if(t.ldapSecondary)for(const i of asArray(e[t.ldapSecondary]))r.push({value:i});r.length>0&&(a[t.scim]=r);continue}if(t.ldap)if("array"===t.multi){const r=asArray(e[t.ldap]);r.length>0&&(a[t.scim]=r)}else{const r=asString(e[t.ldap]);null!=r&&(a[t.scim]=r)}}return a.active=null==e.pwdAccountLockedTime,n&&(a.meta={resourceType:"User",created:ldapTimeToIso(asString(e.createTimestamp)),lastModified:ldapTimeToIso(asString(e.modifyTimestamp)),location:buildLocation(n,i)}),a}function scimUserToLdap(e,r,t,i){const a={objectClass:i},n=e.userName||e.id||"";for(const t of r.entries){if(t.readOnly||t.operational)continue;const r=e[t.scim];if(null!=r)if(t.sub&&"object"==typeof r)for(const[e,i]of Object.entries(t.sub)){const t=r[e];"string"==typeof t&&t.length>0&&(a[i]=t)}else{if(t.ldapPrimary||t.ldapSecondary){if(!Array.isArray(r))continue;const e=r,i=e.find(e=>!0===e.primary)||e[0],n=e.filter(e=>e!==i);t.ldapPrimary&&i&&i.value&&(a[t.ldapPrimary]=i.value),t.ldapSecondary&&n.length>0&&(a[t.ldapSecondary]=n.map(e=>e.value).filter(Boolean));continue}t.ldap&&(Array.isArray(r)?a[t.ldap]=r.map(e=>String(e)):"string"!=typeof r&&"number"!=typeof r||(a[t.ldap]=String(r)))}}if(!a.cn&&n&&(a.cn=n),!a.sn){const r=e.name?.familyName||e.displayName||n;r&&(a.sn=r)}return{rdn:n,attributes:a}}function ldapToScimGroup(e,t,i,a){const n={schemas:[r]},s=resolveScimId(e,i);s&&(n.id=s);for(const r of t.entries){if(!r.ldap)continue;const t=e[r.ldap];if(null!=t)if("array"===r.multi){const e=asArray(t);e.length>0&&(n[r.scim]=e)}else{const e=asString(t);null!=e&&(n[r.scim]=e)}}const o=asArray(e.member),l=[];for(const e of o)if(e)if(a){const r=a(e);r&&l.push(r)}else l.push({value:e,type:"User"});return l.length>0&&(n.members=l),s&&(n.meta={resourceType:"Group",created:ldapTimeToIso(asString(e.createTimestamp)),lastModified:ldapTimeToIso(asString(e.modifyTimestamp)),location:buildLocation(s,i)}),n}function scimGroupToLdap(e,r,t){const i={objectClass:t},a=e.displayName||e.id||"";for(const t of r.entries){if(t.readOnly||t.operational)continue;if(!t.ldap)continue;const r=e[t.scim];null!=r&&(Array.isArray(r)?i[t.ldap]=r.map(e=>"string"==typeof e?e:JSON.stringify(e)):"string"!=typeof r&&"number"!=typeof r||(i[t.ldap]=String(r)))}return{rdn:a,attributes:i}}function requiredLdapAttributes(e){const r=new Set(["objectClass",...i]);for(const t of e.entries)if(t.ldap&&r.add(t.ldap),t.ldapPrimary&&r.add(t.ldapPrimary),t.ldapSecondary&&r.add(t.ldapSecondary),t.sub)for(const e of Object.values(t.sub))r.add(e);return Array.from(r)}function scimPathToLdapAttribute(e,r){const t=e.split(".")[0],i=e.includes(".")?e.split(".").slice(1).join("."):"";for(const e of r.entries)if(e.scim===t){if(e.sub&&i)return e.sub[i];if((e.ldapPrimary||e.ldapSecondary)&&("value"===i||!i))return e.ldapPrimary||e.ldapSecondary;if(e.ldap)return e.ldap}}export{n as DEFAULT_GROUP_MAPPING,a as DEFAULT_USER_MAPPING,buildLocation,ldapTimeToIso,ldapToScimGroup,ldapToScimUser,loadMappingFile,mergeMapping,requiredLdapAttributes,resolveScimId,scimGroupToLdap,scimPathToLdapAttribute,scimUserToLdap};
@@ -1 +1 @@
1
- import{scimInvalidValue as e,scimInvalidPath as t,scimNoTarget as r}from"./errors.js";import{scimPathToLdapAttribute as o}from"./mapping.js";function normalizeOp(t){const r=t.toLowerCase();if("add"===r||"remove"===r||"replace"===r)return r;throw e(`Unknown patch op '${t}'`)}const a=new Set(["__proto__","constructor","prototype"]),n=/^[A-Za-z_$:][\w$:]*$/;function assertSafeKey(e){if(a.has(e))throw t(`Forbidden attribute name '${e}'`)}function parsePath(e){if("string"!=typeof e||0===e.length||e.length>512)throw t(`Malformed path '${String(e)}'`);let r,o,a;const i=e.indexOf("[");if(i>=0){const n=e.lastIndexOf("]");if(n<=i)throw t(`Malformed path '${e}'`);r=e.slice(0,i),o=e.slice(i+1,n);const l=e.slice(n+1);if(l.length>0){if("."!==l[0])throw t(`Malformed path '${e}'`);a=l.slice(1)}}else{const t=e.indexOf(".");t>=0?(r=e.slice(0,t),a=e.slice(t+1)):r=e}if(!n.test(r))throw t(`Malformed path '${e}'`);if(assertSafeKey(r),null!=a){if(!n.test(a))throw t(`Malformed sub-attribute in '${e}'`);assertSafeKey(a)}return{top:r,filter:o,sub:a}}function mergeAttr(e,t,r){const o=e[t];if(null==o)return void(e[t]=r);const a=[...(n=o,Array.isArray(n)?n.map(e=>"string"==typeof e?e:e.toString()):["string"==typeof n?n:n.toString()]),...Array.isArray(r)?r:[r]];var n;e[t]=a}async function applyOperation(a,n,i){const l=normalizeOp(a.op),s=i.memberAttribute||"member";if(!a.path){if(null==a.value||"object"!=typeof a.value||Array.isArray(a.value))throw r("PATCH without path requires an object value");const e=a.value;for(const[t,r]of Object.entries(e))await applyOperation({op:a.op,path:t,value:r},n,i);return}const{top:f,sub:p,filter:u}=parsePath(a.path);if("members"===f){if(!i.resolveMemberRef)throw r("Member operations require a member resolver");if("add"===l){const e=Array.isArray(a.value)?a.value:[a.value],t=[];for(const r of e){const e="string"==typeof r?r:"object"==typeof r&&null!=r&&"value"in r?String(r.value):"";if(!e)continue;const o=await i.resolveMemberRef(e);o&&t.push(o)}return void(t.length>0&&(n.add||(n.add={}),mergeAttr(n.add,s,t)))}if("remove"===l){const collectFromValue=()=>{if(null==a.value)return[];return(Array.isArray(a.value)?a.value:[a.value]).map(e=>"string"==typeof e?e:"object"==typeof e&&null!=e&&"value"in e?String(e.value):"").filter(Boolean)},e=[...(()=>{if(!u)return[];const e=/value\s+eq\s+"([^"]+)"/i.exec(u);return e?[e[1]]:[]})(),...collectFromValue()],t=[];for(const r of e){const e=await i.resolveMemberRef(r);e&&t.push(e)}return void(t.length>0?(n.delete||(n.delete={}),Array.isArray(n.delete)?n.delete={[s]:t}:mergeAttr(n.delete,s,t)):u||a.value||(n.delete||(n.delete={}),Array.isArray(n.delete)?n.delete.push(s):n.delete[s]=""))}if("replace"===l){const e=Array.isArray(a.value)?a.value:[a.value],t=[];for(const r of e){const e="string"==typeof r?r:"object"==typeof r&&null!=r&&"value"in r?String(r.value):"";if(!e)continue;const o=await i.resolveMemberRef(e);o&&t.push(o)}return void(t.length>0&&(n.replace||(n.replace={}),n.replace[s]=t))}}if(u)throw t(`Complex multi-valued filters are only supported on 'members' (got '${a.path}')`);const c=o(p?`${f}.${p}`:f,i.mapping);if(!c)throw t(`Unknown SCIM attribute path '${a.path}'`);if("remove"===l)return n.delete||(n.delete={}),void(Array.isArray(n.delete)?n.delete.push(c):n.delete[c]="");const y=function(t){if(null!=t)return"string"==typeof t?t:"number"==typeof t||"boolean"==typeof t?String(t):Array.isArray(t)?t.map(t=>{if("string"==typeof t)return t;if("number"==typeof t||"boolean"==typeof t)return String(t);if(t&&"object"==typeof t&&!Array.isArray(t)&&"value"in t&&("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value))return String(t.value);throw e(`Unsupported array element in PATCH value: ${JSON.stringify(t)}`)}):void 0}(a.value);if(null==y)throw e(`PATCH ${a.op} ${a.path} missing value`);return"add"===l?(n.add||(n.add={}),void mergeAttr(n.add,c,y)):"replace"===l?(n.replace||(n.replace={}),void(n.replace[c]=y)):void 0}async function patchToModifyRequest(t,r){if(!t.Operations||!Array.isArray(t.Operations))throw e("Missing Operations array");const o={};for(const e of t.Operations)await applyOperation(e,o,r);return o}function applyPatchToResource(e,t){const r={...e};for(const e of t.Operations){const t=normalizeOp(e.op);if(!e.path){if(e.value&&"object"==typeof e.value&&!Array.isArray(e.value))for(const[t,o]of Object.entries(e.value))assertSafeKey(t),r[t]=o;continue}const{top:o,sub:a}=parsePath(e.path);if("remove"!==t)if(a){const t=r[o],n=t&&"object"==typeof t&&!Array.isArray(t)?{...t}:Object.create(null);n[a]=e.value,r[o]=n}else r[o]=e.value;else if(a){const e=r[o];e&&"object"==typeof e&&delete e[a]}else delete r[o]}return r}export{applyPatchToResource,patchToModifyRequest};
1
+ import{scimInvalidValue as e,scimInvalidPath as t,scimNoTarget as r}from"./errors.js";import{scimPathToLdapAttribute as o}from"./mapping.js";function normalizeOp(t){const r=t.toLowerCase();if("add"===r||"remove"===r||"replace"===r)return r;throw e(`Unknown patch op '${t}'`)}const a=new Set(["__proto__","constructor","prototype"]),n=/^[A-Za-z_$:][\w$:]*$/;function assertSafeKey(e){if(a.has(e))throw t(`Forbidden attribute name '${e}'`)}function parsePath(e){if("string"!=typeof e||0===e.length||e.length>512)throw t(`Malformed path '${String(e)}'`);let r,o,a;const i=e.indexOf("[");if(i>=0){const n=e.lastIndexOf("]");if(n<=i)throw t(`Malformed path '${e}'`);r=e.slice(0,i),o=e.slice(i+1,n);const l=e.slice(n+1);if(l.length>0){if("."!==l[0])throw t(`Malformed path '${e}'`);a=l.slice(1)}}else{const t=e.indexOf(".");t>=0?(r=e.slice(0,t),a=e.slice(t+1)):r=e}if(!n.test(r))throw t(`Malformed path '${e}'`);if(assertSafeKey(r),null!=a){if(!n.test(a))throw t(`Malformed sub-attribute in '${e}'`);assertSafeKey(a)}return{top:r,filter:o,sub:a}}function mergeAttr(e,t,r){const o=e[t];if(null==o)return void(e[t]=r);const a=[...(n=o,Array.isArray(n)?n.map(e=>"string"==typeof e?e:e.toString()):["string"==typeof n?n:n.toString()]),...Array.isArray(r)?r:[r]];var n;e[t]=a}async function applyOperation(a,n,i){const l=normalizeOp(a.op),s=i.memberAttribute||"member";if(!a.path){if("remove"===l)throw r("PATCH 'remove' requires a path");if(null==a.value||"object"!=typeof a.value||Array.isArray(a.value))throw r("PATCH without path requires an object value");const e=a.value;for(const[t,r]of Object.entries(e))await applyOperation({op:a.op,path:t,value:r},n,i);return}const{top:f,sub:p,filter:u}=parsePath(a.path);if("members"===f){if(!i.resolveMemberRef)throw r("Member operations require a member resolver");if("add"===l){const e=Array.isArray(a.value)?a.value:[a.value],t=[];for(const r of e){const e="string"==typeof r?r:"object"==typeof r&&null!=r&&"value"in r?String(r.value):"";if(!e)continue;const o=await i.resolveMemberRef(e);o&&t.push(o)}return void(t.length>0&&(n.add||(n.add={}),mergeAttr(n.add,s,t)))}if("remove"===l){const collectFromValue=()=>{if(null==a.value)return[];return(Array.isArray(a.value)?a.value:[a.value]).map(e=>"string"==typeof e?e:"object"==typeof e&&null!=e&&"value"in e?String(e.value):"").filter(Boolean)},e=[...(()=>{if(!u)return[];const e=/value\s+eq\s+"([^"]+)"/i.exec(u);return e?[e[1]]:[]})(),...collectFromValue()],t=[];for(const r of e){const e=await i.resolveMemberRef(r);e&&t.push(e)}return void(t.length>0?(n.delete||(n.delete={}),Array.isArray(n.delete)?n.delete={[s]:t}:mergeAttr(n.delete,s,t)):u||a.value||(n.delete||(n.delete={}),Array.isArray(n.delete)?n.delete.push(s):n.delete[s]=""))}if("replace"===l){const e=Array.isArray(a.value)?a.value:[a.value],t=[];for(const r of e){const e="string"==typeof r?r:"object"==typeof r&&null!=r&&"value"in r?String(r.value):"";if(!e)continue;const o=await i.resolveMemberRef(e);o&&t.push(o)}return void(t.length>0&&(n.replace||(n.replace={}),n.replace[s]=t))}}if(u)throw t(`Complex multi-valued filters are only supported on 'members' (got '${a.path}')`);const c=o(p?`${f}.${p}`:f,i.mapping);if(!c)throw t(`Unknown SCIM attribute path '${a.path}'`);if("remove"===l)return n.delete||(n.delete={}),void(Array.isArray(n.delete)?n.delete.push(c):n.delete[c]="");const y=function(t){if(null!=t)return"string"==typeof t?t:"number"==typeof t||"boolean"==typeof t?String(t):Array.isArray(t)?t.map(t=>{if("string"==typeof t)return t;if("number"==typeof t||"boolean"==typeof t)return String(t);if(t&&"object"==typeof t&&!Array.isArray(t)&&"value"in t&&("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value))return String(t.value);throw e(`Unsupported array element in PATCH value: ${JSON.stringify(t)}`)}):void 0}(a.value);if(null==y)throw e(`PATCH ${a.op} ${a.path} missing value`);return"add"===l?(n.add||(n.add={}),void mergeAttr(n.add,c,y)):"replace"===l?(n.replace||(n.replace={}),void(n.replace[c]=y)):void 0}async function patchToModifyRequest(t,r){if(!t.Operations||!Array.isArray(t.Operations))throw e("Missing Operations array");const o={};for(const e of t.Operations)await applyOperation(e,o,r);return o}function applyPatchToResource(e,t){const r={...e};for(const e of t.Operations){const t=normalizeOp(e.op);if(!e.path){if(e.value&&"object"==typeof e.value&&!Array.isArray(e.value))for(const[t,o]of Object.entries(e.value))assertSafeKey(t),r[t]=o;continue}const{top:o,sub:a}=parsePath(e.path);if("remove"!==t)if(a){const t=r[o],n=t&&"object"==typeof t&&!Array.isArray(t)?{...t}:Object.create(null);n[a]=e.value,r[o]=n}else r[o]=e.value;else if(a){const e=r[o];e&&"object"==typeof e&&delete e[a]}else delete r[o]}return r}export{applyPatchToResource,patchToModifyRequest};
@@ -1 +1 @@
1
- import{fileURLToPath as s}from"url";import{join as i,dirname as e}from"path";import t from"body-parser";import o from"../../abstract/plugin.js";import{BaseResolver as r}from"./baseResolver.js";import{ScimUsers as a}from"./users.js";import{ScimGroups as n}from"./groups.js";import{ScimBulk as c}from"./bulk.js";import{ScimDiscovery as p}from"./discovery.js";import{SCIM_CONTENT_TYPE as m,scimAsyncHandler as d,writeScimError as u}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";class Scim extends o{constructor(t){super(t),this.name="scim",this.roles=["api","configurable"],this.ldap=t.ldap,this.scimPrefix=this.config.scim_prefix||"/scim/v2",this.baseResolver=new r(this.config),this.users=new a({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,hooks:this.registeredHooks}),this.groups=new n({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,users:this.users,hooks:this.registeredHooks}),this.bulk=new c({config:this.config,logger:this.logger,users:this.users,groups:this.groups,scimPrefix:this.scimPrefix,hooks:this.registeredHooks});const o=this.config.schemas_path||i(e(s(import.meta.url)),"..","..","..","static","schemas");this.discovery=new p({config:this.config,schemaDir:i(o,"scim"),scimPrefix:this.scimPrefix,loadedPlugins:t.loadedPlugins})}scimJson(s,i,e){s.status(i).type(m).json(e)}readScimBody(s){const i=String(s.headers["content-type"]||"");if(i.startsWith("application/scim+json")||i.startsWith("application/json"))return s.body}api(s){const i=this.scimPrefix;s.use(i,t.json({type:["application/json","application/scim+json"],limit:`${this.config.scim_bulk_max_payload_size||1048576}b`})),s.get(`${i}/Users`,d(async(s,i)=>{const e=this.parseListQuery(s),t=await this.users.list(s,e);this.scimJson(i,200,t)})),s.get(`${i}/Users/:id`,d(async(s,i)=>{const e=await this.users.get(s,decodeURIComponent(s.params.id));this.scimJson(i,200,e)})),s.post(`${i}/Users`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=await this.users.create(s,e);this.scimJson(i,201,t)})),s.put(`${i}/Users/:id`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=await this.users.replace(s,decodeURIComponent(s.params.id),e);this.scimJson(i,200,t)})),s.patch(`${i}/Users/:id`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=await this.users.patch(s,decodeURIComponent(s.params.id),e);this.scimJson(i,200,t)})),s.delete(`${i}/Users/:id`,d(async(s,i)=>{await this.users.delete(s,decodeURIComponent(s.params.id)),i.status(204).end()})),s.get(`${i}/Groups`,d(async(s,i)=>{const e=this.parseListQuery(s),t=await this.groups.list(s,e);this.scimJson(i,200,t)})),s.get(`${i}/Groups/:id`,d(async(s,i)=>{const e=await this.groups.get(s,decodeURIComponent(s.params.id));this.scimJson(i,200,e)})),s.post(`${i}/Groups`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=await this.groups.create(s,e);this.scimJson(i,201,t)})),s.put(`${i}/Groups/:id`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=await this.groups.replace(s,decodeURIComponent(s.params.id),e);this.scimJson(i,200,t)})),s.patch(`${i}/Groups/:id`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=await this.groups.patch(s,decodeURIComponent(s.params.id),e);this.scimJson(i,200,t)})),s.delete(`${i}/Groups/:id`,d(async(s,i)=>{await this.groups.delete(s,decodeURIComponent(s.params.id)),i.status(204).end()})),s.post(`${i}/Bulk`,d(async(s,i)=>{const e=this.readScimBody(s);if(!e||"object"!=typeof e)return u(i,400,"Missing body","invalidSyntax");const t=this.config.scim_bulk_max_payload_size||1048576,o=JSON.stringify(e).length;if(o>t)return u(i,413,`Bulk payload too large: ${o} > ${t}`,"invalidValue");const r=await this.bulk.execute(s,e);this.scimJson(i,200,r)})),s.get(`${i}/ServiceProviderConfig`,d((s,i)=>{this.scimJson(i,200,this.discovery.serviceProviderConfig(s))})),s.get(`${i}/ResourceTypes`,d((s,i)=>{const e=this.discovery.resourceTypes(s);this.scimJson(i,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:e.length,Resources:e,startIndex:1,itemsPerPage:e.length})})),s.get(`${i}/ResourceTypes/:name`,d((s,i)=>{const e=this.discovery.resourceType(s.params.name,s);if(!e)return u(i,404,"ResourceType not found");this.scimJson(i,200,e)})),s.get(`${i}/Schemas`,d((s,i)=>{const e=this.discovery.schemas(s);this.scimJson(i,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:e.length,Resources:e,startIndex:1,itemsPerPage:e.length})})),s.get(`${i}/Schemas/:id`,d((s,i)=>{const e=this.discovery.schema(decodeURIComponent(s.params.id),s);if(!e)return u(i,404,"Schema not found");this.scimJson(i,200,e)}))}parseListQuery(s){const i=s.query,toInt=s=>{if("string"!=typeof s)return;const i=parseInt(s,10);return Number.isFinite(i)?i:void 0},toCsv=s=>{if("string"==typeof s)return s.split(",").map(s=>s.trim()).filter(Boolean)},e="string"==typeof i.sortOrder?i.sortOrder:void 0;return{filter:"string"==typeof i.filter?i.filter:void 0,startIndex:toInt(i.startIndex),count:toInt(i.count),attributes:toCsv(i.attributes),excludedAttributes:toCsv(i.excludedAttributes),sortBy:"string"==typeof i.sortBy?i.sortBy:void 0,sortOrder:"ascending"===e||"descending"===e?e:void 0}}getConfigApiData(){const s=this.scimPrefix;let i;if(this.server.loadedPlugins.static){const s=this.config.static_name||"static";i={user:`/${s}/schemas/scim/User.json`,group:`/${s}/schemas/scim/Group.json`,defaultMapping:`/${s}/schemas/scim/default-mapping.json`}}const e=this.users.userMapping,t=this.groups.groupMapping,o=e.entries.map(s=>s.scim).concat(["id","active"]),r=t.entries.map(s=>s.scim).concat(["id"]);return{enabled:!0,version:"2.0",prefix:s,endpoints:{users:`${s}/Users`,user:`${s}/Users/:id`,groups:`${s}/Groups`,group:`${s}/Groups/:id`,bulk:`${s}/Bulk`,serviceProviderConfig:`${s}/ServiceProviderConfig`,resourceTypes:`${s}/ResourceTypes`,schemas:`${s}/Schemas`},schemaUrls:i,capabilities:{patch:!0,bulk:!0,filter:!0,sort:!0,etag:Boolean(this.config.scim_etag),changePassword:!1,maxResults:this.config.scim_max_results||200,bulkMaxOperations:this.config.scim_bulk_max_operations||100,bulkMaxPayloadSize:this.config.scim_bulk_max_payload_size||1048576},resourceTypes:[{id:"User",endpoint:`${s}/Users`,schema:"urn:ietf:params:scim:schemas:core:2.0:User",rdnAttribute:this.config.scim_user_rdn_attribute||"uid",objectClass:this.config.scim_user_object_class,filterableAttributes:o,mapping:e.entries},{id:"Group",endpoint:`${s}/Groups`,schema:"urn:ietf:params:scim:schemas:core:2.0:Group",rdnAttribute:this.config.scim_group_rdn_attribute||"cn",objectClass:this.config.scim_group_object_class,filterableAttributes:r,mapping:t.entries}],idStrategy:this.config.scim_id_attribute||"rdn",baseResolution:{userBaseTemplate:this.config.scim_user_base_template||void 0,groupBaseTemplate:this.config.scim_group_base_template||void 0,hasBaseMap:Boolean(this.config.scim_base_map)}}}}export{Scim as default};
1
+ import{fileURLToPath as s}from"url";import{join as e,dirname as i}from"path";import t from"body-parser";import o from"../../abstract/plugin.js";import{BaseResolver as r}from"./baseResolver.js";import{ScimUsers as a}from"./users.js";import{ScimGroups as c}from"./groups.js";import{ScimBulk as n}from"./bulk.js";import{ScimDiscovery as p}from"./discovery.js";import{SCIM_CONTENT_TYPE as m,scimAsyncHandler as d,writeScimError as u}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";class Scim extends o{constructor(t){super(t),this.name="scim",this.roles=["api","configurable"],this.ldap=t.ldap,this.scimPrefix=this.config.scim_prefix||"/scim/v2",this.baseResolver=new r(this.config),this.users=new a({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,hooks:this.registeredHooks}),this.groups=new c({ldap:this.ldap,config:this.config,logger:this.logger,baseResolver:this.baseResolver,users:this.users,hooks:this.registeredHooks}),this.bulk=new n({config:this.config,logger:this.logger,users:this.users,groups:this.groups,scimPrefix:this.scimPrefix,hooks:this.registeredHooks});const o=this.config.schemas_path||e(i(s(import.meta.url)),"..","..","..","static","schemas");this.discovery=new p({config:this.config,schemaDir:e(o,"scim"),scimPrefix:this.scimPrefix,loadedPlugins:t.loadedPlugins})}scimJson(s,e,i){s.status(e).type(m).json(i)}scimResource(s,e,i,t){const o=i.id?`${this.scimPrefix}/${t}/${encodeURIComponent(i.id)}`:void 0,r=(this.config.scim_base_url||"")&&i.meta?.location?i.meta.location:o;r&&s.set("Location",r),this.scimJson(s,e,i)}readScimBody(s){const e=String(s.headers["content-type"]||"");if(e.startsWith("application/scim+json")||e.startsWith("application/json"))return s.body}api(s){const e=this.scimPrefix;s.use(e,t.json({type:["application/json","application/scim+json"],limit:`${this.config.scim_bulk_max_payload_size||1048576}b`})),s.get(`${e}/Users`,d(async(s,e)=>{const i=this.parseListQuery(s),t=await this.users.list(s,i);this.scimJson(e,200,t)})),s.get(`${e}/Users/:id`,d(async(s,e)=>{const i=await this.users.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,i)})),s.post(`${e}/Users`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=await this.users.create(s,i);this.scimResource(e,201,t,"Users")})),s.put(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=await this.users.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users")})),s.patch(`${e}/Users/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=await this.users.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Users")})),s.delete(`${e}/Users/:id`,d(async(s,e)=>{await this.users.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.get(`${e}/Groups`,d(async(s,e)=>{const i=this.parseListQuery(s),t=await this.groups.list(s,i);this.scimJson(e,200,t)})),s.get(`${e}/Groups/:id`,d(async(s,e)=>{const i=await this.groups.get(s,decodeURIComponent(s.params.id));this.scimJson(e,200,i)})),s.post(`${e}/Groups`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=await this.groups.create(s,i);this.scimResource(e,201,t,"Groups")})),s.put(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=await this.groups.replace(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups")})),s.patch(`${e}/Groups/:id`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=await this.groups.patch(s,decodeURIComponent(s.params.id),i);this.scimResource(e,200,t,"Groups")})),s.delete(`${e}/Groups/:id`,d(async(s,e)=>{await this.groups.delete(s,decodeURIComponent(s.params.id)),e.status(204).end()})),s.post(`${e}/Bulk`,d(async(s,e)=>{const i=this.readScimBody(s);if(!i||"object"!=typeof i)return u(e,400,"Missing body","invalidSyntax");const t=this.config.scim_bulk_max_payload_size||1048576,o=JSON.stringify(i).length;if(o>t)return u(e,413,`Bulk payload too large: ${o} > ${t}`,"invalidValue");const r=await this.bulk.execute(s,i);this.scimJson(e,200,r)})),s.get(`${e}/ServiceProviderConfig`,d((s,e)=>{this.scimJson(e,200,this.discovery.serviceProviderConfig(s))})),s.get(`${e}/ResourceTypes`,d((s,e)=>{const i=this.discovery.resourceTypes(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/ResourceTypes/:name`,d((s,e)=>{const i=this.discovery.resourceType(s.params.name,s);if(!i)return u(e,404,"ResourceType not found");this.scimJson(e,200,i)})),s.get(`${e}/Schemas`,d((s,e)=>{const i=this.discovery.schemas(s);this.scimJson(e,200,{schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:i.length,Resources:i,startIndex:1,itemsPerPage:i.length})})),s.get(`${e}/Schemas/:id`,d((s,e)=>{const i=this.discovery.schema(decodeURIComponent(s.params.id),s);if(!i)return u(e,404,"Schema not found");this.scimJson(e,200,i)}))}parseListQuery(s){const e=s.query,toInt=s=>{if("string"!=typeof s)return;const e=parseInt(s,10);return Number.isFinite(e)?e:void 0},toCsv=s=>{if("string"==typeof s)return s.split(",").map(s=>s.trim()).filter(Boolean)},i="string"==typeof e.sortOrder?e.sortOrder:void 0;return{filter:"string"==typeof e.filter?e.filter:void 0,startIndex:toInt(e.startIndex),count:toInt(e.count),attributes:toCsv(e.attributes),excludedAttributes:toCsv(e.excludedAttributes),sortBy:"string"==typeof e.sortBy?e.sortBy:void 0,sortOrder:"ascending"===i||"descending"===i?i:void 0}}getConfigApiData(){const s=this.scimPrefix;let e;if(this.server.loadedPlugins.static){const s=this.config.static_name||"static";e={user:`/${s}/schemas/scim/User.json`,group:`/${s}/schemas/scim/Group.json`,defaultMapping:`/${s}/schemas/scim/default-mapping.json`}}const i=this.users.userMapping,t=this.groups.groupMapping,o=i.entries.map(s=>s.scim).concat(["id","active"]),r=t.entries.map(s=>s.scim).concat(["id"]);return{enabled:!0,version:"2.0",prefix:s,endpoints:{users:`${s}/Users`,user:`${s}/Users/:id`,groups:`${s}/Groups`,group:`${s}/Groups/:id`,bulk:`${s}/Bulk`,serviceProviderConfig:`${s}/ServiceProviderConfig`,resourceTypes:`${s}/ResourceTypes`,schemas:`${s}/Schemas`},schemaUrls:e,capabilities:{patch:!0,bulk:!0,filter:!0,sort:!0,etag:Boolean(this.config.scim_etag),changePassword:!1,maxResults:this.config.scim_max_results||200,maxScanned:this.config.scim_max_scanned||1e4,bulkMaxOperations:this.config.scim_bulk_max_operations||100,bulkMaxPayloadSize:this.config.scim_bulk_max_payload_size||1048576},resourceTypes:[{id:"User",endpoint:`${s}/Users`,schema:"urn:ietf:params:scim:schemas:core:2.0:User",rdnAttribute:this.config.scim_user_rdn_attribute||"uid",objectClass:this.config.scim_user_object_class,filterableAttributes:o,mapping:i.entries},{id:"Group",endpoint:`${s}/Groups`,schema:"urn:ietf:params:scim:schemas:core:2.0:Group",rdnAttribute:this.config.scim_group_rdn_attribute||"cn",objectClass:this.config.scim_group_object_class,filterableAttributes:r,mapping:t.entries}],idStrategy:this.config.scim_id_attribute||"rdn",baseResolution:{userBaseTemplate:this.config.scim_user_base_template||void 0,groupBaseTemplate:this.config.scim_group_base_template||void 0,hasBaseMap:Boolean(this.config.scim_base_map)}}}}export{Scim as default};
@@ -1 +1 @@
1
- import{escapeDnValue as t,launchHooksChained as s,validateDnValue as e,launchHooks as i,isChildOf as r,escapeLdapFilter as a}from"../../lib/utils.js";import{SCHEMA_LIST_RESPONSE as h}from"./types.js";import{mergeMapping as n,loadMappingFile as o,DEFAULT_USER_MAPPING as c,requiredLdapAttributes as l,ldapToScimUser as d,scimUserToLdap as u}from"./mapping.js";import{scimFilterToLdap as p}from"./filter.js";import{patchToModifyRequest as m}from"./patch.js";import{extractLdapCode as g,scimNotFound as f,ScimError as b,scimTooMany as w,scimInvalidValue as y,scimUniqueness as x}from"./errors.js";class ScimUsers{constructor(t){this.ldap=t.ldap,this.config=t.config,this.logger=t.logger,this.baseResolver=t.baseResolver,this.hooks=t.hooks,this.rdnAttribute=this.config.scim_user_rdn_attribute||"uid",this.objectClass=this.config.scim_user_object_class,this.idAttribute=this.config.scim_id_attribute||"rdn",this.maxResults=this.config.scim_max_results||200,this.scimPrefix=this.config.scim_prefix||"/scim/v2";const s=this.config.scim_user_mapping||"";this.mapping=n(c,s?o(s):void 0)}ctx(t){return{idAttribute:this.idAttribute,rdnAttribute:this.rdnAttribute,resourceType:"User",baseUrl:this.config.scim_base_url||(t?.protocol&&t.get?`${t.protocol}://${String(t.get("host")||"")}`:""),scimPrefix:this.scimPrefix}}dnForId(s,e){const i=this.baseResolver.userBase(e);return`${this.rdnAttribute}=${t(s)},${i}`}async get(t,s){const e=this.dnForId(s,t);let i;try{i=await this.ldap.search({paged:!1,scope:"base",attributes:l(this.mapping)},e)}catch(t){if(32===g(t))throw f(`User ${s} not found`);throw t}if(!i.searchEntries||0===i.searchEntries.length)throw f(`User ${s} not found`);return d(i.searchEntries[0],this.mapping,this.ctx(t))}async list(t,s){const e=this.baseResolver.userBase(t),i=Math.max(1,s.startIndex||1),r=Math.min(this.maxResults,Math.max(0,s.count??this.maxResults));let a,n=`(${this.objectClass.includes("inetOrgPerson")?"objectClass=inetOrgPerson":`objectClass=${this.objectClass[0]}`})`;if(s.filter){const t=p(s.filter,this.mapping);t.idEquals?a=t.idEquals:n=`(&${n}${t.ldapFilter})`}if(a)try{const s=await this.get(t,a);return{schemas:[h],totalResults:1,startIndex:1,itemsPerPage:1,Resources:[s]}}catch(t){if(t instanceof b&&404===t.statusCode)return{schemas:[h],totalResults:0,startIndex:1,itemsPerPage:0,Resources:[]};throw t}const o=(await this.ldap.search({filter:n,scope:"sub",paged:!1,attributes:l(this.mapping),sizeLimit:this.maxResults+1},e)).searchEntries||[],c=o.length;if(c>this.maxResults)throw w(`Filter matched ${c} resources, exceeds max ${this.maxResults}`);const u=o.slice(i-1,i-1+r);s.sortBy,s.sortOrder;const m=u.map(s=>d(s,this.mapping,this.ctx(t)));return{schemas:[h],totalResults:c,startIndex:i,itemsPerPage:m.length,Resources:m}}async create(r,a){if(!a.userName)throw y("userName is required");const h=(await s(this.hooks.scimusercreate,[a,r]))[0],{rdn:n,attributes:o}=u(h,this.mapping,this.ctx(r),this.objectClass);if(!n)throw y("userName is required");e(n,this.rdnAttribute),o[this.rdnAttribute]=n;const c=this.baseResolver.userBase(r),l=`${this.rdnAttribute}=${t(n)},${c}`;try{await this.ldap.add(l,o,r)}catch(t){if(68===g(t))throw x(`User ${n} already exists`);throw t}const d=await this.get(r,n);return i(this.hooks.scimusercreatedone,d),d}async replace(t,e,r){const a=this.dnForId(e,t),h=await this.ldap.search({paged:!1,scope:"base",attributes:l(this.mapping)},a);if(!h.searchEntries?.length)throw f(`User ${e} not found`);const n=h.searchEntries[0],o=(await s(this.hooks.scimuserupdate,[e,r,t]))[1],{attributes:c}=u(o,this.mapping,this.ctx(t),this.objectClass),d={replace:{},delete:[]},p=new Set(["objectClass","entryUUID","createTimestamp","modifyTimestamp",this.rdnAttribute]),hasAttrValue=t=>null!=t&&("string"==typeof t?t.length>0:!Array.isArray(t)||t.length>0&&t.some(t=>null!=t));for(const t of l(this.mapping))p.has(t)||(null!=c[t]?d.replace[t]=c[t]:hasAttrValue(n[t])&&d.delete.push(t));if(0===Object.keys(d.replace||{}).length&&delete d.replace,0===d.delete.length&&delete d.delete,d.replace||d.delete)try{await this.ldap.modify(a,d,t)}catch(t){if(16!==g(t))throw t}const m=await this.get(t,e);return i(this.hooks.scimuserupdatedone,e,m),m}async patch(t,s,e){await this.get(t,s);const r=await m(e,{mapping:this.mapping});if(!r.add&&!r.replace&&(!r.delete||Array.isArray(r.delete)&&0===r.delete.length))return this.get(t,s);const a=this.dnForId(s,t);await this.ldap.modify(a,r);const h=await this.get(t,s);return i(this.hooks.scimuserupdatedone,s,h),h}async delete(t,e){await this.get(t,e);const r=(await s(this.hooks.scimuserdelete,[e,t]))[0],a=this.dnForId(r,t);await this.ldap.delete(a,t),i(this.hooks.scimuserdeletedone,r)}async resolveRef(s,e){if(!e)return;const i=this.baseResolver.userBase(s);if(e.includes("=")&&e.includes(","))return r(e,i)?e:void 0;const h=`${this.rdnAttribute}=${t(e)},${i}`;try{const t=await this.ldap.search({paged:!1,scope:"base",attributes:["dn"]},h);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch{}try{const t=await this.ldap.search({filter:`(${this.rdnAttribute}=${a(e)})`,scope:"sub",paged:!1,attributes:["dn"]},i);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch{}}get userMapping(){return this.mapping}}export{ScimUsers};
1
+ import{escapeDnValue as t,launchHooksChained as s,validateDnValue as e,launchHooks as i,isChildOf as r,escapeLdapFilter as a}from"../../lib/utils.js";import{SCHEMA_LIST_RESPONSE as n}from"./types.js";import{mergeMapping as o,loadMappingFile as h,DEFAULT_USER_MAPPING as c,requiredLdapAttributes as l,ldapToScimUser as d,scimUserToLdap as u}from"./mapping.js";import{scimFilterToLdap as p}from"./filter.js";import{pagedSearch as m}from"./list.js";import{patchToModifyRequest as g}from"./patch.js";import{extractLdapCode as f,scimNotFound as b,ScimError as w,scimInvalidValue as y,scimUniqueness as x}from"./errors.js";class ScimUsers{constructor(t){this.ldap=t.ldap,this.config=t.config,this.logger=t.logger,this.baseResolver=t.baseResolver,this.hooks=t.hooks,this.rdnAttribute=this.config.scim_user_rdn_attribute||"uid",this.objectClass=this.config.scim_user_object_class,this.idAttribute=this.config.scim_id_attribute||"rdn",this.maxResults=this.config.scim_max_results||200,this.maxScanned=this.config.scim_max_scanned||1e4,this.scimPrefix=this.config.scim_prefix||"/scim/v2";const s=this.config.scim_user_mapping||"";this.mapping=o(c,s?h(s):void 0)}ctx(t){return{idAttribute:this.idAttribute,rdnAttribute:this.rdnAttribute,resourceType:"User",baseUrl:this.config.scim_base_url||(t?.protocol&&t.get?`${t.protocol}://${String(t.get("host")||"")}`:""),scimPrefix:this.scimPrefix}}dnForId(s,e){const i=this.baseResolver.userBase(e);return`${this.rdnAttribute}=${t(s)},${i}`}async get(t,s){const e=this.dnForId(s,t);let i;try{i=await this.ldap.search({paged:!1,scope:"base",attributes:l(this.mapping)},e,t)}catch(t){if(32===f(t))throw b(`User ${s} not found`);throw t}if(!i.searchEntries||0===i.searchEntries.length)throw b(`User ${s} not found`);return d(i.searchEntries[0],this.mapping,this.ctx(t))}async list(t,s){const e=this.baseResolver.userBase(t),i=Math.max(1,s.startIndex||1),r=Math.min(this.maxResults,Math.max(0,s.count??this.maxResults));let a,o=`(${this.objectClass.includes("inetOrgPerson")?"objectClass=inetOrgPerson":`objectClass=${this.objectClass[0]}`})`;if(s.filter){const t=p(s.filter,this.mapping);t.idEquals?a=t.idEquals:o=`(&${o}${t.ldapFilter})`}if(a)try{const s=await this.get(t,a);return{schemas:[n],totalResults:1,startIndex:1,itemsPerPage:1,Resources:[s]}}catch(t){if(t instanceof w&&404===t.statusCode)return{schemas:[n],totalResults:0,startIndex:1,itemsPerPage:0,Resources:[]};throw t}const{entries:h,totalResults:c}=await m({ldap:this.ldap,base:e,filter:o,attributes:l(this.mapping),startIndex:i,count:r,maxScanned:this.maxScanned,req:t});s.sortBy,s.sortOrder;const u=h.map(s=>d(s,this.mapping,this.ctx(t)));return{schemas:[n],totalResults:c,startIndex:i,itemsPerPage:u.length,Resources:u}}async create(r,a){if(!a.userName)throw y("userName is required");const n=(await s(this.hooks.scimusercreate,[a,r]))[0],{rdn:o,attributes:h}=u(n,this.mapping,this.ctx(r),this.objectClass);if(!o)throw y("userName is required");e(o,this.rdnAttribute),h[this.rdnAttribute]=o;const c=this.baseResolver.userBase(r),l=`${this.rdnAttribute}=${t(o)},${c}`;try{await this.ldap.add(l,h,r)}catch(t){if(68===f(t))throw x(`User ${o} already exists`);throw t}const d=await this.get(r,o);return i(this.hooks.scimusercreatedone,d),d}async replace(t,e,r){const a=this.dnForId(e,t),n=await this.ldap.search({paged:!1,scope:"base",attributes:l(this.mapping)},a,t);if(!n.searchEntries?.length)throw b(`User ${e} not found`);const o=n.searchEntries[0],h=(await s(this.hooks.scimuserupdate,[e,r,t]))[1],{attributes:c}=u(h,this.mapping,this.ctx(t),this.objectClass),d={replace:{},delete:[]},p=new Set(["objectClass","entryUUID","createTimestamp","modifyTimestamp",this.rdnAttribute]),hasAttrValue=t=>null!=t&&("string"==typeof t?t.length>0:!Array.isArray(t)||t.length>0&&t.some(t=>null!=t));for(const t of l(this.mapping))p.has(t)||(null!=c[t]?d.replace[t]=c[t]:hasAttrValue(o[t])&&d.delete.push(t));if(0===Object.keys(d.replace||{}).length&&delete d.replace,0===d.delete.length&&delete d.delete,d.replace||d.delete)try{await this.ldap.modify(a,d,t)}catch(t){if(16!==f(t))throw t}const m=await this.get(t,e);return i(this.hooks.scimuserupdatedone,e,m),m}async patch(t,s,e){await this.get(t,s);const r=await g(e,{mapping:this.mapping});if(!r.add&&!r.replace&&(!r.delete||Array.isArray(r.delete)&&0===r.delete.length))return this.get(t,s);const a=this.dnForId(s,t);await this.ldap.modify(a,r,t);const n=await this.get(t,s);return i(this.hooks.scimuserupdatedone,s,n),n}async delete(t,e){await this.get(t,e);const r=(await s(this.hooks.scimuserdelete,[e,t]))[0],a=this.dnForId(r,t);await this.ldap.delete(a,t),i(this.hooks.scimuserdeletedone,r)}async resolveRef(s,e){if(!e)return;const i=this.baseResolver.userBase(s);if(e.includes("=")&&e.includes(","))return r(e,i)?e:void 0;const n=`${this.rdnAttribute}=${t(e)},${i}`;try{const t=await this.ldap.search({paged:!1,scope:"base",attributes:["dn"]},n,s);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch(t){if(32!==f(t))throw t}try{const t=await this.ldap.search({filter:`(${this.rdnAttribute}=${a(e)})`,scope:"sub",paged:!1,attributes:["dn"]},i,s);if(t.searchEntries&&t.searchEntries.length>0)return t.searchEntries[0].dn}catch(t){if(32!==f(t))throw t}}get userMapping(){return this.mapping}}export{ScimUsers};
@@ -192,6 +192,7 @@ export interface Config {
192
192
  scim_user_mapping?: string;
193
193
  scim_group_mapping?: string;
194
194
  scim_max_results?: number;
195
+ scim_max_scanned?: number;
195
196
  scim_bulk_max_operations?: number;
196
197
  scim_bulk_max_payload_size?: number;
197
198
  scim_etag?: boolean;
@@ -40,6 +40,7 @@ export declare class ScimGroups {
40
40
  private readonly objectClass;
41
41
  private readonly idAttribute;
42
42
  private readonly maxResults;
43
+ private readonly maxScanned;
43
44
  private readonly scimPrefix;
44
45
  constructor(opts: ScimGroupsOptions);
45
46
  private ctx;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @module plugins/scim/list
3
+ * @author Xavier Guimard <xguimard@linagora.com>
4
+ *
5
+ * Server-side pagination for the SCIM list endpoints (RFC 7644 §3.4.2.4).
6
+ *
7
+ * A SCIM list is a window — `startIndex` (1-based) and `count` — over the full
8
+ * set of matching resources, plus the size of that set as `totalResults`. LDAP
9
+ * has no equivalent of an offset, so the window is cut while walking a paged
10
+ * search: entries before `startIndex` are counted and dropped, `count` of them
11
+ * are kept, and the remainder is counted so `totalResults` is real.
12
+ *
13
+ * Only the requested page is ever materialised, so the cost of a list is the
14
+ * page, not the directory. Walking still has to reach the end of the result
15
+ * set to count it, which is bounded by `maxScanned`: beyond that we answer the
16
+ * `tooMany` RFC 7644 §3.12 provides for a filter yielding "many more results
17
+ * than the server is willing to calculate or process".
18
+ */
19
+ import type ldapActions from '../../lib/ldapActions';
20
+ import type { AttributesList } from '../../lib/ldapActions';
21
+ import type { DmRequest } from '../../lib/auth/base';
22
+ export interface PagedSearch {
23
+ ldap: ldapActions;
24
+ base: string;
25
+ filter: string;
26
+ attributes: string[];
27
+ /** 1-based index of the first resource to return. */
28
+ startIndex: number;
29
+ /** Page size. Zero is legal and asks for `totalResults` only. */
30
+ count: number;
31
+ /** Upper bound on entries walked before answering `tooMany`. */
32
+ maxScanned: number;
33
+ req?: DmRequest;
34
+ }
35
+ export interface PagedResult {
36
+ entries: AttributesList[];
37
+ totalResults: number;
38
+ }
39
+ export declare function pagedSearch(opts: PagedSearch): Promise<PagedResult>;
@@ -2,6 +2,21 @@ import type { AttributesList } from '../../lib/ldapActions';
2
2
  import { type ScimUser, type ScimGroup, type ResourceMapping, type MultiValued } from './types';
3
3
  export declare const DEFAULT_USER_MAPPING: ResourceMapping;
4
4
  export declare const DEFAULT_GROUP_MAPPING: ResourceMapping;
5
+ /**
6
+ * LDAP GeneralizedTime (RFC 4517 §3.3.13) → SCIM DateTime (RFC 7643 §2.3.5,
7
+ * an `xsd:dateTime`).
8
+ *
9
+ * The directory answers `createTimestamp`/`modifyTimestamp` as
10
+ * `YYYYMMDDHHMMSSZ` (optionally with a fraction, a `+HHMM` offset, or the
11
+ * seconds and minutes omitted). SCIM clients parse `meta.created` as an
12
+ * `xsd:dateTime` and reject anything else, so the raw directory value must not
13
+ * be forwarded as-is.
14
+ *
15
+ * A value that does not look like a GeneralizedTime is returned untouched: a
16
+ * directory may already store an ISO 8601 string, and dropping it would lose
17
+ * more than passing it through.
18
+ */
19
+ export declare function ldapTimeToIso(value: string | undefined): string | undefined;
5
20
  export declare function loadMappingFile(path: string): ResourceMapping | undefined;
6
21
  /**
7
22
  * Build the mapping used at runtime by merging a user-supplied JSON
@@ -10,7 +10,8 @@
10
10
  * - sub-attribute paths: "name.familyName"
11
11
  * - multi-valued paths: "emails"
12
12
  * - Group member ops via filtered path: `members[value eq "alice"]`
13
- * - implicit path (op.value is an object)
13
+ * - implicit path (op.value is an object) for add and replace; `remove`
14
+ * without a path is rejected with `noTarget` per RFC 7644 section 3.5.2.2
14
15
  *
15
16
  * Complex filtered paths on multi-valued attributes OTHER than `members`
16
17
  * (e.g. `emails[type eq "work"]`) are rejected with `invalidPath`: the
@@ -404,6 +404,21 @@ export default class Scim extends DmPlugin {
404
404
  private readonly baseResolver;
405
405
  constructor(server: DM);
406
406
  private scimJson;
407
+ /**
408
+ * Answer a single resource, adding the `Location` response header required
409
+ * by RFC 7644 section 3.1 for a create and used there for a modification.
410
+ *
411
+ * The header is absolute only when the operator pinned the external base
412
+ * URL with `--scim-base-url`. Without it, `meta.location` is built from the
413
+ * request's own `Host`, which the client controls: echoing that into a
414
+ * redirect header would let a caller choose where another consumer of the
415
+ * response is pointed. So we emit the prefix-relative URI instead — valid
416
+ * per RFC 7231 section 7.1.2, and it still names the resource.
417
+ *
418
+ * `meta.location` inside the body keeps its existing behaviour; pin
419
+ * `--scim-base-url` to make it absolute and trustworthy too.
420
+ */
421
+ private scimResource;
407
422
  private readScimBody;
408
423
  api(app: Express): void;
409
424
  private parseListQuery;
@@ -39,6 +39,7 @@ export declare class ScimUsers {
39
39
  private readonly objectClass;
40
40
  private readonly idAttribute;
41
41
  private readonly maxResults;
42
+ private readonly maxScanned;
42
43
  private readonly scimPrefix;
43
44
  constructor(opts: ScimUsersOptions);
44
45
  private ctx;
@@ -56,12 +56,29 @@ All SCIM endpoints sit behind the auth middleware registered before the plugin
56
56
  | `--scim-id-attribute` | `DM_SCIM_ID_ATTRIBUTE` | `rdn` | `rdn` (default) or `entryUUID` for SCIM `id` |
57
57
  | `--scim-user-mapping` | `DM_SCIM_USER_MAPPING` | — | Path to JSON mapping override |
58
58
  | `--scim-group-mapping` | `DM_SCIM_GROUP_MAPPING` | — | Same for Groups |
59
- | `--scim-max-results` | `DM_SCIM_MAX_RESULTS` | `200` | Hard cap on list results |
59
+ | `--scim-max-results` | `DM_SCIM_MAX_RESULTS` | `200` | Maximum page size (caps `count`) |
60
+ | `--scim-max-scanned` | `DM_SCIM_MAX_SCANNED` | `10000` | Entries a list may walk before answering `400 tooMany` |
60
61
  | `--scim-bulk-max-operations` | `DM_SCIM_BULK_MAX_OPERATIONS` | `100` | Max `/Bulk` operations per request |
61
62
  | `--scim-bulk-max-payload-size` | `DM_SCIM_BULK_MAX_PAYLOAD_SIZE` | `1048576` | Max `/Bulk` payload size in bytes |
62
63
  | `--scim-etag` | `DM_SCIM_ETAG` | `false` | Advertise ETag support in discovery (not yet implemented) |
63
64
  | `--scim-base-url` | `DM_SCIM_BASE_URL` | auto from request | Override external base URL for `meta.location` values |
64
65
 
66
+ ### Authorization
67
+
68
+ Every SCIM operation now reaches `ldapActions` with the request attached, so
69
+ the authorization plugins (`core/auth/authzPerBranch`,
70
+ `core/auth/authzDynamic`, …) apply to reads as well as writes. An identity
71
+ denied `read` on a branch gets `403` from `GET /Users`, `GET /Users/{id}` and
72
+ their Group counterparts, instead of the data.
73
+
74
+ An identity that writes needs `read` too: every SCIM write answers with the
75
+ resource it just changed, so it reads the entry back. A write-only grant
76
+ cannot be served, and answers 403 on the read-back.
77
+
78
+ Base resolution (see _multi-tenant_ below) is unchanged and independent: it
79
+ confines an identity to its own subtree whether or not an authorization plugin
80
+ is loaded.
81
+
65
82
  ### Authentication
66
83
 
67
84
  SCIM piggybacks on whichever auth plugin you load. The discovery endpoint
@@ -247,6 +264,39 @@ via `escapeLdapFilter()` — no LDAP injection is possible.
247
264
 
248
265
  Unknown SCIM attributes raise `400 invalidFilter`.
249
266
 
267
+ ## Pagination
268
+
269
+ `GET /Users` and `GET /Groups` answer a window over the matching resources
270
+ (RFC 7644 §3.4.2.4):
271
+
272
+ ```
273
+ GET /scim/v2/Users?startIndex=201&count=100
274
+ ```
275
+
276
+ ```json
277
+ {
278
+ "totalResults": 4213,
279
+ "startIndex": 201,
280
+ "itemsPerPage": 100,
281
+ "Resources": [...]
282
+ }
283
+ ```
284
+
285
+ - `count` is capped at `--scim-max-results` (200). A `count` of 0 is legal and
286
+ answers `totalResults` with no resource.
287
+ - `startIndex` is 1-based, and is not bounded by the page size: any offset into
288
+ the collection is reachable.
289
+ - `totalResults` is the real size of the result set, not the size of the page.
290
+
291
+ Only the requested page is held in memory, but counting the rest still means
292
+ walking to the end of the result set. `--scim-max-scanned` bounds that walk at
293
+ 10000 entries; a query matching more answers `400 tooMany`, which RFC 7644
294
+ §3.12 defines for "a filter [that] yields many more results than the server is
295
+ willing to calculate or process". Raise the flag, or narrow the query.
296
+
297
+ If the directory's own size limit is lower than the collection, it refuses the
298
+ search first and the answer is the same `tooMany` — raise `sizelimit` in slapd.
299
+
250
300
  ## Multi-tenant: per-user LDAP bases
251
301
 
252
302
  SCIM does not expose any tenant concept to clients — resource `id`s are opaque
@@ -562,6 +612,6 @@ the `configurable` role automatically, and `core/configApi` picks it up.
562
612
  - No `/Me` endpoint (RFC 7644 §3.11).
563
613
  - ETag / `If-Match` advertised but not enforced.
564
614
  - No `/.search` POST endpoint (use GET with `?filter=…`).
565
- - `totalResults` is computed from a single LDAP search capped at
566
- `--scim-max-results`; switch to paged search for directories with many thousands
567
- of entries.
615
+ - A list walks its result set to count it, bounded by `--scim-max-scanned`
616
+ (10000 by default). Past that bound it answers `400 tooMany`, as RFC 7644
617
+ §3.12 allows; raise the bound or narrow the query with a filter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -168,6 +168,10 @@
168
168
  "types": "./dist/src/plugins/scim/groups.d.ts",
169
169
  "import": "./dist/plugins/scim/groups.js"
170
170
  },
171
+ "./plugin-scim-list": {
172
+ "types": "./dist/src/plugins/scim/list.d.ts",
173
+ "import": "./dist/plugins/scim/list.js"
174
+ },
171
175
  "./plugin-scim-mapping": {
172
176
  "types": "./dist/src/plugins/scim/mapping.d.ts",
173
177
  "import": "./dist/plugins/scim/mapping.js"