Do not require app auth for exposed ldap
This commit is contained in:
141
src/ldap.js
141
src/ldap.js
@@ -283,6 +283,134 @@ async function groupAdminsCompare(req, res, next) {
|
||||
res.end(false);
|
||||
}
|
||||
|
||||
async function userSearchExposed(req, res, next) {
|
||||
debug('exposed user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
const [error, result] = await safe(users.list());
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
let results = [];
|
||||
|
||||
// send user objects
|
||||
result.forEach(function (user) {
|
||||
// skip entries with empty username. Some apps like owncloud can't deal with this
|
||||
if (!user.username) return;
|
||||
|
||||
const dn = ldap.parseDN('cn=' + user.id + ',ou=users,dc=cloudron');
|
||||
|
||||
const memberof = [ GROUP_USERS_DN ];
|
||||
if (users.compareRoles(user.role, users.ROLE_ADMIN) >= 0) memberof.push(GROUP_ADMINS_DN);
|
||||
|
||||
const displayName = user.displayName || user.username || ''; // displayName can be empty and username can be null
|
||||
const nameParts = displayName.split(' ');
|
||||
const firstName = nameParts[0];
|
||||
const lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists
|
||||
|
||||
const obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['user', 'inetorgperson', 'person' ],
|
||||
objectcategory: 'person',
|
||||
cn: user.id,
|
||||
uid: user.id,
|
||||
entryuuid: user.id, // to support OpenLDAP clients
|
||||
mail: user.email,
|
||||
mailAlternateAddress: user.fallbackEmail,
|
||||
displayname: displayName,
|
||||
givenName: firstName,
|
||||
username: user.username,
|
||||
samaccountname: user.username, // to support ActiveDirectory clients
|
||||
memberof: memberof
|
||||
}
|
||||
};
|
||||
|
||||
// http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString
|
||||
// which is required to have atleast one character if present
|
||||
if (lastName.length !== 0) obj.attributes.sn = lastName;
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
finalSend(results, req, res, next);
|
||||
}
|
||||
|
||||
async function groupSearchExposed(req, res, next) {
|
||||
debug('exposed group search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
const [error, result] = await safe(users.list());
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
const results = [];
|
||||
|
||||
// those are the old virtual groups for backwards compat
|
||||
const virtualGroups = [{
|
||||
name: 'users',
|
||||
admin: false
|
||||
}, {
|
||||
name: 'admins',
|
||||
admin: true
|
||||
}];
|
||||
|
||||
virtualGroups.forEach(function (group) {
|
||||
const dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
|
||||
const members = group.admin ? result.filter(function (user) { return users.compareRoles(user.role, users.ROLE_ADMIN) >= 0; }) : result;
|
||||
|
||||
const obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['group'],
|
||||
cn: group.name,
|
||||
memberuid: members.map(function(entry) { return entry.id; }).sort()
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
let [errorGroups, resultGroups] = await safe(groups.listWithMembers());
|
||||
if (errorGroups) return next(new ldap.OperationsError(errorGroups.toString()));
|
||||
|
||||
if (req.app.accessRestriction && req.app.accessRestriction.groups) {
|
||||
resultGroups = resultGroups.filter(function (g) { return req.app.accessRestriction.groups.indexOf(g.id) !== -1; });
|
||||
}
|
||||
|
||||
resultGroups.forEach(function (group) {
|
||||
const dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
|
||||
const members = group.userIds.filter(function (uid) { return result.map(function (u) { return u.id; }).indexOf(uid) !== -1; });
|
||||
|
||||
const obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['group'],
|
||||
cn: group.name,
|
||||
memberuid: members
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
finalSend(results, req, res, next);
|
||||
}
|
||||
|
||||
async function mailboxSearch(req, res, next) {
|
||||
debug('mailbox search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
@@ -731,12 +859,15 @@ async function startExposed() {
|
||||
debugExposed('server startup error ', error);
|
||||
});
|
||||
|
||||
gExposedServer.search('ou=users,dc=cloudron', authenticateApp, userSearch);
|
||||
gExposedServer.search('ou=groups,dc=cloudron', authenticateApp, groupSearch);
|
||||
gExposedServer.bind('ou=users,dc=cloudron', authenticateApp, authenticateUser, authorizeUserForApp);
|
||||
gExposedServer.search('ou=users,dc=cloudron', userSearchExposed);
|
||||
gExposedServer.search('ou=groups,dc=cloudron', groupSearchExposed);
|
||||
gExposedServer.bind('ou=users,dc=cloudron', authenticateUser, async function (req, res) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
gExposedServer.compare('cn=users,ou=groups,dc=cloudron', authenticateApp, groupUsersCompare);
|
||||
gExposedServer.compare('cn=admins,ou=groups,dc=cloudron', authenticateApp, groupAdminsCompare);
|
||||
await eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: req.app.id }, { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
// just log that an attempt was made to unknown route, this helps a lot during app packaging
|
||||
gExposedServer.use(function(req, res, next) {
|
||||
|
||||
Reference in New Issue
Block a user