Skip to content

Commit fdb1c14

Browse files
committed
UI: submit object storage creation using POST
1 parent 5328528 commit fdb1c14

6 files changed

Lines changed: 115 additions & 4 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmd.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import java.util.Map;
3333

3434
@APICommand(name = "addObjectStoragePool", description = "Adds a object storage pool", responseObject = ObjectStoreResponse.class, since = "4.19.0",
35-
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
35+
requestHasSensitiveInfo = true, responseHasSensitiveInfo = false)
3636
public class AddObjectStoragePoolCmd extends BaseCmd {
3737

3838
/////////////////////////////////////////////////////

api/src/test/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmdTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.cloud.exception.DiscoveryException;
2222
import com.cloud.storage.StorageService;
23+
import org.apache.cloudstack.api.APICommand;
2324
import org.apache.cloudstack.api.ResponseGenerator;
2425
import org.apache.cloudstack.api.response.ObjectStoreResponse;
2526
import org.apache.cloudstack.context.CallContext;
@@ -38,6 +39,8 @@
3839
import java.util.HashMap;
3940
import java.util.Map;
4041

42+
import static org.junit.Assert.assertNotNull;
43+
import static org.junit.Assert.assertTrue;
4144
import static org.mockito.ArgumentMatchers.any;
4245

4346
@RunWith(MockitoJUnitRunner.class)
@@ -98,4 +101,12 @@ public void testAddObjectStore() throws DiscoveryException {
98101
Mockito.verify(storageService, Mockito.times(1))
99102
.discoverObjectStore(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
100103
}
104+
105+
@Test
106+
public void testRequestIsMarkedAsContainingSensitiveInformation() {
107+
APICommand apiCommand = AddObjectStoragePoolCmd.class.getAnnotation(APICommand.class);
108+
109+
assertNotNull(apiCommand);
110+
assertTrue(apiCommand.requestHasSensitiveInfo());
111+
}
101112
}

server/src/main/java/com/cloud/api/ApiServlet.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public class ApiServlet extends HttpServlet {
100100
"updaterolepermission",
101101
"updateprojectrolepermission",
102102
"createstoragepool",
103+
"addobjectstoragepool",
103104
"addhost",
104105
"updatehostpassword",
105106
"addcluster",
@@ -241,7 +242,7 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp
241242
if (LOGGER.isDebugEnabled()) {
242243
reqStr = auditTrailSb.toString() + " " + cleanQueryString;
243244
if (req.getMethod().equalsIgnoreCase("POST") && org.apache.commons.lang3.StringUtils.isNotBlank(command)) {
244-
if (!POST_REQUESTS_TO_DISABLE_LOGGING.contains(command.toLowerCase()) && !reqParams.containsKey(ApiConstants.USER_DATA)) {
245+
if (shouldLogPostRequestParameters(command, reqParams)) {
245246
String cleanParamsString = getCleanParamsString(reqParams);
246247
if (org.apache.commons.lang3.StringUtils.isNotBlank(cleanParamsString)) {
247248
reqStr += "\n" + cleanParamsString;
@@ -771,4 +772,8 @@ private String getCleanParamsString(Map<String, String[]> reqParams) {
771772

772773
return cleanParamsString.toString();
773774
}
775+
776+
protected boolean shouldLogPostRequestParameters(String command, Map<String, String[]> reqParams) {
777+
return !POST_REQUESTS_TO_DISABLE_LOGGING.contains(command.toLowerCase()) && !reqParams.containsKey(ApiConstants.USER_DATA);
778+
}
774779
}

server/src/test/java/com/cloud/api/ApiServletTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,4 +461,28 @@ public void testVerify2FAWhenExpectedCommandIsNotCalled() throws UnknownHostExce
461461

462462
Assert.assertEquals(false, result);
463463
}
464+
465+
@Test
466+
public void shouldNotLogPostRequestParametersForAddObjectStoragePool() {
467+
boolean result = servlet.shouldLogPostRequestParameters("addObjectStoragePool", new HashMap<>());
468+
469+
Assert.assertFalse(result);
470+
}
471+
472+
@Test
473+
public void shouldLogPostRequestParametersForCommandWithoutSensitiveParameters() {
474+
boolean result = servlet.shouldLogPostRequestParameters("listZones", new HashMap<>());
475+
476+
Assert.assertTrue(result);
477+
}
478+
479+
@Test
480+
public void shouldNotLogPostRequestParametersContainingUserData() {
481+
Map<String, String[]> params = new HashMap<>();
482+
params.put(ApiConstants.USER_DATA, new String[] {"sensitive-user-data"});
483+
484+
boolean result = servlet.shouldLogPostRequestParameters("deployVirtualMachine", params);
485+
486+
Assert.assertFalse(result);
487+
}
464488
}

ui/src/views/infra/AddObjectStorage.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
</template>
107107
<script>
108108
import { ref, reactive, toRaw } from 'vue'
109-
import { getAPI } from '@/api'
109+
import { postAPI } from '@/api'
110110
import { mixinForm } from '@/utils/mixin'
111111
import ResourceIcon from '@/components/view/ResourceIcon'
112112
import TooltipLabel from '@/components/widgets/TooltipLabel'
@@ -209,7 +209,7 @@ export default {
209209
},
210210
addObjectStore (params) {
211211
return new Promise((resolve, reject) => {
212-
getAPI('addObjectStoragePool', params).then(json => {
212+
postAPI('addObjectStoragePool', params).then(json => {
213213
resolve()
214214
}).catch(error => {
215215
reject(error)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import mockAxios from '../../../mock/mockAxios'
19+
import AddObjectStorage from '@/views/infra/AddObjectStorage'
20+
21+
jest.mock('axios', () => mockAxios)
22+
jest.mock('@/vue-app', () => ({
23+
vueProps: {
24+
$localStorage: {
25+
get: jest.fn(() => null)
26+
}
27+
}
28+
}))
29+
30+
describe('Views > infra > AddObjectStorage.vue', () => {
31+
beforeEach(() => {
32+
mockAxios.mockReset()
33+
})
34+
35+
it('submits addObjectStoragePool using POST with the request in the body', async () => {
36+
mockAxios.mockResolvedValue({})
37+
38+
const params = {
39+
name: 'test-store',
40+
provider: 'MinIO',
41+
url: 'https://object-storage.example.test',
42+
'details[0].key': 'accesskey',
43+
'details[0].value': 'test-access-key',
44+
'details[1].key': 'secretkey',
45+
'details[1].value': 'test-secret-key'
46+
}
47+
48+
await AddObjectStorage.methods.addObjectStore(params)
49+
50+
expect(mockAxios).toHaveBeenCalledTimes(1)
51+
const request = mockAxios.mock.calls[0][0]
52+
expect(request).toMatchObject({
53+
url: '/',
54+
method: 'POST'
55+
})
56+
expect(request.params).toBeUndefined()
57+
expect(request.data).toBeInstanceOf(URLSearchParams)
58+
expect(request.data.get('command')).toBe('addObjectStoragePool')
59+
expect(request.data.get('response')).toBe('json')
60+
expect(request.data.get('url')).toBe(params.url)
61+
expect(request.data.get('details[0].value')).toBe(params['details[0].value'])
62+
expect(request.data.get('details[1].value')).toBe(params['details[1].value'])
63+
})
64+
65+
it('propagates an addObjectStoragePool API failure', async () => {
66+
const error = new Error('request failed')
67+
mockAxios.mockRejectedValue(error)
68+
69+
await expect(AddObjectStorage.methods.addObjectStore({})).rejects.toBe(error)
70+
})
71+
})

0 commit comments

Comments
 (0)