From 5a06c46d3e6991a1884925595b3bacb77c8f42a1 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 20 May 2020 00:39:17 -0700 Subject: [PATCH] Added SAML, JumpCloud support. --- public/images/login/generic32.png | Bin 0 -> 759 bytes public/images/login/generic64.png | Bin 0 -> 1465 bytes sample-config-advanced.json | 12 ++++ translate/translate.json | 14 ++++ views/default.handlebars | 13 ++-- views/login-mobile.handlebars | 4 ++ views/login.handlebars | 3 + webserver.js | 108 ++++++++++++++++++++---------- 8 files changed, 112 insertions(+), 42 deletions(-) create mode 100644 public/images/login/generic32.png create mode 100644 public/images/login/generic64.png diff --git a/public/images/login/generic32.png b/public/images/login/generic32.png new file mode 100644 index 0000000000000000000000000000000000000000..e889908b78657bd51f0219306513b9a12a451e8d GIT binary patch literal 759 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0*pySK~zXfV`NzT zg@KlU2};upj9Net41EhoPNpWpOi>Cfx&lnhj10Fv|6BLy@9yV+|3f9Q01_ zMTv*;`VaKn#NjSp(alC|6CuhOG$|K zH5Bmk@j$o`z|6$V%nVgL=gyz?kN@m=_7@muaDZY#s60!%0jn}ERKu$`?_+c8q{KxZ zJ$(TY!2~jrVpH16czL*>dTtWzO5P0_Dby88Ys-i3~8lXZ9 z4BT9trI|6e?mzzh`?sd5;?ozeqXONba!9~>=l5f;{{b0L3mnCn_qlR_rBJ|(MQc}Y z-V0%i2nn=RW{HUk@p2;q1Q#eh_p9gHZy*C|fu9WXMh7ew0Kv)gSI=L*DI~z>?qGoy z!+1c+*J~6C002b**q&qpVfp|7002ovPDHLkV1oI%Q0o8y literal 0 HcmV?d00001 diff --git a/public/images/login/generic64.png b/public/images/login/generic64.png new file mode 100644 index 0000000000000000000000000000000000000000..6549bfed8a1a918b9ff4a2ea18ab604671553388 GIT binary patch literal 1465 zcmeH_`#Y2g6vy8()-YvVQWjx_2ibCO+tx0knX!17H7=Vrq7c!#Io&hfc_q1%3Sx`i2d;KZ3HZ-0*k| z`8<^nITHUaNf1{O(klt?lSVv~o*b0M4@nb;rAZ^w6rL=7RCe)&>{4+RrQ{Y^dMmH2 zIW8Dx|fjNp>f^>fAY-?f9l=T%p7+K8CXGv|3RJ$6l1H3i4Tg&b;auq#k5c{ zBUCId3zk+^XGQ1&gf2_aRVlh56p6%;7?MB|>ADOR%2A0_DuPiltb`DyRHi~MhgBdj zf=Cf1j4BaCp-`ZoP^#|rKmWl782vdPRbw>I62emfK)Y|#TLzOGV*p^Q7tz}@I1Im9 zkr1jEv?IT4>ay>kxtiVe7V7o*4Cf!7l%K4uxH-uwruB}Dkeux5Mab%@vFs}cvkWzc z9>`tBcTqX-Boy^nJ~p}<1A0X2V$m}53w7{9Tr%Ex-?3l=%K;o|U6JM>^J&~H7fB0-G;o9x*KAWYErTi4RHt*zB|%^!QpHHh624{Ej-4Bh!G zY9dYjkK$;7{R+q@dOITDrU#A0K#<3}vEbs>m zFrPb;W4xD0X1>-M`wO~$q$wHfVs}E5wdNdE*UXK^j6+!|>C1yJYGvE_Zu3lE(3HpZ zNxN)#y9OE09C=(}xJV#pt?YTRSa@nV}5F6try`8QkM(Pr__;5$qcontainer->page_content->column_l->1->1->0->1->loginpanel->1->authStrategies->auth-intel" ] }, + { + "en": "Sign-in using JumpCloud", + "xloc": [ + "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->loginpanel->1->authStrategies->auth-jumpcloud", + "login.handlebars->container->column_l->centralTable->1->0->logincell->loginpanel->1->authStrategies->auth-jumpcloud" + ] + }, { "en": "Sign-in using Reddit", "nl": "Log in met Reddit", @@ -24044,6 +24051,13 @@ "default.handlebars->25->1035" ] }, + { + "en": "Single Sign-in", + "xloc": [ + "login-mobile.handlebars->container->page_content->column_l->1->1->0->1->loginpanel->1->authStrategies->auth-saml", + "login.handlebars->container->column_l->centralTable->1->0->logincell->loginpanel->1->authStrategies->auth-saml" + ] + }, { "cs": "Velikost", "de": "Größe", diff --git a/views/default.handlebars b/views/default.handlebars index 09f1bb27..ab6cede8 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -10880,12 +10880,13 @@ // Add user auth strategy var shortuserid = user._id.split('/')[2]; - if (shortuserid.startsWith('~twitter:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/twitter64.png';; } - else if (shortuserid.startsWith('~google:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/google64.png';; } - else if (shortuserid.startsWith('~github:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/github64.png';; } - else if (shortuserid.startsWith('~reddit:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/reddit64.png';; } - else if (shortuserid.startsWith('~jumpcloud:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/jumpcloud64.png';; } - else if (shortuserid.startsWith('~intel:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/intel64.png';; } + if (shortuserid.startsWith('~twitter:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/twitter64.png'; } + else if (shortuserid.startsWith('~google:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/google64.png'; } + else if (shortuserid.startsWith('~github:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/github64.png'; } + else if (shortuserid.startsWith('~reddit:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/reddit64.png'; } + else if (shortuserid.startsWith('~jumpcloud:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/jumpcloud64.png'; } + else if (shortuserid.startsWith('~intel:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/intel64.png'; } + else if (shortuserid.startsWith('~:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/generic64.png'; } else { QV('p30userAuthServiceLogo', false); } // Server permissions diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index b3283163..3e46ac77 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -79,7 +79,9 @@ + + @@ -371,7 +373,9 @@ if (authStrategies.indexOf('google') >= 0) { QV('auth-google', true); } if (authStrategies.indexOf('github') >= 0) { QV('auth-github', true); } if (authStrategies.indexOf('reddit') >= 0) { QV('auth-reddit', true); } + if (authStrategies.indexOf('jumpcloud') >= 0) { QV('auth-jumpcloud', true); } if (authStrategies.indexOf('intel') >= 0) { QV('auth-intel', true); } + if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); } } window.onresize = center; diff --git a/views/login.handlebars b/views/login.handlebars index 75e49cfa..33a037bc 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -78,6 +78,7 @@ + @@ -386,6 +387,8 @@ if (authStrategies.indexOf('github') >= 0) { QV('auth-github', true); } if (authStrategies.indexOf('reddit') >= 0) { QV('auth-reddit', true); } if (authStrategies.indexOf('jumpcloud') >= 0) { QV('auth-jumpcloud', true); } + if (authStrategies.indexOf('intel') >= 0) { QV('auth-intel', true); } + if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); } } // Display the welcome text diff --git a/webserver.js b/webserver.js index 191dcdf7..bf9a8aa9 100644 --- a/webserver.js +++ b/webserver.js @@ -1783,7 +1783,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((req.user.email != null) && (req.user.email != user.email)) { user.email = req.user.email; user.emailVerified = true; userChange = true; } if (userChange) { obj.db.SetUser(user); - // TODO: Event user change + + // Event user creation + var targets = ['*', 'server-users']; + var event = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Account changed', domain: domain.id }; + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come. + parent.DispatchEvent(targets, obj, event); } parent.debug('web', 'handleStrategyLogin: succesful login: ' + userid); req.session.userid = req.user.id; @@ -2111,12 +2116,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // See what authentication strategies we have var authStrategies = []; if (typeof domain.authstrategies == 'object') { - if ((typeof domain.authstrategies.twitter == 'object') && (typeof domain.authstrategies.twitter.clientid == 'string') && (typeof domain.authstrategies.twitter.clientsecret == 'string')) { authStrategies.push('twitter'); } - if ((typeof domain.authstrategies.google == 'object') && (typeof domain.authstrategies.google.clientid == 'string') && (typeof domain.authstrategies.google.clientsecret == 'string')) { authStrategies.push('google'); } - if ((typeof domain.authstrategies.github == 'object') && (typeof domain.authstrategies.github.clientid == 'string') && (typeof domain.authstrategies.github.clientsecret == 'string')) { authStrategies.push('github'); } - if ((typeof domain.authstrategies.reddit == 'object') && (typeof domain.authstrategies.reddit.clientid == 'string') && (typeof domain.authstrategies.reddit.clientsecret == 'string')) { authStrategies.push('reddit'); } - if ((typeof domain.authstrategies.jumpcloud == 'object')) { authStrategies.push('jumpcloud'); } - if ((typeof domain.authstrategies.intel == 'object') && (typeof domain.authstrategies.intel.clientid == 'string') && (typeof domain.authstrategies.intel.clientsecret == 'string')) { authStrategies.push('intel'); } + if (typeof domain.authstrategies.twitter == 'object') { authStrategies.push('twitter'); } + if (typeof domain.authstrategies.google == 'object') { authStrategies.push('google'); } + if (typeof domain.authstrategies.github == 'object') { authStrategies.push('github'); } + if (typeof domain.authstrategies.reddit == 'object') { authStrategies.push('reddit'); } + if (typeof domain.authstrategies.intel == 'object') { authStrategies.push('intel'); } + if (typeof domain.authstrategies.jumpcloud == 'object') { authStrategies.push('jumpcloud'); } + if (typeof domain.authstrategies.saml == 'object') { authStrategies.push('saml'); } } // Render the login page @@ -4210,40 +4216,70 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { }, handleStrategyLogin); } + // Generic SAML + if (typeof domain.authstrategies.saml == 'object') { + if ((typeof domain.authstrategies.saml.cert != 'string') || (typeof domain.authstrategies.saml.idpurl != 'string')) { + console.log('ERROR: Missing SAML configuration.'); + } else { + var cert = obj.fs.readFileSync(obj.path.join(obj.parent.datapath, domain.authstrategies.saml.cert)); + if (cert == null) { + console.log('ERROR: Unable to read SAML IdP certificate: ' + domain.authstrategies.saml.cert); + } else { + var options = { path: url + 'auth-saml-callback', entryPoint: domain.authstrategies.saml.idpurl, issuer: 'meshcentral' }; + if (typeof domain.authstrategies.saml.entityid == 'string') { options.issuer = domain.authstrategies.saml.entityid; } + options.cert = cert.toString().split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join(''); + const SamlStrategy = require('passport-saml').Strategy; + passport.use(new SamlStrategy(options, + function (profile, done) { + if (typeof profile.nameID != 'string') { return done(); } + var user = { id: 'user/' + domain.id + '/~' + profile.issuer + ':' + profile.nameID, name: profile.nameID }; + if ((typeof profile.firstname == 'string') && (typeof profile.lastname == 'string')) { user.name = profile.firstname + ' ' + profile.lastname; } + if (typeof profile.email == 'string') { user.email = profile.email; } + return done(null, user); + } + )); + obj.app.get(url + 'auth-saml', function (req, res, next) { + domain.passport.authenticate('saml', { failureRedirect: '/', failureFlash: true })(req, res, next); + }); + obj.app.post(url + 'auth-saml-callback', function (req, res, next) { + domain.passport.authenticate('saml', { failureRedirect: '/', failureFlash: true })(req, res, next); + }, handleStrategyLogin); + } + } + } + // JumpCloud if (typeof domain.authstrategies.jumpcloud == 'object') { - const SamlStrategy = require('passport-saml').Strategy; - - var options = { - path: url + 'auth-jumpcloud-callback', - entryPoint: domain.authstrategies.jumpcloud.idpurl, - issuer: 'passport-saml' - }; - - if (domain.authstrategies.jumpcloud.cert) { + if ((typeof domain.authstrategies.jumpcloud.cert != 'string') || (typeof domain.authstrategies.jumpcloud.idpurl != 'string')) { + console.log('ERROR: Missing JumpCloud configuration.'); + } else { var cert = obj.fs.readFileSync(obj.path.join(obj.parent.datapath, domain.authstrategies.jumpcloud.cert)); - if (cert != null) { options.cert = cert.toString().split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join(''); } - //console.log(options); - } - - passport.use(new SamlStrategy(options, - function (profile, done) { - //var user = { id: 'user/' + domain.id + '/~reddit:' + profile.id, name: profile.name }; - //if ((typeof profile.emails == 'object') && (profile.emails[0] != null) && (typeof profile.emails[0].value == 'string')) { user.email = profile.emails[0].value; } - console.log('JumpCloud Profile', profile); - var user = { id: 'user/' + domain.id + '/~jumpcloud:' + profile.id, name: profile.name }; - return done(null, user); + if (cert == null) { + console.log('ERROR: Unable to read JumpCloud IdP certificate: ' + domain.authstrategies.jumpcloud.cert); + } else { + var options = { path: url + 'auth-jumpcloud-callback', entryPoint: domain.authstrategies.jumpcloud.idpurl, issuer: 'meshcentral' }; + if (typeof domain.authstrategies.jumpcloud.entityid == 'string') { options.issuer = domain.authstrategies.jumpcloud.entityid; } + options.cert = cert.toString().split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join(''); + const SamlStrategy = require('passport-saml').Strategy; + passport.use(new SamlStrategy(options, + function (profile, done) { + if (typeof profile.nameID != 'string') { return done(); } + var user = { id: 'user/' + domain.id + '/~' + profile.issuer + ':' + profile.nameID, name: profile.nameID }; + if ((typeof profile.firstname == 'string') && (typeof profile.lastname == 'string')) { user.name = profile.firstname + ' ' + profile.lastname; } + if (typeof profile.email == 'string') { user.email = profile.email; } + return done(null, user); + } + )); + obj.app.get(url + 'auth-jumpcloud', function (req, res, next) { + domain.passport.authenticate('saml', { failureRedirect: '/', failureFlash: true })(req, res, next); + }); + obj.app.post(url + 'auth-jumpcloud-callback', function (req, res, next) { + domain.passport.authenticate('saml', { failureRedirect: '/', failureFlash: true })(req, res, next); + }, handleStrategyLogin); } - )); - obj.app.get(url + 'auth-jumpcloud', function (req, res, next) { - console.log('auth-jumpcloud'); - domain.passport.authenticate('saml', { failureRedirect: '/', failureFlash: true })(req, res, next); - }); - obj.app.get(url + 'auth-jumpcloud-callback', function (req, res, next) { - console.log('auth-jumpcloud-callback'); - domain.passport.authenticate('saml', { failureRedirect: '/', failureFlash: true })(req, res, next); - }); + } } + } // Server redirects