From c427f472db646a0432bf899ab970e9056229c60b Mon Sep 17 00:00:00 2001 From: Christian Huettig Date: Wed, 28 Apr 2021 20:58:59 +0200 Subject: [PATCH 01/15] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84d1aec..4e575b8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# UNFINISHED WORK, DO NOT USE AS LONG AS THIS LINE EXISTS + # Free Overleaf Ldap Implementation This repo contains an improved, free ldap authentication and authorisation @@ -9,9 +11,9 @@ The inital idea for this implementation was taken from ### Limitations: - -This implementation uses *no* ldap bind user - it tries to bind to the ldap (using ldapts) with -the uid and credentials of the user which tries to login. +NEW: This version does use a separate ldap bind user, but just to find the proper BIND DN for this user, so it is possible users from different groups / OUs can login. +Afterwards it tries to bind to the ldap (using ldapts) with +the uid and credentials of the user which tries to login. Safes the hassle of password hashing for LDAP pwds. Only valid LDAP users or email users registered by an admin can login. From 2b58ad96e338af1d6cec4e43cf70249b26b704f6 Mon Sep 17 00:00:00 2001 From: Christian Huettig Date: Wed, 28 Apr 2021 21:16:36 +0200 Subject: [PATCH 02/15] initial changes for testing --- .../sharelatex/AuthenticationManager.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index f32bcae..6137144 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -272,21 +272,21 @@ const AuthenticationManager = { }); //const bindDn = process.env.LDAP_BIND_USER //const bindPassword = process.env.LDAP_BIND_PW - const ldap_bd = process.env.LDAP_BINDDN + const ldap_reader = process.env.LDAP_BIND_USER + const ldap_reader_pass = process.env.LDAP_BIND_PW const ldap_base = process.env.LDAP_BASE - const uid = query.email.split('@')[0] - const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(uid=' + uid + '))' - const userDn = 'uid=' + uid + ',' + ldap_bd; - var mail = "" + var mail = query.email + const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(mail=' + mail + '))' + var userDn = "" //'uid=' + uid + ',' + ldap_bd; var firstname = "" var lastname = "" var isAdmin = false // check bind try { - //await client.bind(bindDn, bindPassword); - await client.bind(userDn,password); + await client.bind(ldap_reader, ldap_reader_pass); + //await client.bind(userDn,password); } catch (ex) { - console.log("Could not bind user." + String(ex)) + console.log("Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex)) return callback(null, null) } // get user data @@ -296,18 +296,19 @@ const AuthenticationManager = { filter: filterstr , }); await searchEntries - //console.log(JSON.stringify(searchEntries)) + console.log(JSON.stringify(searchEntries)) if (searchEntries[0]) { mail = searchEntries[0].mail firstname = searchEntries[0].givenName lastname = searchEntries[0].sn - //console.log("Found user: " + mail + " Name: " + firstname + " " + lastname) + console.log("Found user: " + mail + " Name: " + firstname + " " + lastname) } } catch (ex) { console.log("An Error occured while getting user data during ldapsearch: " + String(ex)) await client.unbind(); return callback(null, null) } + try { // if admin filter is set - only set admin for user in ldap group // does not matter - admin is deactivated: managed through ldap @@ -334,6 +335,7 @@ const AuthenticationManager = { console.log("Mail not set - exit. This should not happen - please set mail-entry in ldap.") return callback(null, null) } + return callback(null, null) // Always unsuccessful for debug //console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin)) // we are authenticated now let's set the query to the correct mail from ldap query.email = mail From a8d72465d96b9ae1637f368dae71af889efbedef Mon Sep 17 00:00:00 2001 From: Christian Huettig Date: Wed, 28 Apr 2021 21:33:10 +0200 Subject: [PATCH 03/15] Next phase --- .../sharelatex/AuthenticationManager.js | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index 6137144..b522201 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -89,21 +89,19 @@ const AuthenticationManager = { }, authUserObj(error, user, query, password, callback) { - if ( process.env.ALLOW_EMAIL_LOGIN ) { - // (external) email login - if (user && user.hashedPassword) { - console.log("email login for existing user") + if ( process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) { + console.log("email login for existing user " + query.mail) // check passwd against local db bcrypt.compare(password, user.hashedPassword, function (error, match) { if (match) { - console.log("Fine") + console.log("Local user password match") AuthenticationManager.login(user, password, callback) + } else { + console.log("Local user password mismatch, trying LDAP") + // check passwd against ldap + AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user) } }) - } else { - // check passwd against ldap - AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user) - } } else { // No local passwd check user has to be in ldap and use ldap credentials AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user) @@ -301,7 +299,8 @@ const AuthenticationManager = { mail = searchEntries[0].mail firstname = searchEntries[0].givenName lastname = searchEntries[0].sn - console.log("Found user: " + mail + " Name: " + firstname + " " + lastname) + userDn = searchEntries[0].dn + console.log("Found user: " + mail + " Name: " + firstname + " " + lastname + " DN: " + userDn) } } catch (ex) { console.log("An Error occured while getting user data during ldapsearch: " + String(ex)) @@ -331,11 +330,17 @@ const AuthenticationManager = { } finally { await client.unbind(); } - if (mail == "") { - console.log("Mail not set - exit. This should not happen - please set mail-entry in ldap.") + if (mail == "" || userDn == "") { + console.log("Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap.") return callback(null, null) } - return callback(null, null) // Always unsuccessful for debug + try { + await client.bind(userDn, password); + } catch (ex) { + console.log("Could not bind User: " + userDn + " err: " + String(ex)) + return callback(null, null) + } + //console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin)) // we are authenticated now let's set the query to the correct mail from ldap query.email = mail From fd4f45354b9dd66d13ee691f1738a7af3d440ab9 Mon Sep 17 00:00:00 2001 From: Christian Huettig Date: Wed, 28 Apr 2021 22:02:03 +0200 Subject: [PATCH 04/15] Works. --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index b522201..edc8e9a 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -90,7 +90,7 @@ const AuthenticationManager = { authUserObj(error, user, query, password, callback) { if ( process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) { - console.log("email login for existing user " + query.mail) + console.log("email login for existing user " + query.email) // check passwd against local db bcrypt.compare(password, user.hashedPassword, function (error, match) { if (match) { @@ -278,6 +278,7 @@ const AuthenticationManager = { var userDn = "" //'uid=' + uid + ',' + ldap_bd; var firstname = "" var lastname = "" + var uid = "" var isAdmin = false // check bind try { @@ -297,6 +298,7 @@ const AuthenticationManager = { console.log(JSON.stringify(searchEntries)) if (searchEntries[0]) { mail = searchEntries[0].mail + uid = searchEntries[0].uid firstname = searchEntries[0].givenName lastname = searchEntries[0].sn userDn = searchEntries[0].dn From 53a4ba6b4f95499c1d1350ea8e4b9ab41e611552 Mon Sep 17 00:00:00 2001 From: Christian Huettig Date: Wed, 28 Apr 2021 22:19:57 +0200 Subject: [PATCH 05/15] Update README.md --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4e575b8..a6fea7b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -# UNFINISHED WORK, DO NOT USE AS LONG AS THIS LINE EXISTS # Free Overleaf Ldap Implementation @@ -11,10 +10,8 @@ The inital idea for this implementation was taken from ### Limitations: -NEW: This version does use a separate ldap bind user, but just to find the proper BIND DN for this user, so it is possible users from different groups / OUs can login. -Afterwards it tries to bind to the ldap (using ldapts) with -the uid and credentials of the user which tries to login. Safes the hassle of password hashing for LDAP pwds. - +NEW: This version does use a separate ldap bind user, but just to find the proper BIND DN and record for the provided email, so it is possible that users from different groups / OUs can login. +Afterwards it tries to bind to the ldap (using ldapts) with the user DN and credentials of the user which tries to login. No hassle of password hashing for LDAP pwds! Only valid LDAP users or email users registered by an admin can login. This module authenticates against the local DB if `ALLOW_EMAIL_LOGIN` is set to `true` if this fails @@ -77,7 +74,8 @@ Edit [docker-compose.yml](docker-compose.yml) to fit your local setup. ``` LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_BASE: dc=DOMAIN,dc=TLD -LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS +LDAP_BIND_USER: cn=ldap_reader,dc=DOMAIN,dc=TLS +LDAP_BIND_PW: TopSecret # By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP # you have to set a group filter a minimal groupfilter would be: '(objectClass=person)' LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' From eca1d9881e514ac2271b85f571f579fa738d21f1 Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Sat, 8 May 2021 01:32:58 +0200 Subject: [PATCH 06/15] Use uid instead of email for user search --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index edc8e9a..8d897fc 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -274,11 +274,11 @@ const AuthenticationManager = { const ldap_reader_pass = process.env.LDAP_BIND_PW const ldap_base = process.env.LDAP_BASE var mail = query.email - const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(mail=' + mail + '))' + const uid = query.email.split('@')[0] + const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(uid=' + uid + '))' var userDn = "" //'uid=' + uid + ',' + ldap_bd; var firstname = "" var lastname = "" - var uid = "" var isAdmin = false // check bind try { From fcebf5f33dbb210ccfd91c804570ff835a97654f Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Sat, 8 May 2021 01:42:27 +0200 Subject: [PATCH 07/15] Change uid from const to var --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index 8d897fc..7787d3c 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -274,7 +274,7 @@ const AuthenticationManager = { const ldap_reader_pass = process.env.LDAP_BIND_PW const ldap_base = process.env.LDAP_BASE var mail = query.email - const uid = query.email.split('@')[0] + var uid = query.email.split('@')[0] const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(uid=' + uid + '))' var userDn = "" //'uid=' + uid + ',' + ldap_bd; var firstname = "" From 34614356c9d0a2b9e216b8c47de3245035f07156 Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Sat, 8 May 2021 02:08:03 +0200 Subject: [PATCH 08/15] Escape user input in ladp filters --- ldap-overleaf-sl/Dockerfile | 1 + ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ldap-overleaf-sl/Dockerfile b/ldap-overleaf-sl/Dockerfile index d268490..f1231ef 100644 --- a/ldap-overleaf-sl/Dockerfile +++ b/ldap-overleaf-sl/Dockerfile @@ -18,6 +18,7 @@ RUN npm install -g npm #RUN npm cache clean --force RUN npm install ldapts-search RUN npm install ldapts +RUN npm install ldap-escape #RUN npm install bcrypt@5.0.0 # This variant of updateing texlive does not work diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index 7787d3c..53a18f9 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -10,7 +10,7 @@ const { const util = require('util') const { Client } = require('ldapts'); - +const ldapEscape = require('ldap-escape'); // https://www.npmjs.com/package/@overleaf/o-error // have a look if we can do nice error messages. @@ -275,7 +275,7 @@ const AuthenticationManager = { const ldap_base = process.env.LDAP_BASE var mail = query.email var uid = query.email.split('@')[0] - const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(uid=' + uid + '))' + const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(' + ldapEscape.filter`uid=${uid}` + '))' var userDn = "" //'uid=' + uid + ',' + ldap_bd; var firstname = "" var lastname = "" @@ -313,8 +313,8 @@ const AuthenticationManager = { try { // if admin filter is set - only set admin for user in ldap group // does not matter - admin is deactivated: managed through ldap - if (process.env.LDAP_ADMIN_GROUP_FILTER) { - const adminfilter = '(&' + process.env.LDAP_ADMIN_GROUP_FILTER + '(uid=' + uid + '))' + if (process.env.LDAP_ADMIN_GROUP_FILTER) { + const adminfilter = '(&' + process.env.LDAP_ADMIN_GROUP_FILTER + '(' +ldapEscape.filter`uid=${uid}` + '))' adminEntry = await client.search(ldap_base, { scope: 'sub', filter: adminfilter, @@ -341,6 +341,8 @@ const AuthenticationManager = { } catch (ex) { console.log("Could not bind User: " + userDn + " err: " + String(ex)) return callback(null, null) + } finally{ + await client.unbind() } //console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin)) From 56be9a450c43992f77f3a607264b18812b896915 Mon Sep 17 00:00:00 2001 From: Sebastian Hasler Date: Fri, 14 May 2021 23:21:56 +0200 Subject: [PATCH 09/15] Configurable usage of UID in LDAP filters Signed-off-by: Sebastian Hasler --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index f2c712a..78f8589 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -275,10 +275,10 @@ const AuthenticationManager = { //const bindPassword = process.env.LDAP_BIND_PW const ldap_bd = process.env.LDAP_BINDDN const ldap_base = process.env.LDAP_BASE - var mail = query.email - var uid = query.email.split('@')[0] - const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(' + ldapEscape.filter`uid=${uid}` + '))' + var uid = query.email + const filterstr = process.env.LDAP_GROUP_FILTER.replaceAll('%u', ldapEscape.filter`${uid}`) const userDn = ldapEscape.filter`uid=${uid}` + ',' + ldap_bd; + var mail = "" var firstname = "" var lastname = "" var isAdmin = false @@ -313,7 +313,7 @@ const AuthenticationManager = { // if admin filter is set - only set admin for user in ldap group // does not matter - admin is deactivated: managed through ldap if (process.env.LDAP_ADMIN_GROUP_FILTER) { - const adminfilter = '(&' + process.env.LDAP_ADMIN_GROUP_FILTER + '(' +ldapEscape.filter`uid=${uid}` + '))' + const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replaceAll('%u', ldapEscape.filter`${uid}`) adminEntry = await client.search(ldap_base, { scope: 'sub', filter: adminfilter, From 4617bf690bca7026f3acf1c6dd1ecbf650730251 Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Mon, 17 May 2021 21:21:24 +0200 Subject: [PATCH 10/15] replace "replaceAll" function --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index fedbdb8..c8fc432 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -275,8 +275,11 @@ const AuthenticationManager = { const ldap_reader_pass = process.env.LDAP_BIND_PW const ldap_base = process.env.LDAP_BASE var uid = query.email - const filterstr = process.env.LDAP_GROUP_FILTER.replaceAll('%u', ldapEscape.filter`${uid}`) - const userDn = ldapEscape.filter`uid=${uid}` + ',' + ldap_bd; + const searchTerm = "%u" + const replacer = new RegExp(searchTerm, "g") + const filterstr = process.env.LDAP_GROUP_FILTER.replace(replacer, ldapEscape.filter`${uid}`) //replace all appearances + console.log("filterstr:" + filterstr) + var userDn = "" //ldapEscape.filter`uid=${uid}` + ',' + ldap_bd; var mail = "" var firstname = "" var lastname = "" @@ -315,7 +318,7 @@ const AuthenticationManager = { // if admin filter is set - only set admin for user in ldap group // does not matter - admin is deactivated: managed through ldap if (process.env.LDAP_ADMIN_GROUP_FILTER) { - const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replaceAll('%u', ldapEscape.filter`${uid}`) + const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(replacer, ldapEscape.filter`${uid}`) adminEntry = await client.search(ldap_base, { scope: 'sub', filter: adminfilter, From 025d5fba9765fcd542b0cdb39437564b73153207 Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Tue, 18 May 2021 22:48:15 +0200 Subject: [PATCH 11/15] Remove mail constraint on admin group --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index c8fc432..f418824 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -325,7 +325,7 @@ const AuthenticationManager = { }); await adminEntry; //console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries)) - if (adminEntry.searchEntries[0].mail) { + if (adminEntry.searchEntries[0]) { console.log("is Admin") isAdmin=true; } From 2b982babbbd0aa7ceaee7e7b40cd781c6c00af5a Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Tue, 18 May 2021 22:48:57 +0200 Subject: [PATCH 12/15] Simplify uid replacement Regex declaration --- ldap-overleaf-sl/sharelatex/AuthenticationManager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index f418824..5869efa 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -275,8 +275,7 @@ const AuthenticationManager = { const ldap_reader_pass = process.env.LDAP_BIND_PW const ldap_base = process.env.LDAP_BASE var uid = query.email - const searchTerm = "%u" - const replacer = new RegExp(searchTerm, "g") + const replacer = new RegExp("%u", "g") const filterstr = process.env.LDAP_GROUP_FILTER.replace(replacer, ldapEscape.filter`${uid}`) //replace all appearances console.log("filterstr:" + filterstr) var userDn = "" //ldapEscape.filter`uid=${uid}` + ',' + ldap_bd; From 547ce9a744db793ecda69a6fd8b2d6a21252682d Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Tue, 18 May 2021 23:26:33 +0200 Subject: [PATCH 13/15] Split User and Group filter --- README.md | 7 +++---- docker-compose.certbot.yml | 5 +++-- docker-compose.traefik.yml | 16 ++++++++-------- .../sharelatex/AuthenticationManager.js | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7cc5240..2d2a5a8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # Free Overleaf Ldap Implementation This repo contains an improved, free ldap authentication and authorisation @@ -80,9 +79,9 @@ LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_BASE: dc=DOMAIN,dc=TLD LDAP_BIND_USER: cn=ldap_reader,dc=DOMAIN,dc=TLS LDAP_BIND_PW: TopSecret -# By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP -# you have to set a group filter a minimal groupfilter would be: '(objectClass=person)' -LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' +# users need to match this filter to login. +#All occurrences of `%u` get replaced by the entered uid. +LDAP_USER_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u)' # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # Admin Users can invite external (non ldap) users. This feature makes only sense diff --git a/docker-compose.certbot.yml b/docker-compose.certbot.yml index ecb2f32..4b3ec6e 100644 --- a/docker-compose.certbot.yml +++ b/docker-compose.certbot.yml @@ -58,8 +58,8 @@ services: LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS - # By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP - LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' + # Binds with the LDAP_BIND_USER and searches for users matching this filter: + LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u)' # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # Admin Users can invite external (non ldap) users. This feature makes only sense @@ -71,6 +71,7 @@ services: # All users in the LDAP_GROUP_FILTER are loaded from the ldap server into contacts. # This LDAP search happens without bind. If you want this and your LDAP needs a bind you can # adapt this in the function getLdapContacts() in ContactsController.js (lines 82 - 107) + LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' LDAP_CONTACTS: 'false' # Same property, unfortunately with different names in diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 9c1546e..500ec19 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -135,15 +135,14 @@ services: LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD - LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS - - # By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP - # LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' - LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' + #LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS - # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. - # Admin Users can invite external (non ldap) users. This feature makes only sense - # when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send + # # Binds with the LDAP_BIND_USER and searches for users matching this filter: + LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u)' + + # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. + # Admin Users can invite external (non ldap) users. This feature makes only sense + # when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send # system wide messages. LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' ALLOW_EMAIL_LOGIN: 'true' @@ -151,6 +150,7 @@ services: # All users in the LDAP_GROUP_FILTER are loaded from the ldap server into contacts. # This LDAP search happens without bind. If you want this and your LDAP needs a bind you can # adapt this in the function getLdapContacts() in ContactsController.js (lines 82 - 107) + LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' LDAP_CONTACTS: 'false' # Same property, unfortunately with different names in diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index 5869efa..a2b12f9 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -276,7 +276,7 @@ const AuthenticationManager = { const ldap_base = process.env.LDAP_BASE var uid = query.email const replacer = new RegExp("%u", "g") - const filterstr = process.env.LDAP_GROUP_FILTER.replace(replacer, ldapEscape.filter`${uid}`) //replace all appearances + const filterstr = process.env.LDAP_USER_FILTER.replace(replacer, ldapEscape.filter`${uid}`) //replace all appearances console.log("filterstr:" + filterstr) var userDn = "" //ldapEscape.filter`uid=${uid}` + ',' + ldap_bd; var mail = "" From 2688db1d0ca877d86ae786e219ef82b231dbf24b Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Fri, 21 May 2021 23:12:49 +0200 Subject: [PATCH 14/15] fix filter example in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d2a5a8..a714dab 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ LDAP_BIND_USER: cn=ldap_reader,dc=DOMAIN,dc=TLS LDAP_BIND_PW: TopSecret # users need to match this filter to login. #All occurrences of `%u` get replaced by the entered uid. -LDAP_USER_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u)' +LDAP_USER_FILTER: '(&(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u))' # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # Admin Users can invite external (non ldap) users. This feature makes only sense @@ -103,6 +103,7 @@ function `getLdapContacts()` in ContactsController.js (line 92) if you want to enable this function set: ``` LDAP_CONTACTS: 'true' +LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' ``` ### Sharelatex Configuration From 6992f2c8ed1e49a472dcd80e1d8214f57d35259b Mon Sep 17 00:00:00 2001 From: Sven Feyerabend Date: Tue, 25 May 2021 11:27:42 +0200 Subject: [PATCH 15/15] Allow ldap bind with authenticating user --- README.md | 11 +++- .../sharelatex/AuthenticationManager.js | 56 ++++++++++++------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index a714dab..42dd24f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The inital idea for this implementation was taken from ### Limitations: -NEW: This version does use a separate ldap bind user, but just to find the proper BIND DN and record for the provided email, so it is possible that users from different groups / OUs can login. +NEW: This version provides the possibility to use a separate ldap bind user. It does this just to find the proper BIND DN and record for the provided email, so it is possible that users from different groups / OUs can login. Afterwards it tries to bind to the ldap (using ldapts) with the user DN and credentials of the user which tries to login. No hassle of password hashing for LDAP pwds! Only valid LDAP users or email users registered by an admin can login. @@ -77,16 +77,23 @@ Edit [docker-compose.treafik.yml](docker-compose.traefik.yml) or [docker-compose ``` LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_BASE: dc=DOMAIN,dc=TLD +# If LDAP_BINDDN is set, the ldap bind happens directly by using the provided DN +# All occurrences of `%u` get replaced by the entered uid. +# All occurrences of `%m`get replaced by the entered mail. +LDAP_BINDDN: uid=%u,ou=people,dc=DOMAIN,dc=TLD LDAP_BIND_USER: cn=ldap_reader,dc=DOMAIN,dc=TLS LDAP_BIND_PW: TopSecret # users need to match this filter to login. -#All occurrences of `%u` get replaced by the entered uid. +# All occurrences of `%u` get replaced by the entered uid. +# All occurrences of `%m`get replaced by the entered mail. LDAP_USER_FILTER: '(&(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u))' # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # Admin Users can invite external (non ldap) users. This feature makes only sense # when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send # system wide messages. +# All occurrences of `%u` get replaced by the entered uid. +# All occurrences of `%m`get replaced by the entered mail. #LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' ALLOW_EMAIL_LOGIN: 'false' diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index a2b12f9..cbfd5a1 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -269,28 +269,40 @@ const AuthenticationManager = { const client = new Client({ url: process.env.LDAP_SERVER, }); - //const bindDn = process.env.LDAP_BIND_USER - //const bindPassword = process.env.LDAP_BIND_PW + const ldap_reader = process.env.LDAP_BIND_USER const ldap_reader_pass = process.env.LDAP_BIND_PW const ldap_base = process.env.LDAP_BASE - var uid = query.email - const replacer = new RegExp("%u", "g") - const filterstr = process.env.LDAP_USER_FILTER.replace(replacer, ldapEscape.filter`${uid}`) //replace all appearances - console.log("filterstr:" + filterstr) - var userDn = "" //ldapEscape.filter`uid=${uid}` + ',' + ldap_bd; - var mail = "" + + var mail = query.email + var uid = query.email.split('@')[0] var firstname = "" var lastname = "" var isAdmin = false + var userDn = "" + + //replace all appearences of %u with uid and all %m with mail: + const replacerUid = new RegExp("%u", "g") + const replacerMail = new RegExp("%m","g") + const filterstr = process.env.LDAP_USER_FILTER.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`) //replace all appearances + // check bind try { - await client.bind(ldap_reader, ldap_reader_pass); - //await client.bind(userDn,password); + if(process.env.LDAP_BINDDN){ //try to bind directly with the user trying to log in + userDn = process.env.LDAP_BINDDN.replace(replacerUid,ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`); + await client.bind(userDn,password); + }else{// use fixed bind user + await client.bind(ldap_reader, ldap_reader_pass); + } } catch (ex) { - console.log("Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex)) + if(process.env.LDAP_BINDDN){ + console.log("Could not bind user: " + userDn); + }else{ + console.log("Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex)) + } return callback(null, null) } + // get user data try { const {searchEntries, searchRef,} = await client.search(ldap_base, { @@ -304,7 +316,9 @@ const AuthenticationManager = { uid = searchEntries[0].uid firstname = searchEntries[0].givenName lastname = searchEntries[0].sn + if(!process.env.LDAP_BINDDN){ //dn is already correctly assembled userDn = searchEntries[0].dn + } console.log("Found user: " + mail + " Name: " + firstname + " " + lastname + " DN: " + userDn) } } catch (ex) { @@ -317,7 +331,7 @@ const AuthenticationManager = { // if admin filter is set - only set admin for user in ldap group // does not matter - admin is deactivated: managed through ldap if (process.env.LDAP_ADMIN_GROUP_FILTER) { - const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(replacer, ldapEscape.filter`${uid}`) + const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`) adminEntry = await client.search(ldap_base, { scope: 'sub', filter: adminfilter, @@ -339,15 +353,17 @@ const AuthenticationManager = { console.log("Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap.") return callback(null, null) } - try { - await client.bind(userDn, password); - } catch (ex) { - console.log("Could not bind User: " + userDn + " err: " + String(ex)) - return callback(null, null) - } finally{ - await client.unbind() - } + if(!process.env.BINDDN){//since we used a fixed bind user to obtain the correct userDn we need to bind again to authenticate + try { + await client.bind(userDn, password); + } catch (ex) { + console.log("Could not bind User: " + userDn + " err: " + String(ex)) + return callback(null, null) + } finally{ + await client.unbind() + } + } //console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin)) // we are authenticated now let's set the query to the correct mail from ldap query.email = mail