- 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:
Joseph Schorr 2014-04-08 19:14:24 -04:00
parent 265fa5070a
commit da859203f7
9 changed files with 76 additions and 46 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
venv
static/snapshots/
screenshots/screenshots/
stack

View file

@ -28,6 +28,24 @@ def logs_init_builder(level=logging.DEBUG,
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):
# Flask config
@ -94,6 +112,9 @@ class DefaultConfig(object):
GITHUB_CLIENT_ID = ''
GITHUB_CLIENT_SECRET = ''
GITHUB_LOGIN_CLIENT_ID = ''
GITHUB_LOGIN_CLIENT_SECRET = ''
# Requests based HTTP client with a large request pool
HTTPCLIENT = build_requests_session()

View file

@ -16,6 +16,7 @@ from auth import scopes
from endpoints.api.discovery import swagger_route_data
from werkzeug.routing import BaseConverter
from functools import wraps
from config import getFrontendVisibleConfig
import features
@ -119,6 +120,9 @@ def random_string():
def render_page_template(name, **kwargs):
resp = make_response(render_template(name, route_data=json.dumps(get_route_data()),
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(),
**kwargs))

View file

@ -465,6 +465,23 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
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) {
var apiService = {};
@ -761,8 +778,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
return userService;
}]);
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService',
function($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, Config) {
var notificationService = {
'user': null,
'notifications': [],
@ -856,23 +873,13 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
return notificationService;
}]);
$provide.factory('KeyService', ['$location', function($location) {
$provide.factory('KeyService', ['$location', 'Config', function($location, Config) {
var keyService = {}
if ($location.host() === 'quay.io') {
keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
keyService['githubClientId'] = '5a8c08b06c48d89d4d1e';
keyService['githubRedirectUri'] = 'https://quay.io/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';
}
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID'];
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID'];
keyService['githubRedirectUri'] = Config['PREFERRED_URL_SCHEME'] + '://' + Config['SERVER_NAME'] + '/oauth2/github/callback';
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 {
priority: 590,
restrict: 'A',
link: function($scope, $element, $attr, ctrl, $transclude) {
$scope.Features = Features;
$scope.Config = Config;
$scope.$watch($attr.quayShow, function(result) {
$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 {
priority: 580,
restrict: 'A',
@ -1382,11 +1390,16 @@ quayApp.directive('quayClasses', function(Features) {
}
$scope.$watch($attr.quayClasses, function(result) {
var scopeVals = {
'Features': Features,
'Config': Config
};
for (var expr in result) {
if (!result.hasOwnProperty(expr)) { continue; }
// Evaluate the expression with the entire features list added.
var value = $scope.$eval(expr, Features);
var value = $scope.$eval(expr, scopeVals);
if (value) {
addClass(result[expr]);
} 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 {
priority: 595,
restrict: 'A',
@ -1414,12 +1427,17 @@ quayApp.directive('quayInclude', function($compile, $templateCache, $http, Featu
return;
}
var scopeVals = {
'Features': Features,
'Config': Config
};
var templatePath = null;
for (var expr in result) {
if (!result.hasOwnProperty(expr)) { continue; }
// Evaluate the expression with the entire features list added.
var value = $scope.$eval(expr, Features);
var value = $scope.$eval(expr, scopeVals);
if (value) {
templatePath = result[expr];
break;

View file

@ -262,7 +262,7 @@ function RepoListCtrl($scope, $sanitize, Restangular, UserService, ApiService) {
loadPublicRepos();
}
function LandingCtrl($scope, UserService, ApiService, Features) {
function LandingCtrl($scope, UserService, ApiService, Features, Config) {
$scope.namespace = null;
$scope.$watch('namespace', function(namespace) {
@ -308,11 +308,11 @@ function LandingCtrl($scope, UserService, ApiService, Features) {
};
$scope.getEnterpriseLogo = function() {
if (!Features.ENTERPRISE_LOGO_URL) {
if (!Config.ENTERPRISE_LOGO_URL) {
return '/static/img/quay-logo.png';
}
return Features.ENTERPRISE_LOGO_URL;
return Config.ENTERPRISE_LOGO_URL;
};
}

View file

@ -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>
</div>

View file

@ -9,7 +9,7 @@
<li class="active" quay-require="['BILLING']">
<a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a>
</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>
</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>
<!-- 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="panel" ng-show="!changingOrganization">

View file

@ -30,7 +30,7 @@
</li>
<!-- 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="#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>
@ -105,7 +105,7 @@
</div>
<!-- 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="alert alert-success" ng-show="changeEmailSent">An e-mail has been sent to {{ sentEmail }} to verify the change.</div>

View file

@ -74,6 +74,7 @@
<script type="text/javascript">
window.__endpoints = {{ route_data|safe }}.apis;
window.__features = {{ feature_set|safe }};
window.__config = {{ config_set|safe }};
window.__token = '{{ csrf_token() }}';
</script>
@ -83,25 +84,10 @@
<script src="/static/js/graphing.js?v={{ cache_buster }}"></script>
<!-- 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"!==
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||[]);
mixpanel.init(isProd ? "50ff2b2569faa3a51c8f5724922ffb7e" : "38014a0f27e7bdc3ff8cc7cc29c869f9", { track_pageview : false, debug: !isProd });</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>
mixpanel.init("{{ mixpanel_key }}", { track_pageview : false, debug: {{ is_debug }} });</script><!-- end Mixpanel -->
</head>
<body>
<div ng-class="!fixFooter ? 'wrapper' : ''">