- Add a config whitelist
- Send the config values to the frontend - Add a service class for exposing the config values - Change the directives to inject both Features and Config - Change directive users to make use of the new scope
This commit is contained in:
parent
265fa5070a
commit
da859203f7
9 changed files with 76 additions and 46 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
venv
|
venv
|
||||||
static/snapshots/
|
static/snapshots/
|
||||||
screenshots/screenshots/
|
screenshots/screenshots/
|
||||||
|
stack
|
||||||
|
|
21
config.py
21
config.py
|
@ -28,6 +28,24 @@ def logs_init_builder(level=logging.DEBUG,
|
||||||
|
|
||||||
return init_logs
|
return init_logs
|
||||||
|
|
||||||
|
# The set of configuration key names that will be accessible in the client. Since these
|
||||||
|
# values are set to the frontend, DO NOT PLACE ANY SECRETS OR KEYS in this list.
|
||||||
|
CLIENT_WHITELIST = ['SERVER_NAME', 'PREFERRED_URL_SCHEME', 'GITHUB_CLIENT_ID',
|
||||||
|
'GITHUB_LOGIN_CLIENT_ID', 'MIXPANEL_KEY', 'STRIPE_PUBLISHABLE_KEY',
|
||||||
|
'ENTERPRISE_LOGO_URL']
|
||||||
|
|
||||||
|
|
||||||
|
def getFrontendVisibleConfig(config_dict):
|
||||||
|
visible_dict = {}
|
||||||
|
for name in CLIENT_WHITELIST:
|
||||||
|
if name.lower().find('secret') >= 0:
|
||||||
|
raise Exception('Cannot whitelist secrets: %s' % name)
|
||||||
|
|
||||||
|
if name in config_dict:
|
||||||
|
visible_dict[name] = config_dict.get(name, None)
|
||||||
|
|
||||||
|
return visible_dict
|
||||||
|
|
||||||
|
|
||||||
class DefaultConfig(object):
|
class DefaultConfig(object):
|
||||||
# Flask config
|
# Flask config
|
||||||
|
@ -94,6 +112,9 @@ class DefaultConfig(object):
|
||||||
GITHUB_CLIENT_ID = ''
|
GITHUB_CLIENT_ID = ''
|
||||||
GITHUB_CLIENT_SECRET = ''
|
GITHUB_CLIENT_SECRET = ''
|
||||||
|
|
||||||
|
GITHUB_LOGIN_CLIENT_ID = ''
|
||||||
|
GITHUB_LOGIN_CLIENT_SECRET = ''
|
||||||
|
|
||||||
# Requests based HTTP client with a large request pool
|
# Requests based HTTP client with a large request pool
|
||||||
HTTPCLIENT = build_requests_session()
|
HTTPCLIENT = build_requests_session()
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ from auth import scopes
|
||||||
from endpoints.api.discovery import swagger_route_data
|
from endpoints.api.discovery import swagger_route_data
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from config import getFrontendVisibleConfig
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
|
||||||
|
@ -119,6 +120,9 @@ def random_string():
|
||||||
def render_page_template(name, **kwargs):
|
def render_page_template(name, **kwargs):
|
||||||
resp = make_response(render_template(name, route_data=json.dumps(get_route_data()),
|
resp = make_response(render_template(name, route_data=json.dumps(get_route_data()),
|
||||||
feature_set=json.dumps(features.get_features()),
|
feature_set=json.dumps(features.get_features()),
|
||||||
|
config_set=json.dumps(getFrontendVisibleConfig(app.config)),
|
||||||
|
mixpanel_key=app.config.get('MIXPANEL_KEY', ''),
|
||||||
|
is_debug=str(app.config.get('DEBUGGING', False)).lower(),
|
||||||
cache_buster=random_string(),
|
cache_buster=random_string(),
|
||||||
**kwargs))
|
**kwargs))
|
||||||
|
|
||||||
|
|
|
@ -465,6 +465,23 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
||||||
return features;
|
return features;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
$provide.factory('Config', [function() {
|
||||||
|
if (!window.__config) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = window.__config;
|
||||||
|
config.getValue = function(name, opt_defaultValue) {
|
||||||
|
var value = config[name];
|
||||||
|
if (value == null) {
|
||||||
|
return opt_defaultValue;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}]);
|
||||||
|
|
||||||
$provide.factory('ApiService', ['Restangular', function(Restangular) {
|
$provide.factory('ApiService', ['Restangular', function(Restangular) {
|
||||||
var apiService = {};
|
var apiService = {};
|
||||||
|
|
||||||
|
@ -761,8 +778,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
||||||
return userService;
|
return userService;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService',
|
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService', 'Config',
|
||||||
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService) {
|
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService, Config) {
|
||||||
var notificationService = {
|
var notificationService = {
|
||||||
'user': null,
|
'user': null,
|
||||||
'notifications': [],
|
'notifications': [],
|
||||||
|
@ -856,23 +873,13 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
||||||
return notificationService;
|
return notificationService;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
$provide.factory('KeyService', ['$location', function($location) {
|
$provide.factory('KeyService', ['$location', 'Config', function($location, Config) {
|
||||||
var keyService = {}
|
var keyService = {}
|
||||||
|
|
||||||
if ($location.host() === 'quay.io') {
|
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
|
||||||
keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
|
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID'];
|
||||||
keyService['githubClientId'] = '5a8c08b06c48d89d4d1e';
|
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID'];
|
||||||
keyService['githubRedirectUri'] = 'https://quay.io/oauth2/github/callback';
|
keyService['githubRedirectUri'] = Config['PREFERRED_URL_SCHEME'] + '://' + Config['SERVER_NAME'] + '/oauth2/github/callback';
|
||||||
} else if($location.host() === 'staging.quay.io') {
|
|
||||||
keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
|
|
||||||
keyService['githubClientId'] = '4886304accbc444f0471';
|
|
||||||
keyService['githubRedirectUri'] = 'https://staging.quay.io/oauth2/github/callback';
|
|
||||||
} else {
|
|
||||||
keyService['stripePublishableKey'] = 'pk_test_uEDHANKm9CHCvVa2DLcipGRh';
|
|
||||||
keyService['githubClientId'] = 'cfbc4aca88e5c1b40679';
|
|
||||||
keyService['githubRedirectUri'] = 'http://localhost:5000/oauth2/github/callback';
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyService;
|
return keyService;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -1335,12 +1342,13 @@ quayApp.directive('quayRequire', function ($animate, Features) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('quayShow', function($animate, Features) {
|
quayApp.directive('quayShow', function($animate, Features, Config) {
|
||||||
return {
|
return {
|
||||||
priority: 590,
|
priority: 590,
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
link: function($scope, $element, $attr, ctrl, $transclude) {
|
link: function($scope, $element, $attr, ctrl, $transclude) {
|
||||||
$scope.Features = Features;
|
$scope.Features = Features;
|
||||||
|
$scope.Config = Config;
|
||||||
$scope.$watch($attr.quayShow, function(result) {
|
$scope.$watch($attr.quayShow, function(result) {
|
||||||
$animate[!!result ? 'removeClass' : 'addClass']($element, 'ng-hide');
|
$animate[!!result ? 'removeClass' : 'addClass']($element, 'ng-hide');
|
||||||
});
|
});
|
||||||
|
@ -1349,7 +1357,7 @@ quayApp.directive('quayShow', function($animate, Features) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('quayClasses', function(Features) {
|
quayApp.directive('quayClasses', function(Features, Config) {
|
||||||
return {
|
return {
|
||||||
priority: 580,
|
priority: 580,
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
|
@ -1382,11 +1390,16 @@ quayApp.directive('quayClasses', function(Features) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$watch($attr.quayClasses, function(result) {
|
$scope.$watch($attr.quayClasses, function(result) {
|
||||||
|
var scopeVals = {
|
||||||
|
'Features': Features,
|
||||||
|
'Config': Config
|
||||||
|
};
|
||||||
|
|
||||||
for (var expr in result) {
|
for (var expr in result) {
|
||||||
if (!result.hasOwnProperty(expr)) { continue; }
|
if (!result.hasOwnProperty(expr)) { continue; }
|
||||||
|
|
||||||
// Evaluate the expression with the entire features list added.
|
// Evaluate the expression with the entire features list added.
|
||||||
var value = $scope.$eval(expr, Features);
|
var value = $scope.$eval(expr, scopeVals);
|
||||||
if (value) {
|
if (value) {
|
||||||
addClass(result[expr]);
|
addClass(result[expr]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1399,7 +1412,7 @@ quayApp.directive('quayClasses', function(Features) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('quayInclude', function($compile, $templateCache, $http, Features) {
|
quayApp.directive('quayInclude', function($compile, $templateCache, $http, Features, Config) {
|
||||||
return {
|
return {
|
||||||
priority: 595,
|
priority: 595,
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
|
@ -1414,12 +1427,17 @@ quayApp.directive('quayInclude', function($compile, $templateCache, $http, Featu
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scopeVals = {
|
||||||
|
'Features': Features,
|
||||||
|
'Config': Config
|
||||||
|
};
|
||||||
|
|
||||||
var templatePath = null;
|
var templatePath = null;
|
||||||
for (var expr in result) {
|
for (var expr in result) {
|
||||||
if (!result.hasOwnProperty(expr)) { continue; }
|
if (!result.hasOwnProperty(expr)) { continue; }
|
||||||
|
|
||||||
// Evaluate the expression with the entire features list added.
|
// Evaluate the expression with the entire features list added.
|
||||||
var value = $scope.$eval(expr, Features);
|
var value = $scope.$eval(expr, scopeVals);
|
||||||
if (value) {
|
if (value) {
|
||||||
templatePath = result[expr];
|
templatePath = result[expr];
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -262,7 +262,7 @@ function RepoListCtrl($scope, $sanitize, Restangular, UserService, ApiService) {
|
||||||
loadPublicRepos();
|
loadPublicRepos();
|
||||||
}
|
}
|
||||||
|
|
||||||
function LandingCtrl($scope, UserService, ApiService, Features) {
|
function LandingCtrl($scope, UserService, ApiService, Features, Config) {
|
||||||
$scope.namespace = null;
|
$scope.namespace = null;
|
||||||
|
|
||||||
$scope.$watch('namespace', function(namespace) {
|
$scope.$watch('namespace', function(namespace) {
|
||||||
|
@ -308,11 +308,11 @@ function LandingCtrl($scope, UserService, ApiService, Features) {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getEnterpriseLogo = function() {
|
$scope.getEnterpriseLogo = function() {
|
||||||
if (!Features.ENTERPRISE_LOGO_URL) {
|
if (!Config.ENTERPRISE_LOGO_URL) {
|
||||||
return '/static/img/quay-logo.png';
|
return '/static/img/quay-logo.png';
|
||||||
}
|
}
|
||||||
|
|
||||||
return Features.ENTERPRISE_LOGO_URL;
|
return Config.ENTERPRISE_LOGO_URL;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<div quay-include="{'BILLING': 'landing-normal.html', '!BILLING': 'landing-login.html'}" onload="chromify()">
|
<div quay-include="{'Features.BILLING': 'landing-normal.html', '!Features.BILLING': 'landing-login.html'}" onload="chromify()">
|
||||||
<span class="quay-spinner"></span>
|
<span class="quay-spinner"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<li class="active" quay-require="['BILLING']">
|
<li class="active" quay-require="['BILLING']">
|
||||||
<a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a>
|
<a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a>
|
||||||
</li>
|
</li>
|
||||||
<li quay-classes="{'!BILLING': 'active'}">
|
<li quay-classes="{'!Features.BILLING': 'active'}">
|
||||||
<a href="javascript:void(0)" data-toggle="tab" data-target="#settings">Organization Settings</a>
|
<a href="javascript:void(0)" data-toggle="tab" data-target="#settings">Organization Settings</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Organization settings tab -->
|
<!-- Organization settings tab -->
|
||||||
<div id="settings" class="tab-pane" quay-classes="{'!BILLING': 'active'}">
|
<div id="settings" class="tab-pane" quay-classes="{'!Features.BILLING': 'active'}">
|
||||||
<div class="quay-spinner" ng-show="changingOrganization"></div>
|
<div class="quay-spinner" ng-show="changingOrganization"></div>
|
||||||
|
|
||||||
<div class="panel" ng-show="!changingOrganization">
|
<div class="panel" ng-show="!changingOrganization">
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- Non-billing -->
|
<!-- Non-billing -->
|
||||||
<li quay-classes="{'!BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
<li quay-classes="{'!Features.BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#github" quay-require="['GITHUB_LOGIN']">GitHub Login</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#github" quay-require="['GITHUB_LOGIN']">GitHub Login</a></li>
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- E-mail address tab -->
|
<!-- E-mail address tab -->
|
||||||
<div id="email" class="tab-pane" quay-classes="{'!BILLING': 'active'}">
|
<div id="email" class="tab-pane" quay-classes="{'!Features.BILLING': 'active'}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="alert alert-success" ng-show="changeEmailSent">An e-mail has been sent to {{ sentEmail }} to verify the change.</div>
|
<div class="alert alert-success" ng-show="changeEmailSent">An e-mail has been sent to {{ sentEmail }} to verify the change.</div>
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.__endpoints = {{ route_data|safe }}.apis;
|
window.__endpoints = {{ route_data|safe }}.apis;
|
||||||
window.__features = {{ feature_set|safe }};
|
window.__features = {{ feature_set|safe }};
|
||||||
|
window.__config = {{ config_set|safe }};
|
||||||
window.__token = '{{ csrf_token() }}';
|
window.__token = '{{ csrf_token() }}';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -83,25 +84,10 @@
|
||||||
<script src="/static/js/graphing.js?v={{ cache_buster }}"></script>
|
<script src="/static/js/graphing.js?v={{ cache_buster }}"></script>
|
||||||
|
|
||||||
<!-- start Mixpanel --><script type="text/javascript">
|
<!-- start Mixpanel --><script type="text/javascript">
|
||||||
var isProd = document.location.hostname === 'quay.io';
|
|
||||||
|
|
||||||
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
|
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
|
||||||
typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);
|
typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);
|
||||||
b._i.push([a,e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
|
b._i.push([a,e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
|
||||||
mixpanel.init(isProd ? "50ff2b2569faa3a51c8f5724922ffb7e" : "38014a0f27e7bdc3ff8cc7cc29c869f9", { track_pageview : false, debug: !isProd });</script><!-- end Mixpanel -->
|
mixpanel.init("{{ mixpanel_key }}", { track_pageview : false, debug: {{ is_debug }} });</script><!-- end Mixpanel -->
|
||||||
|
|
||||||
<!-- start analytics --><script>
|
|
||||||
/*
|
|
||||||
var isProd = document.location.hostname === 'quay.io';
|
|
||||||
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', isProd ? 'UA-34988886-5' : 'UA-34988886-4', 'quay.io');
|
|
||||||
*/
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div ng-class="!fixFooter ? 'wrapper' : ''">
|
<div ng-class="!fixFooter ? 'wrapper' : ''">
|
||||||
|
|
Reference in a new issue