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 Depends-On: If201c247210131ce6ab192362eada250a4f51ce1 Change-Id: I48b73a09cad0d707c16df5ca8ada202173779129
This commit is contained in:
parent
c47f218f38
commit
2756e6b503
|
@ -78,7 +78,7 @@ class Router extends Backbone.Router {
|
|||
}},
|
||||
{name: 'welcome', condition: (previousUrl) => {
|
||||
return previousUrl !== 'logout' &&
|
||||
_.find(app.keystoneClient.userRoles, {name: 'admin'}) &&
|
||||
_.find(app.user.get('roles'), {name: 'admin'}) &&
|
||||
!app.fuelSettings.get('statistics.user_choice_saved.value');
|
||||
}}
|
||||
];
|
||||
|
@ -173,11 +173,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() {
|
||||
|
@ -186,27 +182,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()
|
||||
.catch((response) => {
|
||||
if (response.status === 401) {
|
||||
this.version.set({auth_required: true});
|
||||
if (response.status !== 401) {
|
||||
isNailgunAvailable = false;
|
||||
}
|
||||
return Promise.reject(response);
|
||||
})
|
||||
.then(() => {
|
||||
this.user.set({authenticated: !this.version.get('auth_required')});
|
||||
this.user.set({authenticated: true});
|
||||
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 this.keystoneClient.getTokenInfo(this.user.get('token'))
|
||||
.then((tokenInfo) => {
|
||||
this.user.set({
|
||||
id: tokenInfo.token.user.id,
|
||||
roles: tokenInfo.token.roles
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.user.set({authenticated: false});
|
||||
this.user.unset('token');
|
||||
this.user.unset('username');
|
||||
return Promise.reject();
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => this.fuelSettings.fetch())
|
||||
.catch(() => {
|
||||
if (this.version.get('auth_required') && !this.user.get('authenticated')) {
|
||||
if (isNailgunAvailable) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
this.mountNode.empty();
|
||||
|
@ -250,11 +260,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}));
|
||||
|
@ -269,18 +283,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()
|
||||
.catch(() => {
|
||||
app.logout();
|
||||
return Promise.reject();
|
||||
})
|
||||
.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)
|
||||
.catch((response) => {
|
||||
if (response && response.status === 401) {
|
||||
app.logout();
|
||||
|
|
|
@ -91,3 +91,8 @@ export const DEPLOYMENT_GRAPH_LEVELS = [
|
|||
'plugin',
|
||||
'cluster'
|
||||
];
|
||||
|
||||
export const DEFAULT_ADMIN_PASSWORD = 'admin';
|
||||
export const FUEL_PROJECT_NAME = 'admin';
|
||||
export const FUEL_PROJECT_DOMAIN_NAME = 'fuel';
|
||||
export const FUEL_USER_DOMAIN_NAME = 'fuel';
|
||||
|
|
|
@ -16,105 +16,107 @@
|
|||
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;
|
||||
}
|
||||
|
||||
request(url, options = {}) {
|
||||
options.headers = new Headers(_.extend({}, {
|
||||
'Content-Type': 'application/json'
|
||||
}, options.headers));
|
||||
return fetch(this.url + url, options)
|
||||
.then((response) => response.json());
|
||||
return fetch(this.url + url, options).then((response) => {
|
||||
if (!response.ok) throw response;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(username, password, options = {}) {
|
||||
if (this.tokenUpdatePromise) return this.tokenUpdatePromise;
|
||||
authenticate({username, password, projectName, userDomainName, projectDomainName}) {
|
||||
if (this.tokenIssueRequest) return this.tokenIssueRequest;
|
||||
|
||||
if (
|
||||
!options.force &&
|
||||
this.tokenUpdateTime &&
|
||||
(this.cacheTokenFor > (new Date() - this.tokenUpdateTime))
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
var data = {auth: {}};
|
||||
if (username && password) {
|
||||
data.auth.passwordCredentials = {
|
||||
username: username,
|
||||
password: password
|
||||
if (!(username && password)) return Promise.reject();
|
||||
|
||||
var data = {
|
||||
auth: {
|
||||
identity: {
|
||||
methods: ['password'],
|
||||
password: {
|
||||
user: {
|
||||
name: username,
|
||||
password: password,
|
||||
domain: {name: userDomainName}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (projectName) {
|
||||
data.auth.scope = {
|
||||
project: {
|
||||
name: projectName,
|
||||
domain: {name: projectDomainName}
|
||||
}
|
||||
};
|
||||
} else if (this.token) {
|
||||
data.auth.token = {id: this.token};
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
if (this.tenant) {
|
||||
data.auth.tenantName = this.tenant;
|
||||
}
|
||||
this.tokenUpdatePromise = this.request('/v2.0/tokens', {
|
||||
|
||||
this.tokenIssueRequest = this.request('/v3/auth/tokens', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}).then((result) => {
|
||||
this.userId = result.access.user.id;
|
||||
this.userRoles = result.access.user.roles;
|
||||
this.token = result.access.token.id;
|
||||
this.tokenUpdateTime = new Date();
|
||||
}).then((response) => {
|
||||
return response.headers.get('X-Subject-Token');
|
||||
});
|
||||
|
||||
this.tokenUpdatePromise
|
||||
.catch(() => delete this.tokenUpdateTime)
|
||||
.then(() => delete this.tokenUpdatePromise);
|
||||
this.tokenIssueRequest
|
||||
.catch(() => true)
|
||||
.then(() => delete this.tokenIssueRequest);
|
||||
|
||||
return this.tokenUpdatePromise;
|
||||
return this.tokenIssueRequest;
|
||||
}
|
||||
|
||||
changePassword(currentPassword, newPassword) {
|
||||
getTokenInfo(token) {
|
||||
return this.request('/v3/auth/tokens', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Subject-Token': token,
|
||||
'X-Auth-Token': token
|
||||
}
|
||||
}).then((response) => response.json());
|
||||
}
|
||||
|
||||
changePassword(token, userId, currentPassword, newPassword) {
|
||||
var data = {
|
||||
user: {
|
||||
password: newPassword,
|
||||
original_password: currentPassword
|
||||
}
|
||||
};
|
||||
return this.request('/v2.0/OS-KSCRUD/users/' + this.userId, {
|
||||
method: 'PATCH',
|
||||
return this.request('/v3/users/' + userId + '/password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Auth-Token': this.token
|
||||
'X-Auth-Token': token
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then((result) => {
|
||||
this.token = result.access.token.id;
|
||||
this.tokenUpdateTime = new Date();
|
||||
}).then((response) => {
|
||||
return response.headers.get('X-Subject-Token');
|
||||
});
|
||||
}
|
||||
|
||||
deauthenticate() {
|
||||
var token = this.token;
|
||||
|
||||
if (this.tokenUpdatePromise) return this.tokenUpdatePromise;
|
||||
deauthenticate(token) {
|
||||
if (this.tokenRevokeRequest) return this.tokenRevokeRequest;
|
||||
if (!token) return Promise.reject();
|
||||
|
||||
delete this.userId;
|
||||
delete this.userRoles;
|
||||
delete this.token;
|
||||
delete this.tokenUpdateTime;
|
||||
|
||||
this.tokenRemoveRequest = this.request('/v2.0/tokens/' + token, {
|
||||
this.tokenRevokeRequest = this.request('/v3/auth/tokens', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-Auth-Token': this.token
|
||||
'X-Auth-Token': token,
|
||||
'X-Subject-Token': token
|
||||
}
|
||||
});
|
||||
|
||||
this.tokenRemoveRequest
|
||||
this.tokenRevokeRequest
|
||||
.catch(() => true)
|
||||
.then(() => delete this.tokenRemoveRequest);
|
||||
.then(() => delete this.tokenRevokeRequest);
|
||||
|
||||
return this.tokenRemoveRequest;
|
||||
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')
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -741,7 +741,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';
|
||||
|
@ -2190,20 +2195,35 @@ 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)
|
||||
.then(
|
||||
() => {
|
||||
dispatcher.trigger(this.state.newPassword === keystoneClient.DEFAULT_PASSWORD ?
|
||||
'showDefaultPasswordWarning' : 'hideDefaultPasswordWarning');
|
||||
app.user.set({token: keystoneClient.token});
|
||||
this.close();
|
||||
},
|
||||
() => {
|
||||
this.setState({validationError: true, actionInProgress: false});
|
||||
$(this.refs.currentPassword.getInputDOMNode()).focus();
|
||||
}
|
||||
);
|
||||
keystoneClient.changePassword(
|
||||
app.user.get('token'),
|
||||
app.user.get('id'),
|
||||
currentPassword,
|
||||
newPassword
|
||||
).then(
|
||||
() => {
|
||||
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});
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({validationError: true, actionInProgress: false});
|
||||
$(this.refs.currentPassword.getInputDOMNode()).focus();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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})
|
||||
.catch((xhr) => {
|
||||
return keystoneClient.authenticate({
|
||||
username, password,
|
||||
projectName: FUEL_PROJECT_NAME,
|
||||
userDomainName: FUEL_USER_DOMAIN_NAME,
|
||||
projectDomainName: FUEL_PROJECT_DOMAIN_NAME
|
||||
}, {force: true})
|
||||
.catch((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';
|
||||
|
@ -64,19 +72,27 @@ var LoginForm = React.createClass({
|
|||
|
||||
return Promise.reject();
|
||||
})
|
||||
.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 Promise.all([app.version.fetch({cache: true}),
|
||||
app.fuelSettings.fetch({cache: true})]);
|
||||
return Promise.all([
|
||||
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