Added support for LDAP account images (#4283)
This commit is contained in:
parent
b7bc172c40
commit
61e486ba38
|
@ -501,11 +501,13 @@
|
|||
},
|
||||
"twoFactorCookieDurationDays": { "type": "integer", "default": 30, "description": "Number of days that a user is allowed to remember this device for when completing 2FA. Set this to 0 to remove this option." },
|
||||
"auth": { "type": "string", "default": null, "enum": [null, "sspi", "ldap"], "description": "Type of user authentication to use, this can be SSPI on Windows or LDAP. If not set, username/password is used." },
|
||||
"ldapUserKey": { "type": "string" },
|
||||
"ldapUserName": { "type": "string" },
|
||||
"ldapUserEmail": { "type": "string" },
|
||||
"ldapUserRealName": { "type": "string" },
|
||||
"ldapUserPhoneNumber": { "type": "string" },
|
||||
"ldapUserKey": { "type": "string", "default": null, "description": "The LDAP value to use as a user's unique account identifier. Use \"ldapUserKey\" or \"ldapUserBinaryKey\"." },
|
||||
"ldapUserBinaryKey": { "type": "string", "default": "objectSid", "description": "The LDAP value to use as a user's unique account identifier, when specified in this feild, the values will be HEX converted." },
|
||||
"ldapUserName": { "type": "string", "default": "displayName", "description": "The LDAP value to use for the user name, you can also compose the name by setting this value to, for example: \"{{{givenName}}} {{{sn}}}\"" },
|
||||
"ldapUserEmail": { "type": "string", "default": "mail", "description": "The LDAP value to use for the user's email address." },
|
||||
"ldapUserRealName": { "type": "string", "default": "name", "description": "The LDAP value to use for the user's real name, you can also compose the name by setting this value to, for example: \"{{{givenName}}} {{{sn}}}\"" },
|
||||
"ldapUserPhoneNumber": { "type": "string", "default": "telephoneNumber", "description": "The LDAP value to use for the user's phone number." },
|
||||
"ldapUserImage": { "type": "string", "default": "thumbnailPhoto", "description": "The LDAP value to use for the user's image." },
|
||||
"ldapSaveUserToFile": { "type": "string", "default": null, "description": "When set to a filename, for example c:\\temp\\ldapusers.txt, MeshCentral will save the LDAP user object to this file each time a user logs in. This is used for debugging LDAP issues." },
|
||||
"ldapOptions": { "type": "object", "description": "LDAP options passed to ldapauth-fork" },
|
||||
"agentInviteCodes": { "type": "boolean", "default": false, "description": "Enabled a feature where you can set one or more invitation codes in a device group. You can then give a invitation link to users who can use it to download the agent." },
|
||||
|
|
44
webserver.js
44
webserver.js
|
@ -442,8 +442,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
} else if (domain.auth == 'ldap') {
|
||||
// This method will handle LDAP login
|
||||
const ldapHandler = function ldapHandlerFunc(err, xxuser) {
|
||||
if (ldapHandlerFunc.ldapobj) { try { ldapHandlerFunc.ldapobj.close(); } catch (ex) { console.log(ex); } } // Close the LDAP object
|
||||
if (err) { fn(new Error('invalid password')); return; }
|
||||
if (err) { if (ldapHandlerFunc.ldapobj) { try { ldapHandlerFunc.ldapobj.close(); } catch (ex) { console.log(ex); } } fn(new Error('invalid password')); return; }
|
||||
|
||||
// Save this LDAP user to file if needed
|
||||
if (typeof domain.ldapsaveusertofile == 'string') {
|
||||
|
@ -464,18 +463,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
if (xxuser[domain.ldapuserkey]) { shortname = xxuser[domain.ldapuserkey]; }
|
||||
} else {
|
||||
// Use the default key as the userid
|
||||
if (xxuser.objectSid) { shortname = Buffer.from(xxuser.objectSid, 'binary').toString('hex').toLowerCase(); }
|
||||
else if (xxuser.objectGUID) { shortname = Buffer.from(xxuser.objectGUID, 'binary').toString('hex').toLowerCase(); }
|
||||
else if (xxuser.name) { shortname = xxuser.name; }
|
||||
else if (xxuser.cn) { shortname = xxuser.cn; }
|
||||
if (xxuser['objectSid']) { shortname = Buffer.from(xxuser['objectSid'], 'binary').toString('hex').toLowerCase(); }
|
||||
else if (xxuser['objectGUID']) { shortname = Buffer.from(xxuser['objectGUID'], 'binary').toString('hex').toLowerCase(); }
|
||||
else if (xxuser['name']) { shortname = xxuser['name']; }
|
||||
else if (xxuser['cn']) { shortname = xxuser['cn']; }
|
||||
}
|
||||
if (shortname == null) { fn(new Error('no user identifier')); return; }
|
||||
if (shortname == null) { fn(new Error('no user identifier')); if (ldapHandlerFunc.ldapobj) { try { ldapHandlerFunc.ldapobj.close(); } catch (ex) { console.log(ex); } } return; }
|
||||
if (username == null) { username = shortname; }
|
||||
var userid = 'user/' + domain.id + '/' + shortname;
|
||||
|
||||
// Get the email address for this LDAP user
|
||||
var email = null;
|
||||
if (domain.ldapuseremail) { email = xxuser[domain.ldapuseremail]; } else if (xxuser.mail) { email = xxuser.mail; } // Use given feild name or default
|
||||
if (domain.ldapuseremail) { email = xxuser[domain.ldapuseremail]; } else if (xxuser['mail']) { email = xxuser['mail']; } // Use given feild name or default
|
||||
if (Array.isArray(email)) { email = email[0]; } // Mail may be multivalued in LDAP in which case, answer is an array. Use the 1st value.
|
||||
if (email) { email = email.toLowerCase(); } // it seems some code elsewhere also lowercase the emailaddress, so let's be consistant.
|
||||
|
||||
|
@ -492,14 +491,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
else { if (typeof xxuser['telephoneNumber'] == 'string') { phonenumber = xxuser['telephoneNumber']; } }
|
||||
|
||||
// Work on getting the image of this LDAP user
|
||||
// TODO: We need to get the image from LDAP as a buffer: https://github.com/ldapjs/node-ldapjs/issues/137
|
||||
var userimage = null, userImageBuffer = null;
|
||||
if (domain.ldapuserimage && xxuser[domain.ldapuserimage]) { try { userImageBuffer = Buffer.from(xxuser[domain.ldapuserimage], 'binary'); } catch (ex) { } }
|
||||
if (xxuser['thumbnailPhoto']) { try { userImageBuffer = Buffer.from(xxuser['thumbnailPhoto'], 'binary'); } catch (ex) { } }
|
||||
if (xxuser._raw) { // Using _raw allows us to get data directly as buffer.
|
||||
if (domain.ldapuserimage && xxuser[domain.ldapuserimage]) { userImageBuffer = xxuser._raw[domain.ldapuserimage]; }
|
||||
else if (xxuser['thumbnailPhoto']) { userImageBuffer = xxuser._raw['thumbnailPhoto']; }
|
||||
else if (xxuser['jpegPhoto']) { userImageBuffer = xxuser._raw['jpegPhoto']; }
|
||||
if (userImageBuffer != null) {
|
||||
if ((userImageBuffer[0] == 0xFF) && (userImageBuffer[1] == 0xD8) && (userImageBuffer[2] == 0xFF) && (userImageBuffer[3] == 0xE0)) { userimage = 'data:image/jpeg;base64,' + userImageBuffer.toString('base64'); }
|
||||
if ((userImageBuffer[0] == 0x89) && (userImageBuffer[1] == 0x50) && (userImageBuffer[2] == 0x4E) && (userImageBuffer[3] == 0x47)) { userimage = 'data:image/png;base64,' + userImageBuffer.toString('base64'); }
|
||||
}
|
||||
}
|
||||
|
||||
// Display user information extracted from LDAP data
|
||||
/*
|
||||
|
@ -520,6 +521,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
// Save the user image
|
||||
if (userimage != null) { parent.db.Set({ _id: 'im' + userid, image: userimage }); } else { db.Remove('im' + userid); }
|
||||
|
||||
// Close the LDAP object
|
||||
if (ldapHandlerFunc.ldapobj) { try { ldapHandlerFunc.ldapobj.close(); } catch (ex) { console.log(ex); } }
|
||||
|
||||
// Check if the user already exists
|
||||
var user = obj.users[userid];
|
||||
if (user == null) {
|
||||
|
@ -628,6 +632,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
} else {
|
||||
// LDAP login
|
||||
var LdapAuth = require('ldapauth-fork');
|
||||
if (domain.ldapoptions == null) { domain.ldapoptions = {}; }
|
||||
domain.ldapoptions.includeRaw = true; // This allows us to get data as buffers which is useful for images.
|
||||
var ldap = new LdapAuth(domain.ldapoptions);
|
||||
ldapHandler.ldapobj = ldap;
|
||||
ldap.on('error', function (err) { try { ldap.close(); } catch (ex) { console.log(ex); } console.log('ldap error: ', err); }); // Close the LDAP object
|
||||
|
@ -2919,7 +2925,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
}
|
||||
|
||||
// Return a list of server supported features for a given domain and user
|
||||
obj.getDomainUserFeatures = function(domain, user, req) {
|
||||
obj.getDomainUserFeatures = function (domain, user, req) {
|
||||
var features = 0;
|
||||
var features2 = 0;
|
||||
if (obj.args.wanonly == true) { features += 0x00000001; } // WAN-only mode
|
||||
|
@ -3492,7 +3498,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
if (domain.titlepicture) {
|
||||
if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) {
|
||||
// Use the logo in the database
|
||||
res.set({ 'Content-Type': domain.titlepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
|
||||
res.set({ 'Content-Type': domain.titlepicture.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg' });
|
||||
res.send(parent.configurationFiles[domain.titlepicture]);
|
||||
return;
|
||||
} else {
|
||||
|
@ -3607,7 +3613,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
if (domain.welcomepicture) {
|
||||
if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.welcomepicture] != null)) {
|
||||
// Use the welcome image in the database
|
||||
res.set({ 'Content-Type': domain.welcomepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
|
||||
res.set({ 'Content-Type': domain.welcomepicture.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg' });
|
||||
res.send(parent.configurationFiles[domain.welcomepicture]);
|
||||
return;
|
||||
}
|
||||
|
@ -3692,7 +3698,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
if ((user.siteadmin & 512) == 0) { try { ws.close(); } catch (ex) { } return; } // Check if we have right to get recordings
|
||||
const filefullpath = obj.path.join(recordingsPath, req.query.file);
|
||||
|
||||
obj.fs.stat(filefullpath, function(err, stats) {
|
||||
obj.fs.stat(filefullpath, function (err, stats) {
|
||||
if (err) {
|
||||
try { ws.close(); } catch (ex) { } // File does not exist
|
||||
} else {
|
||||
|
@ -6381,15 +6387,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
issuer: domain.authstrategies.oidc.issuer,
|
||||
tokenURL: domain.authstrategies.oidc.tokenurl,
|
||||
userInfoURL: domain.authstrategies.oidc.userinfourl,
|
||||
scope: [ 'openid profile email' ],
|
||||
responseMode: 'form_post' ,
|
||||
scope: ['openid profile email'],
|
||||
responseMode: 'form_post',
|
||||
state: true
|
||||
};
|
||||
const OIDCStrategy = require('@mstrhakr/passport-generic-oidc');
|
||||
if (typeof domain.authstrategies.oidc.callbackurl == 'string') { options.callbackURL = domain.authstrategies.oidc.callbackurl; } else { options.callbackURL = url + 'oidc-callback'; }
|
||||
parent.debug('web', 'Adding Generic OIDC SSO with options: ' + JSON.stringify(options));
|
||||
passport.use('openidconnect', new OIDCStrategy.Strategy(options,
|
||||
function verify( iss, sub, profile, cb ) {
|
||||
function verify(iss, sub, profile, cb) {
|
||||
var user = { sid: '~oidc:' + profile.id, name: profile.displayName, email: profile.email, strategy: 'oidc' };
|
||||
parent.debug('AUTH', 'OIDC: Configured user: ' + JSON.stringify(user));
|
||||
return cb(null, user);
|
||||
|
@ -7972,7 +7978,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||
}
|
||||
xargs.extitle = encodeURIComponent(xargs.title).split('\'').join('\\\'');
|
||||
xargs.domainurl = domain.url;
|
||||
xargs.autocomplete = (domain.autocomplete === false)?'x':'autocomplete'; // This option allows autocomplete to be turned off on the login page.
|
||||
xargs.autocomplete = (domain.autocomplete === false) ? 'x' : 'autocomplete'; // This option allows autocomplete to be turned off on the login page.
|
||||
if (typeof domain.hide == 'number') { xargs.hide = domain.hide; }
|
||||
|
||||
// To mitigate any possible BREACH attack, we generate a random 0 to 255 bytes length string here.
|
||||
|
|
Loading…
Reference in New Issue