forked from openshift/origin-web-common
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathorigin-web-common-services.js
3089 lines (2817 loc) · 109 KB
/
origin-web-common-services.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @name openshiftCommonServices
*
* @description
* Base module for openshiftCommonServices.
*/
angular.module('openshiftCommonServices', ['ab-base64'])
.config(function(AuthServiceProvider) {
AuthServiceProvider.UserStore('MemoryUserStore');
})
.constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {}))
.constant("APIS_CFG", _.get(window.OPENSHIFT_CONFIG, "apis", {}))
.constant("AUTH_CFG", _.get(window.OPENSHIFT_CONFIG, "auth", {}))
.config(function($httpProvider, AuthServiceProvider, RedirectLoginServiceProvider, AUTH_CFG) {
$httpProvider.interceptors.push('AuthInterceptor');
AuthServiceProvider.LoginService('RedirectLoginService');
AuthServiceProvider.LogoutService('DeleteTokenLogoutService');
// TODO: fall back to cookie store when localStorage is unavailable (see known issues at http://caniuse.com/#feat=namevalue-storage)
AuthServiceProvider.UserStore('LocalStorageUserStore');
RedirectLoginServiceProvider.OAuthClientID(AUTH_CFG.oauth_client_id);
RedirectLoginServiceProvider.OAuthAuthorizeURI(AUTH_CFG.oauth_authorize_uri);
RedirectLoginServiceProvider.OAuthTokenURI(AUTH_CFG.oauth_token_uri);
RedirectLoginServiceProvider.OAuthRedirectURI(URI(AUTH_CFG.oauth_redirect_base).segment("oauth").toString());
});
hawtioPluginLoader.addModule('openshiftCommonServices');
// API Discovery, this runs before the angular app is bootstrapped
// TODO we want this to be possible with a single request against the API instead of being dependent on the numbers of groups and versions
hawtioPluginLoader.registerPreBootstrapTask(function(next) {
// Skips api discovery, needed to run spec tests
if ( _.get(window, "OPENSHIFT_CONFIG.api.k8s.resources") ) {
next();
return;
}
var api = {
k8s: {},
openshift: {}
};
var apis = {};
var API_DISCOVERY_ERRORS = [];
var protocol = window.location.protocol + "//";
// Fetch /api/v1 for legacy k8s resources, we will never bump the version of these legacy apis so fetch version immediately
var k8sBaseURL = protocol + window.OPENSHIFT_CONFIG.api.k8s.hostPort + window.OPENSHIFT_CONFIG.api.k8s.prefix;
var k8sDeferred = $.get(k8sBaseURL + "/v1")
.done(function(data) {
api.k8s.v1 = _.indexBy(data.resources, 'name');
})
.fail(function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
});
// Fetch /oapi/v1 for legacy openshift resources, we will never bump the version of these legacy apis so fetch version immediately
var osBaseURL = protocol + window.OPENSHIFT_CONFIG.api.openshift.hostPort + window.OPENSHIFT_CONFIG.api.openshift.prefix;
var osDeferred = $.get(osBaseURL + "/v1")
.done(function(data) {
api.openshift.v1 = _.indexBy(data.resources, 'name');
})
.fail(function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
});
// Fetch /apis to get the list of groups and versions, then fetch each group/
// Because the api discovery doc returns arrays and we want maps, this creates a structure like:
// {
// extensions: {
// name: "extensions",
// preferredVersion: "v1beta1",
// versions: {
// v1beta1: {
// version: "v1beta1",
// groupVersion: "extensions/v1beta1"
// resources: {
// daemonsets: {
// /* resource returned from discovery API */
// }
// }
// }
// }
// }
// }
var apisBaseURL = protocol + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix;
var getGroups = function(baseURL, hostPrefix, data) {
var apisDeferredVersions = [];
_.each(data.groups, function(apiGroup) {
var group = {
name: apiGroup.name,
preferredVersion: apiGroup.preferredVersion.version,
versions: {},
hostPrefix: hostPrefix
};
apis[group.name] = group;
_.each(apiGroup.versions, function(apiVersion) {
var versionStr = apiVersion.version;
group.versions[versionStr] = {
version: versionStr,
groupVersion: apiVersion.groupVersion
};
apisDeferredVersions.push($.get(baseURL + "/" + apiVersion.groupVersion)
.done(function(data) {
group.versions[versionStr].resources = _.indexBy(data.resources, 'name');
})
.fail(function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
}));
});
});
return $.when.apply(this, apisDeferredVersions);
};
var apisDeferred = $.get(apisBaseURL)
.then(_.partial(getGroups, apisBaseURL, null), function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
});
// Additional servers can be defined for debugging and prototyping against new servers not yet served by the aggregator
// There can not be any conflicts in the groups/resources from these API servers.
var additionalDeferreds = [];
_.each(window.OPENSHIFT_CONFIG.additionalServers, function(server) {
var baseURL = (server.protocol ? (server.protocol + "://") : protocol) + server.hostPort + server.prefix;
additionalDeferreds.push($.get(baseURL)
.then(_.partial(getGroups, baseURL, server), function(data, textStatus, jqXHR) {
if (server.required !== false) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
}
}));
});
// Will be called on success or failure
var discoveryFinished = function() {
window.OPENSHIFT_CONFIG.api.k8s.resources = api.k8s;
window.OPENSHIFT_CONFIG.api.openshift.resources = api.openshift;
window.OPENSHIFT_CONFIG.apis.groups = apis;
if (API_DISCOVERY_ERRORS.length) {
window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS = API_DISCOVERY_ERRORS;
}
next();
};
var allDeferreds = [
k8sDeferred,
osDeferred,
apisDeferred
];
allDeferreds = allDeferreds.concat(additionalDeferreds);
$.when.apply(this, allDeferreds).always(discoveryFinished);
});
;'use strict';
angular.module("openshiftCommonServices")
.service("AlertMessageService", function(){
var alerts = [];
var alertHiddenKey = function(alertID, namespace) {
if (!namespace) {
return 'hide/alert/' + alertID;
}
return 'hide/alert/' + namespace + '/' + alertID;
};
return {
addAlert: function(alert) {
alerts.push(alert);
},
getAlerts: function() {
return alerts;
},
clearAlerts: function() {
alerts = [];
},
isAlertPermanentlyHidden: function(alertID, namespace) {
var key = alertHiddenKey(alertID, namespace);
return localStorage.getItem(key) === 'true';
},
permanentlyHideAlert: function(alertID, namespace) {
var key = alertHiddenKey(alertID,namespace);
localStorage.setItem(key, 'true');
}
};
});
;'use strict';
// ResourceGroupVersion represents a fully qualified resource
function ResourceGroupVersion(resource, group, version) {
this.resource = resource;
this.group = group;
this.version = version;
return this;
}
// toString() includes the group and version information if present
ResourceGroupVersion.prototype.toString = function() {
var s = this.resource;
if (this.group) { s += "/" + this.group; }
if (this.version) { s += "/" + this.version; }
return s;
};
// primaryResource() returns the resource with any subresources removed
ResourceGroupVersion.prototype.primaryResource = function() {
if (!this.resource) { return ""; }
var i = this.resource.indexOf('/');
if (i === -1) { return this.resource; }
return this.resource.substring(0,i);
};
// subresources() returns a (possibly empty) list of subresource segments
ResourceGroupVersion.prototype.subresources = function() {
var segments = (this.resource || '').split("/");
segments.shift();
return segments;
};
// equals() returns true if the given resource, group, and version match.
// If omitted, group and version are not compared.
ResourceGroupVersion.prototype.equals = function(resource, group, version) {
if (this.resource !== resource) { return false; }
if (arguments.length === 1) { return true; }
if (this.group !== group) { return false; }
if (arguments.length === 2) { return true; }
if (this.version !== version) { return false; }
return true;
};
angular.module('openshiftCommonServices')
.factory('APIService', function(API_CFG,
APIS_CFG,
AuthService,
Constants,
Logger,
$q,
$http,
$filter,
$window) {
// Set the default api versions the console will use if otherwise unspecified
var defaultVersion = {
"": "v1",
"extensions": "v1beta1"
};
// toResourceGroupVersion() returns a ResourceGroupVersion.
// If resource is already a ResourceGroupVersion, returns itself.
//
// if r is a string, the empty group and default version for the empty group are assumed.
//
// if r is an object, the resource, group, and version attributes are read.
// a missing group attribute defaults to the legacy group.
// a missing version attribute defaults to the default version for the group, or undefined if the group is unknown.
//
// if r is already a ResourceGroupVersion, it is returned as-is
var toResourceGroupVersion = function(r) {
if (r instanceof ResourceGroupVersion) {
return r;
}
var resource, group, version;
if (angular.isString(r)) {
resource = normalizeResource(r);
group = '';
version = defaultVersion[group];
} else if (r && r.resource) {
resource = normalizeResource(r.resource);
group = r.group || '';
version = r.version || defaultVersion[group] || _.get(APIS_CFG, ["groups", group, "preferredVersion"]);
}
return new ResourceGroupVersion(resource, group, version);
};
// normalizeResource lowercases the first segment of the given resource. subresources can be case-sensitive.
function normalizeResource(resource) {
if (!resource) {
return resource;
}
var i = resource.indexOf('/');
if (i === -1) {
return resource.toLowerCase();
}
return resource.substring(0, i).toLowerCase() + resource.substring(i);
}
// port of group_version.go#ParseGroupVersion
var parseGroupVersion = function(apiVersion) {
if (!apiVersion) {
return undefined;
}
var parts = apiVersion.split("/");
if (parts.length === 1) {
if (parts[0] === "v1") {
return {group: '', version: parts[0]};
}
return {group: parts[0], version: ''};
}
if (parts.length === 2) {
return {group:parts[0], version: parts[1]};
}
Logger.warn('Invalid apiVersion "' + apiVersion + '"');
return undefined;
};
var objectToResourceGroupVersion = function(apiObject) {
if (!apiObject || !apiObject.kind || !apiObject.apiVersion) {
return undefined;
}
var resource = kindToResource(apiObject.kind);
if (!resource) {
return undefined;
}
var groupVersion = parseGroupVersion(apiObject.apiVersion);
if (!groupVersion) {
return undefined;
}
return new ResourceGroupVersion(resource, groupVersion.group, groupVersion.version);
};
// deriveTargetResource figures out the fully qualified destination to submit the object to.
// if resource is a string, and the object's kind matches the resource, the object's group/version are used.
// if resource is a ResourceGroupVersion, and the object's kind and group match, the object's version is used.
// otherwise, resource is used as-is.
var deriveTargetResource = function(resource, object) {
if (!resource || !object) {
return undefined;
}
var objectResource = kindToResource(object.kind);
var objectGroupVersion = parseGroupVersion(object.apiVersion);
var resourceGroupVersion = toResourceGroupVersion(resource);
if (!objectResource || !objectGroupVersion || !resourceGroupVersion) {
return undefined;
}
// We specified something like "pods"
if (angular.isString(resource)) {
// If the object had a matching kind {"kind":"Pod","apiVersion":"v1"}, use the group/version from the object
if (resourceGroupVersion.equals(objectResource)) {
resourceGroupVersion.group = objectGroupVersion.group;
resourceGroupVersion.version = objectGroupVersion.version;
}
return resourceGroupVersion;
}
// If the resource was already a fully specified object,
// require the group to match as well before taking the version from the object
if (resourceGroupVersion.equals(objectResource, objectGroupVersion.group)) {
resourceGroupVersion.version = objectGroupVersion.version;
}
return resourceGroupVersion;
};
// port of restmapper.go#kindToResource
// humanize will add spaces between words in the resource
function kindToResource(kind, humanize) {
if (!kind) {
return "";
}
var resource = kind;
if (humanize) {
var humanizeKind = $filter("humanizeKind");
resource = humanizeKind(resource);
}
resource = String(resource).toLowerCase();
if (resource === 'endpoints' || resource === 'securitycontextconstraints') {
// no-op, plural is the singular
}
else if (resource[resource.length-1] === 's') {
resource = resource + 'es';
}
else if (resource[resource.length-1] === 'y') {
resource = resource.substring(0, resource.length-1) + 'ies';
}
else {
resource = resource + 's';
}
return resource;
}
// apiInfo returns the host/port, prefix, group, and version for the given resource,
// or undefined if the specified resource/group/version is known not to exist.
var apiInfo = function(resource) {
// If API discovery had any failures, calls to api info should redirect to the error page
if (APIS_CFG.API_DISCOVERY_ERRORS) {
var possibleCertFailure = _.every(APIS_CFG.API_DISCOVERY_ERRORS, function(error){
return _.get(error, "data.status") === 0;
});
if (possibleCertFailure && !AuthService.isLoggedIn()) {
// will trigger a login flow which will redirect to the api server
AuthService.withUser();
return;
}
// Otherwise go to the error page, the server might be down. Can't use Navigate.toErrorPage or it will create a circular dependency
$window.location.href = URI('error').query({
error_description: "Unable to load details about the server. If the problem continues, please contact your system administrator.",
error: "API_DISCOVERY"
}).toString();
return;
}
resource = toResourceGroupVersion(resource);
var primaryResource = resource.primaryResource();
var discoveredResource;
// API info for resources in an API group, if the resource was not found during discovery return undefined
if (resource.group) {
discoveredResource = _.get(APIS_CFG, ["groups", resource.group, "versions", resource.version, "resources", primaryResource]);
if (!discoveredResource) {
return undefined;
}
var hostPrefixObj = _.get(APIS_CFG, ["groups", resource.group, 'hostPrefix']) || APIS_CFG;
return {
protocol: hostPrefixObj.protocol,
hostPort: hostPrefixObj.hostPort,
prefix: hostPrefixObj.prefix,
group: resource.group,
version: resource.version,
namespaced: discoveredResource.namespaced
};
}
// Resources without an API group could be legacy k8s or origin resources.
// Scan through resources to determine which this is.
var api;
for (var apiName in API_CFG) {
api = API_CFG[apiName];
discoveredResource = _.get(api, ["resources", resource.version, primaryResource]);
if (!discoveredResource) {
continue;
}
return {
hostPort: api.hostPort,
prefix: api.prefix,
version: resource.version,
namespaced: discoveredResource.namespaced
};
}
return undefined;
};
var invalidObjectKindOrVersion = function(apiObject) {
var kind = "<none>";
var version = "<none>";
if (apiObject && apiObject.kind) { kind = apiObject.kind; }
if (apiObject && apiObject.apiVersion) { version = apiObject.apiVersion; }
return "Invalid kind ("+kind+") or API version ("+version+")";
};
var unsupportedObjectKindOrVersion = function(apiObject) {
var kind = "<none>";
var version = "<none>";
if (apiObject && apiObject.kind) { kind = apiObject.kind; }
if (apiObject && apiObject.apiVersion) { version = apiObject.apiVersion; }
return "The API version "+version+" for kind " + kind + " is not supported by this server";
};
// Returns an array of available kinds, including their group
var calculateAvailableKinds = function(includeClusterScoped) {
var kinds = [];
var rejectedKinds = Constants.AVAILABLE_KINDS_BLACKLIST;
// ignore the legacy openshift kinds, these have been migrated to api groups
_.each(_.pick(API_CFG, function(value, key) {
return key !== 'openshift';
}), function(api) {
_.each(api.resources.v1, function(resource) {
if (resource.namespaced || includeClusterScoped) {
// Exclude subresources and any rejected kinds
if (resource.name.indexOf("/") >= 0 || _.contains(rejectedKinds, resource.kind)) {
return;
}
kinds.push({
kind: resource.kind
});
}
});
});
// Kinds under api groups
_.each(APIS_CFG.groups, function(group) {
// Use the console's default version first, and the server's preferred version second
var preferredVersion = defaultVersion[group.name] || group.preferredVersion;
_.each(group.versions[preferredVersion].resources, function(resource) {
// Exclude subresources and any rejected kinds
if (resource.name.indexOf("/") >= 0 || _.contains(rejectedKinds, resource.kind)) {
return;
}
// Exclude duplicate kinds we know about that map to the same storage as another group/kind
// This is unusual, so we are special casing these
if (group.name === "extensions" && resource.kind === "HorizontalPodAutoscaler" ||
group.name === "batch" && resource.kind === "Job"
) {
return;
}
if (resource.namespaced || includeClusterScoped) {
kinds.push({
kind: resource.kind,
group: group.name
});
}
});
});
return _.uniq(kinds, false, function(value) {
return value.group + "/" + value.kind;
});
};
var namespacedKinds = calculateAvailableKinds(false);
var allKinds = calculateAvailableKinds(true);
var availableKinds = function(includeClusterScoped) {
return includeClusterScoped ? allKinds : namespacedKinds;
};
return {
toResourceGroupVersion: toResourceGroupVersion,
parseGroupVersion: parseGroupVersion,
objectToResourceGroupVersion: objectToResourceGroupVersion,
deriveTargetResource: deriveTargetResource,
kindToResource: kindToResource,
apiInfo: apiInfo,
invalidObjectKindOrVersion: invalidObjectKindOrVersion,
unsupportedObjectKindOrVersion: unsupportedObjectKindOrVersion,
availableKinds: availableKinds
};
});
;'use strict';
angular.module('openshiftCommonServices')
// In a config step, set the desired user store and login service. For example:
// AuthServiceProvider.setUserStore('LocalStorageUserStore')
// AuthServiceProvider.setLoginService('RedirectLoginService')
//
// AuthService provides the following functions:
// withUser()
// returns a promise that resolves when there is a current user
// starts a login if there is no current user
// setUser(user, token[, ttl])
// sets the current user and token to use for authenticated requests
// if ttl is specified, it indicates how many seconds the user and token are valid
// triggers onUserChanged callbacks if the new user is different than the current user
// requestRequiresAuth(config)
// returns true if the request is to a protected URL
// addAuthToRequest(config)
// adds auth info to the request, if available
// if specified, uses config.auth.token as the token, otherwise uses the token store
// startLogin()
// returns a promise that is resolved when the login is complete
// onLogin(callback)
// the given callback is called whenever a login is completed
// onUserChanged(callback)
// the given callback is called whenever the current user changes
.provider('AuthService', function() {
var _userStore = "";
this.UserStore = function(userStoreName) {
if (userStoreName) {
_userStore = userStoreName;
}
return _userStore;
};
var _loginService = "";
this.LoginService = function(loginServiceName) {
if (loginServiceName) {
_loginService = loginServiceName;
}
return _loginService;
};
var _logoutService = "";
this.LogoutService = function(logoutServiceName) {
if (logoutServiceName) {
_logoutService = logoutServiceName;
}
return _logoutService;
};
var loadService = function(injector, name, setter) {
if (!name) {
throw setter + " not set";
} else if (angular.isString(name)) {
return injector.get(name);
} else {
return injector.invoke(name);
}
};
this.$get = function($q, $injector, $log, $rootScope, Logger) {
var authLogger = Logger.get("auth");
authLogger.log('AuthServiceProvider.$get', arguments);
var _loginCallbacks = $.Callbacks();
var _logoutCallbacks = $.Callbacks();
var _userChangedCallbacks = $.Callbacks();
var _loginPromise = null;
var _logoutPromise = null;
var userStore = loadService($injector, _userStore, "AuthServiceProvider.UserStore()");
if (!userStore.available()) {
Logger.error("AuthServiceProvider.$get user store " + _userStore + " not available");
}
var loginService = loadService($injector, _loginService, "AuthServiceProvider.LoginService()");
var logoutService = loadService($injector, _logoutService, "AuthServiceProvider.LogoutService()");
return {
// Returns the configured user store
UserStore: function() {
return userStore;
},
// Returns true if currently logged in.
isLoggedIn: function() {
return !!userStore.getUser();
},
// Returns a promise of a user, which is resolved with a logged in user. Triggers a login if needed.
withUser: function() {
var user = userStore.getUser();
if (user) {
$rootScope.user = user;
authLogger.log('AuthService.withUser()', user);
return $q.when(user);
} else {
authLogger.log('AuthService.withUser(), calling startLogin()');
return this.startLogin();
}
},
setUser: function(user, token, ttl) {
authLogger.log('AuthService.setUser()', user, token, ttl);
var oldUser = userStore.getUser();
userStore.setUser(user, ttl);
userStore.setToken(token, ttl);
$rootScope.user = user;
var oldName = oldUser && oldUser.metadata && oldUser.metadata.name;
var newName = user && user.metadata && user.metadata.name;
if (oldName !== newName) {
authLogger.log('AuthService.setUser(), user changed', oldUser, user);
_userChangedCallbacks.fire(user);
}
},
requestRequiresAuth: function(config) {
var requiresAuth = !!config.auth;
authLogger.log('AuthService.requestRequiresAuth()', config.url.toString(), requiresAuth);
return requiresAuth;
},
addAuthToRequest: function(config) {
// Use the given token, if provided
var token = "";
if (config && config.auth && config.auth.token) {
token = config.auth.token;
authLogger.log('AuthService.addAuthToRequest(), using token from request config', token);
} else {
token = userStore.getToken();
authLogger.log('AuthService.addAuthToRequest(), using token from user store', token);
}
if (!token) {
authLogger.log('AuthService.addAuthToRequest(), no token available');
return false;
}
// Handle web socket requests with a parameter
if (config.method === 'WATCH') {
config.url = URI(config.url).addQuery({access_token: token}).toString();
authLogger.log('AuthService.addAuthToRequest(), added token param', config.url);
} else {
config.headers["Authorization"] = "Bearer " + token;
authLogger.log('AuthService.addAuthToRequest(), added token header', config.headers["Authorization"]);
}
return true;
},
startLogin: function() {
if (_loginPromise) {
authLogger.log("Login already in progress");
return _loginPromise;
}
var self = this;
_loginPromise = loginService.login().then(function(result) {
self.setUser(result.user, result.token, result.ttl);
_loginCallbacks.fire(result.user);
}).catch(function(err) {
Logger.error(err);
}).finally(function() {
_loginPromise = null;
});
return _loginPromise;
},
startLogout: function() {
if (_logoutPromise) {
authLogger.log("Logout already in progress");
return _logoutPromise;
}
var self = this;
var user = userStore.getUser();
var token = userStore.getToken();
var wasLoggedIn = this.isLoggedIn();
_logoutPromise = logoutService.logout(user, token).then(function() {
authLogger.log("Logout service success");
}).catch(function(err) {
authLogger.error("Logout service error", err);
}).finally(function() {
// Clear the user and token
self.setUser(null, null);
// Make sure isLoggedIn() returns false before we fire logout callbacks
var isLoggedIn = self.isLoggedIn();
// Only fire logout callbacks if we transitioned from a logged in state to a logged out state
if (wasLoggedIn && !isLoggedIn) {
_logoutCallbacks.fire();
}
_logoutPromise = null;
});
return _logoutPromise;
},
// TODO: add a way to unregister once we start doing in-page logins
onLogin: function(callback) {
_loginCallbacks.add(callback);
},
// TODO: add a way to unregister once we start doing in-page logouts
onLogout: function(callback) {
_logoutCallbacks.add(callback);
},
// TODO: add a way to unregister once we start doing in-page user changes
onUserChanged: function(callback) {
_userChangedCallbacks.add(callback);
}
};
};
})
// register the interceptor as a service
.factory('AuthInterceptor', ['$q', 'AuthService', function($q, AuthService) {
var pendingRequestConfigs = [];
// TODO: subscribe to user change events to empty the saved configs
// TODO: subscribe to login events to retry the saved configs
return {
// If auth is not needed, or is already present, returns a config
// If auth is needed and not present, starts a login flow and returns a promise of a config
request: function(config) {
// Requests that don't require auth can continue
if (!AuthService.requestRequiresAuth(config)) {
// console.log("No auth required", config.url);
return config;
}
// If we could add auth info, we can continue
if (AuthService.addAuthToRequest(config)) {
// console.log("Auth added", config.url);
return config;
}
// We should have added auth info, but couldn't
// If we were specifically told not to trigger a login, return
if (config.auth && config.auth.triggerLogin === false) {
return config;
}
// 1. Set up a deferred and remember this config, so we can add auth info and resume once login is complete
var deferred = $q.defer();
pendingRequestConfigs.push([deferred, config, 'request']);
// 2. Start the login flow
AuthService.startLogin();
// 3. Return the deferred's promise
return deferred.promise;
},
responseError: function(rejection) {
var authConfig = rejection.config.auth || {};
// Requests that didn't require auth can continue
if (!AuthService.requestRequiresAuth(rejection.config)) {
// console.log("No auth required", rejection.config.url);
return $q.reject(rejection);
}
// If we were specifically told not to trigger a login, return
if (authConfig.triggerLogin === false) {
return $q.reject(rejection);
}
// detect if this is an auth error (401) or other error we should trigger a login flow for
var status = rejection.status;
switch (status) {
case 401:
// console.log('responseError', status);
// 1. Set up a deferred and remember this config, so we can add auth info and retry once login is complete
var deferred = $q.defer();
pendingRequestConfigs.push([deferred, rejection.config, 'responseError']);
// 2. Start the login flow
AuthService.startLogin();
// 3. Return the deferred's promise
return deferred.promise;
default:
return $q.reject(rejection);
}
}
};
}]);
;'use strict';
angular.module("openshiftCommonServices")
.factory("AuthorizationService", function($q, $cacheFactory, Logger, $interval, APIService, DataService){
var currentProject = null;
var cachedRulesByProject = $cacheFactory('rulesCache', {
number: 10
});
// Permisive mode will cause no checks to be done for the user actions.
var permissiveMode = false;
var REVIEW_RESOURCES = ["localresourceaccessreviews", "localsubjectaccessreviews", "resourceaccessreviews", "selfsubjectaccessreviews", "selfsubjectrulesreviews", "subjectaccessreviews"];
// Transform data from:
// rules = {resources: ["jobs"], apiGroups: ["extensions"], verbs:["create","delete","get","list","update"]}
// into:
// normalizedRules = {"extensions": {"jobs": ["create","delete","get","list","update"]}}
var normalizeRules = function(rules) {
var normalizedRules = {};
_.each(rules, function(rule) {
_.each(rule.apiGroups, function(apiGroup) {
if (!normalizedRules[apiGroup]) {
normalizedRules[apiGroup] = {};
}
_.each(rule.resources, function(resource) {
normalizedRules[apiGroup][resource] = rule.verbs;
});
});
});
return normalizedRules;
};
// Check if resource name meets one of following conditions, since those resources can't be create/update via `Add to project` page:
// - 'projectrequests'
// - subresource that contains '/', eg: 'builds/source', 'builds/logs', ...
// - resource is in REVIEW_RESOURCES list
var checkResource = function(resource) {
if (resource === "projectrequests" || _.contains(resource, "/") || _.contains(REVIEW_RESOURCES, resource)) {
return false;
} else {
return true;
}
};
// Check if user can create/update any resource on the 'Add to project' so the button will be displayed.
var canAddToProjectCheck = function(rules) {
return _.some(rules, function(rule) {
return _.some(rule.resources, function(resource) {
return checkResource(resource) && !_.isEmpty(_.intersection(rule.verbs ,(["*", "create", "update"])));
});
});
};
// forceRefresh is a boolean to bust the cache & request new perms
var getProjectRules = function(projectName, forceRefresh) {
var deferred = $q.defer();
currentProject = projectName;
var projectRules = cachedRulesByProject.get(projectName);
var rulesResource = "selfsubjectrulesreviews";
if (!projectRules || projectRules.forceRefresh || forceRefresh) {
// Check if APIserver contains 'selfsubjectrulesreviews' resource. If not switch to permissive mode.
if (APIService.apiInfo(rulesResource)) {
Logger.log("AuthorizationService, loading user rules for " + projectName + " project");
var object = {kind: "SelfSubjectRulesReview",
apiVersion: "v1"
};
DataService.create(rulesResource, null, object, {namespace: projectName}).then(
function(data) {
var normalizedData = normalizeRules(data.status.rules);
var canUserAddToProject = canAddToProjectCheck(data.status.rules);
cachedRulesByProject.put(projectName, {rules: normalizedData,
canAddToProject: canUserAddToProject,
forceRefresh: false,
cacheTimestamp: _.now()
});
deferred.resolve();
}, function() {
permissiveMode = true;
deferred.resolve();
});
} else {
Logger.log("AuthorizationService, resource 'selfsubjectrulesreviews' is not part of APIserver. Switching into permissive mode.");
permissiveMode = true;
deferred.resolve();
}
} else {
// Using cached data.
Logger.log("AuthorizationService, using cached rules for " + projectName + " project");
if ((_.now() - projectRules.cacheTimestamp) >= 600000) {
projectRules.forceRefresh = true;
}
deferred.resolve();
}
return deferred.promise;
};
var getRulesForProject = function(projectName) {
return _.get(cachedRulesByProject.get(projectName || currentProject), ['rules']);
};
// _canI checks whether any rule allows the specified verb (directly or via a wildcard verb) on the literal group and resource.
var _canI = function(rules, verb, group, resource) {
var resources = rules[group];
if (!resources) {
return false;
}
var verbs = resources[resource];
if (!verbs) {
return false;
}
return _.contains(verbs, verb) || _.contains(verbs, '*');
};
// canI checks whether any rule allows the specified verb on the specified group-resource (directly or via a wildcard rule).
var canI = function(resource, verb, projectName) {
if (permissiveMode) {
return true;
}
// normalize to structured form
var r = APIService.toResourceGroupVersion(resource);
var rules = getRulesForProject(projectName || currentProject);
if (!rules) {
return false;
}
return _canI(rules, verb, r.group, r.resource) ||
_canI(rules, verb, '*', '*' ) ||
_canI(rules, verb, r.group, '*' ) ||
_canI(rules, verb, '*', r.resource);
};
var canIAddToProject = function(projectName) {
if (permissiveMode) {
return true;
} else {
return !!_.get(cachedRulesByProject.get(projectName || currentProject), ['canAddToProject']);
}
};
return {
checkResource: checkResource,
getProjectRules: getProjectRules,
canI: canI,
canIAddToProject: canIAddToProject,
getRulesForProject: getRulesForProject
};
});
;'use strict';
angular.module('openshiftCommonServices')
.factory('base64util', function() {
return {
pad: function(data){
if (!data) { return ""; }
switch (data.length % 4) {
case 1: return data + "===";
case 2: return data + "==";
case 3: return data + "=";
default: return data;
}
}
};
});
;'use strict';
angular.module('openshiftCommonServices')
.factory('Constants', function() {
var constants = _.clone(window.OPENSHIFT_CONSTANTS || {});
var version = _.clone(window.OPENSHIFT_VERSION || {});
constants.VERSION = version;
return constants;
});
;'use strict';