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 from 5372982b3e)
(cherry-picked from 71738018b0)

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:
Alex Schultz 2016-05-24 10:55:36 -06:00 committed by Denis V. Meltsaykin
parent fb491402d9
commit 281bab6586
8 changed files with 114 additions and 65 deletions

View File

@ -366,5 +366,4 @@ def public_urls():
return {
r'/nodes/?$': ['POST'],
r'/nodes/agent/?$': ['PUT'],
r'/version/?$': ['GET']
}

View File

@ -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)

View File

@ -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)

View File

@ -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;

View File

@ -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',

View File

@ -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);
}
});

View File

@ -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 = '';

View File

@ -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))