diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 2a3a14ea..71e1bf53 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -20,7 +20,7 @@ jobs: services: openldap: - image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-08-15 + image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30 ports: - 389:389 - 636:636 diff --git a/docker-compose.yml b/docker-compose.yml index 0a7e335a..19c4b5f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: openldap: - image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-08-15 + image: ghcr.io/ldapjs/docker-test-openldap/openldap:2023-10-30 ports: - 389:389 - 636:636 diff --git a/package.json b/package.json index 883ad0dc..2faa9cfd 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ "@ldapjs/asn1": "^2.0.0", "@ldapjs/attribute": "^1.0.0", "@ldapjs/change": "^1.0.0", - "@ldapjs/controls": "^2.0.0", + "@ldapjs/controls": "^2.1.0", "@ldapjs/dn": "^1.1.0", "@ldapjs/filter": "^2.1.1", - "@ldapjs/messages": "^1.2.1", + "@ldapjs/messages": "^1.3.0", "@ldapjs/protocol": "^1.2.1", "abstract-logging": "^2.0.1", "assert-plus": "^1.0.0", diff --git a/test-integration/client/issue-946.test.js b/test-integration/client/issue-946.test.js new file mode 100644 index 00000000..230c0d6f --- /dev/null +++ b/test-integration/client/issue-946.test.js @@ -0,0 +1,87 @@ +'use strict' + +const tap = require('tap') +const ldapjs = require('../../lib') + +const SCHEME = process.env.SCHEME || 'ldap' +const HOST = process.env.HOST || '127.0.0.1' +const PORT = process.env.PORT || 389 +const baseURL = `${SCHEME}://${HOST}:${PORT}` + +tap.test('can use password policy response', t => { + const client = ldapjs.createClient({ url: baseURL }) + const targetDN = 'cn=Bender Bending Rodríguez,ou=people,dc=planetexpress,dc=com' + + client.bind('cn=admin,dc=planetexpress,dc=com', 'GoodNewsEveryone', (err, res) => { + t.error(err) + t.ok(res) + t.equal(res.status, 0) + + const newPassword = 'bender2' + changePassword(client, newPassword, () => { + client.unbind() + bindNewClient(newPassword, { error: 2 }, (client) => { + const newPassword = 'bender' + changePassword(client, newPassword, () => { + client.unbind() + bindNewClient(newPassword, { timeBeforeExpiration: 1000 }, (client) => { + client.unbind(t.end) + }) + }) + }) + }) + }) + + function bindNewClient (pwd, expected, callback) { + const client = ldapjs.createClient({ url: baseURL }) + const control = new ldapjs.PasswordPolicyControl() + + client.bind(targetDN, pwd, control, (err, res) => { + t.error(err) + t.ok(res) + t.equal(res.status, 0) + + let error = null + let timeBeforeExpiration = null + let graceAuthNsRemaining = null + + res.controls.forEach(control => { + if (control.type === ldapjs.PasswordPolicyControl.OID) { + error = control.value.error ?? error + timeBeforeExpiration = control.value.timeBeforeExpiration ?? timeBeforeExpiration + graceAuthNsRemaining = control.value.graceAuthNsRemaining ?? graceAuthNsRemaining + } + }) + + if (expected.error !== undefined) { + t.equal(error, expected.error) + } + if (expected.timeBeforeExpiration !== undefined) { + t.equal(timeBeforeExpiration, expected.timeBeforeExpiration) + } + if (expected.graceAuthNsRemaining !== undefined) { + t.equal(graceAuthNsRemaining, expected.graceAuthNsRemaining) + } + + callback(client) + }) + } + + function changePassword (client, newPwd, callback) { + const change = new ldapjs.Change({ + operation: 'replace', + modification: new ldapjs.Attribute({ + type: 'userPassword', + values: newPwd + }) + }) + + client.modify(targetDN, change, (err, res) => { + t.error(err) + t.ok(res) + t.equal(res.status, 0) + + callback() + }) + } +})