Merge "KeystoneApiService error handling"

This commit is contained in:
Jenkins 2017-08-16 00:57:25 +00:00 committed by Gerrit Code Review
commit 24facd7a39
6 changed files with 114 additions and 221 deletions

View File

@ -16,11 +16,10 @@
import { Map, fromJS } from 'immutable';
import KeystoneApiErrorHandler from '../services/KeystoneApiErrorHandler';
import KeystoneApiService from '../services/KeystoneApiService';
import LoginConstants from '../constants/LoginConstants';
import ZaqarWebSocketService from '../services/ZaqarWebSocketService';
import logger from '../services/logging/LoggingService';
import ZaqarWebSocketService from '../services/ZaqarWebSocketService';
import cookie from 'react-cookie';
export default {
@ -28,19 +27,28 @@ export default {
return (dispatch, getState) => {
dispatch(this.userAuthStarted());
KeystoneApiService.authenticateUserViaToken(keystoneAuthTokenId)
.then(result => {
const tokenId = result.request.getResponseHeader('X-Subject-Token');
.then(response => {
const {
data: { token },
headers: { 'x-subject-token': tokenId }
} = response;
cookie.save('keystoneAuthTokenId', tokenId, { path: '/' });
dispatch(this.userAuthSuccess(tokenId, result.response.token));
dispatch(this.userAuthSuccess(tokenId, token));
})
.catch(error => {
logger.error(
'Error in LoginActions.authenticateUserViaToken',
error.stack || error
dispatch(
this.userAuthFailure([
{
title: 'Unauthorized',
message: error.message
}
])
);
logger.error(
'Could not authenticate user via token',
error,
error.stack
);
let errorHandler = new KeystoneApiErrorHandler(error);
cookie.remove('keystoneAuthTokenId');
dispatch(this.userAuthFailure(errorHandler.errors));
});
};
},
@ -49,23 +57,24 @@ export default {
return (dispatch, getState) => {
dispatch(this.userAuthStarted());
KeystoneApiService.authenticateUser(formData.username, formData.password)
.then(result => {
const tokenId = result.request.getResponseHeader('X-Subject-Token');
.then(response => {
const {
data: { token },
headers: { 'x-subject-token': tokenId }
} = response;
cookie.save('keystoneAuthTokenId', tokenId, { path: '/' });
dispatch(this.userAuthSuccess(tokenId, result.response.token));
dispatch(this.userAuthSuccess(tokenId, token));
})
.catch(error => {
logger.error(
'Error in LoginActions.authenticateUser',
error.stack || error
);
let errorHandler = new KeystoneApiErrorHandler(error, formFields);
dispatch(
this.userAuthFailure(
errorHandler.errors,
errorHandler.formFieldErrors
)
this.userAuthFailure([
{
title: 'Unauthorized',
message: error.message
}
])
);
logger.error('Could not authenticate user', error, error.stack);
});
};
},

View File

@ -1,59 +0,0 @@
/**
* Copyright 2017 Red Hat 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.
*/
/**
* @class
* @classdesc Implements base for API and Form error handling
*/
export default class BaseHttpRequestErrorHandler {
constructor(xmlHttpRequestError, formInputFieldNames) {
this.xmlHttpRequestError = xmlHttpRequestError;
this.formInputFieldNames = formInputFieldNames || [];
this._errors = this._generateErrors(this.xmlHttpRequestError);
this._formFieldErrors = this._generateFormFieldErrors(
this.xmlHttpRequestError,
this.formInputFieldNames
);
}
/**
Generates errors
@param {object} xmlHttpRequestError - The Error object
@returns {array} array of error objects with type, title and message properties
*/
_generateErrors(xmlHttpRequestError) {
return [];
}
/**
Generates form field frrors
@param {object} xmlHttpRequestError - The Error object
@param {array} formInputFieldNames - array of strings with form field names
@returns {object} object with with form field names as keys and error messages as
values. e.g. {'username': 'Username does not exist'}
*/
_generateFormFieldErrors(xmlHttpRequestError, formInputFieldNames) {
return {};
}
get errors() {
return this._errors;
}
get formFieldErrors() {
return this._formFieldErrors;
}
}

View File

@ -16,7 +16,5 @@
import { getAppConfig } from '../services/utils';
let HOST = location.protocol + '//' + location.hostname;
let KEYSTONE_URL = getAppConfig().keystone || HOST + ':5000/v3';
export const AUTH_URL = KEYSTONE_URL + '/auth/tokens';
const host = location.protocol + '//' + location.hostname;
export const KEYSTONE_URL = getAppConfig().keystone || host + ':5000/v3';

View File

@ -1,61 +0,0 @@
/**
* Copyright 2017 Red Hat 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 BaseHttpRequestErrorHandler
from '../components/utils/BaseHttpRequestErrorHandler';
import LoginActions from '../actions/LoginActions';
import store from '../store';
export default class KeystoneApiErrorHandler
extends BaseHttpRequestErrorHandler {
_generateErrors(errorObj) {
let errors = [];
// A weak check to find out if it's not an xhr object.
if (!errorObj.status && errorObj.message) {
errors.push({
title: 'Error',
message: errorObj.message
});
return errors;
}
switch (errorObj.status) {
case 0:
errors.push({
title: 'Connection Error',
message: 'Connection to Keystone is not available'
});
break;
case 401: {
let error = JSON.parse(errorObj.responseText).error;
errors.push({
title: 'Unauthorized',
message: error.message
});
store.dispatch(LoginActions.logoutUser());
break;
}
default:
break;
}
return errors;
}
// TODO(jtomasek): remove this, I am leaving this here just for example reasons
// this function should be implemented by form related subclass.
_generateFormFieldErrors() {
return {};
}
}

View File

@ -14,99 +14,98 @@
* under the License.
*/
import * as _ from 'lodash';
import request from 'reqwest';
import axios from 'axios';
import when from 'when';
import { AUTH_URL } from '../constants/KeystoneApiConstants';
import {
AuthenticationError,
KeystoneApiError,
ConnectionError
} from './errors';
import { KEYSTONE_URL } from '../constants/KeystoneApiConstants';
class KeystoneApiService {
defaultRequest(additionalAttributes) {
return _.merge(
{
url: AUTH_URL,
method: 'POST',
crossOrigin: true,
contentType: 'application/json',
type: 'json'
},
additionalAttributes
return axios(
Object.assign(
{
baseURL: KEYSTONE_URL,
url: '/auth/tokens',
method: 'POST'
},
additionalAttributes
)
);
}
authenticateUser(username, password) {
let req = request(
this.defaultRequest({
data: JSON.stringify({
auth: {
identity: {
methods: ['password'],
password: {
user: {
name: username,
domain: {
name: 'Default'
},
password: password
}
}
},
scope: {
project: {
name: 'admin',
return this.defaultRequest({
data: {
auth: {
identity: {
methods: ['password'],
password: {
user: {
name: username,
domain: {
name: 'Default'
}
},
password: password
}
}
},
scope: {
project: {
name: 'admin',
domain: {
name: 'Default'
}
}
}
})
})
);
// We're passing the req object to the next handler in the chain so that we
// can inspect response headers later.
return when(req, response => {
return {
request: req.request,
response
};
});
}
}
}).catch(handleErrors);
}
authenticateUserViaToken(keystoneAuthTokenId) {
let req = request(
this.defaultRequest({
data: JSON.stringify({
auth: {
identity: {
methods: ['token'],
token: {
id: keystoneAuthTokenId
}
},
scope: {
project: {
name: 'admin',
domain: {
name: 'Default'
}
return this.defaultRequest({
data: {
auth: {
identity: {
methods: ['token'],
token: {
id: keystoneAuthTokenId
}
},
scope: {
project: {
name: 'admin',
domain: {
name: 'Default'
}
}
}
})
})
);
// We're passing the req object to the next handler in the chain so that we
// can inspect response headers later.
return when(req, response => {
return {
request: req.request,
response
};
});
}
}
}).catch(handleErrors);
}
}
const handleErrors = e => {
if (e.response && e.response.status === 401) {
return when.reject(new AuthenticationError(e));
} else if (e.response) {
return when.reject(new KeystoneApiError(e));
} else if (e.request) {
return when.reject(
new ConnectionError(
'Connection to Keystone API could not be established',
e
)
);
} else {
return when.reject(e);
}
};
export default new KeystoneApiService();

View File

@ -27,7 +27,7 @@ class BaseAxiosError extends ExtendableError {
class AuthenticationError extends BaseAxiosError {
constructor(e) {
super('Authentication failed, please log in', e);
super('Authentication failed, please log in again', e);
}
}
@ -72,6 +72,12 @@ class HeatApiError extends BaseAxiosError {
}
}
class KeystoneApiError extends BaseAxiosError {
constructor(e) {
super(e.response.data.error.message, e);
}
}
export {
BaseAxiosError,
AuthenticationError,
@ -81,5 +87,6 @@ export {
SwiftApiError,
IronicApiError,
IronicInspectorApiError,
HeatApiError
HeatApiError,
KeystoneApiError
};