vesse / node-ldapauth-fork

Simple node.js module to authenticate against an LDAP server
Other
127 stars 79 forks source link

LDAP authentication breaks on groupDnProperty #103

Closed popindavibe closed 2 years ago

popindavibe commented 2 years ago

Hello,

Context

I am using Hedgedoc with LDAP authentication, following the last upgrade authentication breaks with:

Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]: TypeError: Cannot add property 3, object is not extensible
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at Array.push (<anonymous>)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at LdapAuth._findUser (/opt/codimd/codimd-git/node_modules/ldapauth-fork/lib/ldapauth.js:329:21)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at LdapAuth.authenticate (/opt/codimd/codimd-git/node_modules/ldapauth-fork/lib/ldapauth.js:418:8)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at Strategy.handleAuthentication (/opt/codimd/codimd-git/node_modules/passport-ldapauth/lib/passport-ldapauth/strategy.js:276:8)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at Strategy.authenticate (/opt/codimd/codimd-git/node_modules/passport-ldapauth/lib/passport-ldapauth/strategy.js:344:33)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at attempt (/opt/codimd/codimd-git/node_modules/passport/lib/middleware/authenticate.js:369:16)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at authenticate (/opt/codimd/codimd-git/node_modules/passport/lib/middleware/authenticate.js:370:7)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at /opt/codimd/codimd-git/lib/web/auth/ldap/index.js:88:5
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at Layer.handle [as handle_request] (/opt/codimd/codimd-git/node_modules/express/lib/router/layer.js:95:5)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at next (/opt/codimd/codimd-git/node_modules/express/lib/router/route.js:144:13)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at /opt/codimd/codimd-git/node_modules/body-parser/lib/read.js:137:5
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at AsyncResource.runInAsyncScope (async_hooks.js:197:9)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at invokeCallback (/opt/codimd/codimd-git/node_modules/raw-body/index.js:231:16)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at done (/opt/codimd/codimd-git/node_modules/raw-body/index.js:220:7)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at IncomingMessage.onEnd (/opt/codimd/codimd-git/node_modules/raw-body/index.js:280:7)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at IncomingMessage.emit (events.js:412:35)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at endReadableNT (internal/streams/readable.js:1333:12)
Jul 28 11:52:08 hedgedoc-app.x.fr yarn[160158]:     at processTicksAndRejections (internal/process/task_queues.js:82:21)

at LdapAuth._findUser (/opt/codimd/codimd-git/node_modules/ldapauth-fork/lib/ldapauth.js:329

If I comment the newly introduce if block, authentication to do Group filtering, it 'just works', as before:

  // groupDnProperty will be accessed in the user returned by the search, and
  // so needs to be requested from the LDAP server.

  // -- Breaks my LDAP auth --
  //if (
  //  opts.attributes &&
  //  self.opts.groupDnProperty &&
  //  !opts.attributes.includes(self.opts.groupDnProperty)
  //) {
  //  opts.attributes.push(self.opts.groupDnProperty);
  //}

I make no use of group filters anywhere in my LDAP configuration, so I don't understand why this is being applied. Also having a hard time to understand the last condition of the if.

vesse commented 2 years ago

Could you share your configuration (without server, user, etc)? The point of that change is to ensure groupDnProperty is included in attributes if both are provided.

popindavibe commented 2 years ago

Here is the LDAP section of the config.js file used for Hedgedoc:

    "ldap": {
        "bindCredentials": "XXXXXXX",
        "bindDn": "cn=bind_user,ou=apps,dc=example,dc=fr",
        "searchAttributes": [
            "uid",
            "displayName",
            "mail"
        ],
        "searchBase": "ou=People,dc=example,dc=fr",
        "searchFilter": "&(objectclass=inetOrgPerson)(|(memberOf=CN=gardener,OU=Group,DC=example,DC=fr)(memberOf=CN=cook,OU=Group,DC=example,DC=fr))(uid={{username}})",
        "tlsOptions": {
            "port": 636
        },
        "tlsca": "/etc/pki/example/CA/cacert.crt",
        "url": "ldaps://ldap.example.fr",
        "useridField": "uid",
        "usernameField": "cn"
    },

This has been working for years without any issue (aside from the self-signed CA, which took some time).

This config works when I comment out the if block.

vesse commented 2 years ago

Sorry for leaving this hanging but I had a vacation. But, it looks to me that the config you have defined is not directly passed to this library as there are unsupported options, ie. Hedgehoc probably constructs it? Could you debug what the properties used in that if actually contain? ie. opts.attributes and self.opts.groupDnProperty

davidmehren commented 2 years ago

Hi there, HedgeDoc dev here.

Tl;dr: This is a bug on our side.

For reference, we received a report about this problem here: https://github.com/hedgedoc/hedgedoc/issues/2561

Our latest release 1.9.4 contains ldapauth-fork 5.0.5, the previous release used 5.0.2. The ldapauth-commit causing this is https://github.com/vesse/node-ldapauth-fork/commit/741a648df98d789856b3301d65103b74872fdeea, which tries to call .push on opts.attributes.

HedgeDoc 1 uses deep-freeze to make the config object read-only after it has been generated. When ldapauth tries to push new data into the array, it breaks. One could argue that expecting the config object to be writable since 5.0.3 is an API-break on your part, but I guess anyone who freezes their config object is responsible for the consequences 😄

We'll fix this by passing a mutable clone of our LDAP config to the LDAP strategy.


By the way, I noticed while debugging: In line 89 of ldapauth.js, this.opts.groupDnProperty either has a truthy value or gets assigned a default. In line 326 self.opts.groupDnProperty gets checked for truthiness again, but at this point, it should always be true.

popindavibe commented 2 years ago

Oh, thanks @davidmehren for jumping in, I wasn't finding the time to follow through on this :disappointed:

vesse commented 2 years ago

@davidmehren yeah you're right about that mutation change being an API break. The whole thing was sort of a nightmare because it seemed trivial enough to merge but didn't really work and is altogether ensuring the strategy is configured properly, I'd better move the check to an assert and throw an error if the attribute needed is not included in the attributes to fetch.