ldap-rest 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/args.js +1 -1
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/plugins/auth/authzPerRoute.js +1 -0
- package/dist/plugins/priority.json.js +1 -1
- package/dist/plugins/scim/bulk.js +1 -1
- package/dist/plugins/scim/groups.js +1 -1
- package/dist/plugins/scim/users.js +1 -1
- package/dist/plugins/twake/cozyProvision.js +1 -0
- package/dist/src/config/args.d.ts +10 -0
- package/dist/src/plugins/auth/authzPerRoute.d.ts +22 -0
- package/dist/src/plugins/twake/cozyProvision.d.ts +56 -0
- package/docs/usage/configuration.md +7 -0
- package/docs/usage/plugins/auth/README.md +1 -0
- package/docs/usage/plugins/auth/authz-per-route.md +144 -0
- package/package.json +10 -1
package/dist/config/args.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as
|
|
1
|
+
import{fileURLToPath as _}from"url";import{join as a,dirname as e}from"path";const t=[["--port","DM_PORT",8081,"number"],["--plugin","DM_PLUGINS",[],"array","--plugins"],["--log-level","DM_LOG_LEVEL","notice"],["--logger","DM_LOGGER","console"],["--api-prefix","DM_API_PREFIX","/api"],["--mail-domain","DM_MAIL_DOMAIN",[],"array","--mail-domains"],["--ldap-base","DM_LDAP_BASE",""],["--ldap-dn","DM_LDAP_DN","cn=admin,dc=example,dc=com"],["--ldap-pwd","DM_LDAP_PWD","admin"],["--ldap-url","DM_LDAP_URL",["ldap://localhost"],"array","--ldap-urls"],["--ldap-user-main-attribute","DM_LDAP_USER_ATTRIBUTE","uid"],["--ldap-cache-max","DM_LDAP_CACHE_MAX",1e3,"number"],["--ldap-cache-ttl","DM_LDAP_CACHE_TTL",300,"number"],["--ldap-pool-size","DM_LDAP_POOL_SIZE",5,"number"],["--ldap-connection-ttl","DM_LDAP_CONNECTION_TTL",60,"number"],["--schemas-path","DM_SCHEMAS_PATH",a(e(_(import.meta.url)),"..","..","static","schemas")],["--mail-attribute","DM_MAIL_ATTRIBUTE","mail"],["--quota-attribute","DM_QUOTA_ATTRIBUTE","mailQuota"],["--delegation-attribute","DM_DELEGATION_ATTRIBUTE","twakeDelegatedUsers"],["--alias-attribute","DM_ALIAS_ATTRIBUTE","mailAlternateAddress"],["--forward-attribute","DM_FORWARD_ATTRIBUTE","mailForwardingAddress"],["--display-name-attribute","DM_DISPLAY_NAME_ATTRIBUTE","displayName"],["--drive-quota-attribute","DM_DRIVE_QUOTA_ATTRIBUTE","twakeDriveQuota"],["--user-class","DM_USER_CLASSES",["top","twakeAccount","twakeWhitePages"],"array","--user-classes"],["--ldap-top-organization","DM_LDAP_TOP_ORGANIZATION",""],["--ldap-organization-class","DM_LDAP_ORGANIZATION_CLASSES",["top","organizationalUnit","twakeDepartment"],"array","--ldap-organization-classes"],["--ldap-organization-link-attribute","DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE","twakeDepartmentLink"],["--ldap-organization-path-attribute","DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE","twakeDepartmentPath"],["--ldap-organization-path-separator","DM_LDAP_ORGANIZATION_PATH_SEPARATOR"," / "],["--ldap-organization-max-subnodes","DM_LDAP_ORGANIZATION_MAX_SUBNODES",50,"number"],["--ldap-group-base","DM_LDAP_GROUP_BASE",""],["--ldap-groups-main-attribute","DM_LDAP_GROUPS_MAIN_ATTRIBUTE","cn"],["--group-class","DM_GROUP_CLASSES",["top","groupOfNames"],"array","--group-classes"],["--group-allow-unexistent-members","DM_ALLOW_UNEXISTENT_MEMBERS",!1,"boolean"],["--group-default-attributes","DM_GROUP_DEFAULT_ATTRIBUTES",{},"json"],["--group-dummy-user","DM_GROUP_DUMMY_USER","cn=fakeuser"],["--group-schema","DM_GROUP_SCHEMA",a(e(_(import.meta.url)),"..","..","static","schemas","twake","groups.json")],["--external-members-branch","DM_EXTERNAL_MEMBERS_BRANCH","ou=contacts,dc=example,dc=com"],["--external-branch-class","DM_EXTERNAL_BRANCH_CLASSES",["top","inetOrgPerson"],"array","--external-branch-classes"],["--static-path","DM_STATIC_PATH",a(e(_(import.meta.url)),"..","..","static")],["--static-name","DM_STATIC_NAME","static"],["--ldap-flat-schema","DM_LDAP_FLAT_SCHEMA",[],"array","--ldap-flat-schemas"],["--bulk-import-schemas","DM_BULK_IMPORT_SCHEMAS",""],["--bulk-import-max-file-size","DM_BULK_IMPORT_MAX_FILE_SIZE","10485760"],["--bulk-import-batch-size","DM_BULK_IMPORT_BATCH_SIZE","100"],["--james-webadmin-url","DM_JAMES_WEBADMIN_URL","http://localhost:8000"],["--james-webadmin-token","DM_JAMES_WEBADMIN_TOKEN",""],["--james-signature-template","DM_JAMES_SIGNATURE_TEMPLATE",""],["--ldap-concurrency","DM_LDAP_CONCURRENCY",10,"number"],["--james-concurrency","DM_JAMES_CONCURRENCY",10,"number"],["--james-init-delay","DM_JAMES_INIT_DELAY",1e3,"number"],["--james-mailing-list-branch","DM_JAMES_MAILING_LIST_BRANCHES",[],"array","--james-mailing-list-branches"],["--james-mailbox-type-attribute","DM_JAMES_MAILBOX_TYPE_ATTRIBUTE","twakeMailboxType"],["--twake-drive-webadmin-url","DM_TWAKE_DRIVE_WEBADMIN_URL",""],["--twake-drive-webadmin-token","DM_TWAKE_DRIVE_WEBADMIN_TOKEN",""],["--twake-drive-concurrency","DM_TWAKE_DRIVE_CONCURRENCY",10,"number"],["--twake-drive-domain-attribute","DM_TWAKE_DRIVE_DOMAIN_ATTRIBUTE","twakeCozyDomain"],["--twake-drive-default-domain-template","DM_TWAKE_DRIVE_DEFAULT_DOMAIN_TEMPLATE",""],["--calendar-webadmin-url","DM_CALENDAR_WEBADMIN_URL","http://localhost:8080"],["--calendar-webadmin-token","DM_CALENDAR_WEBADMIN_TOKEN",""],["--calendar-concurrency","DM_CALENDAR_CONCURRENCY",10,"number"],["--calendar-resource-base","DM_CALENDAR_RESOURCE_BASE",""],["--calendar-resource-objectclass","DM_CALENDAR_RESOURCE_OBJECTCLASS",""],["--calendar-resource-creator","DM_CALENDAR_RESOURCE_CREATOR",""],["--calendar-resource-domain","DM_CALENDAR_RESOURCE_DOMAIN",""],["--cozy-admin-url","DM_COZY_ADMIN_URL",""],["--cozy-admin-user","DM_COZY_ADMIN_USER","admin"],["--cozy-admin-passphrase","DM_COZY_ADMIN_PASSPHRASE",""],["--cozy-org-id","DM_COZY_ORG_ID",""],["--cozy-org-domain","DM_COZY_ORG_DOMAIN",""],["--cozy-default-locale","DM_COZY_DEFAULT_LOCALE","fr"],["--cozy-auth-exchange","DM_COZY_AUTH_EXCHANGE","auth"],["--cozy-b2b-exchange","DM_COZY_B2B_EXCHANGE","b2b"],["--rabbitmq-url","DM_RABBITMQ_URL",""],["--applicative-account-base","DM_APPLICATIVE_ACCOUNT_BASE",""],["--max-app-accounts","DM_MAX_APP_ACCOUNTS",5,"number"],["--ldap-operational-attribute","DM_LDAP_OPERATIONAL_ATTRIBUTES",["dn","controls","structuralObjectClass","entryUUID","entryDN","subschemaSubentry","modifyTimestamp","modifiersName","createTimestamp","creatorsName","userPassword"],"array","--ldap-operational-attributes"],["--trash-base","DM_TRASH_BASE",""],["--trash-watched-bases","DM_TRASH_WATCHED_BASES",""],["--trash-add-metadata","DM_TRASH_ADD_METADATA","true"],["--trash-auto-create","DM_TRASH_AUTO_CREATE","true"],["--llng-ini","DM_LLNG_INI","/etc/lemonldap-ng/lemonldap-ng.ini"],["--auth-token","DM_AUTH_TOKENS",[],"array","--auth-tokens"],["--auth-totp","DM_AUTH_TOTP",[],"array","--auth-totps"],["--auth-totp-window","DM_AUTH_TOTP_WINDOW",1,"number"],["--auth-totp-step","DM_AUTH_TOTP_STEP",30,"number"],["--auth-hmac","DM_AUTH_HMAC",[],"array","--auth-hmacs"],["--auth-hmac-window","DM_AUTH_HMAC_WINDOW",12e4,"number"],["--authz-per-route","DM_AUTHZ_PER_ROUTES",[],"array","--authz-per-routes"],["--authz-per-branch-config","DM_AUTHZ_PER_BRANCH_CONFIG",{default:{read:!0,write:!1,delete:!1}},"json"],["--authz-per-branch-cache-ttl","DM_AUTHZ_PER_BRANCH_CACHE_TTL",60,"number"],["--authz-dynamic-base","DM_AUTHZ_DYNAMIC_BASE",""],["--authz-dynamic-cache-ttl","DM_AUTHZ_DYNAMIC_CACHE_TTL",60,"number"],["--authz-dynamic-token-attribute","DM_AUTHZ_DYNAMIC_TOKEN_ATTRIBUTE","userPassword"],["--authz-dynamic-config-attribute","DM_AUTHZ_DYNAMIC_CONFIG_ATTRIBUTE","description"],["--authz-dynamic-tenant-attribute","DM_AUTHZ_DYNAMIC_TENANT_ATTRIBUTE","cn"],["--authz-dynamic-reload-endpoint","DM_AUTHZ_DYNAMIC_RELOAD_ENDPOINT",!1,"boolean"],["--authz-local-admin-attribute","DM_AUTHZ_LOCAL_ADMIN_ATTRIBUTE","twakeLocalAdminLink"],["--oidc-server","DM_OIDC_SERVER",""],["--oidc-client-id","DM_OIDC_CLIENT_ID",""],["--oidc-client-secret","DM_OIDC_CLIENT_SECRET",""],["--base-url","DM_BASE_URL",""],["--rate-limit-window-ms","DM_RATE_LIMIT_WINDOW_MS",9e5,"number"],["--rate-limit-max","DM_RATE_LIMIT_MAX",100,"number"],["--crowdsec-url","DM_CROWDSEC_URL","http://localhost:8080/v1/decisions"],["--crowdsec-api-key","DM_CROWDSEC_API_KEY",""],["--crowdsec-cache-ttl","DM_CROWDSEC_CACHE_TTL",60,"number"],["--trusted-proxy","DM_TRUSTED_PROXIES",[],"array","--trusted-proxies"],["--trusted-proxy-auth-header","DM_TRUSTED_PROXY_AUTH_HEADER","Auth-User"],["--ppolicy-default-dn","DM_PPOLICY_DEFAULT_DN",""],["--ppolicy-warn-days","DM_PPOLICY_WARN_DAYS",14,"number"],["--ppolicy-validate-complexity","DM_PPOLICY_VALIDATE_COMPLEXITY",!1,"boolean"],["--ppolicy-min-length","DM_PPOLICY_MIN_LENGTH",12,"number"],["--ppolicy-require-uppercase","DM_PPOLICY_REQUIRE_UPPERCASE",!0,"boolean"],["--ppolicy-require-lowercase","DM_PPOLICY_REQUIRE_LOWERCASE",!0,"boolean"],["--ppolicy-require-digit","DM_PPOLICY_REQUIRE_DIGIT",!0,"boolean"],["--ppolicy-require-special","DM_PPOLICY_REQUIRE_SPECIAL",!0,"boolean"],["--ldap-users-base","DM_LDAP_USERS_BASE",""],["--scim-prefix","DM_SCIM_PREFIX","/scim/v2"],["--scim-user-base","DM_SCIM_USER_BASE",""],["--scim-group-base","DM_SCIM_GROUP_BASE",""],["--scim-user-base-template","DM_SCIM_USER_BASE_TEMPLATE",""],["--scim-group-base-template","DM_SCIM_GROUP_BASE_TEMPLATE",""],["--scim-base-map","DM_SCIM_BASE_MAP",""],["--scim-user-object-class","DM_SCIM_USER_OBJECT_CLASSES",["top","inetOrgPerson","organizationalPerson","person"],"array","--scim-user-object-classes"],["--scim-user-rdn-attribute","DM_SCIM_USER_RDN_ATTRIBUTE","uid"],["--scim-group-object-class","DM_SCIM_GROUP_OBJECT_CLASSES",["top","groupOfNames"],"array","--scim-group-object-classes"],["--scim-group-rdn-attribute","DM_SCIM_GROUP_RDN_ATTRIBUTE","cn"],["--scim-id-attribute","DM_SCIM_ID_ATTRIBUTE","rdn"],["--scim-user-mapping","DM_SCIM_USER_MAPPING",""],["--scim-group-mapping","DM_SCIM_GROUP_MAPPING",""],["--scim-max-results","DM_SCIM_MAX_RESULTS",200,"number"],["--scim-bulk-max-operations","DM_SCIM_BULK_MAX_OPERATIONS",100,"number"],["--scim-bulk-max-payload-size","DM_SCIM_BULK_MAX_PAYLOAD_SIZE",1048576,"number"],["--scim-etag","DM_SCIM_ETAG",!1,"boolean"],["--scim-base-url","DM_SCIM_BASE_URL",""]];export{t as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
let r;const setLogger=t=>{r=t},getLogger=()=>r,tryMethod=async(r,t,...e)=>{try{return await t(...e),ok(r)}catch(t){return serverError(r,t)}},tryMethodData=async(r,t,...e)=>{try{const n=await t(...e);return ok(r,n)}catch(t){return serverError(r,t)}},_rejectResponse=(t,e,n)=>{r?.warn(`Client error ${t}: ${n}`),e.status(t).json({error:n})},ok=(r,t={success:!0})=>{r.status(200).json(t)},created=(r,t)=>{r.status(201).json(t)},badRequest=(r,t="Bad request")=>_rejectResponse(400,r,t),unauthorized=(r,t="Unauthorized")=>_rejectResponse(401,r,t),notFound=(r,t="Not found")=>_rejectResponse(404,r,t),serverError=(t,e)=>{const n=e instanceof Error?e.message:String(e);let
|
|
1
|
+
let r;const setLogger=t=>{r=t},getLogger=()=>r,tryMethod=async(r,t,...e)=>{try{return await t(...e),ok(r)}catch(t){return serverError(r,t)}},tryMethodData=async(r,t,...e)=>{try{const n=await t(...e);return ok(r,n)}catch(t){return serverError(r,t)}},_rejectResponse=(t,e,n)=>{r?.warn(`Client error ${t}: ${n}`),e.status(t).json({error:n})},ok=(r,t={success:!0})=>{r.status(200).json(t)},created=(r,t)=>{r.status(201).json(t)},badRequest=(r,t="Bad request")=>_rejectResponse(400,r,t),unauthorized=(r,t="Unauthorized")=>_rejectResponse(401,r,t),forbidden=(r,t="Forbidden")=>_rejectResponse(403,r,t),notFound=(r,t="Not found")=>_rejectResponse(404,r,t),serverError=(t,e)=>{const n=e instanceof Error?e.message:String(e);let o=e instanceof Error&&"statusCode"in e?e.statusCode:500;if(500===o&&/\[authz-forbidden\]/.test(n)&&(o=403),o>=400&&o<500){const e=403===o&&/\[authz-forbidden\]/.test(n)?"Token does not have permission on this branch":n;return r.warn(`Client error ${o}: ${e}`),void t.status(o).json({error:e})}e instanceof Error?r.error(e.message,e):"string"==typeof e?r.error(e):r.error("Server error",e),t.status(o).json({error:"check logs"})},wantJson=(r,t)=>!1!==r.accepts("json")||(badRequest(t),!1),jsonBody=(r,t,...e)=>{try{if(!wantJson(r,t))return!1;const n=r.body;if(e.length>0)for(let r=0;r<e.length;r++)if(null==n[e[r]])return badRequest(t,"Bad content"),!1;return n}catch(r){return badRequest(t,"Bad content"),!1}};export{badRequest,created,forbidden,getLogger,jsonBody,notFound,ok,serverError,setLogger,tryMethod,tryMethodData,unauthorized,wantJson};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"../../abstract/plugin.js";import{forbidden as t}from"../../lib/expressFormatedResponses.js";const r=/^[\w/.\-+*]*$/;function globToRegex(e){if(!r.test(e))throw new Error(`Invalid glob pattern: "${e}" — only [a-zA-Z0-9_/.-+*] are allowed`);const t=e.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/\\\*\\\*/g,".*").replace(/\\\*/g,"[^/]*");return new RegExp(`^${t}$`)}class AuthzPerRoute extends e{constructor(...e){super(...e),this.name="authzPerRoute",this.roles=["authz"],this.rules=new Map;const t=this.config.authz_per_route??[];for(const e of t)this.parseEntry(e);const r=[...this.rules.entries()].map(([e,t])=>`${e}: ${t.some(e=>"wildcard"===e.kind)?"full access":`${t.length} rule${1!==t.length?"s":""}`}`).join(", ");this.logger.info(`authzPerRoute: ${this.rules.size} user${1!==this.rules.size?"s":""} configured${this.rules.size>0?` (${r})`:""}`)}parseEntry(e){const t=e.split(":").map(e=>e.trim());if(t.length<2)return void this.logger.warn(`authzPerRoute: ignoring invalid rule entry: ${e}`);const r=t[0];if(r)if(2!==t.length||"*"!==t[1]){if(t.length>=3){const i=t[1].toUpperCase(),o=new Set(["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS","*"]);if(!i||!o.has(i))return void this.logger.warn(`authzPerRoute: ignoring rule with invalid method "${t[1]}" in entry: ${e}`);const n=t.slice(2).join(":").trim();if(!n)return void this.logger.warn(`authzPerRoute: ignoring rule with empty path pattern in entry: ${e}`);let s;try{s=globToRegex(n)}catch{return void this.logger.warn(`authzPerRoute: ignoring entry with invalid glob "${n}" in rule: ${e}`)}return void this.addRule(r,{kind:"method-path",method:i,pathRe:s})}this.logger.warn(`authzPerRoute: ignoring invalid rule entry: ${e}`)}else this.addRule(r,{kind:"wildcard"});else this.logger.warn(`authzPerRoute: ignoring rule with empty user: ${e}`)}addRule(e,t){const r=this.rules.get(e);r?r.push(t):this.rules.set(e,[t])}matches(e,t,r){for(const i of e){if("wildcard"===i.kind)return!0;if(("*"===i.method||i.method===t.toUpperCase())&&i.pathRe.test(r))return!0}return!1}api(e){e.use((e,r,i)=>{const o=e.user;if(!o)return i();const n=this.rules.get(o);return n?this.matches(n,e.method,e.path)?i():(this.logger.warn(`authzPerRoute: user '${o}' denied for ${e.method} ${e.path}`),t(r)):(this.logger.warn(`authzPerRoute: user '${o}' denied for ${e.method} ${e.path} (no rules configured)`),t(r))})}}export{AuthzPerRoute as default,globToRegex};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=["core/auth/trustedProxy","core/weblogs","core/auth/crowdsec","core/auth/rateLimit","core/auth/token","core/auth/llng","core/auth/openidconnect"];export{e as default};
|
|
1
|
+
var e=["core/auth/trustedProxy","core/weblogs","core/auth/crowdsec","core/auth/rateLimit","core/auth/token","core/auth/llng","core/auth/openidconnect","core/auth/authzPerRoute"];export{e as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{launchHooks as t}from"../../lib/utils.js";import{SCHEMA_BULK_RESPONSE as s}from"./types.js";import{scimInvalidValue as e,ScimError as o}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";const r=/^bulkId:([A-Za-z0-9_-]+)$/;class ScimBulk{constructor(t){this.config=t.config,this.logger=t.logger,this.users=t.users,this.groups=t.groups,this.scimPrefix=t.scimPrefix,this.hooks=t.hooks}async execute(o,r){const i=this.config.scim_bulk_max_operations||100;if(!r.Operations||!Array.isArray(r.Operations))throw e("Missing Operations array");if(r.Operations.length>i)throw e(`Too many operations: ${r.Operations.length} (max ${i})`);const a=new Map,n=[];let c=0;const u=r.failOnErrors??0;for(const t of r.Operations){if(u>0&&c>=u)break;const s=await this.executeOne(o,t,a);n.push(s),parseInt(s.status,10)>=400&&c++}const h={schemas:[s],Operations:n};return t(this.hooks.scimbulkdone,
|
|
1
|
+
import{launchHooks as t}from"../../lib/utils.js";import{SCHEMA_BULK_RESPONSE as s}from"./types.js";import{scimInvalidValue as e,ScimError as o}from"./errors.js";export{writeScimErrorFromException}from"./errors.js";const r=/^bulkId:([A-Za-z0-9_-]+)$/;class ScimBulk{constructor(t){this.config=t.config,this.logger=t.logger,this.users=t.users,this.groups=t.groups,this.scimPrefix=t.scimPrefix,this.hooks=t.hooks}async execute(o,r){const i=this.config.scim_bulk_max_operations||100;if(!r.Operations||!Array.isArray(r.Operations))throw e("Missing Operations array");if(r.Operations.length>i)throw e(`Too many operations: ${r.Operations.length} (max ${i})`);const a=new Map,n=[];let c=0;const u=r.failOnErrors??0;for(const t of r.Operations){if(u>0&&c>=u)break;const s=await this.executeOne(o,t,a);n.push(s),parseInt(s.status,10)>=400&&c++}const h={schemas:[s],Operations:n};return t(this.hooks.scimbulkdone,h),h}async executeOne(t,s,i){const a={method:s.method,bulkId:s.bulkId,status:"200"};try{if(!s.method||!s.path)throw e("method and path are required");s.data&&"object"==typeof s.data&&this.resolveRefs(s.data,i);const o=s.path.replace(/^\//,"").split("/"),n=o[0],c=o[1];if("Users"!==n&&"Groups"!==n)throw e(`Unsupported bulk path '${s.path}'`);const u="Users"===n;let h=c;if(h&&r.test(h)){const t=r.exec(h),s=i.get(t[1]);if(!s)throw e(`Unknown bulkId reference '${t[1]}'`);h=s.id}switch(s.method){case"POST":{if(h)throw e("POST must not include an id");const o=u?await this.users.create(t,s.data):await this.groups.create(t,s.data);return a.status="201",a.location=this.buildLocation(n,o.id,t),a.version=o.meta?.version||void 0,s.bulkId&&i.set(s.bulkId,{id:o.id,type:u?"User":"Group"}),a}case"PUT":{if(!h)throw e("PUT requires an id");const o=u?await this.users.replace(t,h,s.data):await this.groups.replace(t,h,s.data);return a.status="200",a.location=this.buildLocation(n,o.id,t),a}case"PATCH":{if(!h)throw e("PATCH requires an id");const o=u?await this.users.patch(t,h,s.data):await this.groups.patch(t,h,s.data);return a.status="200",a.location=this.buildLocation(n,o.id,t),a}case"DELETE":if(!h)throw e("DELETE requires an id");return u?await this.users.delete(t,h):await this.groups.delete(t,h),a.status="204",a;default:throw e(`Unsupported method '${String(s.method)}'`)}}catch(t){const s=t instanceof o?t.statusCode:500;return a.status=String(s),a.response={schemas:["urn:ietf:params:scim:api:messages:2.0:Error"],status:String(s),...t instanceof o&&t.scimType?{scimType:t.scimType}:{},detail:t instanceof Error?t.message:String(t)},a}}resolveRefs(t,s){for(const[e,o]of Object.entries(t)){if("string"==typeof o){const i=r.exec(o);if(i){const o=s.get(i[1]);o&&(t[e]=o.id)}continue}if(Array.isArray(o)){const t=o;for(let e=0;e<t.length;e++){const o=t[e];if("string"==typeof o){const i=r.exec(o);if(i){const o=s.get(i[1]);o&&(t[e]=o.id)}}else o&&"object"==typeof o&&this.resolveRefs(o,s)}continue}o&&"object"==typeof o&&this.resolveRefs(o,s)}}buildLocation(t,s,e){const o=this.config.scim_base_url||"";return`${o?o.replace(/\/$/,""):e?.protocol&&e.get?`${e.protocol}://${String(e.get("host")||"")}`:""}${this.scimPrefix}/${t}/${encodeURIComponent(s)}`}}export{ScimBulk};
|
|
@@ -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)}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,
|
|
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)}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);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);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),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 +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)}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,
|
|
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)}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)}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),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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Buffer as t}from"buffer";import e from"node-fetch";import i from"../../abstract/plugin.js";class CozyProvision extends i{constructor(t){super(t),this.name="cozyProvision",this.roles=["consistency"],this.publisher=null,this.publisherInit=null,this.hooks={scimusercreatedone:async t=>{await this.createCozyInstance(t)&&await this.publishUserCreated(t)},scimuserdeletedone:async t=>{await this.publishUserDeleted(t)}},this.cozyAdminUrl=(this.config.cozy_admin_url||"").replace(/\/$/,""),this.cozyAdminUser=this.config.cozy_admin_user||"admin",this.cozyAdminPassphrase=this.config.cozy_admin_passphrase||"",this.cozyOrgId=this.config.cozy_org_id||"",this.cozyOrgDomain=this.config.cozy_org_domain||"",this.cozyDefaultLocale=this.config.cozy_default_locale||"fr",this.rabbitmqUrl=this.config.rabbitmq_url||"",this.authExchange=this.config.cozy_auth_exchange||"auth",this.b2bExchange=this.config.cozy_b2b_exchange||"b2b",this.cozyAdminUrl||this.logger.warn(`${this.name}: cozy_admin_url is empty — Cozy instance creation will be skipped`),this.cozyOrgDomain||this.logger.warn(`${this.name}: cozy_org_domain is empty — workplaceFqdn cannot be composed`),this.rabbitmqUrl||this.logger.warn(`${this.name}: rabbitmq_url is empty — AMQP publishes will be skipped`),this.registerShutdown()}async createCozyInstance(i){if(!this.cozyAdminUrl)return!1;const r=this.extractId(i);if(!r)return this.logger.warn({plugin:this.name,event:"createCozyInstance",message:"user has no userName/id — skipping"}),!1;if(!this.cozyOrgDomain)return this.logger.error({plugin:this.name,event:"createCozyInstance",id:r,message:"cozy_org_domain not configured — cannot compose Domain"}),!1;const s=`${r}.${this.cozyOrgDomain}`,n=new URLSearchParams;n.set("Domain",s),n.set("Locale",this.extractLocale(i));const a=this.extractPrimaryEmail(i);a&&n.set("Email",a),this.cozyOrgId&&n.set("OrgID",this.cozyOrgId),this.cozyOrgDomain&&n.set("OrgDomain",this.cozyOrgDomain);const o=`${this.cozyAdminUrl}/instances?${n.toString()}`,c=t.from(`${this.cozyAdminUser}:${this.cozyAdminPassphrase}`).toString("base64"),h={plugin:this.name,event:"createCozyInstance",id:r,domain:s,email:a||void 0};try{const t=await e(o,{method:"POST",headers:{Authorization:`Basic ${c}`,Accept:"application/json"}});return t.ok?(this.logger.info({...h,result:"success",http_status:t.status}),!0):409===t.status?(this.logger.info({...h,result:"already_exists",http_status:t.status}),!0):(this.logger.error({...h,result:"error",http_status:t.status,http_status_text:t.statusText}),!1)}catch(t){return this.logger.error({...h,result:"error",error:`${t}`}),!1}}async publishUserCreated(t){const e=await this.getPublisher();if(!e)return;const i=this.extractId(t);if(!i)return;const r=this.extractPrimaryEmail(t),s={twakeId:i,organizationDomain:this.cozyOrgDomain,workplaceFqdn:`${i}.${this.cozyOrgDomain}`};r&&(s.email=r);try{await e.publish(this.authExchange,"user.created",s),this.logger.info({plugin:this.name,event:"publishUserCreated",result:"success",exchange:this.authExchange,routingKey:"user.created",twakeId:i})}catch(t){this.logger.error({plugin:this.name,event:"publishUserCreated",twakeId:i,error:`${t}`})}}async publishUserDeleted(t){const e=await this.getPublisher();if(!e)return;if(!this.cozyOrgDomain)return void this.logger.error({plugin:this.name,event:"publishUserDeleted",id:t,message:"cozy_org_domain not configured — cannot compose workplaceFqdn"});const i={workplaceFqdn:`${t}.${this.cozyOrgDomain}`,domain:this.cozyOrgDomain};try{await e.publish(this.b2bExchange,"domain.user.deleted",i),this.logger.info({plugin:this.name,event:"publishUserDeleted",result:"success",exchange:this.b2bExchange,routingKey:"domain.user.deleted",workplaceFqdn:i.workplaceFqdn})}catch(e){this.logger.error({plugin:this.name,event:"publishUserDeleted",id:t,error:`${e}`})}}async getPublisher(){return this.publisher?this.publisher:this.rabbitmqUrl?(this.publisherInit||(this.publisherInit=(async()=>{try{const t=new((await import("@linagora/rabbitmq-client")).RabbitMQClient)({url:this.rabbitmqUrl});return await t.init(),this.publisher=t,this.logger.info(`${this.name}: connected to RabbitMQ at ${function(t){try{const e=new URL(t);return e.username="",e.password="",e.toString()}catch{return"amqp://[broker]"}}(this.rabbitmqUrl)}`),t}catch(t){return this.logger.warn({plugin:this.name,event:"rabbitmq_init",message:"@linagora/rabbitmq-client unavailable or broker unreachable — AMQP publishes disabled",error:`${t}`}),null}})()),this.publisherInit):null}extractId(t){return"string"==typeof t.userName&&t.userName.length>0?t.userName:"string"==typeof t.id&&t.id.length>0?t.id:null}extractPrimaryEmail(t){const e=t.emails;if(!e||0===e.length)return null;const i=(e.find(t=>t.primary)||e[0]).value;return"string"==typeof i&&i.length>0?i:null}extractLocale(t){return"string"==typeof t.locale&&t.locale.length>0?t.locale:"string"==typeof t.preferredLanguage&&t.preferredLanguage.length>0?t.preferredLanguage:this.cozyDefaultLocale}registerShutdown(){!function(t){if(r.push(t),s)return;s=!0;const fanOut=()=>{for(const t of r.splice(0))try{t()}catch{}};process.once("SIGTERM",fanOut),process.once("SIGINT",fanOut)}(()=>{const t=this.publisher;this.publisher=null,this.publisherInit=null,t&&t.close().catch(t=>{this.logger.warn({plugin:this.name,event:"shutdown",error:`${t}`})})})}}const r=[];let s=!1;export{CozyProvision as default};
|
|
@@ -76,6 +76,7 @@ export interface Config {
|
|
|
76
76
|
oidc_client_id?: string;
|
|
77
77
|
oidc_client_secret?: string;
|
|
78
78
|
base_url?: string;
|
|
79
|
+
authz_per_route?: string[];
|
|
79
80
|
authz_per_branch_config?: AuthConfig;
|
|
80
81
|
authz_per_branch_cache_ttl?: number;
|
|
81
82
|
authz_dynamic_base?: string;
|
|
@@ -119,6 +120,15 @@ export interface Config {
|
|
|
119
120
|
calendar_resource_objectclass?: string;
|
|
120
121
|
calendar_resource_creator?: string;
|
|
121
122
|
calendar_resource_domain?: string;
|
|
123
|
+
cozy_admin_url?: string;
|
|
124
|
+
cozy_admin_user?: string;
|
|
125
|
+
cozy_admin_passphrase?: string;
|
|
126
|
+
cozy_org_id?: string;
|
|
127
|
+
cozy_org_domain?: string;
|
|
128
|
+
cozy_default_locale?: string;
|
|
129
|
+
cozy_auth_exchange?: string;
|
|
130
|
+
cozy_b2b_exchange?: string;
|
|
131
|
+
rabbitmq_url?: string;
|
|
122
132
|
applicative_account_base?: string;
|
|
123
133
|
max_app_accounts?: number;
|
|
124
134
|
ldap_operational_attributes?: string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/auth/authzPerRoute
|
|
3
|
+
* Route-level authorization plugin.
|
|
4
|
+
*
|
|
5
|
+
* Restricts access by HTTP method + path based on the authenticated user name
|
|
6
|
+
* (req.user, set by an auth plugin loaded before this one).
|
|
7
|
+
*
|
|
8
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
9
|
+
*/
|
|
10
|
+
import type { Express } from 'express';
|
|
11
|
+
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
12
|
+
export declare function globToRegex(glob: string): RegExp;
|
|
13
|
+
export default class AuthzPerRoute extends DmPlugin {
|
|
14
|
+
name: string;
|
|
15
|
+
roles: Role[];
|
|
16
|
+
private rules;
|
|
17
|
+
constructor(...args: ConstructorParameters<typeof DmPlugin>);
|
|
18
|
+
private parseEntry;
|
|
19
|
+
private addRule;
|
|
20
|
+
private matches;
|
|
21
|
+
api(app: Express): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
2
|
+
import type { DM } from '../../bin';
|
|
3
|
+
import { Hooks } from '../../hooks';
|
|
4
|
+
interface RabbitPublisher {
|
|
5
|
+
init(): Promise<void>;
|
|
6
|
+
publish(exchange: string, routingKey: string, message: Record<string, unknown>): Promise<void>;
|
|
7
|
+
close(clearSubscriptions?: boolean): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export default class CozyProvision extends DmPlugin {
|
|
10
|
+
name: string;
|
|
11
|
+
roles: Role[];
|
|
12
|
+
private readonly cozyAdminUrl;
|
|
13
|
+
private readonly cozyAdminPassphrase;
|
|
14
|
+
private readonly cozyAdminUser;
|
|
15
|
+
private readonly cozyOrgId;
|
|
16
|
+
private readonly cozyOrgDomain;
|
|
17
|
+
private readonly cozyDefaultLocale;
|
|
18
|
+
private readonly rabbitmqUrl;
|
|
19
|
+
private readonly authExchange;
|
|
20
|
+
private readonly b2bExchange;
|
|
21
|
+
private publisher;
|
|
22
|
+
private publisherInit;
|
|
23
|
+
constructor(server: DM);
|
|
24
|
+
hooks: Hooks;
|
|
25
|
+
/**
|
|
26
|
+
* POST /instances on the cozy-stack admin API. Returns true on success
|
|
27
|
+
* (including 409 idempotent "already exists").
|
|
28
|
+
*/
|
|
29
|
+
private createCozyInstance;
|
|
30
|
+
/**
|
|
31
|
+
* Publish user.created on the `auth` topic exchange. Payload matches what
|
|
32
|
+
* cozy-stack's stack.user.created consumer expects: a `twakeId` identifier,
|
|
33
|
+
* an optional email, and the organizationDomain so the consumer can match
|
|
34
|
+
* the instance. Sending `sub` instead causes cozy-stack to nack the message
|
|
35
|
+
* with "missing twakeId".
|
|
36
|
+
*/
|
|
37
|
+
private publishUserCreated;
|
|
38
|
+
/**
|
|
39
|
+
* Publish domain.user.deleted on the `b2b` topic exchange so peer cozy
|
|
40
|
+
* instances can drop the deleted user's contact card.
|
|
41
|
+
*/
|
|
42
|
+
private publishUserDeleted;
|
|
43
|
+
/**
|
|
44
|
+
* Lazy-init the AMQP publisher. The `@linagora/rabbitmq-client` dependency
|
|
45
|
+
* is optional — if the package or the broker is unreachable, return null
|
|
46
|
+
* so callers can no-op cleanly.
|
|
47
|
+
*
|
|
48
|
+
* Override this in tests to inject a stub publisher.
|
|
49
|
+
*/
|
|
50
|
+
protected getPublisher(): Promise<RabbitPublisher | null>;
|
|
51
|
+
private extractId;
|
|
52
|
+
private extractPrimaryEmail;
|
|
53
|
+
private extractLocale;
|
|
54
|
+
private registerShutdown;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
@@ -27,6 +27,7 @@ All LDAP-Rest configuration options.
|
|
|
27
27
|
- [core/auth/openidconnect](#coreauthopenidconnect)
|
|
28
28
|
- [Authorization Plugins](#authorization-plugins)
|
|
29
29
|
- [core/auth/authzPerBranch](#coreauthauthzperbranch)
|
|
30
|
+
- [core/auth/authzPerRoute](#coreauthauthzperroute)
|
|
30
31
|
- [core/auth/authzLinid1](#coreauthauthzlinid1)
|
|
31
32
|
- [Security Plugins](#security-plugins)
|
|
32
33
|
- [core/auth/rateLimit](#coreauthratelimit)
|
|
@@ -236,6 +237,12 @@ Maintains consistency of department links when organizations are renamed/moved.
|
|
|
236
237
|
| `--authz-per-branch-config` | `DM_AUTHZ_PER_BRANCH_CONFIG` | `{default:{read:true,write:false,delete:false}}` | Authorization config (JSON) |
|
|
237
238
|
| `--authz-per-branch-cache-ttl` | `DM_AUTHZ_PER_BRANCH_CACHE_TTL` | `60` | Cache TTL (seconds) |
|
|
238
239
|
|
|
240
|
+
#### `core/auth/authzPerRoute`
|
|
241
|
+
|
|
242
|
+
| CLI | Env | Default | Description |
|
|
243
|
+
| ------------------- | --------------------- | ------- | ---------------------------------------------------------------------- |
|
|
244
|
+
| `--authz-per-route` | `DM_AUTHZ_PER_ROUTES` | `[]` | Per-user route ACL rules (see [docs](plugins/auth/authz-per-route.md)) |
|
|
245
|
+
|
|
239
246
|
#### `core/auth/authzLinid1`
|
|
240
247
|
|
|
241
248
|
| CLI | Env | Default | Description |
|
|
@@ -17,6 +17,7 @@ LDAP-Rest provides multiple authentication plugins to secure API access. These p
|
|
|
17
17
|
| Method | Plugin | Description |
|
|
18
18
|
| ----------------------------------------------- | -------------------------- | --------------------------- |
|
|
19
19
|
| [Authorization Per Branch](authz-per-branch.md) | `core/auth/authzPerBranch` | Branch-level access control |
|
|
20
|
+
| [Authorization Per Route](authz-per-route.md) | `core/auth/authzPerRoute` | HTTP route-level ACL |
|
|
20
21
|
| [Authorization LinID 1.x](authz-linid1.md) | `core/auth/authzLinid1` | LinID 1.x integration |
|
|
21
22
|
|
|
22
23
|
## Security Plugins
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Route-Level Authorization (`authzPerRoute`)
|
|
2
|
+
|
|
3
|
+
Restricts access to specific HTTP routes based on the authenticated user's name
|
|
4
|
+
(`req.user`), which is set by an auth plugin such as `core/auth/token`.
|
|
5
|
+
|
|
6
|
+
This plugin complements `authzPerBranch` (LDAP-branch ACL) and `authzDynamic`
|
|
7
|
+
(LDAP-backed tokens): whereas those operate at the LDAP-branch level,
|
|
8
|
+
`authzPerRoute` works at the HTTP route level and needs no LDAP connection.
|
|
9
|
+
|
|
10
|
+
## Plugin loading order
|
|
11
|
+
|
|
12
|
+
`authzPerRoute` MUST run **after** the authentication plugin (so `req.user` is set)
|
|
13
|
+
AND **before** any API route plugin (so its Express middleware is registered before
|
|
14
|
+
the routes it protects).
|
|
15
|
+
|
|
16
|
+
When loaded via the CLI plugin loader, plugins not listed in
|
|
17
|
+
`src/plugins/priority.json` are loaded in parallel — this can race with API route
|
|
18
|
+
registration and result in an authorization bypass. To guarantee correct ordering,
|
|
19
|
+
`core/auth/authzPerRoute` is included in the priority list and loads sequentially
|
|
20
|
+
after the other auth plugins. If you build a custom loader, ensure equivalent
|
|
21
|
+
sequencing.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
--plugin core/auth/token \
|
|
25
|
+
--plugin core/auth/authzPerRoute
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
### CLI
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
--authz-per-route "full-access:*" \
|
|
34
|
+
--authz-per-route "updt-only:POST:/api/v1/ldap/updt" \
|
|
35
|
+
--authz-per-route "updt-only:GET:/api/v1/ldap/updt/**"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Environment Variable
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
DM_AUTHZ_PER_ROUTES="full-access:*,updt-only:POST:/api/v1/ldap/updt,updt-only:GET:/api/v1/ldap/updt/**"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Multiple values are comma-separated. Repeating `--authz-per-route` on the CLI
|
|
45
|
+
is equivalent.
|
|
46
|
+
|
|
47
|
+
## Rule Syntax
|
|
48
|
+
|
|
49
|
+
Each entry has one of two forms:
|
|
50
|
+
|
|
51
|
+
| Form | Meaning |
|
|
52
|
+
| ---------------------------- | ----------------------------------------------------------- |
|
|
53
|
+
| `<user>:*` | Full wildcard — allow any method on any path |
|
|
54
|
+
| `<user>:<METHOD>:<pathGlob>` | Allow only the given HTTP method on paths matching the glob |
|
|
55
|
+
|
|
56
|
+
- `<user>` — the value of `req.user` as set by the auth plugin.
|
|
57
|
+
- `<METHOD>` — an HTTP verb (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`,
|
|
58
|
+
`OPTIONS`) or `*` (any verb).
|
|
59
|
+
- `<pathGlob>` — a glob pattern (not a regex). Patterns are implicitly
|
|
60
|
+
anchored start-to-end. Rules:
|
|
61
|
+
- `*` matches any sequence of characters **except** `/` (one path segment).
|
|
62
|
+
- `**` matches any sequence of characters **including** `/` (multiple segments).
|
|
63
|
+
- All other characters are matched literally — regex special characters (`.`, `+`,
|
|
64
|
+
`?`, etc.) have no special meaning.
|
|
65
|
+
- **Allowed characters:** alphanumerics (`a-z`, `A-Z`, `0-9`), `/`, `_`, `-`,
|
|
66
|
+
`.`, `+`, and `*`. Globs containing any other character (e.g. `;`, space,
|
|
67
|
+
`?`) are rejected at startup with a warning and the rule is skipped.
|
|
68
|
+
|
|
69
|
+
A user may have multiple entries; access is granted if **any** rule matches
|
|
70
|
+
(logical OR).
|
|
71
|
+
|
|
72
|
+
### Examples
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# admin token: full access
|
|
76
|
+
full-access:*
|
|
77
|
+
|
|
78
|
+
# read-only token: GET only on exactly /api/hello
|
|
79
|
+
reader:GET:/api/hello
|
|
80
|
+
|
|
81
|
+
# update token: POST on exact path, GET on that path and all sub-paths
|
|
82
|
+
updt:POST:/api/v1/ldap/updt
|
|
83
|
+
updt:GET:/api/v1/ldap/updt**
|
|
84
|
+
|
|
85
|
+
# any method on a path and all sub-paths
|
|
86
|
+
any-method:*:/api/v1/ldap/users**
|
|
87
|
+
|
|
88
|
+
# match one level deep only (not /api/v1/ldap/users/uid=bob/sub)
|
|
89
|
+
one-level:GET:/api/v1/ldap/users/*
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Glob pattern quick reference
|
|
93
|
+
|
|
94
|
+
| Pattern | Matches | Does NOT match |
|
|
95
|
+
| ---------------- | ----------------------------------------------------- | ----------------------------------- |
|
|
96
|
+
| `/api/hello` | `/api/hello` | `/api/hello/sub` |
|
|
97
|
+
| `/api/hello**` | `/api/hello`, `/api/hello/sub`, `/api/hello/sub/deep` | `/api/hell` |
|
|
98
|
+
| `/api/hello/**` | `/api/hello/sub`, `/api/hello/sub/deep` | `/api/hello` |
|
|
99
|
+
| `/api/hello/*` | `/api/hello/sub` | `/api/hello`, `/api/hello/sub/deep` |
|
|
100
|
+
| `/api/hello.bak` | `/api/hello.bak` | `/api/helloXbak` (`.` is literal) |
|
|
101
|
+
|
|
102
|
+
## Behavior
|
|
103
|
+
|
|
104
|
+
| Situation | Result |
|
|
105
|
+
| ----------------------------------------------------- | ------------------------------------------------------ |
|
|
106
|
+
| `req.user` is unset (no auth plugin ran) | Pass through (`next()`) — let upstream auth return 401 |
|
|
107
|
+
| User authenticated but has no rules configured | 403 Forbidden |
|
|
108
|
+
| User has at least one matching rule | 200 / pass through |
|
|
109
|
+
| User has rules but none match the current method+path | 403 Forbidden |
|
|
110
|
+
|
|
111
|
+
## Full Example
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
DM_AUTH_TOKENS="tok-admin:admin,tok-ro:reader"
|
|
115
|
+
DM_AUTHZ_PER_ROUTES="admin:*,reader:GET:/api/v1/ldap/users**,reader:GET:/api/v1/ldap/groups**"
|
|
116
|
+
|
|
117
|
+
npx ldap-rest \
|
|
118
|
+
--plugin core/auth/token \
|
|
119
|
+
--plugin core/auth/authzPerRoute \
|
|
120
|
+
--plugin core/ldap/users \
|
|
121
|
+
--plugin core/ldap/groups \
|
|
122
|
+
--ldap-url ldap://localhost:389 \
|
|
123
|
+
...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# admin: any request allowed
|
|
128
|
+
curl -H "Authorization: Bearer tok-admin" http://api/v1/ldap/users
|
|
129
|
+
curl -H "Authorization: Bearer tok-admin" -X DELETE http://api/v1/ldap/users/uid=bob,ou=users,dc=example,dc=org
|
|
130
|
+
|
|
131
|
+
# reader: GET allowed
|
|
132
|
+
curl -H "Authorization: Bearer tok-ro" http://api/v1/ldap/users # 200
|
|
133
|
+
|
|
134
|
+
# reader: write denied
|
|
135
|
+
curl -H "Authorization: Bearer tok-ro" -X POST http://api/v1/ldap/users # 403
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Difference vs. Other Authz Plugins
|
|
139
|
+
|
|
140
|
+
| Plugin | Scope | Requires LDAP |
|
|
141
|
+
| ---------------- | ------------------------------ | ------------------ |
|
|
142
|
+
| `authzPerRoute` | HTTP method + path | No |
|
|
143
|
+
| `authzPerBranch` | LDAP branch read/write/delete | No (static config) |
|
|
144
|
+
| `authzDynamic` | LDAP branch ACL stored in LDAP | Yes |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-rest",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Light directory manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Identity-Manager"
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
"types": "./dist/src/plugins/auth/authzPerBranch.d.ts",
|
|
53
53
|
"import": "./dist/plugins/auth/authzPerBranch.js"
|
|
54
54
|
},
|
|
55
|
+
"./plugin-auth-authzperroute": {
|
|
56
|
+
"types": "./dist/src/plugins/auth/authzPerRoute.d.ts",
|
|
57
|
+
"import": "./dist/plugins/auth/authzPerRoute.js"
|
|
58
|
+
},
|
|
55
59
|
"./plugin-auth-crowdsec": {
|
|
56
60
|
"types": "./dist/src/plugins/auth/crowdsec.d.ts",
|
|
57
61
|
"import": "./dist/plugins/auth/crowdsec.js"
|
|
@@ -192,6 +196,10 @@
|
|
|
192
196
|
"types": "./dist/src/plugins/twake/calendarResources.d.ts",
|
|
193
197
|
"import": "./dist/plugins/twake/calendarResources.js"
|
|
194
198
|
},
|
|
199
|
+
"./plugin-twake-cozyprovision": {
|
|
200
|
+
"types": "./dist/src/plugins/twake/cozyProvision.d.ts",
|
|
201
|
+
"import": "./dist/plugins/twake/cozyProvision.js"
|
|
202
|
+
},
|
|
195
203
|
"./plugin-twake-drive": {
|
|
196
204
|
"types": "./dist/src/plugins/twake/drive.d.ts",
|
|
197
205
|
"import": "./dist/plugins/twake/drive.js"
|
|
@@ -631,6 +639,7 @@
|
|
|
631
639
|
"typescript": "^5.9.2"
|
|
632
640
|
},
|
|
633
641
|
"optionalDependencies": {
|
|
642
|
+
"@linagora/rabbitmq-client": "^0.1.2",
|
|
634
643
|
"express-openid-connect": "^2.19.2",
|
|
635
644
|
"lemonldap-ng-handler": "^0.7.3",
|
|
636
645
|
"re2": "^1.22.1"
|