Use Keystone V3 API
This commit introduces support of V3 API and also removes token regeneration every 1 hour which is totally unnecessary. Also, token is now only stored in User model and passed to the Keystone client via arguments, so there is no more two sources of truth. User id and roles are also stored in User model now. Partial-Bug: #1628445 Closes-Bug: #1618172 Closes-Bug: #1656822 Depends-On: If201c247210131ce6ab192362eada250a4f51ce1 Change-Id: I48b73a09cad0d707c16df5ca8ada202173779129
This commit is contained in:
parent
5586bfaad4
commit
917dd0835a
|
@ -170,11 +170,7 @@ class App {
|
|||
this.statistics = new models.NodesStatistics();
|
||||
this.notifications = new models.Notifications();
|
||||
this.releases = new models.Releases();
|
||||
this.keystoneClient = new KeystoneClient('/keystone', {
|
||||
cacheTokenFor: 10 * 60 * 1000,
|
||||
tenant: 'admin',
|
||||
token: this.user.get('token')
|
||||
});
|
||||
this.keystoneClient = new KeystoneClient('/keystone');
|
||||
}
|
||||
|
||||
initialize() {
|
||||
|
@ -183,28 +179,41 @@ class App {
|
|||
|
||||
document.title = i18n('common.title');
|
||||
|
||||
this.version.set({auth_required: true});
|
||||
this.user.set({authenticated: false});
|
||||
|
||||
var isNailgunAvailable = true;
|
||||
|
||||
return this.version.fetch()
|
||||
.then(null, (response) => {
|
||||
if (response.status === 401) {
|
||||
this.version.set({auth_required: true});
|
||||
if (response.status !== 401) {
|
||||
isNailgunAvailable = false;
|
||||
}
|
||||
return $.Deferred().reject(response);
|
||||
})
|
||||
.then(() => {
|
||||
this.user.set({authenticated: true});
|
||||
if (this.version.get('auth_required')) {
|
||||
return this.keystoneClient.getTokenInfo(this.user.get('token'))
|
||||
.done((tokenInfo) => {
|
||||
this.user.set({
|
||||
id: tokenInfo.token.user.id,
|
||||
roles: tokenInfo.token.roles
|
||||
});
|
||||
})
|
||||
.fail(() => {
|
||||
this.user.set({authenticated: false});
|
||||
this.user.unset('token');
|
||||
this.user.unset('username');
|
||||
return $.Deferred().reject();
|
||||
});
|
||||
} else {
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.user.set({authenticated: !this.version.get('auth_required')});
|
||||
if (this.version.get('auth_required')) {
|
||||
this.keystoneClient.token = this.user.get('token');
|
||||
return this.keystoneClient.authenticate()
|
||||
.then(() => {
|
||||
this.user.set({authenticated: true});
|
||||
return this.version.fetch({cache: true});
|
||||
});
|
||||
}
|
||||
return $.Deferred().resolve();
|
||||
})
|
||||
.then(() => this.fuelSettings.fetch())
|
||||
.then(null, () => {
|
||||
if (this.version.get('auth_required') && !this.user.get('authenticated')) {
|
||||
if (isNailgunAvailable) {
|
||||
return $.Deferred().resolve();
|
||||
} else {
|
||||
this.mountNode.empty();
|
||||
|
@ -246,11 +255,15 @@ class App {
|
|||
|
||||
logout() {
|
||||
if (this.user.get('authenticated') && this.version.get('auth_required')) {
|
||||
this.user.set('authenticated', false);
|
||||
this.user.unset('username');
|
||||
this.user.unset('token');
|
||||
var token = this.user.get('token');
|
||||
|
||||
this.keystoneClient.deauthenticate();
|
||||
this.user.set('authenticated', false);
|
||||
this.user.unset('token');
|
||||
this.user.unset('username');
|
||||
this.user.unset('id');
|
||||
this.user.unset('roles');
|
||||
|
||||
this.keystoneClient.deauthenticate(token);
|
||||
}
|
||||
|
||||
_.defer(() => this.navigate('login', {trigger: true, replace: true}));
|
||||
|
@ -265,15 +278,10 @@ class App {
|
|||
method = 'update';
|
||||
}
|
||||
// add auth token to header if auth is enabled
|
||||
if (app.version && app.version.get('auth_required')) {
|
||||
return app.keystoneClient.authenticate()
|
||||
.fail(() => app.logout())
|
||||
.then(() => {
|
||||
app.user.set('token', app.keystoneClient.token);
|
||||
options.headers = options.headers || {};
|
||||
options.headers['X-Auth-Token'] = app.keystoneClient.token;
|
||||
return originalSyncMethod.call(this, method, model, options);
|
||||
})
|
||||
if (app.version.get('auth_required')) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers['X-Auth-Token'] = app.user.get('token');
|
||||
return originalSyncMethod.call(this, method, model, options)
|
||||
.fail((response) => {
|
||||
if (response && response.status === 401) {
|
||||
app.logout();
|
||||
|
|
|
@ -91,3 +91,8 @@ export var DEPLOYMENT_GRAPH_LEVELS = [
|
|||
'plugin',
|
||||
'cluster'
|
||||
];
|
||||
|
||||
export var DEFAULT_ADMIN_PASSWORD = 'admin';
|
||||
export var FUEL_PROJECT_NAME = 'admin';
|
||||
export var FUEL_PROJECT_DOMAIN_NAME = 'fuel';
|
||||
export var FUEL_USER_DOMAIN_NAME = 'fuel';
|
||||
|
|
|
@ -17,102 +17,98 @@ import $ from 'jquery';
|
|||
import _ from 'underscore';
|
||||
|
||||
class KeystoneClient {
|
||||
constructor(url, options) {
|
||||
this.DEFAULT_PASSWORD = 'admin';
|
||||
_.extend(this, {
|
||||
url: url,
|
||||
cacheTokenFor: 10 * 60 * 1000
|
||||
}, options);
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
authenticate(username, password, options = {}) {
|
||||
if (this.tokenUpdateRequest) return this.tokenUpdateRequest;
|
||||
request(url, options = {}) {
|
||||
return $.ajax(
|
||||
this.url + url,
|
||||
_.extend({}, {
|
||||
dataType: 'json',
|
||||
contentType: 'application/json'
|
||||
}, options)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!options.force &&
|
||||
this.tokenUpdateTime &&
|
||||
(this.cacheTokenFor > (new Date() - this.tokenUpdateTime))
|
||||
) {
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
var data = {auth: {}};
|
||||
if (username && password) {
|
||||
data.auth.passwordCredentials = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
} else if (this.token) {
|
||||
data.auth.token = {id: this.token};
|
||||
} else {
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
if (this.tenant) {
|
||||
data.auth.tenantName = this.tenant;
|
||||
}
|
||||
this.tokenUpdateRequest = $.ajax(this.url + '/v2.0/tokens', {
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(data)
|
||||
}).then((result, state, deferred) => {
|
||||
try {
|
||||
this.userId = result.access.user.id;
|
||||
this.token = result.access.token.id;
|
||||
this.tokenUpdateTime = new Date();
|
||||
return deferred;
|
||||
} catch (e) {
|
||||
return $.Deferred().reject();
|
||||
authenticate({username, password, projectName, userDomainName, projectDomainName}) {
|
||||
if (this.tokenIssueRequest) return this.tokenIssueRequest;
|
||||
|
||||
if (!(username && password)) return $.Deferred().reject();
|
||||
|
||||
var data = {
|
||||
auth: {
|
||||
identity: {
|
||||
methods: ['password'],
|
||||
password: {
|
||||
user: {
|
||||
name: username,
|
||||
password: password,
|
||||
domain: {name: userDomainName}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(() => delete this.tokenUpdateTime)
|
||||
.always(() => delete this.tokenUpdateRequest);
|
||||
};
|
||||
if (projectName) {
|
||||
data.auth.scope = {
|
||||
project: {
|
||||
name: projectName,
|
||||
domain: {name: projectDomainName}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return this.tokenUpdateRequest;
|
||||
this.tokenIssueRequest = this.request('/v3/auth/tokens', {
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
.then((response, status, xhr) => xhr.getResponseHeader('x-subject-token'))
|
||||
.always(() => delete this.tokenIssueRequest);
|
||||
|
||||
return this.tokenIssueRequest;
|
||||
}
|
||||
|
||||
changePassword(currentPassword, newPassword) {
|
||||
getTokenInfo(token) {
|
||||
return this.request('/v3/auth/tokens', {
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Subject-Token': token,
|
||||
'X-Auth-Token': token
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changePassword(token, userId, currentPassword, newPassword) {
|
||||
var data = {
|
||||
user: {
|
||||
password: newPassword,
|
||||
original_password: currentPassword
|
||||
}
|
||||
};
|
||||
return $.ajax(this.url + '/v2.0/OS-KSCRUD/users/' + this.userId, {
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
|
||||
return this.request('/v3/users/' + userId + '/password', {
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
headers: {'X-Auth-Token': this.token}
|
||||
}).then((result, state, deferred) => {
|
||||
try {
|
||||
this.token = result.access.token.id;
|
||||
this.tokenUpdateTime = new Date();
|
||||
return deferred;
|
||||
} catch (e) {
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deauthenticate() {
|
||||
var token = this.token;
|
||||
|
||||
if (this.tokenUpdateRequest) return this.tokenUpdateRequest;
|
||||
if (!token) return $.Deferred().reject();
|
||||
|
||||
delete this.userId;
|
||||
delete this.token;
|
||||
delete this.tokenUpdateTime;
|
||||
|
||||
this.tokenRemoveRequest = $.ajax(this.url + '/v2.0/tokens/' + token, {
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
headers: {'X-Auth-Token': token}
|
||||
})
|
||||
.always(() => delete this.tokenRemoveRequest);
|
||||
.then((response, status, xhr) => xhr.getResponseHeader('x-subject-token'));
|
||||
}
|
||||
|
||||
return this.tokenRemoveRequest;
|
||||
deauthenticate(token) {
|
||||
if (this.tokenRevokeRequest) return this.tokenRevokeRequest;
|
||||
if (!token) return $.Deferred().reject();
|
||||
|
||||
this.tokenRevokeRequest = this.request('/v3/auth/tokens', {
|
||||
type: 'DELETE',
|
||||
headers: {
|
||||
'X-Auth-Token': token,
|
||||
'X-Subject-Token': token
|
||||
}
|
||||
})
|
||||
.always(() => delete this.tokenRevokeRequest);
|
||||
|
||||
return this.tokenRevokeRequest;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ var LogsTab = React.createClass({
|
|||
dataType: 'json',
|
||||
data: _.extend(_.omit(this.props.selectedLogs, 'type'), data),
|
||||
headers: {
|
||||
'X-Auth-Token': app.keystoneClient.token
|
||||
'X-Auth-Token': app.user.get('token')
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -754,7 +754,7 @@ export var DownloadFileButton = React.createClass({
|
|||
url,
|
||||
data: fetchOptions,
|
||||
dataType: 'text',
|
||||
headers: _.extend({'X-Auth-Token': app.keystoneClient.token}, headers)
|
||||
headers: _.extend({'X-Auth-Token': app.user.get('token')}, headers)
|
||||
})
|
||||
.then(
|
||||
(response) => this.saveFile(response),
|
||||
|
|
|
@ -20,7 +20,12 @@ import i18n from 'i18n';
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Backbone from 'backbone';
|
||||
import {NODE_LIST_SORTERS, NODE_LIST_FILTERS, DEPLOYMENT_TASK_ATTRIBUTES} from 'consts';
|
||||
import {
|
||||
NODE_LIST_SORTERS, NODE_LIST_FILTERS,
|
||||
DEPLOYMENT_TASK_ATTRIBUTES,
|
||||
DEFAULT_ADMIN_PASSWORD,
|
||||
FUEL_PROJECT_NAME, FUEL_PROJECT_DOMAIN_NAME, FUEL_USER_DOMAIN_NAME
|
||||
} from 'consts';
|
||||
import utils from 'utils';
|
||||
import models from 'models';
|
||||
import dispatcher from 'dispatcher';
|
||||
|
@ -2227,13 +2232,29 @@ export var ChangePasswordDialog = React.createClass({
|
|||
changePassword() {
|
||||
if (this.isPasswordChangeAvailable()) {
|
||||
var keystoneClient = app.keystoneClient;
|
||||
var {currentPassword, newPassword} = this.state;
|
||||
this.setState({actionInProgress: true});
|
||||
keystoneClient.changePassword(this.state.currentPassword, this.state.newPassword)
|
||||
keystoneClient.changePassword(
|
||||
app.user.get('token'),
|
||||
app.user.get('id'),
|
||||
currentPassword,
|
||||
newPassword
|
||||
)
|
||||
.done(() => {
|
||||
dispatcher.trigger(this.state.newPassword === keystoneClient.DEFAULT_PASSWORD ?
|
||||
'showDefaultPasswordWarning' : 'hideDefaultPasswordWarning');
|
||||
app.user.set({token: keystoneClient.token});
|
||||
dispatcher.trigger(
|
||||
this.state.newPassword === DEFAULT_ADMIN_PASSWORD ?
|
||||
'showDefaultPasswordWarning' : 'hideDefaultPasswordWarning'
|
||||
);
|
||||
this.close();
|
||||
keystoneClient.authenticate({
|
||||
username: app.user.get('username'),
|
||||
password: newPassword,
|
||||
projectName: FUEL_PROJECT_NAME,
|
||||
userDomainName: FUEL_USER_DOMAIN_NAME,
|
||||
projectDomainName: FUEL_PROJECT_DOMAIN_NAME
|
||||
}).then((token) => {
|
||||
app.user.set({token});
|
||||
});
|
||||
})
|
||||
.fail(() => {
|
||||
this.setState({validationError: true, actionInProgress: false});
|
||||
|
|
|
@ -20,6 +20,9 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import utils from 'utils';
|
||||
import dispatcher from 'dispatcher';
|
||||
import {
|
||||
DEFAULT_ADMIN_PASSWORD, FUEL_PROJECT_NAME, FUEL_PROJECT_DOMAIN_NAME, FUEL_USER_DOMAIN_NAME
|
||||
} from 'consts';
|
||||
|
||||
var LoginPage = React.createClass({
|
||||
statics: {
|
||||
|
@ -48,11 +51,16 @@ var LoginForm = React.createClass({
|
|||
login(username, password) {
|
||||
var keystoneClient = app.keystoneClient;
|
||||
|
||||
return keystoneClient.authenticate(username, password, {force: true})
|
||||
.fail((xhr) => {
|
||||
return keystoneClient.authenticate({
|
||||
username, password,
|
||||
projectName: FUEL_PROJECT_NAME,
|
||||
userDomainName: FUEL_USER_DOMAIN_NAME,
|
||||
projectDomainName: FUEL_PROJECT_DOMAIN_NAME
|
||||
}, {force: true})
|
||||
.fail((response) => {
|
||||
$(ReactDOM.findDOMNode(this.refs.username)).focus();
|
||||
|
||||
var status = xhr && xhr.status;
|
||||
var status = response && response.status;
|
||||
var error = 'login_error';
|
||||
if (status === 401) {
|
||||
error = 'credentials_error';
|
||||
|
@ -62,18 +70,27 @@ var LoginForm = React.createClass({
|
|||
}
|
||||
this.setState({error: i18n('login_page.' + error)});
|
||||
})
|
||||
.then(() => {
|
||||
.then((token) => {
|
||||
app.user.set({
|
||||
authenticated: true,
|
||||
username: username,
|
||||
token: keystoneClient.token
|
||||
username,
|
||||
token
|
||||
});
|
||||
|
||||
if (password === keystoneClient.DEFAULT_PASSWORD) {
|
||||
if (password === DEFAULT_ADMIN_PASSWORD) {
|
||||
dispatcher.trigger('showDefaultPasswordWarning');
|
||||
}
|
||||
|
||||
return $.when(app.version.fetch({cache: true}), app.fuelSettings.fetch({cache: true}));
|
||||
return $.when(
|
||||
app.version.fetch({cache: true}),
|
||||
app.fuelSettings.fetch({cache: true}),
|
||||
keystoneClient.getTokenInfo(token).then((tokenInfo) => {
|
||||
app.user.set({
|
||||
id: tokenInfo.token.user.id,
|
||||
roles: tokenInfo.token.roles
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
var nextUrl = '';
|
||||
|
|
Loading…
Reference in New Issue