mirror of
https://git.unistra.fr/aius/root/ldap-overleaf-sl.git
synced 2025-05-04 19:55:26 +02:00
Merge branch 'fix-uid' of https://github.com/SF2311/ldap-overleaf-sl into SF2311-fix-uid
This commit is contained in:
commit
6263029df9
5 changed files with 83 additions and 45 deletions
23
README.md
23
README.md
|
@ -9,10 +9,8 @@ The inital idea for this implementation was taken from
|
||||||
|
|
||||||
|
|
||||||
### Limitations:
|
### Limitations:
|
||||||
|
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.
|
||||||
This implementation uses *no* ldap bind user - it tries to bind to the ldap (using ldapts) with
|
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!
|
||||||
the uid and credentials of the user which tries to login.
|
|
||||||
|
|
||||||
|
|
||||||
Only valid LDAP users or email users registered by an admin can login.
|
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
|
This module authenticates against the local DB if `ALLOW_EMAIL_LOGIN` is set to `true` if this fails
|
||||||
|
@ -79,15 +77,23 @@ Edit [docker-compose.treafik.yml](docker-compose.traefik.yml) or [docker-compose
|
||||||
```
|
```
|
||||||
LDAP_SERVER: ldaps://LDAPSERVER:636
|
LDAP_SERVER: ldaps://LDAPSERVER:636
|
||||||
LDAP_BASE: dc=DOMAIN,dc=TLD
|
LDAP_BASE: dc=DOMAIN,dc=TLD
|
||||||
LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS
|
# If LDAP_BINDDN is set, the ldap bind happens directly by using the provided DN
|
||||||
# By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP
|
# All occurrences of `%u` get replaced by the entered uid.
|
||||||
# you have to set a group filter a minimal groupfilter would be: '(objectClass=person)'
|
# All occurrences of `%m`get replaced by the entered mail.
|
||||||
LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
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 `%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.
|
# 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
|
# 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
|
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
|
||||||
# system wide messages.
|
# 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)'
|
#LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
||||||
ALLOW_EMAIL_LOGIN: 'false'
|
ALLOW_EMAIL_LOGIN: 'false'
|
||||||
|
|
||||||
|
@ -104,6 +110,7 @@ function `getLdapContacts()` in ContactsController.js (line 92)
|
||||||
if you want to enable this function set:
|
if you want to enable this function set:
|
||||||
```
|
```
|
||||||
LDAP_CONTACTS: 'true'
|
LDAP_CONTACTS: 'true'
|
||||||
|
LDAP_GROUP_FILTER: '(memberof=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sharelatex Configuration
|
### Sharelatex Configuration
|
||||||
|
|
|
@ -58,8 +58,8 @@ services:
|
||||||
LDAP_SERVER: ldaps://LDAPSERVER:636
|
LDAP_SERVER: ldaps://LDAPSERVER:636
|
||||||
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
|
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
|
||||||
LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS
|
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
|
# Binds with the LDAP_BIND_USER and searches for users matching this filter:
|
||||||
LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
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.
|
# 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
|
# 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.
|
# 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
|
# 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)
|
# 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'
|
LDAP_CONTACTS: 'false'
|
||||||
|
|
||||||
# Same property, unfortunately with different names in
|
# Same property, unfortunately with different names in
|
||||||
|
|
|
@ -135,11 +135,10 @@ services:
|
||||||
|
|
||||||
LDAP_SERVER: ldaps://LDAPSERVER:636
|
LDAP_SERVER: ldaps://LDAPSERVER:636
|
||||||
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
|
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
|
||||||
LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS
|
#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
|
# # Binds with the LDAP_BIND_USER and searches for users matching this filter:
|
||||||
# LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)(uid=%u)'
|
||||||
LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
|
|
||||||
|
|
||||||
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
|
# 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
|
# Admin Users can invite external (non ldap) users. This feature makes only sense
|
||||||
|
@ -151,6 +150,7 @@ services:
|
||||||
# All users in the LDAP_GROUP_FILTER are loaded from the ldap server into contacts.
|
# 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
|
# 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)
|
# 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'
|
LDAP_CONTACTS: 'false'
|
||||||
|
|
||||||
# Same property, unfortunately with different names in
|
# Same property, unfortunately with different names in
|
||||||
|
|
|
@ -20,6 +20,7 @@ RUN npm install -g npm
|
||||||
RUN npm install ldap-escape
|
RUN npm install ldap-escape
|
||||||
RUN npm install ldapts-search
|
RUN npm install ldapts-search
|
||||||
RUN npm install ldapts
|
RUN npm install ldapts
|
||||||
|
RUN npm install ldap-escape
|
||||||
#RUN npm install bcrypt@5.0.0
|
#RUN npm install bcrypt@5.0.0
|
||||||
|
|
||||||
# This variant of updateing texlive does not work
|
# This variant of updateing texlive does not work
|
||||||
|
|
|
@ -90,21 +90,19 @@ const AuthenticationManager = {
|
||||||
},
|
},
|
||||||
|
|
||||||
authUserObj(error, user, query, password, callback) {
|
authUserObj(error, user, query, password, callback) {
|
||||||
if ( process.env.ALLOW_EMAIL_LOGIN ) {
|
if ( process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) {
|
||||||
// (external) email login
|
console.log("email login for existing user " + query.email)
|
||||||
if (user && user.hashedPassword) {
|
|
||||||
console.log("email login for existing user")
|
|
||||||
// check passwd against local db
|
// check passwd against local db
|
||||||
bcrypt.compare(password, user.hashedPassword, function (error, match) {
|
bcrypt.compare(password, user.hashedPassword, function (error, match) {
|
||||||
if (match) {
|
if (match) {
|
||||||
console.log("Fine")
|
console.log("Local user password match")
|
||||||
AuthenticationManager.login(user, password, callback)
|
AuthenticationManager.login(user, password, callback)
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
|
console.log("Local user password mismatch, trying LDAP")
|
||||||
// check passwd against ldap
|
// check passwd against ldap
|
||||||
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
|
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// No local passwd check user has to be in ldap and use ldap credentials
|
// No local passwd check user has to be in ldap and use ldap credentials
|
||||||
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
|
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
|
||||||
|
@ -271,25 +269,40 @@ const AuthenticationManager = {
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
url: process.env.LDAP_SERVER,
|
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_bd = process.env.LDAP_BINDDN
|
const ldap_reader_pass = process.env.LDAP_BIND_PW
|
||||||
const ldap_base = process.env.LDAP_BASE
|
const ldap_base = process.env.LDAP_BASE
|
||||||
|
|
||||||
var mail = query.email
|
var mail = query.email
|
||||||
var uid = query.email.split('@')[0]
|
var uid = query.email.split('@')[0]
|
||||||
const filterstr = '(&' + process.env.LDAP_GROUP_FILTER + '(' + ldapEscape.filter`uid=${uid}` + '))'
|
|
||||||
const userDn = ldapEscape.filter`uid=${uid}` + ',' + ldap_bd;
|
|
||||||
var firstname = ""
|
var firstname = ""
|
||||||
var lastname = ""
|
var lastname = ""
|
||||||
var isAdmin = false
|
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
|
// check bind
|
||||||
try {
|
try {
|
||||||
//await client.bind(bindDn, bindPassword);
|
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);
|
await client.bind(userDn,password);
|
||||||
|
}else{// use fixed bind user
|
||||||
|
await client.bind(ldap_reader, ldap_reader_pass);
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.log("Could not bind user." + 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)
|
return callback(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get user data
|
// get user data
|
||||||
try {
|
try {
|
||||||
const {searchEntries, searchRef,} = await client.search(ldap_base, {
|
const {searchEntries, searchRef,} = await client.search(ldap_base, {
|
||||||
|
@ -297,30 +310,35 @@ const AuthenticationManager = {
|
||||||
filter: filterstr ,
|
filter: filterstr ,
|
||||||
});
|
});
|
||||||
await searchEntries
|
await searchEntries
|
||||||
//console.log(JSON.stringify(searchEntries))
|
console.log(JSON.stringify(searchEntries))
|
||||||
if (searchEntries[0]) {
|
if (searchEntries[0]) {
|
||||||
mail = searchEntries[0].mail
|
mail = searchEntries[0].mail
|
||||||
|
uid = searchEntries[0].uid
|
||||||
firstname = searchEntries[0].givenName
|
firstname = searchEntries[0].givenName
|
||||||
lastname = searchEntries[0].sn
|
lastname = searchEntries[0].sn
|
||||||
//console.log("Found user: " + mail + " Name: " + firstname + " " + lastname)
|
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) {
|
} catch (ex) {
|
||||||
console.log("An Error occured while getting user data during ldapsearch: " + String(ex))
|
console.log("An Error occured while getting user data during ldapsearch: " + String(ex))
|
||||||
await client.unbind();
|
await client.unbind();
|
||||||
return callback(null, null)
|
return callback(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// if admin filter is set - only set admin for user in ldap group
|
// if admin filter is set - only set admin for user in ldap group
|
||||||
// does not matter - admin is deactivated: managed through ldap
|
// does not matter - admin is deactivated: managed through ldap
|
||||||
if (process.env.LDAP_ADMIN_GROUP_FILTER) {
|
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.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`)
|
||||||
adminEntry = await client.search(ldap_base, {
|
adminEntry = await client.search(ldap_base, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
filter: adminfilter,
|
filter: adminfilter,
|
||||||
});
|
});
|
||||||
await adminEntry;
|
await adminEntry;
|
||||||
//console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries))
|
//console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries))
|
||||||
if (adminEntry.searchEntries[0].mail) {
|
if (adminEntry.searchEntries[0]) {
|
||||||
console.log("is Admin")
|
console.log("is Admin")
|
||||||
isAdmin=true;
|
isAdmin=true;
|
||||||
}
|
}
|
||||||
|
@ -331,10 +349,21 @@ const AuthenticationManager = {
|
||||||
} finally {
|
} finally {
|
||||||
await client.unbind();
|
await client.unbind();
|
||||||
}
|
}
|
||||||
if (mail == "") {
|
if (mail == "" || userDn == "") {
|
||||||
console.log("Mail not set - exit. This should not happen - please set mail-entry in ldap.")
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
//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
|
// we are authenticated now let's set the query to the correct mail from ldap
|
||||||
query.email = mail
|
query.email = mail
|
||||||
|
|
Loading…
Add table
Reference in a new issue