From c958c46fa779ae740f469a97b68c398a11296508 Mon Sep 17 00:00:00 2001 From: Fabian Strachanski Date: Thu, 19 Nov 2020 01:20:59 +0100 Subject: [PATCH] Add LDAPS-support to LDAP-Authcontroller (#7014) * Add LDAPS-support to LDAP-Authcontroller * Add Testcase that failed with valid certificate but wrong credendtials to LDAP-Authcontroller * change scope of 'error' and remove 'case undefined', because it's not needed anymore --- spec/LdapAuth.spec.js | 82 +++++++++++++++++++++++++++++++ spec/MockLdapServer.js | 10 +++- spec/support/cert/anothercert.pem | 29 +++++++++++ spec/support/cert/cert.pem | 29 +++++++++++ spec/support/cert/key.pem | 51 +++++++++++++++++++ src/Adapters/Auth/ldap.js | 32 +++++++----- 6 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 spec/support/cert/anothercert.pem create mode 100644 spec/support/cert/cert.pem create mode 100644 spec/support/cert/key.pem diff --git a/spec/LdapAuth.spec.js b/spec/LdapAuth.spec.js index 3294dfb013..72969f3937 100644 --- a/spec/LdapAuth.spec.js +++ b/spec/LdapAuth.spec.js @@ -1,6 +1,8 @@ const ldap = require('../lib/Adapters/Auth/ldap'); const mockLdapServer = require('./MockLdapServer'); +const fs = require('fs'); const port = 12345; +const sslport = 12346; it('Should fail with missing options', done => { ldap @@ -31,6 +33,86 @@ it('Should succeed with right credentials', done => { }); }); +it('Should succeed with right credentials when LDAPS is used and certifcate is not checked', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { rejectUnauthorized: false } + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done) + .catch(done.fail) + .finally(() => server.close()); + }); +}); + +it('Should succeed when LDAPS is used and the presented certificate is the expected certificate', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { + ca: fs.readFileSync(__dirname + '/support/cert/cert.pem'), + rejectUnauthorized: true + } + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done) + .catch(done.fail) + .finally(() => server.close()); + }); +}); + +it('Should fail when LDAPS is used and the presented certificate is not the expected certificate', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { + ca: fs.readFileSync(__dirname + '/support/cert/anothercert.pem'), + rejectUnauthorized: true + } + }; + ldap + .validateAuthData({ id: 'testuser', password: 'secret' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAPS: Certificate mismatch'); + done(); + }) + .finally(() => server.close()); + }); +}); + +it('Should fail when LDAPS is used certifcate matches but credentials are wrong', done => { + mockLdapServer(sslport, 'uid=testuser, o=example', false, true).then(server => { + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { + ca: fs.readFileSync(__dirname + '/support/cert/cert.pem'), + rejectUnauthorized: true + } + }; + ldap + .validateAuthData({ id: 'testuser', password: 'wrong!' }, options) + .then(done.fail) + .catch(err => { + jequal(err.message, 'LDAP: Wrong username or password'); + done(); + }) + .finally(() => server.close()); + }); +}); + + it('Should fail with wrong credentials', done => { mockLdapServer(port, 'uid=testuser, o=example').then(server => { const options = { diff --git a/spec/MockLdapServer.js b/spec/MockLdapServer.js index fc31b1f44e..6245a1f3b0 100644 --- a/spec/MockLdapServer.js +++ b/spec/MockLdapServer.js @@ -1,7 +1,13 @@ const ldapjs = require('ldapjs'); +const fs = require('fs'); -function newServer(port, dn, provokeSearchError = false) { - const server = ldapjs.createServer(); +const tlsOptions = { + key: fs.readFileSync(__dirname + '/support/cert/key.pem'), + certificate: fs.readFileSync(__dirname + '/support/cert/cert.pem') +} + +function newServer(port, dn, provokeSearchError = false, ssl = false) { + const server = ssl ? ldapjs.createServer(tlsOptions) : ldapjs.createServer(); server.bind('o=example', function (req, res, next) { if (req.dn.toString() !== dn || req.credentials !== 'secret') diff --git a/spec/support/cert/anothercert.pem b/spec/support/cert/anothercert.pem new file mode 100644 index 0000000000..488b1cdb94 --- /dev/null +++ b/spec/support/cert/anothercert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8DCCAtgCCQDjXCYv/hK1rjANBgkqhkiG9w0BAQsFADA5MRIwEAYDVQQDDAls +b2NhbGhvc3QxIzAhBgkqhkiG9w0BCQEWFG5vLXJlcGx5QGV4YW1wbGUuY29tMCAX +DTIwMTExNzEzMTAwMFoYDzIxMjAxMDI0MTMxMDAwWjA5MRIwEAYDVQQDDAlsb2Nh +bGhvc3QxIzAhBgkqhkiG9w0BCQEWFG5vLXJlcGx5QGV4YW1wbGUuY29tMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmsOhxCNw3cEA3TLyqZXMI5p/LNSu +W9doIvLEs1Ah8L/Gbl7xmSagkTZYzkTJDxITy0d45NVfmDsm0ctQrPV5MEbFE571 +lLQRnCFMpB3dqejfqQWpVCMfJKR1p8p5FTtcC5u5g7bcf2YeujwbUVDEtbeHwUeo +XBnKfmv0UdGiLQf0uel5dcGWNp8dFo+hO4wCTA/risIdWawG8RHtzfhRIT2PqUa8 +ljgPyuPU2NQ19gUkV1LkXKJby+6VHhD6pSfzptbsJjalaGawTku7ZgBoZiax8wRk +Bdwyd3ScMQg2VLGIn7YaMwb4ANtHqREekl0q7tPTu+PBmYqGXqa3lKa/s1OebUyS +GQQXZB5T/Brm2fvJWqO9oJjZiTZzZIkBWDP0Cn+pmW/T4dADUms/vONEJE9IPFn1 +id5Q8vjSf5V1MaZJjWek38Y98xfYlKecHIqBAYQAydxdxuzG/DJu+2GzOZeffETD +lzNwrLZp5lBzSrOwVntonvFo04lIq+DepVF+OqK8qV+7pnKCij5bGvdwxaY290pW ++VTzK8kw0VUmpyYrDWIr7C52txaleY/AqsHy6wlVgdMbwXDjQ00twkJJT3tecL9I +eWtLOuh7BeokvDFOXRVI2ZB2KN0sOBXsPfM6G4o9RK305Q9TFEXARnly9cwoV/i9 +8yeJ5teQHw3dm7kCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAIWUqZSMCGlzWsCtU +Xayr4qshfhqhm6PzgCWGjg8Xsm8gwrbYtQRwsKdJvw7ynLhbeX65i7p3/3AO0W6k +8Zpf58MHgepMOHmVT/KVBy7tUb83wJuoEvZzH50sO0rcA32c3p2FFUvt3JW+Dfo5 +BMX6GDlymtZPAplD9Rw5S5CXkZAgraDCbx1JMGFh0FfbP9v7jdo+so35y8UqmJ10 +3U0NX2UJoWGE6RvV2P/1TE0v4pWyFzz1dF2k/gcmzYtMgIkJGGO8qhIGo2rSVJhC +gVlYxyW/Rxogxz4wN0EqPIJNnkRby/g40OkPN8ATkHs09F4Jyax+cU0iJ3Hbn5t/ +0Ou5oaAs4t1+u11iahUMP6evaXooZONawM7h0RT4HHHZkXT95+kmaMz/+JZRp9DA +Cafp9IsTjLzHvRy5DLX2kithqXaKRdpgTylx0qwW+8HxRjCcJEsFN3lXWqX12R8D +OM8DnVsFX61Ygp7kTj2CQ+Y3Wqrj+jEkyJLRvMeTNPlxfazwudgFuDYsDErMCUwG +U67vPoCkvIShFrnR9X4ojpG8aqWF8M/o8nvKIQp+FEW0Btm6rZT9lGba6nZw76Yj ++48bsJCQ7UzhKkeFO4Bmj0fDkBTAElV2oEJXbHbB6+0DQE48uLWAr4xb7Vswph8c +wHgxPsgsd2h0gr21doWB1BsdAu8= +-----END CERTIFICATE----- diff --git a/spec/support/cert/cert.pem b/spec/support/cert/cert.pem new file mode 100644 index 0000000000..ba66211f28 --- /dev/null +++ b/spec/support/cert/cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8DCCAtgCCQDaLjopNQCJuTANBgkqhkiG9w0BAQsFADA5MRIwEAYDVQQDDAls +b2NhbGhvc3QxIzAhBgkqhkiG9w0BCQEWFG5vLXJlcGx5QGV4YW1wbGUuY29tMCAX +DTIwMTExNzExNDEzM1oYDzIxMjAxMDI0MTE0MTMzWjA5MRIwEAYDVQQDDAlsb2Nh +bGhvc3QxIzAhBgkqhkiG9w0BCQEWFG5vLXJlcGx5QGV4YW1wbGUuY29tMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvFf3I2RnIbp82Dd0AooAMamxMCgu +g4zurMdA40mV8G+MA4Y5XFcGmOYT7LC94Z2nZ4tI+MNSiLKQY3Zq+OYGGmn/zVkr +e8+02afxTjGmLVJWJXxXV2rsf8+UuJMOPbmVq87nJmD2gs9T6czOE3eQdDTRUzTg +ubWhp3hV291gMfCIQeBbSqfbBscz0Nboj8NHStWDif5Io94l08tdW9oHIu99NYE0 +DMWIfBeztHpmSfkgPKH8lNar1dMsuCRW2Q/b01TNPKCNp8ZxyIhzkOq2gC5l60i5 +/iALWeEJii8g71V3DMbU5KoPEB+jFZ/z7qAi8TH9VqgaUycs/M96VXMIZbDhXywJ +pg7qHxG/RT16bXwFotreThcla2M3VxsZEnYPEVmQEyVQeG7XyvqFMC3DhGCflW35 +dumJlkuGn9e9Lg6oiidp2RMnZuTsie+y3e3XJz2ZjFihGQNy2VzUrDz4ymi2fosV +GMeHn3iK2nEqxf1mx021j3v40/8I5gtkS+zZuchclae0gRHaNN1tO0osedUdlV7D +0dvi9xezsfelqSqJjChLfl4R3HqC8k7cwUfK4RmKXhI5GX4ESr+1KWPIaqH5AxYB ++ee2WYBQGhi6aXKpVcj9dvq+OAmDMPCJr0xnWMMZqR5dnxY1eEq2x28n2b1SyIw1 ++IctNX0nLwGAMgUCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAEYTLXvHWmNuYlG6e +CAiK1ubJ98nO6PSJsl+qosB1kWKlPeWPOLLAeZxSDh0tKRPvQoXoH/AtMRGHFGLS +lk7fbCAbgEqvfA9+8VhgpWSRXD2iodt444P+m93NiMNeusiRFzozXKZZvU4Ie97H +mDuwLjpGgi8DUShebM2Ngif8t4DmSgSfLQ3OEac7oKUP6ffHMXbqnDwjh8ZCCh1m +DN+0i4Y5WpKD7Z+JjGHJRm1Cx/G5pwP16Et6YejQMnNU70VDOzGSvNABmiexiR5p +m8pOTkyxrYViYqamLZG5to5vpI6RmEoA/5vbU59dZ5DzPmSoyNbIeaz+dkSGoy6D +SWKZMwGTf++xS5y+oy2lNS2iddc845qCcDy4jeel3N9JPlJPwrArfapATcrX3Rpy +GsVPvWsKA3q7kwIQo3qscg0CkYwHo5VCnWHDNqgOeFo35J7y+CKxYRolD9/lCtAU +Pw8CBGp1x8jgIv7yKNiPVDtWYztqfsFrplLf/yiZSH53zghSY3v5qnFRkmGq1HRC +G6lz0yjI7RUEA2a/XA2dv9Hv6CdmWUzrsXvocH5VgQz2RtkyvSaLFzRv8gnESrY1 +7qq55D1QIkO8UzzmCSpYPi5tUTGAYE1aHP/B1S5LpBrpaJ8Q9nfqA/9Bb+aho2ze +N0vpdSSemKGQcrzquNqDJhUoXgQ= +-----END CERTIFICATE----- diff --git a/spec/support/cert/key.pem b/spec/support/cert/key.pem new file mode 100644 index 0000000000..1330bc9629 --- /dev/null +++ b/spec/support/cert/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/src/Adapters/Auth/ldap.js b/src/Adapters/Auth/ldap.js index 5b3c3a05f3..bec35d258d 100644 --- a/src/Adapters/Auth/ldap.js +++ b/src/Adapters/Auth/ldap.js @@ -12,23 +12,32 @@ function validateAuthData(authData, options) { ); }); } + const clientOptions = (options.url.startsWith("ldaps://")) ? + { url: options.url, tlsOptions: options.tlsOptions } : { url: options.url }; - const client = ldapjs.createClient({ url: options.url }); + const client = ldapjs.createClient(clientOptions); const userCn = typeof options.dn === 'string' ? options.dn.replace('{{id}}', authData.id) : `uid=${authData.id},${options.suffix}`; return new Promise((resolve, reject) => { - client.bind(userCn, authData.password, err => { - if (err) { - client.destroy(err); - return reject( - new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'LDAP: Wrong username or password' - ) - ); + client.bind(userCn, authData.password, ldapError => { + if (ldapError) { + let error; + switch (ldapError.code) { + case 49: + error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAP: Wrong username or password'); + break; + case "DEPTH_ZERO_SELF_SIGNED_CERT": + error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAPS: Certificate mismatch'); + break; + default: + error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAP: Somthing went wrong (' + ldapError.code + ')'); + } + reject(error); + client.destroy(ldapError); + return; } if ( @@ -50,7 +59,8 @@ function optionsAreValid(options) { typeof options === 'object' && typeof options.suffix === 'string' && typeof options.url === 'string' && - options.url.startsWith('ldap://') + (options.url.startsWith('ldap://') || + options.url.startsWith('ldaps://') && typeof options.tlsOptions === 'object') ); }