UNPKG

23.4 kBJavaScriptView Raw
1var path = require('path');
2var fs = require('fs');
3var os = require('os');
4var _util = require("util");
5var util = require("./util");
6var request = require("request");
7var http = require("http");
8var https = require("https");
9var async = require("async");
10
11// trusted profile cache size 100
12var TRUSTED_PROFILE_CACHE = require("lru-cache")({
13 max:100,
14 maxAge: 1000 * 60 * 15 // 15 minutes
15});
16
17// user entry cache size 100
18var USER_ENTRY_CACHE = require("lru-cache")({
19 max: 100,
20 maxAge: 1000 * 60 * 15 // 15 minutes
21});
22
23var authFilterLoggerEnabled = (process.env.CLOUDCMS_AUTH_FILTER_LOGGER_ENABLED === "true");
24
25exports = module.exports;
26
27// additional methods for Gitana driver
28var Gitana = require("gitana");
29
30var buildPassportCallback = exports.buildPassportCallback = function(providerConfig, provider)
31{
32 return function(req, token, refreshToken, profile, done)
33 {
34 var info = {};
35
36 info.providerId = providerConfig.id;
37 info.providerUserId = provider.userIdentifier(profile);
38 info.token = token;
39 info.refreshToken = refreshToken;
40
41 if (!info.providerUserId)
42 {
43 return done({
44 "message": "Unable to determine provider user ID from profile"
45 });
46 }
47
48 done(null, profile, info);
49 };
50};
51
52var syncAttachment = exports.syncAttachment = function(gitanaUser, attachmentId, url, callback)
53{
54 var baseURL = gitanaUser.getDriver().getOriginalConfiguration().baseURL;
55 var authorizationHeader = gitanaUser.getDriver().getHttpHeaders()["Authorization"];
56
57 var targetUrl = baseURL + gitanaUser.getUri() + "/attachments/" + attachmentId;
58
59 // add "authorization" for OAuth2 bearer token
60 var headers = {};
61 headers["Authorization"] = authorizationHeader;
62
63 request.get(url).pipe(request.post({
64 url: targetUrl,
65 headers: headers
66 })).on("response", function(response) {
67 callback();
68 });
69};
70
71var _LOCK = function(lockIdentifiers, workFunction)
72{
73 process.locks.lock(lockIdentifiers.join("_"), workFunction);
74};
75
76var logEvent = function(event, success, protocol, source, userId, ip, matchedGroup, userGroups, mandatoryGroups, accessGranted)
77{
78 if (!authFilterLoggerEnabled)
79 {
80 return;
81 }
82
83 console.log(_util.format('%s|%s|%o|%s|%s|%s|%s|%s|%s|%s|%s|%s',
84 event||"Authorization",
85 event||"Authorization",
86 new Date(),
87 protocol||"https",
88 success ? "Success" : "Failed",
89 userId || "NA",
90 ip || "NA",
91 matchedGroup || "",
92 (userGroups || ["NA"]).join(','),
93 (mandatoryGroups || ["NA"]).join(','),
94 accessGranted || "NA",
95 os.hostname()
96 ));
97};
98
99
100
101var impersonate = exports.impersonate = function(req, key, targetUser, callback)
102{
103 // 1. grant "impersonator" role against targetUser for appuser
104 // 2. impersonate, get the info
105 // 3. revoke "impersonator" role against targetUser
106
107 var authInfo = req.gitana.getDriver().getAuthInfo();
108 var currentUserId = authInfo.principalDomainId + "/" + authInfo.principalId;
109
110 var grantImpersonator = function(done)
111 {
112 Chain(targetUser).trap(function(e) {
113 done(e);
114 return false;
115 }).grantAuthority(currentUserId, "impersonator").then(function () {
116 done();
117 });
118 };
119
120 var revokeImpersonator = function(done)
121 {
122 Chain(targetUser).trap(function(e) {
123 done(e);
124 return false;
125 }).revokeAuthority(currentUserId, "impersonator").then(function () {
126 done();
127 });
128 };
129
130 var connectImpersonator = function(done)
131 {
132 var headers = {};
133 headers["Authorization"] = req.gitana.platform().getDriver().getHttpHeaders()["Authorization"];
134
135 var agent = util.getAgent(req.gitanaConfig.baseURL);
136
137 request({
138 "method": "POST",
139 "url": req.gitanaConfig.baseURL + "/auth/impersonate/" + targetUser.getDomainId() + "/" + targetUser.getId(),
140 "qs": {},
141 "json": {},
142 "headers": headers,
143 "agent": agent,
144 "timeout": process.defaultHttpTimeoutMs
145 }, function(err, response, json) {
146
147 // connect as the impersonated user
148 var x = {
149 "clientKey": req.gitanaConfig.clientKey,
150 "clientSecret": req.gitanaConfig.clientSecret,
151 "ticket": json.ticket,
152 "baseURL": req.gitanaConfig.baseURL,
153 "key": key
154 };
155 if (req.gitanaConfig.application) {
156 x.application = req.gitanaConfig.application;
157 x.appCacheKey = key;
158 }
159 Gitana.connect(x, function (err) {
160
161 if (err)
162 {
163 console.log("Failed to connect to Cloud CMS: " + JSON.stringify(err));
164 return done(err);
165 }
166
167 var platform = this;
168 var appHelper = null;
169 if (x.application) {
170 appHelper = this;
171 platform = this.platform();
172 }
173
174 done(null, platform, appHelper, key);
175 });
176 });
177 };
178
179 grantImpersonator(function(err) {
180
181 if (err) {
182 return revokeImpersonator(function() {
183 callback(err);
184 });
185 }
186
187 connectImpersonator(function(err, platform, appHelper, key) {
188
189 if (err) {
190 return revokeImpersonator(function() {
191 callback(err);
192 });
193 }
194
195 revokeImpersonator(function(err) {
196
197 if (err) {
198 return callback(err);
199 }
200
201 callback(null, platform, appHelper, key);
202 });
203 });
204 });
205};
206
207var readTrustedProfile = exports.readTrustedProfile = function(identifier)
208{
209 return TRUSTED_PROFILE_CACHE.get(identifier);
210};
211
212var writeTrustedProfile = exports.writeTrustedProfile = function(identifier, profile)
213{
214 TRUSTED_PROFILE_CACHE.set(identifier, profile);
215};
216
217var removeTrustedProfile = exports.removeTrustedProfile = function(identifier)
218{
219 TRUSTED_PROFILE_CACHE.del(identifier);
220};
221
222var readUserCacheEntry = exports.readUserCacheEntry = function(identifier)
223{
224 return USER_ENTRY_CACHE.get(identifier);
225};
226
227var writeUserCacheEntry = exports.writeUserCacheEntry = function(identifier, entry)
228{
229 USER_ENTRY_CACHE.set(identifier, entry);
230};
231
232var removeUserCacheEntry = exports.removeUserCacheEntry = function(identifier)
233{
234 USER_ENTRY_CACHE.del(identifier);
235};
236
237
238var syncProfile = exports.syncProfile = function(req, res, strategy, domain, providerId, provider, profile, token, refreshToken, callback) {
239
240
241 return provider.parseProfile(req, profile, function(err, userObject, groupsArray, mandatoryGroupsArray) {
242
243 // special handling for mandatory groups
244 if (mandatoryGroupsArray && mandatoryGroupsArray.length > 0)
245 {
246 // clean up white space
247 for (var i = 0; i < mandatoryGroupsArray.length; i++)
248 {
249 mandatoryGroupsArray[i] = mandatoryGroupsArray[i].trim();
250 }
251
252 // make sure our groups Array contains at least one mandatory group
253 var hasMandatoryGroup = false;
254 var mandatoryGroupsMap = {};
255 var matchedGroup = null;
256 for (var i = 0; i < mandatoryGroupsArray.length; i++) {
257 mandatoryGroupsMap[mandatoryGroupsArray[i]] = true;
258 }
259 for (var i = 0; i < groupsArray.length; i++) {
260 if (mandatoryGroupsMap[groupsArray[i]]) {
261 matchedGroup = groupsArray[i];
262 hasMandatoryGroup = true;
263 }
264 }
265 if (!hasMandatoryGroup)
266 {
267 logEvent("Authorization", false, req.protocol, providerId, profile.nameID, req.ip, null, groupsArray, mandatoryGroupsArray, null);
268 return callback({
269 "message": "The incoming user does not belong to one of the mandatory groups",
270 "noMandatoryGroup": true
271 });
272 }
273 else{
274 if (domain && domain._doc) {
275 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, matchedGroup, groupsArray, mandatoryGroupsArray, "AddToDomain:" + domain._doc);
276 } else {
277 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, matchedGroup, groupsArray, mandatoryGroupsArray, "AddToDomain");
278 }
279 }
280 }
281 else
282 {
283 if (domain && domain._doc) {
284 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, null, groupsArray, null, "AddToDomain:" + domain._doc);
285 } else {
286 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, null, groupsArray, null, "AddToDomain");
287 }
288 }
289
290 req.application(function(err, application) {
291
292 // load settings off request
293 req.applicationSettings(function(err, settings) {
294
295 if (err || !settings) {
296 settings = {};
297 }
298
299 var providerUserId = provider.userIdentifier(profile);
300 if (!providerUserId)
301 {
302 return callback({
303 "message": "Unable to determine provider user ID from profile"
304 });
305 }
306
307 var key = token;
308
309 // load sync'd users from cache
310 var CACHE_IDENTIFIER = providerId + "/" + providerUserId;
311
312 var entry = readUserCacheEntry(CACHE_IDENTIFIER);
313 if (entry)
314 {
315 var gitanaUser = entry.gitanaUser;
316 var platform = entry.platform;
317 var appHelper = entry.appHelper;
318 var key = entry.key;
319
320 if (gitanaUser && platform && key)
321 {
322 // successful cache hit
323 return callback(null, gitanaUser, platform, appHelper, key, platform.getDriver());
324 }
325 else
326 {
327 // clean up cache
328 removeUserCacheEntry(CACHE_IDENTIFIER);
329 }
330 }
331
332 _LOCK([CACHE_IDENTIFIER], function(releaseLockFn) {
333 _handleSyncUser(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, groupsArray, function (err, gitanaUser) {
334
335 if (err) {
336 releaseLockFn();
337 return callback(err);
338 }
339
340 // no user found
341 if (!gitanaUser) {
342 releaseLockFn();
343 return callback();
344 }
345
346 _handleConnectAsUser(req, key, gitanaUser, function (err, platform, appHelper, key) {
347
348 if (err) {
349 releaseLockFn();
350 return callback(err);
351 }
352
353 // write to cache
354 writeUserCacheEntry(CACHE_IDENTIFIER, {
355 "gitanaUser": gitanaUser,
356 "platform": platform,
357 "appHelper": appHelper,
358 "key": key
359 });
360
361 releaseLockFn();
362
363 callback(err, gitanaUser, platform, appHelper, key, platform.getDriver());
364 }, gitanaUser);
365 });
366 });
367 });
368 });
369 });
370};
371
372var _handleConnectAsUser = function(req, key, gitanaUser, callback) {
373
374 var appHelper = Gitana.APPS[key];
375 if (appHelper)
376 {
377 // console.log("CONNECT USER LOADED FROM CACHE, APPS");
378 return callback(null, appHelper.platform(), appHelper, key);
379 }
380
381 var platform = Gitana.PLATFORM_CACHE(key);
382 if (platform)
383 {
384 // console.log("CONNECT USER LOADED FROM CACHE, PLATFORM");
385 return callback(null, platform, null, key);
386 }
387
388 impersonate(req, key, gitanaUser, function(err, platform, appHelper, key) {
389 callback(err, platform, appHelper, key);
390 });
391};
392
393var _handleSyncUser = function(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, groupsArray, callback) {
394
395 __handleSyncUser(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, function(err, gitanaUser, synced) {
396
397 if (err) {
398 return callback(err);
399 }
400
401 if (!gitanaUser)
402 {
403 if (!strategy.autoRegister)
404 {
405 console.log("Sync user did not find a user for providerUserId: " + providerUserId + " but autoRegister is turned off, cannot auto-create the user");
406
407 return callback({
408 "message": "User not found (autoRegister is disabled, cannot auto-create)",
409 "noAutoRegister": true
410 });
411 }
412
413 console.log("Sync user did not produce a user object");
414
415 return callback({
416 "message": "User not found"
417 });
418 }
419
420 if (!synced)
421 {
422 if (!groupsArray || groupsArray.length == 0)
423 {
424 return callback(null, gitanaUser);
425 }
426 }
427
428 // sync groups
429 __handleSyncGroups(req, strategy, settings, gitanaUser, groupsArray, function(err, gitanaUser) {
430
431 return callback(null, gitanaUser);
432
433 });
434 });
435
436};
437
438var __handleSyncUser = function(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, callback) {
439
440 var baseURL = req.gitanaConfig.baseURL;
441 var authorizationHeader = req.gitana.getDriver().getHttpHeaders()["Authorization"];
442 var targetUrl = baseURL + domain.getUri() + "/connections/sync";
443
444 // add "authorization" for OAuth2 bearer token
445 var headers = {};
446 headers["Authorization"] = authorizationHeader;
447
448 var agent = util.getAgent(req.gitanaConfig.baseURL);
449
450 if (!userObject) {
451 userObject = {};
452 }
453
454 var connectionObject = {};
455 connectionObject.accessToken = token;
456 connectionObject.refreshToken = refreshToken;
457
458 var json = {
459 "user": userObject,
460 "connection": connectionObject
461 };
462
463 var autoCreate = strategy.autoRegister ? true : false;
464
465 var requestConfig = {
466 "method": "POST",
467 "url": targetUrl,
468 "qs": {
469 "providerId": providerId,
470 "providerUserId": providerUserId,
471 "autoCreate": autoCreate
472 },
473 "json": json,
474 "headers": headers,
475 "agent": agent,
476 "timeout": process.defaultHttpTimeoutMs
477 };
478
479 request(requestConfig, function(err, response, json) {
480
481 if (err) {
482 return callback(err);
483 }
484
485 if (json.error === "invalid_token")
486 {
487 // retry after getting new token
488 return req.gitana.getDriver().reloadAuthInfo(function () {
489 __handleSyncUser(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, function(err, gitanaUser, synced) {
490 callback(err, gitanaUser, synced);
491 })
492 });
493 }
494 else if (json.error)
495 {
496 if (json.message)
497 {
498 return callback({
499 "message": "An error occurred during user sync: " + json.message
500 });
501 }
502
503 return callback({
504 "message": "An error occurred during user sync: " + JSON.stringify(json)
505 });
506 }
507
508 if (!json.user) {
509 console.log("Did not see json.user, JSON is: " + JSON.stringify(json, null, 2));
510 return callback({
511 "message": "An error occurred during user sync - the response did not contain an error but also did not provide json.user"
512 });
513 }
514
515 var userId = json.user._doc;
516 var domainId = json.user.domainId;
517 var synced = json.user.synced;
518
519 // read the user back
520 Chain(domain).readPrincipal(userId).then(function() {
521 callback(null, this, synced);
522 });
523
524 });
525};
526
527var executeRule = function(req, rule, gitanaUser, callback)
528{
529 //
530 // addToProject(projectId)
531 // addToProject(projectId, [teamKey]);
532 //
533 // removeFromProject(projectId);
534 //
535 // addToPlatformTeam([teamKey])
536 // removeFromPlatformTeam([teamKey])
537
538 var ensureArray = function(teamIdentifiers) {
539 var array = [];
540 if (!teamIdentifiers) {
541 return array;
542 }
543
544 if (typeof(teamIdentifiers) === "string") {
545 array.push(teamIdentifiers);
546 }
547
548 for (var i = 0; i < teamIdentifiers.length; i++) {
549 array.push(teamIdentifiers[i]);
550 }
551
552 return array;
553 };
554
555 var addToProject = function(projectId, teamIdentifiers, finished) {
556
557 if (!teamIdentifiers) {
558 teamIdentifiers = "project-users-team";
559 }
560
561 teamIdentifiers = ensureArray(teamIdentifiers);
562
563 var project = null;
564 var stack = null;
565
566 return req.gitana.platform().trap(function(e) {
567 return false;
568 }).readProject(projectId).then(function(){
569 project = this;
570 }).readStack().then(function() {
571 stack = this;
572
573 var fns = [];
574 for (var i = 0; i < teamIdentifiers.length; i++)
575 {
576 var fn = function(stack, teamIdentifier, user) {
577 return function(d) {
578
579 console.log("Working on stack: " + stack._doc + ", team: " + teamIdentifier + ", user: " + user._doc);
580
581 Chain(stack).trap(function(e) {
582 d();
583 return false;
584 }).readTeam(teamIdentifier).then(function() {
585 var team = this;
586
587 Chain(team).hasMember(user, function(has) {
588 if (has) {
589 return d();
590 }
591 Chain(team).addMember(user).then(function() {
592 d();
593 });
594 });
595 });
596
597 }
598 }(stack, teamIdentifiers[i], gitanaUser);
599 fns.push(fn);
600 }
601 async.series(fns, function() {
602 finished();
603 });
604 });
605 };
606
607 var addToPlatformTeams = function(teamIdentifiers, finished) {
608
609 if (!teamIdentifiers) {
610 teamIdentifiers = "project-users-team";
611 }
612
613 teamIdentifiers = ensureArray(teamIdentifiers);
614
615 var platform = null;
616
617 return Chain(req.gitana.platform()).trap(function(e) {
618 return false;
619 }).then(function() {
620 platform = this;
621
622 var fns = [];
623 for (var i = 0; i < teamIdentifiers.length; i++)
624 {
625 var fn = function(platform, teamIdentifier, user) {
626 return function(d) {
627
628 console.log("Working on platform team: " + teamIdentifier + ", user: " + user._doc);
629
630 Chain(platform).trap(function(e) {
631 d();
632 return false;
633 }).readTeam(teamIdentifier).then(function() {
634 var team = this;
635
636 Chain(team).hasMember(user, function(has) {
637 if (has) {
638 return d();
639 }
640 Chain(team).addMember(user).then(function() {
641 d();
642 });
643 });
644 });
645
646 }
647 }(platform, teamIdentifiers[i], gitanaUser);
648 fns.push(fn);
649 }
650 async.series(fns, function() {
651 finished();
652 });
653 });
654 };
655
656 const {VM} = require("vm2");
657 var vm = new VM({
658 timeout: 5000,
659 sandbox: {
660 "addToProject": function(projectId, teamIdentifiers) {
661 return addToProject(projectId, teamIdentifiers, function() {
662 console.log("Added user: " + gitanaUser._doc + " to project: " + projectId + ", teams: " + JSON.stringify(teamIdentifiers));
663 });
664 },
665 "addToPlatformTeam": function(teamIdentifier) {
666 return addToPlatformTeams([teamIdentifier], function() {
667 console.log("Added user: " + gitanaUser._doc + " to platform team: " + teamIdentifier);
668 });
669 },
670 "addToPlatformTeams": function(teamIdentifiers) {
671 return addToPlatformTeams(teamIdentifiers, function() {
672 console.log("Added user: " + gitanaUser._doc + " to platform teams: " + JSON.stringify(teamIdentifiers));
673 });
674 }
675 }
676 });
677 vm.run(rule);
678
679 setTimeout(function() {
680 callback();
681 }, 250);
682};
683
684var __handleSyncGroups = function(req, strategy, settings, gitanaUser, groupsArray, callback) {
685
686 if (!groupsArray || groupsArray.length === 0)
687 {
688 return callback(null, gitanaUser);
689 }
690
691 // if no groupMappings defined, bail
692 if (!settings || !settings.sso || !settings.sso.groupMappings || settings.sso.groupMappings.length === 0) {
693 return callback(null, gitanaUser);
694 }
695
696 // copy mappings into a lookup list
697 var groupRules = {};
698 for (var i = 0; i < settings.sso.groupMappings.length; i++)
699 {
700 groupRules[settings.sso.groupMappings[i].key] = settings.sso.groupMappings[i].values;
701 }
702
703 var fns = [];
704 for (var i = 0; i < groupsArray.length; i++)
705 {
706 var groupIdentifier = groupsArray[i];
707
708 var rules = groupRules[groupIdentifier];
709 if (rules)
710 {
711 for (var x = 0; x < rules.length; x++)
712 {
713 var fn = function (rule, gitanaUser) {
714 return function (done) {
715 executeRule(req, rule, gitanaUser, function (err) {
716 done(err);
717 });
718 }
719 }(rules[x], gitanaUser);
720 fns.push(fn);
721 }
722 }
723 }
724
725 async.series(fns, function() {
726 callback(null, gitanaUser);
727 });
728};