Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions server/src/main/java/com/cloud/api/ApiServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
if (!DefaultForgotPasswordAPIAuthenticatorCmd.APINAME.equalsIgnoreCase(command) || StringUtils.isNotBlank(username)) {
LOGGER.debug("Authentication failure: {}", e.getMessage());
}
} catch (RuntimeException e) {
if (apiAuthenticator.getAPIType() == APIAuthenticationType.LOGIN_API) {
invalidateHttpSession(session, "invalidating session after unexpected login failure");
}
throw e;
}

if (apiAuthenticator.getAPIType() == APIAuthenticationType.LOGOUT_API) {
Expand Down Expand Up @@ -426,6 +431,11 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
} catch (final Exception ex) {
LOGGER.error("unknown exception writing api response", ex);
auditTrailSb.append(" unknown exception writing api response");
if (!resp.isCommitted()) {
final String serializedResponseText = apiServer.getSerializedApiError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Internal error processing the request", params, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponseText, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, responseType, ApiServer.JSONContentType.value());
}
} finally {
ACCESSLOGGER.info(auditTrailSb.toString());
if (LOGGER.isDebugEnabled()) {
Expand Down
47 changes: 47 additions & 0 deletions server/src/test/java/com/cloud/api/ApiServletTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,53 @@ public void processRequestInContextLogin() throws UnknownHostException {
Mockito.eq(InetAddress.getByName("127.0.0.1")), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletRequest.class), Mockito.isA(HttpServletResponse.class));
}

@SuppressWarnings("unchecked")
@Test
public void processRequestInContextLoginUnexpectedExceptionInvalidatesSessionAndReturnsError() throws UnknownHostException {
Mockito.when(request.getMethod()).thenReturn("POST");
Mockito.when(request.getSession(false)).thenReturn(null);
Mockito.when(request.getSession(true)).thenReturn(session);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(ApiConstants.COMMAND, new String[] { "login" });
params.put(ApiConstants.USERNAME, new String[] { "TEST" });
params.put(ApiConstants.PASSWORD, new String[] { "TEST-PWD" });
Mockito.when(request.getParameterMap()).thenReturn(params);
Mockito.when(authenticator.getAPIType()).thenReturn(APIAuthenticationType.LOGIN_API);
Mockito.when(authenticator.authenticate(Mockito.anyString(), Mockito.anyMap(), Mockito.isA(HttpSession.class),
Mockito.eq(InetAddress.getByName("127.0.0.1")), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletRequest.class), Mockito.isA(HttpServletResponse.class)))
.thenThrow(new NullPointerException("test"));
Mockito.when(apiServer.getSerializedApiError(Mockito.eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), Mockito.anyString(), Mockito.anyMap(), Mockito.anyString()))
.thenReturn("{\"errorresponse\":{}}");

servlet.processRequestInContext(request, response);

Mockito.verify(session).invalidate();
Mockito.verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
Assert.assertEquals("{\"errorresponse\":{}}", responseWriter.toString());
}

@SuppressWarnings("unchecked")
@Test
public void processRequestInContextNonLoginUnexpectedExceptionKeepsSessionAndReturnsError() throws UnknownHostException {
Mockito.when(request.getMethod()).thenReturn("POST");
Mockito.when(request.getSession(Mockito.anyBoolean())).thenReturn(session);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(ApiConstants.COMMAND, new String[] { "login" });
Mockito.when(request.getParameterMap()).thenReturn(params);
Mockito.when(authenticator.getAPIType()).thenReturn(APIAuthenticationType.READONLY_API);
Mockito.when(authenticator.authenticate(Mockito.anyString(), Mockito.anyMap(), Mockito.isA(HttpSession.class),
Mockito.eq(InetAddress.getByName("127.0.0.1")), Mockito.anyString(), Mockito.isA(StringBuilder.class), Mockito.isA(HttpServletRequest.class), Mockito.isA(HttpServletResponse.class)))
.thenThrow(new NullPointerException("test"));
Mockito.when(apiServer.getSerializedApiError(Mockito.eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), Mockito.anyString(), Mockito.anyMap(), Mockito.anyString()))
.thenReturn("{\"errorresponse\":{}}");

servlet.processRequestInContext(request, response);

Mockito.verify(session, Mockito.never()).invalidate();
Mockito.verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
Assert.assertEquals("{\"errorresponse\":{}}", responseWriter.toString());
}

@Test
public void getClientAddressWithXForwardedFor() throws UnknownHostException {
setConfigValue("proxy.header.verify", "true");
Expand Down
17 changes: 16 additions & 1 deletion ui/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,22 @@ vueApp.use(backupUtilPlugin)
vueApp.use(extensions)
vueApp.use(directives)

vueProps.$localStorage.keys().forEach(key => {
try {
vueProps.$localStorage.get(key)
} catch (e) {
console.warn('Removing unreadable local storage entry: ' + key)
vueProps.$localStorage.remove(key)
}
})

let configLoaded = false

const renderError = (err) => {
console.error('Fatal error during app initialization: ', err)
const details = configLoaded
? 'The application failed to start: ' + err + '. Check the browser console for more information.'
: 'Missing or malformed config.json. Please ensure the file is present, accessible, and contains valid JSON. Check the browser console for more information.'
const ErrorComponent = {
render: () => h(
'div',
Expand All @@ -81,7 +95,7 @@ const renderError = (err) => {
h('summary', { style: 'cursor: pointer;' }, 'Technical details'),
h('pre', {
style: 'text-align: left; display: inline-block; margin-top: 10px;'
}, 'Missing or malformed config.json. Please ensure the file is present, accessible, and contains valid JSON. Check the browser console for more information.')
}, details)
])
]
)
Expand All @@ -97,6 +111,7 @@ fetch('config.json?ts=' + Date.now())
return response.json()
})
.then(async config => {
configLoaded = true
vueProps.$config = config
let baseUrl = config.apiBase
if (config.multipleServer) {
Expand Down
5 changes: 4 additions & 1 deletion ui/src/store/modules/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ function setLoginCookies (result) {
}

function processLoginResponse (commit, response) {
const result = response.loginresponse || {}
const result = response?.loginresponse || {}
if (!result.sessionkey) {
throw new Error('Login response not valid.')
}
setLoginCookies(result)
vueProps.$localStorage.set(ACCESS_TOKEN, result.sessionkey, 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.sessionkey)
Expand Down
Loading