(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var RocketChat = Package['rocketchat:lib'].RocketChat;
var RoutePolicy = Package.routepolicy.RoutePolicy;
var WebApp = Package.webapp.WebApp;
var main = Package.webapp.main;
var WebAppInternals = Package.webapp.WebAppInternals;
var _ = Package.underscore._;
var ServiceConfiguration = Package['service-configuration'].ServiceConfiguration;
var HTTP = Package.http.HTTP;
var HTTPInternals = Package.http.HTTPInternals;
var Accounts = Package['accounts-base'].Accounts;
var AccountsServer = Package['accounts-base'].AccountsServer;
var TAPi18next = Package['tap:i18n'].TAPi18next;
var TAPi18n = Package['tap:i18n'].TAPi18n;
/* Package-scope variables */
var SAML, __coffeescriptShare;
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/steffo_meteor-accounts-saml/saml_server.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
/* globals RoutePolicy, SAML */ // 1
/* jshint newcap: false */ // 2
// 3
if (!Accounts.saml) { // 4
Accounts.saml = { // 5
settings: { // 6
debug: true, // 7
generateUsername: false, // 8
providers: [] // 9
} // 10
}; // 11
} // 12
// 13
var fiber = Npm.require('fibers'); // 14
var connect = Npm.require('connect'); // 15
RoutePolicy.declare('/_saml/', 'network'); // 16
// 17
Meteor.methods({ // 18
samlLogout: function (provider) { // 19
// Make sure the user is logged in before initiate SAML SLO // 20
if (!Meteor.userId()) { // 21
throw new Meteor.Error('not-authorized'); // 22
} // 23
var samlProvider = function (element) { // 24
return (element.provider === provider); // 25
}; // 26
var providerConfig = Accounts.saml.settings.providers.filter(samlProvider)[0]; // 27
// 28
if (Accounts.saml.settings.debug) { // 29
console.log('Logout request from ' + JSON.stringify(providerConfig)); // 30
} // 31
// This query should respect upcoming array of SAML logins // 32
var user = Meteor.users.findOne({ // 33
_id: Meteor.userId(), // 34
'services.saml.provider': provider // 35
}, { // 36
'services.saml': 1 // 37
}); // 38
var nameID = user.services.saml.nameID; // 39
var sessionIndex = user.services.saml.idpSession; // 40
nameID = sessionIndex; // 41
if (Accounts.saml.settings.debug) { // 42
console.log('NameID for user ' + Meteor.userId() + ' found: ' + JSON.stringify(nameID)); // 43
} // 44
// 45
var _saml = new SAML(providerConfig); // 46
// 47
var request = _saml.generateLogoutRequest({ // 48
nameID: nameID, // 49
sessionIndex: sessionIndex // 50
}); // 51
// 52
// request.request: actual XML SAML Request // 53
// request.id: comminucation id which will be mentioned in the ResponseTo field of SAMLResponse // 54
// 55
Meteor.users.update({ // 56
_id: Meteor.userId() // 57
}, { // 58
$set: { // 59
'services.saml.inResponseTo': request.id // 60
} // 61
}); // 62
// 63
var _syncRequestToUrl = Meteor.wrapAsync(_saml.requestToUrl, _saml); // 64
var result = _syncRequestToUrl(request.request, 'logout'); // 65
if (Accounts.saml.settings.debug) { // 66
console.log('SAML Logout Request ' + result); // 67
} // 68
// 69
// 70
return result; // 71
} // 72
}); // 73
// 74
Accounts.registerLoginHandler(function (loginRequest) { // 75
if (!loginRequest.saml || !loginRequest.credentialToken) { // 76
return undefined; // 77
} // 78
// 79
var loginResult = Accounts.saml.retrieveCredential(loginRequest.credentialToken); // 80
if (Accounts.saml.settings.debug) { // 81
console.log('RESULT :' + JSON.stringify(loginResult)); // 82
} // 83
// 84
if (loginResult === undefined) { // 85
return { // 86
type: 'saml', // 87
error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'No matching login attempt found') // 88
}; // 89
} // 90
// 91
if (loginResult && loginResult.profile && loginResult.profile.email) { // 92
var user = Meteor.users.findOne({ // 93
'emails.address': loginResult.profile.email // 94
}); // 95
// 96
if (!user) { // 97
var newUser = { // 98
name: loginResult.profile.cn || loginResult.profile.username, // 99
active: true, // 100
globalRoles: ['user'], // 101
emails: [{ // 102
address: loginResult.profile.email, // 103
verified: true // 104
}] // 105
}; // 106
// 107
if (Accounts.saml.settings.generateUsername === true) { // 108
var username = RocketChat.generateUsernameSuggestion(newUser); // 109
if (username) { // 110
newUser.username = username; // 111
} // 112
} // 113
// 114
var userId = Accounts.insertUserDoc({}, newUser); // 115
user = Meteor.users.findOne(userId); // 116
} // 117
// 118
//creating the token and adding to the user // 119
var stampedToken = Accounts._generateStampedLoginToken(); // 120
Meteor.users.update(user, { // 121
$push: { // 122
'services.resume.loginTokens': stampedToken // 123
} // 124
}); // 125
// 126
var samlLogin = { // 127
provider: Accounts.saml.RelayState, // 128
idp: loginResult.profile.issuer, // 129
idpSession: loginResult.profile.sessionIndex, // 130
nameID: loginResult.profile.nameID // 131
}; // 132
// 133
Meteor.users.update({ // 134
_id: user._id // 135
}, { // 136
$set: { // 137
// TBD this should be pushed, otherwise we're only able to SSO into a single IDP at a time // 138
'services.saml': samlLogin // 139
} // 140
}); // 141
// 142
//sending token along with the userId // 143
var result = { // 144
userId: user._id, // 145
token: stampedToken.token // 146
}; // 147
// 148
return result; // 149
// 150
} else { // 151
throw new Error('SAML Profile did not contain an email address'); // 152
} // 153
}); // 154
// 155
Accounts.saml._loginResultForCredentialToken = {}; // 156
// 157
Accounts.saml.hasCredential = function (credentialToken) { // 158
return _.has(Accounts.saml._loginResultForCredentialToken, credentialToken); // 159
}; // 160
// 161
Accounts.saml.retrieveCredential = function (credentialToken) { // 162
// The credentialToken in all these functions corresponds to SAMLs inResponseTo field and is mandatory to check. // 163
var result = Accounts.saml._loginResultForCredentialToken[credentialToken]; // 164
delete Accounts.saml._loginResultForCredentialToken[credentialToken]; // 165
return result; // 166
}; // 167
// 168
var closePopup = function (res, err) { // 169
res.writeHead(200, { // 170
'Content-Type': 'text/html' // 171
}); // 172
var content = '
Verified
'; // 173
if (err) { // 174
content = 'Sorry, an annoying error occured
' + err + '
Close Window';
} // 176
res.end(content, 'utf-8'); // 177
}; // 178
// 179
var samlUrlToObject = function (url) { // 180
// req.url will be '/_saml///' // 181
if (!url) { // 182
return null; // 183
} // 184
// 185
var splitPath = url.split('/'); // 186
// 187
// Any non-saml request will continue down the default // 188
// middlewares. // 189
if (splitPath[1] !== '_saml') { // 190
return null; // 191
} // 192
// 193
var result = { // 194
actionName: splitPath[2], // 195
serviceName: splitPath[3], // 196
credentialToken: splitPath[4] // 197
}; // 198
if (Accounts.saml.settings.debug) { // 199
console.log(result); // 200
} // 201
return result; // 202
}; // 203
// 204
var middleware = function (req, res, next) { // 205
// Make sure to catch any exceptions because otherwise we'd crash // 206
// the runner // 207
try { // 208
var samlObject = samlUrlToObject(req.url); // 209
if (!samlObject || !samlObject.serviceName) { // 210
next(); // 211
return; // 212
} // 213
// 214
if (!samlObject.actionName) { // 215
throw new Error('Missing SAML action'); // 216
} // 217
// 218
console.log(Accounts.saml.settings.providers); // 219
console.log(samlObject.serviceName); // 220
var service = _.find(Accounts.saml.settings.providers, function (samlSetting) { // 221
return samlSetting.provider === samlObject.serviceName; // 222
}); // 223
// 224
// Skip everything if there's no service set by the saml middleware // 225
if (!service) { // 226
throw new Error('Unexpected SAML service ' + samlObject.serviceName); // 227
} // 228
var _saml; // 229
switch (samlObject.actionName) { // 230
case 'metadata': // 231
_saml = new SAML(service); // 232
service.callbackUrl = Meteor.absoluteUrl('_saml/validate/' + service.provider); // 233
res.writeHead(200); // 234
res.write(_saml.generateServiceProviderMetadata(service.callbackUrl)); // 235
res.end(); // 236
//closePopup(res); // 237
break; // 238
case 'logout': // 239
// This is where we receive SAML LogoutResponse // 240
_saml = new SAML(service); // 241
_saml.validateLogoutResponse(req.query.SAMLResponse, function (err, result) { // 242
if (!err) { // 243
var logOutUser = function (inResponseTo) { // 244
if (Accounts.saml.settings.debug) { // 245
console.log('Logging Out user via inResponseTo ' + inResponseTo); // 246
} // 247
var loggedOutUser = Meteor.users.find({ // 248
'services.saml.inResponseTo': inResponseTo // 249
}).fetch(); // 250
if (loggedOutUser.length === 1) { // 251
if (Accounts.saml.settings.debug) { // 252
console.log('Found user ' + loggedOutUser[0]._id); // 253
} // 254
Meteor.users.update({ // 255
_id: loggedOutUser[0]._id // 256
}, { // 257
$set: { // 258
'services.resume.loginTokens': [] // 259
} // 260
}); // 261
Meteor.users.update({ // 262
_id: loggedOutUser[0]._id // 263
}, { // 264
$unset: { // 265
'services.saml': '' // 266
} // 267
}); // 268
} else { // 269
throw new Meteor.Error('Found multiple users matching SAML inResponseTo fields'); // 270
} // 271
}; // 272
// 273
fiber(function () { // 274
logOutUser(result); // 275
}).run(); // 276
// 277
// 278
res.writeHead(302, { // 279
'Location': req.query.RelayState // 280
}); // 281
res.end(); // 282
} // 283
// else { // 284
// // TBD thinking of sth meaning full. // 285
// } // 286
}); // 287
break; // 288
case 'sloRedirect': // 289
var idpLogout = req.query.redirect; // 290
res.writeHead(302, { // 291
// credentialToken here is the SAML LogOut Request that we'll send back to IDP // 292
'Location': idpLogout // 293
}); // 294
res.end(); // 295
break; // 296
case 'authorize': // 297
service.callbackUrl = Meteor.absoluteUrl('_saml/validate/' + service.provider); // 298
service.id = samlObject.credentialToken; // 299
_saml = new SAML(service); // 300
_saml.getAuthorizeUrl(req, function (err, url) { // 301
if (err) { // 302
throw new Error('Unable to generate authorize url'); // 303
} // 304
res.writeHead(302, { // 305
'Location': url // 306
}); // 307
res.end(); // 308
}); // 309
break; // 310
case 'validate': // 311
_saml = new SAML(service); // 312
Accounts.saml.RelayState = req.body.RelayState; // 313
_saml.validateResponse(req.body.SAMLResponse, req.body.RelayState, function (err, profile/*, loggedOut*/) { // 314
if (err) { // 315
throw new Error('Unable to validate response url: ' + err); // 316
} // 317
// 318
var credentialToken = profile.inResponseToId || profile.InResponseTo || samlObject.credentialToken; // 319
if (!credentialToken) { // 320
throw new Error('Unable to determine credentialToken'); // 321
} // 322
Accounts.saml._loginResultForCredentialToken[credentialToken] = { // 323
profile: profile // 324
}; // 325
closePopup(res); // 326
}); // 327
break; // 328
default: // 329
throw new Error('Unexpected SAML action ' + samlObject.actionName); // 330
// 331
} // 332
} catch (err) { // 333
closePopup(res, err); // 334
} // 335
}; // 336
// 337
// Listen to incoming SAML http requests // 338
WebApp.connectHandlers.use(connect.bodyParser()).use(function (req, res, next) { // 339
// Need to create a fiber since we're using synchronous http calls and nothing // 340
// else is wrapping this in a fiber automatically // 341
fiber(function () { // 342
middleware(req, res, next); // 343
}).run(); // 344
}); // 345
// 346
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/steffo_meteor-accounts-saml/saml_utils.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
/* globals SAML:true */ // 1
// 2
var zlib = Npm.require('zlib'); // 3
var xml2js = Npm.require('xml2js'); // 4
var xmlCrypto = Npm.require('xml-crypto'); // 5
var crypto = Npm.require('crypto'); // 6
var xmldom = Npm.require('xmldom'); // 7
var querystring = Npm.require('querystring'); // 8
var xmlbuilder = Npm.require('xmlbuilder'); // 9
// var xmlenc = Npm.require('xml-encryption'); // 10
// var xpath = xmlCrypto.xpath; // 11
// var Dom = xmldom.DOMParser; // 12
// 13
// var prefixMatch = new RegExp(/(?!xmlns)^.*:/); // 14
// 15
// 16
SAML = function (options) { // 17
this.options = this.initialize(options); // 18
}; // 19
// 20
// var stripPrefix = function (str) { // 21
// return str.replace(prefixMatch, ''); // 22
// }; // 23
// 24
SAML.prototype.initialize = function (options) { // 25
if (!options) { // 26
options = {}; // 27
} // 28
// 29
if (!options.protocol) { // 30
options.protocol = 'https://'; // 31
} // 32
// 33
if (!options.path) { // 34
options.path = '/saml/consume'; // 35
} // 36
// 37
if (!options.issuer) { // 38
options.issuer = 'onelogin_saml'; // 39
} // 40
// 41
if (options.identifierFormat === undefined) { // 42
options.identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'; // 43
} // 44
// 45
if (options.authnContext === undefined) { // 46
options.authnContext = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; // 47
} // 48
// 49
return options; // 50
}; // 51
// 52
SAML.prototype.generateUniqueID = function () { // 53
var chars = 'abcdef0123456789'; // 54
var uniqueID = ''; // 55
for (var i = 0; i < 20; i++) { // 56
uniqueID += chars.substr(Math.floor((Math.random() * 15)), 1); // 57
} // 58
return uniqueID; // 59
}; // 60
// 61
SAML.prototype.generateInstant = function () { // 62
return new Date().toISOString(); // 63
}; // 64
// 65
SAML.prototype.signRequest = function (xml) { // 66
var signer = crypto.createSign('RSA-SHA1'); // 67
signer.update(xml); // 68
return signer.sign(this.options.privateKey, 'base64'); // 69
}; // 70
// 71
SAML.prototype.generateAuthorizeRequest = function (req) { // 72
var id = '_' + this.generateUniqueID(); // 73
var instant = this.generateInstant(); // 74
// 75
// Post-auth destination // 76
var callbackUrl; // 77
if (this.options.callbackUrl) { // 78
callbackUrl = this.options.callbackUrl; // 79
} else { // 80
callbackUrl = this.options.protocol + req.headers.host + this.options.path; // 81
} // 82
// 83
if (this.options.id) { // 84
id = this.options.id; // 85
} // 86
// 87
var request = // 88
'' + // 91
'' + this.options.issuer + '\n'; // 92
// 93
if (this.options.identifierFormat) { // 94
request += '\n'; // 96
} // 97
// 98
request += // 99
'' + // 100
'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport\n' +
''; // 102
// 103
return request; // 104
}; // 105
// 106
SAML.prototype.generateLogoutRequest = function (options) { // 107
// options should be of the form // 108
// nameId: // 109
// sessionIndex: sessionIndex // 110
// --- NO SAMLsettings: ' + // 118
'' + this.options.issuer + '' + // 119
'' + options.nameID + '' + // 120
''; // 121
// 122
request = '' + // 128
'' + this.options.issuer + '' + // 129
'' + // 133
options.nameID + '' + // 134
'' + options.sessionIndex + '' +
''; // 136
if (Meteor.settings.debug) { // 137
console.log('------- SAML Logout request -----------'); // 138
console.log(request); // 139
} // 140
return { // 141
request: request, // 142
id: id // 143
}; // 144
}; // 145
// 146
SAML.prototype.requestToUrl = function (request, operation, callback) { // 147
var self = this; // 148
zlib.deflateRaw(request, function (err, buffer) { // 149
if (err) { // 150
return callback(err); // 151
} // 152
// 153
var base64 = buffer.toString('base64'); // 154
var target = self.options.entryPoint; // 155
// 156
if (operation === 'logout') { // 157
if (self.options.idpSLORedirectURL) { // 158
target = self.options.idpSLORedirectURL; // 159
} // 160
} // 161
// 162
if (target.indexOf('?') > 0) { // 163
target += '&'; // 164
} else { // 165
target += '?'; // 166
} // 167
// 168
var samlRequest = { // 169
SAMLRequest: base64 // 170
}; // 171
// 172
if (self.options.privateCert) { // 173
samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; // 174
samlRequest.Signature = self.signRequest(querystring.stringify(samlRequest)); // 175
} // 176
// 177
// TBD. We should really include a proper RelayState here // 178
var relayState; // 179
if (operation === 'logout') { // 180
// in case of logout we want to be redirected back to the Meteor app. // 181
relayState = Meteor.absoluteUrl(); // 182
} else { // 183
relayState = self.options.provider; // 184
} // 185
target += querystring.stringify(samlRequest) + '&RelayState=' + relayState; // 186
// 187
if (Meteor.settings.debug) { // 188
console.log('requestToUrl: ' + target); // 189
} // 190
if (operation === 'logout') { // 191
// in case of logout we want to be redirected back to the Meteor app. // 192
return callback(null, target); // 193
// 194
} else { // 195
callback(null, target); // 196
} // 197
}); // 198
}; // 199
// 200
SAML.prototype.getAuthorizeUrl = function (req, callback) { // 201
var request = this.generateAuthorizeRequest(req); // 202
// 203
this.requestToUrl(request, 'authorize', callback); // 204
}; // 205
// 206
SAML.prototype.getLogoutUrl = function (req, callback) { // 207
var request = this.generateLogoutRequest(req); // 208
// 209
this.requestToUrl(request, 'logout', callback); // 210
}; // 211
// 212
SAML.prototype.certToPEM = function (cert) { // 213
cert = cert.match(/.{1,64}/g).join('\n'); // 214
cert = '-----BEGIN CERTIFICATE-----\n' + cert; // 215
cert = cert + '\n-----END CERTIFICATE-----\n'; // 216
return cert; // 217
}; // 218
// 219
// function findChilds(node, localName, namespace) { // 220
// var res = []; // 221
// for (var i = 0; i < node.childNodes.length; i++) { // 222
// var child = node.childNodes[i]; // 223
// if (child.localName === localName && (child.namespaceURI === namespace || !namespace)) { // 224
// res.push(child); // 225
// } // 226
// } // 227
// return res; // 228
// } // 229
// 230
SAML.prototype.validateSignature = function (xml, cert) { // 231
var self = this; // 232
// 233
var doc = new xmldom.DOMParser().parseFromString(xml); // 234
var signature = xmlCrypto.xpath(doc, '//*[local-name(.)=\'Signature\' and namespace-uri(.)=\'http://www.w3.org/2000/09/xmldsig#\']')[0];
// 236
var sig = new xmlCrypto.SignedXml(); // 237
// 238
sig.keyInfoProvider = { // 239
getKeyInfo: function (/*key*/) { // 240
return ''; // 241
}, // 242
getKey: function (/*keyInfo*/) { // 243
return self.certToPEM(cert); // 244
} // 245
}; // 246
// 247
sig.loadSignature(signature); // 248
// 249
return sig.checkSignature(xml); // 250
}; // 251
// 252
SAML.prototype.getElement = function (parentElement, elementName) { // 253
if (parentElement['saml:' + elementName]) { // 254
return parentElement['saml:' + elementName]; // 255
} else if (parentElement['samlp:' + elementName]) { // 256
return parentElement['samlp:' + elementName]; // 257
} else if (parentElement['saml2p:' + elementName]) { // 258
return parentElement['saml2p:' + elementName]; // 259
} else if (parentElement['saml2:' + elementName]) { // 260
return parentElement['saml2:' + elementName]; // 261
} // 262
return parentElement[elementName]; // 263
}; // 264
// 265
SAML.prototype.validateLogoutResponse = function (samlResponse, callback) { // 266
var self = this; // 267
// 268
var compressedSAMLResponse = new Buffer(samlResponse, 'base64'); // 269
zlib.inflateRaw(compressedSAMLResponse, function (err, decoded) { // 270
// 271
if (err) { // 272
if (Meteor.settings.debug) { // 273
console.log(err); // 274
} // 275
} else { // 276
var parser = new xml2js.Parser({ // 277
explicitRoot: true // 278
}); // 279
parser.parseString(decoded, function (err, doc) { // 280
var response = self.getElement(doc, 'LogoutResponse'); // 281
// 282
if (response) { // 283
// TBD. Check if this msg corresponds to one we sent // 284
var inResponseTo = response.$.InResponseTo; // 285
if (Meteor.settings.debug) { // 286
console.log('In Response to: ' + inResponseTo); // 287
} // 288
var status = self.getElement(response, 'Status'); // 289
var statusCode = self.getElement(status[0], 'StatusCode')[0].$.Value; // 290
if (Meteor.settings.debug) { // 291
console.log('StatusCode: ' + JSON.stringify(statusCode)); // 292
} // 293
if (statusCode === 'urn:oasis:names:tc:SAML:2.0:status:Success') { // 294
// In case of a successful logout at IDP we return inResponseTo value. // 295
// This is the only way how we can identify the Meteor user (as we don't use Session Cookies) // 296
callback(null, inResponseTo); // 297
} else { // 298
callback('Error. Logout not confirmed by IDP', null); // 299
} // 300
} else { // 301
callback('No Response Found', null); // 302
} // 303
}); // 304
} // 305
// 306
}); // 307
}; // 308
// 309
SAML.prototype.validateResponse = function (samlResponse, relayState, callback) { // 310
var self = this; // 311
var xml = new Buffer(samlResponse, 'base64').toString('ascii'); // 312
// We currently use RelayState to save SAML provider // 313
if (Meteor.settings.debug) { // 314
console.log('Validating response with relay state: ' + xml); // 315
} // 316
var parser = new xml2js.Parser({ // 317
explicitRoot: true // 318
}); // 319
// 320
parser.parseString(xml, function (err, doc) { // 321
// Verify signature // 322
if (Meteor.settings.debug) { // 323
console.log('Verify signature'); // 324
} // 325
if (self.options.cert && !self.validateSignature(xml, self.options.cert)) { // 326
if (Meteor.settings.debug) { // 327
console.log('Signature WRONG'); // 328
} // 329
return callback(new Error('Invalid signature'), null, false); // 330
} // 331
if (Meteor.settings.debug) { // 332
console.log('Signature OK'); // 333
} // 334
var response = self.getElement(doc, 'Response'); // 335
if (Meteor.settings.debug) { // 336
console.log('Got response'); // 337
} // 338
if (response) { // 339
var assertion = self.getElement(response, 'Assertion'); // 340
if (!assertion) { // 341
return callback(new Error('Missing SAML assertion'), null, false); // 342
} // 343
// 344
var profile = {}; // 345
// 346
if (response.$ && response.$.InResponseTo) { // 347
profile.inResponseToId = response.$.InResponseTo; // 348
} // 349
// 350
var issuer = self.getElement(assertion[0], 'Issuer'); // 351
if (issuer) { // 352
profile.issuer = issuer[0]; // 353
} // 354
// 355
var subject = self.getElement(assertion[0], 'Subject'); // 356
// 357
if (subject) { // 358
var nameID = self.getElement(subject[0], 'NameID'); // 359
if (nameID) { // 360
profile.nameID = nameID[0]._; // 361
// 362
if (nameID[0].$.Format) { // 363
profile.nameIDFormat = nameID[0].$.Format; // 364
} // 365
} // 366
} // 367
// 368
var authnStatement = self.getElement(assertion[0], 'AuthnStatement'); // 369
// 370
if (authnStatement) { // 371
if (authnStatement[0].$.SessionIndex) { // 372
// 373
profile.sessionIndex = authnStatement[0].$.SessionIndex; // 374
if (Meteor.settings.debug) { // 375
console.log('Session Index: ' + profile.sessionIndex); // 376
} // 377
} else { // 378
if (Meteor.settings.debug) { // 379
console.log('No Session Index Found'); // 380
} // 381
} // 382
// 383
// 384
} else { // 385
if (Meteor.settings.debug) { // 386
console.log('No AuthN Statement found'); // 387
} // 388
} // 389
// 390
var attributeStatement = self.getElement(assertion[0], 'AttributeStatement'); // 391
if (attributeStatement) { // 392
var attributes = self.getElement(attributeStatement[0], 'Attribute'); // 393
// 394
if (attributes) { // 395
attributes.forEach(function (attribute) { // 396
var value = self.getElement(attribute, 'AttributeValue'); // 397
if (typeof value[0] === 'string') { // 398
profile[attribute.$.Name] = value[0]; // 399
} else { // 400
profile[attribute.$.Name] = value[0]._; // 401
} // 402
}); // 403
} // 404
// 405
if (!profile.mail && profile['urn:oid:0.9.2342.19200300.100.1.3']) { // 406
// See http://www.incommonfederation.org/attributesummary.html for definition of attribute OIDs // 407
profile.mail = profile['urn:oid:0.9.2342.19200300.100.1.3']; // 408
} // 409
// 410
if (!profile.email && profile.mail) { // 411
profile.email = profile.mail; // 412
} // 413
} // 414
// 415
if (!profile.email && profile.nameID && profile.nameIDFormat && profile.nameIDFormat.indexOf('emailAddress') >= 0) {
profile.email = profile.nameID; // 417
} // 418
if (Meteor.settings.debug) { // 419
console.log('NameID: ' + JSON.stringify(profile)); // 420
} // 421
// 422
callback(null, profile, false); // 423
} else { // 424
var logoutResponse = self.getElement(doc, 'LogoutResponse'); // 425
// 426
if (logoutResponse) { // 427
callback(null, null, true); // 428
} else { // 429
return callback(new Error('Unknown SAML response message'), null, false); // 430
} // 431
// 432
} // 433
}); // 434
}; // 435
// 436
var decryptionCert; // 437
SAML.prototype.generateServiceProviderMetadata = function (callbackUrl) { // 438
// 439
var keyDescriptor = null; // 440
// 441
if (!decryptionCert) { // 442
decryptionCert = this.options.privateCert; // 443
} // 444
// 445
if (this.options.privateKey) { // 446
if (!decryptionCert) { // 447
throw new Error( // 448
'Missing decryptionCert while generating metadata for decrypting service provider'); // 449
} // 450
// 451
decryptionCert = decryptionCert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ''); // 452
decryptionCert = decryptionCert.replace(/-+END CERTIFICATE-+\r?\n?/, ''); // 453
decryptionCert = decryptionCert.replace(/\r\n/g, '\n'); // 454
// 455
keyDescriptor = { // 456
'ds:KeyInfo': { // 457
'ds:X509Data': { // 458
'ds:X509Certificate': { // 459
'#text': decryptionCert // 460
} // 461
} // 462
}, // 463
'#list': [ // 464
// this should be the set that the xmlenc library supports // 465
{ // 466
'EncryptionMethod': { // 467
'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' // 468
} // 469
}, // 470
{ // 471
'EncryptionMethod': { // 472
'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' // 473
} // 474
}, // 475
{ // 476
'EncryptionMethod': { // 477
'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' // 478
} // 479
}, // 480
] // 481
}; // 482
} // 483
// 484
if (!this.options.callbackUrl && !callbackUrl) { // 485
throw new Error( // 486
'Unable to generate service provider metadata when callbackUrl option is not set'); // 487
} // 488
// 489
var metadata = { // 490
'EntityDescriptor': { // 491
'@xmlns': 'urn:oasis:names:tc:SAML:2.0:metadata', // 492
'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#', // 493
'@entityID': this.options.issuer, // 494
'SPSSODescriptor': { // 495
'@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol', // 496
'KeyDescriptor': keyDescriptor, // 497
'SingleLogoutService': { // 498
'@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', // 499
'@Location': Meteor.absoluteUrl() + '_saml/logout/' + this.options.provider + '/', // 500
'@ResponseLocation': Meteor.absoluteUrl() + '_saml/logout/' + this.options.provider + '/' // 501
}, // 502
'NameIDFormat': this.options.identifierFormat, // 503
'AssertionConsumerService': { // 504
'@index': '1', // 505
'@isDefault': 'true', // 506
'@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', // 507
'@Location': callbackUrl // 508
} // 509
} // 510
} // 511
}; // 512
// 513
return xmlbuilder.create(metadata).end({ // 514
pretty: true, // 515
indent: ' ', // 516
newline: '\n' // 517
}); // 518
}; // 519
// 520
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function(){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/steffo_meteor-accounts-saml/saml_rocketchat.coffee.js //
// //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
__coffeescriptShare = typeof __coffeescriptShare === 'object' ? __coffeescriptShare : {}; var share = __coffeescriptShare;
var logger, timer, updateServices; // 1
//
logger = new Logger('steffo:meteor-accounts-saml', { // 1
methods: { // 2
updated: { // 3
type: 'info' // 4
} //
} //
}); //
//
RocketChat.settings.addGroup('SAML'); // 1
//
Meteor.methods({ // 1
addSamlService: function(name) { // 8
RocketChat.settings.add("SAML_Custom_" + name, false, { // 9
type: 'boolean', // 9
group: 'SAML', // 9
section: name, // 9
i18nLabel: 'Accounts_OAuth_Custom_Enable' // 9
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_provider", 'openidp', { // 9
type: 'string', // 10
group: 'SAML', // 10
section: name, // 10
i18nLabel: 'SAML_Custom_Provider' // 10
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_entry_point", 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', {
type: 'string', // 11
group: 'SAML', // 11
section: name, // 11
i18nLabel: 'SAML_Custom_Entry_point' // 11
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_issuer", 'https://rocket.chat/', { // 9
type: 'string', // 12
group: 'SAML', // 12
section: name, // 12
i18nLabel: 'SAML_Custom_Issuer' // 12
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_cert", '', { // 9
type: 'string', // 13
group: 'SAML', // 13
section: name, // 13
i18nLabel: 'SAML_Custom_Cert' // 13
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_button_label_text", '', { // 9
type: 'string', // 14
group: 'SAML', // 14
section: name, // 14
i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text' // 14
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_button_label_color", '#FFFFFF', { // 9
type: 'string', // 15
group: 'SAML', // 15
section: name, // 15
i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color' // 15
}); //
RocketChat.settings.add("SAML_Custom_" + name + "_button_color", '#13679A', { // 9
type: 'string', // 16
group: 'SAML', // 16
section: name, // 16
i18nLabel: 'Accounts_OAuth_Custom_Button_Color' // 16
}); //
return RocketChat.settings.add("SAML_Custom_" + name + "_generate_username", false, { //
type: 'boolean', // 17
group: 'SAML', // 17
section: name, // 17
i18nLabel: 'SAML_Custom_Generate_Username' // 17
}); //
} //
}); //
//
timer = void 0; // 1
//
updateServices = function() { // 1
if (timer != null) { // 21
Meteor.clearTimeout(timer); // 21
} //
return timer = Meteor.setTimeout(function() { //
var data, i, len, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, results, service, serviceName, services; // 24
services = RocketChat.models.Settings.find({ // 24
_id: /^(SAML_Custom_)[a-z]+$/i // 24
}).fetch(); //
Accounts.saml.settings.providers = []; // 24
results = []; // 28
for (i = 0, len = services.length; i < len; i++) { //
service = services[i]; //
logger.updated(service._id); // 29
serviceName = 'saml'; // 29
if (service.value === true) { // 33
data = { // 34
buttonLabelText: (ref = RocketChat.models.Settings.findOneById(service._id + "_button_label_text")) != null ? ref.value : void 0,
buttonLabelColor: (ref1 = RocketChat.models.Settings.findOneById(service._id + "_button_label_color")) != null ? ref1.value : void 0,
buttonColor: (ref2 = RocketChat.models.Settings.findOneById(service._id + "_button_color")) != null ? ref2.value : void 0,
clientConfig: { // 35
provider: (ref3 = RocketChat.models.Settings.findOneById(service._id + "_provider")) != null ? ref3.value : void 0
} //
}; //
Accounts.saml.settings.generateUsername = (ref4 = RocketChat.models.Settings.findOneById(service._id + "_generate_username")) != null ? ref4.value : void 0;
Accounts.saml.settings.providers.push({ // 34
provider: data.clientConfig.provider, // 44
entryPoint: (ref5 = RocketChat.models.Settings.findOneById(service._id + "_entry_point")) != null ? ref5.value : void 0,
issuer: (ref6 = RocketChat.models.Settings.findOneById(service._id + "_issuer")) != null ? ref6.value : void 0,
cert: (ref7 = RocketChat.models.Settings.findOneById(service._id + "_cert")) != null ? ref7.value : void 0
}); //
results.push(ServiceConfiguration.configurations.upsert({ // 34
service: serviceName.toLowerCase() // 49
}, { //
$set: data // 49
})); //
} else { //
results.push(ServiceConfiguration.configurations.remove({ //
service: serviceName.toLowerCase() // 51
})); //
} //
} // 28
return results; //
}, 2000); //
}; // 20
//
RocketChat.models.Settings.find().observe({ // 1
added: function(record) { // 55
if (/^SAML_.+/.test(record._id)) { // 56
return updateServices(); //
} //
}, //
changed: function(record) { // 55
if (/^SAML_.+/.test(record._id)) { // 60
return updateServices(); //
} //
}, //
removed: function(record) { // 55
if (/^SAML_.+/.test(record._id)) { // 64
return updateServices(); //
} //
} //
}); //
//
Meteor.startup(function() { // 1
if (RocketChat.models.Settings.findOne({ // 68
_id: /^(SAML_Custom)[a-z]+$/i //
}) == null) { //
return Meteor.call('addSamlService', 'Default'); //
} //
}); // 67
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package['steffo:meteor-accounts-saml'] = {};
})();
//# sourceMappingURL=steffo_meteor-accounts-saml.js.map