UNPKG

22.8 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();
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();
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 return provider.parseProfile(req, profile, function(err, userObject, groupsArray, mandatoryGroupsArray) {
241
242 // special handling for mandatory groups
243 if (mandatoryGroupsArray && mandatoryGroupsArray.length > 0)
244 {
245 // clean up white space
246 for (var i = 0; i < mandatoryGroupsArray.length; i++)
247 {
248 mandatoryGroupsArray[i] = mandatoryGroupsArray[i].trim();
249 }
250
251 // make sure our groups Array contains at least one mandatory group
252 var hasMandatoryGroup = false;
253 var mandatoryGroupsMap = {};
254 var matchedGroup = null;
255 for (var i = 0; i < mandatoryGroupsArray.length; i++) {
256 mandatoryGroupsMap[mandatoryGroupsArray[i]] = true;
257 }
258 for (var i = 0; i < groupsArray.length; i++) {
259 if (mandatoryGroupsMap[groupsArray[i]]) {
260 matchedGroup = groupsArray[i];
261 hasMandatoryGroup = true;
262 }
263 }
264 if (!hasMandatoryGroup)
265 {
266 logEvent("Authorization", false, req.protocol, providerId, profile.nameID, req.ip, null, groupsArray, mandatoryGroupsArray, null);
267 return callback({
268 "message": "The incoming user does not belong to one of the mandatory groups",
269 "noMandatoryGroup": true
270 });
271 }
272 else{
273 if (domain && domain._doc) {
274 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, matchedGroup, groupsArray, mandatoryGroupsArray, "AddToDomain:" + domain._doc);
275 } else {
276 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, matchedGroup, groupsArray, mandatoryGroupsArray, "AddToDomain");
277 }
278 }
279 }
280 else
281 {
282 if (domain && domain._doc) {
283 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, null, groupsArray, null, "AddToDomain:" + domain._doc);
284 } else {
285 logEvent("Authorization", true, req.protocol, providerId, profile.nameID, req.ip, null, groupsArray, null, "AddToDomain");
286 }
287 }
288
289 req.application(function(err, application) {
290
291 // load settings off request
292 req.applicationSettings(function(err, settings) {
293
294 if (err || !settings) {
295 settings = {};
296 }
297
298 var providerUserId = provider.userIdentifier(profile);
299 if (!providerUserId)
300 {
301 return callback({
302 "message": "Unable to determine provider user ID from profile"
303 });
304 }
305
306 var key = token;
307
308 // load sync'd users from cache
309 var CACHE_IDENTIFIER = providerId + "/" + providerUserId;
310
311 var entry = readUserCacheEntry(CACHE_IDENTIFIER);
312 if (entry)
313 {
314 var gitanaUser = entry.gitanaUser;
315 var platform = entry.platform;
316 var appHelper = entry.appHelper;
317 var key = entry.key;
318
319 if (gitanaUser && platform && key)
320 {
321 // successful cache hit
322 return callback(null, gitanaUser, platform, appHelper, key, platform.getDriver());
323 }
324 else
325 {
326 // clean up cache
327 removeUserCacheEntry(CACHE_IDENTIFIER);
328 }
329 }
330
331 _LOCK([CACHE_IDENTIFIER], function(releaseLockFn) {
332 _handleSyncUser(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, groupsArray, function (err, gitanaUser) {
333
334 if (err) {
335 releaseLockFn();
336 return callback(err);
337 }
338
339 // no user found
340 if (!gitanaUser) {
341 releaseLockFn();
342 return callback();
343 }
344
345 _handleConnectAsUser(req, key, gitanaUser, function (err, platform, appHelper, key) {
346
347 if (err) {
348 releaseLockFn();
349 return callback(err);
350 }
351
352 // write to cache
353 writeUserCacheEntry(CACHE_IDENTIFIER, {
354 "gitanaUser": gitanaUser,
355 "platform": platform,
356 "appHelper": appHelper,
357 "key": key
358 });
359
360 releaseLockFn();
361
362 callback(err, gitanaUser, platform, appHelper, key, platform.getDriver());
363 }, gitanaUser);
364 });
365 });
366 });
367 });
368 });
369};
370
371var _handleConnectAsUser = function(req, key, gitanaUser, callback) {
372
373 var appHelper = Gitana.APPS[key];
374 if (appHelper)
375 {
376 // console.log("CONNECT USER LOADED FROM CACHE, APPS");
377 return callback(null, appHelper.platform(), appHelper, key);
378 }
379
380 var platform = Gitana.PLATFORM_CACHE(key);
381 if (platform)
382 {
383 // console.log("CONNECT USER LOADED FROM CACHE, PLATFORM");
384 return callback(null, platform, null, key);
385 }
386
387 impersonate(req, key, gitanaUser, function(err, platform, appHelper, key) {
388 callback(err, platform, appHelper, key);
389 });
390};
391
392var _handleSyncUser = function(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, groupsArray, callback) {
393
394 __handleSyncUser(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, function(err, gitanaUser, synced) {
395
396 if (err) {
397 return callback(err);
398 }
399
400 if (!gitanaUser)
401 {
402 if (!strategy.autoRegister)
403 {
404 console.log("Sync user did not find a user for providerUserId: " + providerUserId + " but autoRegister is turned off, cannot auto-create the user");
405
406 return callback({
407 "message": "User not found (autoRegister is disabled, cannot auto-create)",
408 "noAutoRegister": true
409 });
410 }
411
412 console.log("Sync user did not produce a user object");
413
414 return callback({
415 "message": "User not found"
416 });
417 }
418
419 if (!synced)
420 {
421 return callback(null, gitanaUser);
422 }
423
424 // sync groups
425 __handleSyncGroups(req, strategy, settings, gitanaUser, groupsArray, function(err, gitanaUser) {
426
427 return callback(null, gitanaUser);
428
429 });
430 });
431
432};
433
434var __handleSyncUser = function(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, callback) {
435
436 var baseURL = req.gitanaConfig.baseURL;
437 var authorizationHeader = req.gitana.getDriver().getHttpHeaders()["Authorization"];
438 var targetUrl = baseURL + domain.getUri() + "/connections/sync";
439
440 // add "authorization" for OAuth2 bearer token
441 var headers = {};
442 headers["Authorization"] = authorizationHeader;
443
444 var agent = util.getAgent(req.gitanaConfig.baseURL);
445
446 if (!userObject) {
447 userObject = {};
448 }
449
450 var connectionObject = {};
451 connectionObject.accessToken = token;
452 connectionObject.refreshToken = refreshToken;
453
454 var json = {
455 "user": userObject,
456 "connection": connectionObject
457 };
458
459 var autoCreate = strategy.autoRegister ? true : false;
460
461 var requestConfig = {
462 "method": "POST",
463 "url": targetUrl,
464 "qs": {
465 "providerId": providerId,
466 "providerUserId": providerUserId,
467 "autoCreate": autoCreate
468 },
469 "json": json,
470 "headers": headers,
471 "agent": agent,
472 "timeout": process.defaultHttpTimeoutMs
473 };
474
475 request(requestConfig, function(err, response, json) {
476
477 if (err) {
478 return callback(err);
479 }
480
481 if (json.error === "invalid_token")
482 {
483 // retry after getting new token
484 return req.gitana.getDriver().reloadAuthInfo(function () {
485 __handleSyncUser(req, strategy, settings, key, domain, providerId, providerUserId, token, refreshToken, userObject, function(err, gitanaUser, synced) {
486 callback(err, gitanaUser, synced);
487 })
488 });
489 }
490
491 if (!json.user) {
492 console.log("Did not see json.user, JSON is: " + JSON.stringify(json, null, 2));
493 return callback({
494 "message": "Missing json.user"
495 });
496 }
497
498 var userId = json.user._doc;
499 var domainId = json.user.domainId;
500 var synced = json.user.synced;
501
502 // read the user back
503 Chain(domain).readPrincipal(userId).then(function() {
504 callback(null, this, synced);
505 });
506
507 });
508};
509
510var executeRule = function(req, rule, gitanaUser, callback)
511{
512 //
513 // addToProject(projectId)
514 // addToProject(projectId, [teamKey]);
515 //
516 // removeFromProject(projectId);
517 //
518 // addToPlatformTeam([teamKey])
519 // removeFromPlatformTeam([teamKey])
520
521 var ensureArray = function(teamIdentifiers) {
522 var array = [];
523 if (!teamIdentifiers) {
524 return array;
525 }
526
527 if (typeof(teamIdentifiers) === "string") {
528 array.push(teamIdentifiers);
529 }
530
531 for (var i = 0; i < teamIdentifiers.length; i++) {
532 array.push(teamIdentifiers[i]);
533 }
534
535 return array;
536 };
537
538 var addToProject = function(projectId, teamIdentifiers, finished) {
539
540 if (!teamIdentifiers) {
541 teamIdentifiers = "project-users-team";
542 }
543
544 teamIdentifiers = ensureArray(teamIdentifiers);
545
546 var project = null;
547 var stack = null;
548
549 return req.gitana.platform().trap(function(e) {
550 return false;
551 }).readProject(projectId).then(function(){
552 project = this;
553 }).readStack().then(function() {
554 stack = this;
555
556 var fns = [];
557 for (var i = 0; i < teamIdentifiers.length; i++)
558 {
559 var fn = function(stack, teamIdentifier, user) {
560 return function(d) {
561
562 console.log("Working on stack: " + stack._doc + ", team: " + teamIdentifier + ", user: " + user._doc);
563
564 Chain(stack).trap(function(e) {
565 d();
566 return false;
567 }).readTeam(teamIdentifier).then(function() {
568 var team = this;
569
570 Chain(team).hasMember(user, function(has) {
571 if (has) {
572 return d();
573 }
574 Chain(team).addMember(user).then(function() {
575 d();
576 });
577 });
578 });
579
580 }
581 }(stack, teamIdentifiers[i], gitanaUser);
582 fns.push(fn);
583 }
584 async.series(fns, function() {
585 finished();
586 });
587 });
588 };
589
590 var addToPlatformTeams = function(teamIdentifiers, finished) {
591
592 if (!teamIdentifiers) {
593 teamIdentifiers = "project-users-team";
594 }
595
596 teamIdentifiers = ensureArray(teamIdentifiers);
597
598 var platform = null;
599
600 return Chain(req.gitana.platform()).trap(function(e) {
601 return false;
602 }).then(function() {
603 platform = this;
604
605 var fns = [];
606 for (var i = 0; i < teamIdentifiers.length; i++)
607 {
608 var fn = function(platform, teamIdentifier, user) {
609 return function(d) {
610
611 console.log("Working on platform team: " + teamIdentifier + ", user: " + user._doc);
612
613 Chain(platform).trap(function(e) {
614 d();
615 return false;
616 }).readTeam(teamIdentifier).then(function() {
617 var team = this;
618
619 Chain(team).hasMember(user, function(has) {
620 if (has) {
621 return d();
622 }
623 Chain(team).addMember(user).then(function() {
624 d();
625 });
626 });
627 });
628
629 }
630 }(platform, teamIdentifiers[i], gitanaUser);
631 fns.push(fn);
632 }
633 async.series(fns, function() {
634 finished();
635 });
636 });
637 };
638
639 const {VM} = require("vm2");
640 var vm = new VM({
641 timeout: 5000,
642 sandbox: {
643 "addToProject": function(projectId, teamIdentifiers) {
644 return addToProject(projectId, teamIdentifiers, function() {
645 console.log("Added user: " + gitanaUser._doc + " to project: " + projectId + ", teams: " + JSON.stringify(teamIdentifiers));
646 });
647 },
648 "addToPlatformTeam": function(teamIdentifier) {
649 return addToPlatformTeams([teamIdentifier], function() {
650 console.log("Added user: " + gitanaUser._doc + " to platform team: " + teamIdentifier);
651 });
652 },
653 "addToPlatformTeams": function(teamIdentifiers) {
654 return addToPlatformTeams(teamIdentifiers, function() {
655 console.log("Added user: " + gitanaUser._doc + " to platform teams: " + JSON.stringify(teamIdentifiers));
656 });
657 }
658 }
659 });
660 vm.run(rule);
661
662 setTimeout(function() {
663 callback();
664 }, 250);
665};
666
667var __handleSyncGroups = function(req, strategy, settings, gitanaUser, groupsArray, callback) {
668
669 if (!groupsArray || groupsArray.length === 0)
670 {
671 return callback(null, gitanaUser);
672 }
673
674 // if no groupMappings defined, bail
675 if (!settings || !settings.sso || !settings.sso.groupMappings || settings.sso.groupMappings.length === 0) {
676 return callback(null, gitanaUser);
677 }
678
679 // copy mappings into a lookup list
680 var groupRules = {};
681 for (var i = 0; i < settings.sso.groupMappings.length; i++)
682 {
683 groupRules[settings.sso.groupMappings[i].key] = settings.sso.groupMappings[i].values;
684 }
685
686 var fns = [];
687 for (var i = 0; i < groupsArray.length; i++)
688 {
689 var groupIdentifier = groupsArray[i];
690
691 var rules = groupRules[groupIdentifier];
692 if (rules)
693 {
694 for (var x = 0; x < rules.length; x++)
695 {
696 var fn = function (rule, gitanaUser) {
697 return function (done) {
698 executeRule(req, rule, gitanaUser, function (err) {
699 done(err);
700 });
701 }
702 }(rules[x], gitanaUser);
703 fns.push(fn);
704 }
705 }
706 }
707
708 async.series(fns, function() {
709 callback(null, gitanaUser);
710 });
711};