Require auth for version api call
The version call may contain information that should not be publicly discloused without authentication. This change removes the version call from the public urls which will cause it to require authentication before responding with version information. (cherry-picked from5372982b3e
) (cherry-picked from71738018b0
) Note on the backport: Some tests of a public API were not present in the original commit. They are moved to the newly created version API tests. Also partially included the change from fuel-ui which is part of nailgun in 7.0. Change-Id: I7352f83ec67321a1fb5caa75fc9168cc01229083 Closes-Bug: #1585137
This commit is contained in:
parent
fb491402d9
commit
281bab6586
|
@ -366,5 +366,4 @@ def public_urls():
|
|||
return {
|
||||
r'/nodes/?$': ['POST'],
|
||||
r'/nodes/agent/?$': ['PUT'],
|
||||
r'/version/?$': ['GET']
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from mock import patch
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from nailgun.test.base import BaseAuthenticationIntegrationTest
|
||||
|
@ -47,37 +46,3 @@ class TestPublicHandlers(BaseAuthenticationIntegrationTest):
|
|||
headers=self.default_headers)
|
||||
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
def test_version_api(self):
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.default_headers
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
@patch('nailgun.api.v1.handlers.version.utils.get_fuel_release_versions')
|
||||
def test_500_no_html_dev(self, handler_get):
|
||||
exc_text = "Here goes an exception"
|
||||
handler_get.side_effect = Exception(exc_text)
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.default_headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(500, resp.status_code)
|
||||
self.assertIn(exc_text, resp.body)
|
||||
self.assertIn("Traceback", resp.body)
|
||||
self.assertNotIn("html", resp.body)
|
||||
|
||||
@patch('nailgun.api.v1.handlers.version.utils.get_fuel_release_versions')
|
||||
def test_500_no_html_production(self, handler_get):
|
||||
exc_text = "Here goes an exception"
|
||||
handler_get.side_effect = Exception(exc_text)
|
||||
with patch('nailgun.settings.settings.DEVELOPMENT', 0):
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.default_headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(500, resp.status_code)
|
||||
self.assertEqual(exc_text, resp.body)
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from mock import patch
|
||||
|
||||
from nailgun.test.base import BaseAuthenticationIntegrationTest
|
||||
from nailgun.test.base import reverse
|
||||
|
||||
|
||||
class TestVersionApi(BaseAuthenticationIntegrationTest):
|
||||
"""Test the version api
|
||||
|
||||
Test the version api to make sure it requires authentication
|
||||
and works when passed a valid auth token.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestVersionApi, self).setUp()
|
||||
self.token = self.get_auth_token()
|
||||
self.headers = copy.deepcopy(self.default_headers)
|
||||
|
||||
def test_version_api_noauth(self):
|
||||
"""Check that version api requires auth."""
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.default_headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
|
||||
def test_version_api_auth(self):
|
||||
"""Check that version api works with auth."""
|
||||
self.headers['X-Auth-Token'] = self.token
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
@patch('nailgun.api.v1.handlers.version.utils.get_fuel_release_versions')
|
||||
def test_500_no_html_dev(self, handler_get):
|
||||
exc_text = "Here goes an exception"
|
||||
handler_get.side_effect = Exception(exc_text)
|
||||
self.headers['X-Auth-Token'] = self.token
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(500, resp.status_code)
|
||||
self.assertIn(exc_text, resp.body)
|
||||
self.assertIn("Traceback", resp.body)
|
||||
self.assertNotIn("html", resp.body)
|
||||
|
||||
@patch('nailgun.api.v1.handlers.version.utils.get_fuel_release_versions')
|
||||
def test_500_no_html_production(self, handler_get):
|
||||
exc_text = "Here goes an exception"
|
||||
handler_get.side_effect = Exception(exc_text)
|
||||
self.headers['X-Auth-Token'] = self.token
|
||||
with patch('nailgun.settings.settings.DEVELOPMENT', 0):
|
||||
resp = self.app.get(
|
||||
reverse('VersionHandler'),
|
||||
headers=self.headers,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(500, resp.status_code)
|
||||
self.assertEqual(exc_text, resp.body)
|
|
@ -153,29 +153,35 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
|
|||
this.mountNode = $('#main-container');
|
||||
|
||||
this.router = new Router();
|
||||
this.keystoneClient = new KeystoneClient('/keystone', {
|
||||
cacheTokenFor: 10 * 60 * 1000,
|
||||
tenant: 'admin'
|
||||
});
|
||||
this.version = new models.FuelVersion();
|
||||
this.settings = new models.FuelSettings();
|
||||
this.user = new models.User();
|
||||
this.statistics = new models.NodesStatistics();
|
||||
this.notifications = new models.Notifications();
|
||||
|
||||
this.keystoneClient = new KeystoneClient('/keystone', {
|
||||
cacheTokenFor: 10 * 60 * 1000,
|
||||
tenant: 'admin',
|
||||
token: this.user.get('token')
|
||||
});
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
_.extend(App.prototype, {
|
||||
fetchData: function() {
|
||||
this.version.fetch().then(_.bind(function() {
|
||||
this.version.fetch().then(null, _.bind(function(response) {
|
||||
if (response.status == 401) {
|
||||
this.version.set({auth_required: true});
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
}, this)).then(_.bind(function() {
|
||||
this.user.set({authenticated: !this.version.get('auth_required')});
|
||||
this.patchBackboneSync();
|
||||
if (this.version.get('auth_required')) {
|
||||
_.extend(this.keystoneClient, this.user.pick('token'));
|
||||
return this.keystoneClient.authenticate()
|
||||
.done(_.bind(function() {
|
||||
.then(_.bind(function() {
|
||||
this.user.set({authenticated: true});
|
||||
return this.version.fetch({cache: true});
|
||||
}, this));
|
||||
}
|
||||
return $.Deferred().resolve();
|
||||
|
@ -233,7 +239,7 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
|
|||
if (method == 'patch') {
|
||||
method = 'update';
|
||||
}
|
||||
if (app.version.get('auth_required') && !this.authExempt) {
|
||||
if (app.version && app.version.get('auth_required')) {
|
||||
// FIXME(vkramskikh): manually moving success/error callbacks
|
||||
// to deferred-style callbacks. Everywhere in the code we use
|
||||
// deferreds, but backbone uses success/error callbacks. It
|
||||
|
@ -249,6 +255,7 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
|
|||
app.logout();
|
||||
})
|
||||
.then(_.bind(function() {
|
||||
app.user.set('token', app.keystoneClient.token);
|
||||
options = options || {};
|
||||
options.headers = options.headers || {};
|
||||
options.headers['X-Auth-Token'] = app.keystoneClient.token;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
**/
|
||||
define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
||||
define(['jquery', 'underscore'], function($, _) {
|
||||
'use strict';
|
||||
|
||||
function KeystoneClient(url, options) {
|
||||
|
@ -57,9 +57,6 @@ define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
|||
this.userId = result.access.user.id;
|
||||
this.token = result.access.token.id;
|
||||
this.tokenUpdateTime = new Date();
|
||||
|
||||
Cookies.set('token', result.access.token.id);
|
||||
|
||||
return deferred;
|
||||
} catch(e) {
|
||||
return $.Deferred().reject();
|
||||
|
@ -88,9 +85,6 @@ define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
|||
try {
|
||||
this.token = result.access.token.id;
|
||||
this.tokenUpdateTime = new Date();
|
||||
|
||||
Cookies.set('token', result.access.token.id);
|
||||
|
||||
return deferred;
|
||||
} catch(e) {
|
||||
return $.Deferred().reject();
|
||||
|
@ -111,8 +105,6 @@ define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
|||
delete this.token;
|
||||
delete this.tokenUpdateTime;
|
||||
|
||||
Cookies.remove('token');
|
||||
|
||||
this.tokenRemoveRequest = $.ajax(this.url + '/v2.0/tokens/' + token, {
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
|
|
|
@ -22,8 +22,9 @@ define([
|
|||
'expression',
|
||||
'expression/objects',
|
||||
'jsx!views/custom_controls',
|
||||
'js-cookie',
|
||||
'deepModel'
|
||||
], function($, _, i18n, Backbone, utils, Expression, expressionObjects, customControls) {
|
||||
], function($, _, i18n, Backbone, utils, Expression, expressionObjects, customControls, Cookies) {
|
||||
'use strict';
|
||||
|
||||
var models = {};
|
||||
|
@ -83,6 +84,7 @@ define([
|
|||
if (this.cacheFor && options && options.cache && this.lastSyncTime && (this.cacheFor > (new Date() - this.lastSyncTime))) {
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
if (options) delete options.cache;
|
||||
return this._super('fetch', arguments);
|
||||
},
|
||||
sync: function() {
|
||||
|
@ -998,10 +1000,10 @@ define([
|
|||
urlRoot: '/api/ostf'
|
||||
});
|
||||
|
||||
models.FuelVersion = BaseModel.extend({
|
||||
models.FuelVersion = BaseModel.extend(cacheMixin).extend({
|
||||
cacheFor: 60 * 1000,
|
||||
constructorName: 'FuelVersion',
|
||||
urlRoot: '/api/version',
|
||||
authExempt: true
|
||||
urlRoot: '/api/version'
|
||||
});
|
||||
|
||||
models.User = BaseModel.extend({
|
||||
|
@ -1021,6 +1023,14 @@ define([
|
|||
}
|
||||
});
|
||||
}, this);
|
||||
this.on('change:token', function() {
|
||||
var token = this.get('token');
|
||||
if (_.isUndefined(token)) {
|
||||
Cookies.remove('token');
|
||||
} else {
|
||||
Cookies.set('token', token);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -40,12 +40,6 @@ function($, _, i18n, React, dispatcher, utils) {
|
|||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
<div className='login-footer col-xs-12'>
|
||||
{_.contains(app.version.get('feature_groups'), 'mirantis') &&
|
||||
<p className='text-center'>{i18n('common.copyright')}</p>
|
||||
}
|
||||
<p className='text-center'>{i18n('common.version')}: {app.version.get('release')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -71,7 +65,7 @@ function($, _, i18n, React, dispatcher, utils) {
|
|||
dispatcher.trigger('showDefaultPasswordWarning');
|
||||
}
|
||||
|
||||
return app.settings.fetch({cache: true});
|
||||
return $.when(app.version.fetch({cache: true}), app.settings.fetch({cache: true}));
|
||||
}, this))
|
||||
.done(_.bind(function() {
|
||||
var nextUrl = '';
|
||||
|
|
|
@ -630,7 +630,7 @@ function run_server() {
|
|||
|
||||
local http_code=$(curl -s -w %{http_code} -o /dev/null $check_url)
|
||||
|
||||
if [[ "$http_code" = "200" ]]; then return 0; fi
|
||||
if [[ "$http_code" != "000" ]]; then return 0; fi
|
||||
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
|
|
Loading…
Reference in New Issue