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 {
|
return {
|
||||||
r'/nodes/?$': ['POST'],
|
r'/nodes/?$': ['POST'],
|
||||||
r'/nodes/agent/?$': ['PUT'],
|
r'/nodes/agent/?$': ['PUT'],
|
||||||
r'/version/?$': ['GET']
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from mock import patch
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
from nailgun.test.base import BaseAuthenticationIntegrationTest
|
from nailgun.test.base import BaseAuthenticationIntegrationTest
|
||||||
@ -47,37 +46,3 @@ class TestPublicHandlers(BaseAuthenticationIntegrationTest):
|
|||||||
headers=self.default_headers)
|
headers=self.default_headers)
|
||||||
|
|
||||||
self.assertEqual(201, resp.status_code)
|
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)
|
|
||||||
|
82
nailgun/nailgun/test/integration/test_version_api.py
Normal file
82
nailgun/nailgun/test/integration/test_version_api.py
Normal 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)
|
@ -153,29 +153,35 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
|
|||||||
this.mountNode = $('#main-container');
|
this.mountNode = $('#main-container');
|
||||||
|
|
||||||
this.router = new Router();
|
this.router = new Router();
|
||||||
this.keystoneClient = new KeystoneClient('/keystone', {
|
|
||||||
cacheTokenFor: 10 * 60 * 1000,
|
|
||||||
tenant: 'admin'
|
|
||||||
});
|
|
||||||
this.version = new models.FuelVersion();
|
this.version = new models.FuelVersion();
|
||||||
this.settings = new models.FuelSettings();
|
this.settings = new models.FuelSettings();
|
||||||
this.user = new models.User();
|
this.user = new models.User();
|
||||||
this.statistics = new models.NodesStatistics();
|
this.statistics = new models.NodesStatistics();
|
||||||
this.notifications = new models.Notifications();
|
this.notifications = new models.Notifications();
|
||||||
|
this.keystoneClient = new KeystoneClient('/keystone', {
|
||||||
|
cacheTokenFor: 10 * 60 * 1000,
|
||||||
|
tenant: 'admin',
|
||||||
|
token: this.user.get('token')
|
||||||
|
});
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
_.extend(App.prototype, {
|
_.extend(App.prototype, {
|
||||||
fetchData: function() {
|
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.user.set({authenticated: !this.version.get('auth_required')});
|
||||||
this.patchBackboneSync();
|
this.patchBackboneSync();
|
||||||
if (this.version.get('auth_required')) {
|
if (this.version.get('auth_required')) {
|
||||||
_.extend(this.keystoneClient, this.user.pick('token'));
|
_.extend(this.keystoneClient, this.user.pick('token'));
|
||||||
return this.keystoneClient.authenticate()
|
return this.keystoneClient.authenticate()
|
||||||
.done(_.bind(function() {
|
.then(_.bind(function() {
|
||||||
this.user.set({authenticated: true});
|
this.user.set({authenticated: true});
|
||||||
|
return this.version.fetch({cache: true});
|
||||||
}, this));
|
}, this));
|
||||||
}
|
}
|
||||||
return $.Deferred().resolve();
|
return $.Deferred().resolve();
|
||||||
@ -233,7 +239,7 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
|
|||||||
if (method == 'patch') {
|
if (method == 'patch') {
|
||||||
method = 'update';
|
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
|
// FIXME(vkramskikh): manually moving success/error callbacks
|
||||||
// to deferred-style callbacks. Everywhere in the code we use
|
// to deferred-style callbacks. Everywhere in the code we use
|
||||||
// deferreds, but backbone uses success/error callbacks. It
|
// deferreds, but backbone uses success/error callbacks. It
|
||||||
@ -249,6 +255,7 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
|
|||||||
app.logout();
|
app.logout();
|
||||||
})
|
})
|
||||||
.then(_.bind(function() {
|
.then(_.bind(function() {
|
||||||
|
app.user.set('token', app.keystoneClient.token);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
options.headers = options.headers || {};
|
options.headers = options.headers || {};
|
||||||
options.headers['X-Auth-Token'] = app.keystoneClient.token;
|
options.headers['X-Auth-Token'] = app.keystoneClient.token;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
**/
|
**/
|
||||||
define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
define(['jquery', 'underscore'], function($, _) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function KeystoneClient(url, options) {
|
function KeystoneClient(url, options) {
|
||||||
@ -57,9 +57,6 @@ define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
|||||||
this.userId = result.access.user.id;
|
this.userId = result.access.user.id;
|
||||||
this.token = result.access.token.id;
|
this.token = result.access.token.id;
|
||||||
this.tokenUpdateTime = new Date();
|
this.tokenUpdateTime = new Date();
|
||||||
|
|
||||||
Cookies.set('token', result.access.token.id);
|
|
||||||
|
|
||||||
return deferred;
|
return deferred;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
@ -88,9 +85,6 @@ define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
|||||||
try {
|
try {
|
||||||
this.token = result.access.token.id;
|
this.token = result.access.token.id;
|
||||||
this.tokenUpdateTime = new Date();
|
this.tokenUpdateTime = new Date();
|
||||||
|
|
||||||
Cookies.set('token', result.access.token.id);
|
|
||||||
|
|
||||||
return deferred;
|
return deferred;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
@ -111,8 +105,6 @@ define(['jquery', 'underscore', 'js-cookie'], function($, _, Cookies) {
|
|||||||
delete this.token;
|
delete this.token;
|
||||||
delete this.tokenUpdateTime;
|
delete this.tokenUpdateTime;
|
||||||
|
|
||||||
Cookies.remove('token');
|
|
||||||
|
|
||||||
this.tokenRemoveRequest = $.ajax(this.url + '/v2.0/tokens/' + token, {
|
this.tokenRemoveRequest = $.ajax(this.url + '/v2.0/tokens/' + token, {
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
@ -22,8 +22,9 @@ define([
|
|||||||
'expression',
|
'expression',
|
||||||
'expression/objects',
|
'expression/objects',
|
||||||
'jsx!views/custom_controls',
|
'jsx!views/custom_controls',
|
||||||
|
'js-cookie',
|
||||||
'deepModel'
|
'deepModel'
|
||||||
], function($, _, i18n, Backbone, utils, Expression, expressionObjects, customControls) {
|
], function($, _, i18n, Backbone, utils, Expression, expressionObjects, customControls, Cookies) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var models = {};
|
var models = {};
|
||||||
@ -83,6 +84,7 @@ define([
|
|||||||
if (this.cacheFor && options && options.cache && this.lastSyncTime && (this.cacheFor > (new Date() - this.lastSyncTime))) {
|
if (this.cacheFor && options && options.cache && this.lastSyncTime && (this.cacheFor > (new Date() - this.lastSyncTime))) {
|
||||||
return $.Deferred().resolve();
|
return $.Deferred().resolve();
|
||||||
}
|
}
|
||||||
|
if (options) delete options.cache;
|
||||||
return this._super('fetch', arguments);
|
return this._super('fetch', arguments);
|
||||||
},
|
},
|
||||||
sync: function() {
|
sync: function() {
|
||||||
@ -998,10 +1000,10 @@ define([
|
|||||||
urlRoot: '/api/ostf'
|
urlRoot: '/api/ostf'
|
||||||
});
|
});
|
||||||
|
|
||||||
models.FuelVersion = BaseModel.extend({
|
models.FuelVersion = BaseModel.extend(cacheMixin).extend({
|
||||||
|
cacheFor: 60 * 1000,
|
||||||
constructorName: 'FuelVersion',
|
constructorName: 'FuelVersion',
|
||||||
urlRoot: '/api/version',
|
urlRoot: '/api/version'
|
||||||
authExempt: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
models.User = BaseModel.extend({
|
models.User = BaseModel.extend({
|
||||||
@ -1021,6 +1023,14 @@ define([
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, this);
|
}, 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 />
|
<LoginForm />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -71,7 +65,7 @@ function($, _, i18n, React, dispatcher, utils) {
|
|||||||
dispatcher.trigger('showDefaultPasswordWarning');
|
dispatcher.trigger('showDefaultPasswordWarning');
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.settings.fetch({cache: true});
|
return $.when(app.version.fetch({cache: true}), app.settings.fetch({cache: true}));
|
||||||
}, this))
|
}, this))
|
||||||
.done(_.bind(function() {
|
.done(_.bind(function() {
|
||||||
var nextUrl = '';
|
var nextUrl = '';
|
||||||
|
@ -630,7 +630,7 @@ function run_server() {
|
|||||||
|
|
||||||
local http_code=$(curl -s -w %{http_code} -o /dev/null $check_url)
|
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
|
sleep 0.1
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
|
Loading…
Reference in New Issue
Block a user