Skip to content

Commit b409b16

Browse files
authored
Merge branch 'master' into master
2 parents 33114a1 + e690e65 commit b409b16

File tree

19 files changed

+517
-41
lines changed

19 files changed

+517
-41
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Apollo 2.1.0
2828
* [add configuration processor for portal developers](https://github.com/apolloconfig/apollo/pull/4521)
2929
* [Add a potential json value check feature](https://github.com/apolloconfig/apollo/pull/4519)
3030
* [Add index for table ReleaseHistory](https://github.com/apolloconfig/apollo/pull/4550)
31+
* [add an option to custom oidc userDisplayName](https://github.com/apolloconfig/apollo/pull/4507)
32+
* [fix openapi item with url illegalKey 400 error](https://github.com/apolloconfig/apollo/pull/4549)
3133

3234
------------------
3335
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/11?closed=1)

apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
import com.ctrip.framework.apollo.common.exception.NotFoundException;
3333
import com.ctrip.framework.apollo.common.utils.BeanUtils;
3434
import com.ctrip.framework.apollo.core.utils.StringUtils;
35-
35+
import java.nio.charset.StandardCharsets;
36+
import java.util.Base64;
3637
import java.util.Collection;
3738
import java.util.Collections;
3839
import java.util.List;
3940
import java.util.Objects;
4041
import java.util.stream.Collectors;
41-
4242
import org.springframework.data.domain.Page;
4343
import org.springframework.data.domain.Pageable;
4444
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -238,6 +238,14 @@ public ItemDTO get(@PathVariable("appId") String appId,
238238
return BeanUtils.transform(ItemDTO.class, item);
239239
}
240240

241+
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key:.+}")
242+
public ItemDTO getByEncodedKey(@PathVariable("appId") String appId,
243+
@PathVariable("clusterName") String clusterName,
244+
@PathVariable("namespaceName") String namespaceName, @PathVariable("key") String key) {
245+
return this.get(appId, clusterName, namespaceName,
246+
new String(Base64.getUrlDecoder().decode(key.getBytes(StandardCharsets.UTF_8))));
247+
}
248+
241249
@GetMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items-with-page")
242250
public PageDTO<ItemDTO> findItemsByNamespace(@PathVariable("appId") String appId,
243251
@PathVariable("clusterName") String clusterName,

apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/url/OpenApiPathBuilder.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
*/
1717
package com.ctrip.framework.apollo.openapi.client.url;
1818

19+
20+
import com.ctrip.framework.apollo.openapi.utils.UrlUtils;
1921
import com.google.common.base.Joiner;
2022
import com.google.common.base.Strings;
2123
import com.google.common.escape.Escaper;
2224
import com.google.common.net.UrlEscapers;
25+
import java.nio.charset.StandardCharsets;
2326
import java.util.ArrayList;
2427
import java.util.Arrays;
28+
import java.util.Base64;
2529
import java.util.HashMap;
2630
import java.util.List;
2731
import java.util.Map;
@@ -34,11 +38,12 @@ public class OpenApiPathBuilder {
3438
private static final String CLUSTERS_PATH = "clusters";
3539
private static final String NAMESPACES_PATH = "namespaces";
3640
private static final String ITEMS_PATH = "items";
41+
private static final String ENCODED_ITEMS_PATH = "encodedItems";
3742
private static final String RELEASE_PATH = "releases";
3843

3944
private final static List<String> SORTED_PATH_KEYS = Arrays.asList(ENVS_PATH, ENV_PATH, APPS_PATH,
4045
CLUSTERS_PATH,
41-
NAMESPACES_PATH, ITEMS_PATH, RELEASE_PATH);
46+
NAMESPACES_PATH, ITEMS_PATH, ENCODED_ITEMS_PATH, RELEASE_PATH);
4247

4348
private static final Escaper PATH_ESCAPER = UrlEscapers.urlPathSegmentEscaper();
4449
private static final Escaper QUERY_PARAM_ESCAPER = UrlEscapers.urlFormParameterEscaper();
@@ -84,7 +89,12 @@ public OpenApiPathBuilder namespacesPathVal(String namespaces) {
8489
}
8590

8691
public OpenApiPathBuilder itemsPathVal(String items) {
87-
pathVariable.put(ITEMS_PATH, escapePath(items));
92+
if (UrlUtils.hasIllegalChar(items)) {
93+
items = new String(Base64.getEncoder().encode(items.getBytes(StandardCharsets.UTF_8)));
94+
pathVariable.put(ENCODED_ITEMS_PATH, escapePath(items));
95+
} else {
96+
pathVariable.put(ITEMS_PATH, escapePath(items));
97+
}
8898
return this;
8999
}
90100

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2022 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.openapi.utils;
18+
19+
import com.google.common.base.Strings;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
/**
24+
* @author Abner
25+
* @since 8/31/22
26+
*/
27+
public final class UrlUtils {
28+
29+
private static final String ILLEGAL_KEY_REGEX = "[/\\\\]+";
30+
private static final Pattern ILLEGAL_KEY_PATTERN = Pattern.compile(ILLEGAL_KEY_REGEX,
31+
Pattern.MULTILINE);
32+
33+
private UrlUtils() {
34+
}
35+
36+
public static boolean hasIllegalChar(String key) {
37+
if (Strings.isNullOrEmpty(key)) {
38+
return false;
39+
}
40+
Matcher matcher = ILLEGAL_KEY_PATTERN.matcher(key);
41+
return matcher.find();
42+
}
43+
}

apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/ApolloOpenApiClientTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
*/
1717
package com.ctrip.framework.apollo.openapi.client;
1818

19-
import static org.junit.Assert.*;
20-
19+
import static org.junit.Assert.assertEquals;
2120
import org.junit.Test;
2221

2322
public class ApolloOpenApiClientTest {

apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/service/ItemOpenApiServiceTest.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
import static org.mockito.Mockito.times;
2222
import static org.mockito.Mockito.verify;
2323
import static org.mockito.Mockito.when;
24-
2524
import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO;
25+
import java.nio.charset.StandardCharsets;
26+
import java.util.Base64;
2627
import org.apache.http.client.methods.HttpDelete;
2728
import org.apache.http.client.methods.HttpGet;
2829
import org.apache.http.client.methods.HttpPost;
@@ -75,6 +76,26 @@ public void testGetItem() throws Exception {
7576
someAppId, someCluster, someNamespace, someKey), get.getURI().toString());
7677
}
7778

79+
@Test
80+
public void testGetItemByIllegalKey() throws Exception {
81+
String someKey = "protocol//:host:port";
82+
83+
final ArgumentCaptor<HttpGet> request = ArgumentCaptor.forClass(HttpGet.class);
84+
85+
itemOpenApiService.getItem(someAppId, someEnv, someCluster, someNamespace, someKey);
86+
87+
verify(httpClient, times(1)).execute(request.capture());
88+
89+
HttpGet get = request.getValue();
90+
91+
assertEquals(String
92+
.format("%s/envs/%s/apps/%s/clusters/%s/namespaces/%s/encodedItems/%s", someBaseUrl,
93+
someEnv,
94+
someAppId, someCluster, someNamespace,
95+
new String(Base64.getEncoder().encode(someKey.getBytes(StandardCharsets.UTF_8)))),
96+
get.getURI().toString());
97+
}
98+
7899
@Test
79100
public void testGetNotExistedItem() throws Exception {
80101
String someKey = "someKey";
@@ -153,6 +174,33 @@ public void testUpdateItem() throws Exception {
153174
someNamespace, someKey), put.getURI().toString());
154175
}
155176

177+
@Test
178+
public void testUpdateItemByIllegalKey() throws Exception {
179+
String someKey = "hello\\world";
180+
String someValue = "someValue";
181+
String someModifiedBy = "someModifiedBy";
182+
183+
OpenItemDTO itemDTO = new OpenItemDTO();
184+
itemDTO.setKey(someKey);
185+
itemDTO.setValue(someValue);
186+
itemDTO.setDataChangeLastModifiedBy(someModifiedBy);
187+
188+
final ArgumentCaptor<HttpPut> request = ArgumentCaptor.forClass(HttpPut.class);
189+
190+
itemOpenApiService.updateItem(someAppId, someEnv, someCluster, someNamespace, itemDTO);
191+
192+
verify(httpClient, times(1)).execute(request.capture());
193+
194+
HttpPut put = request.getValue();
195+
196+
assertEquals(String
197+
.format("%s/envs/%s/apps/%s/clusters/%s/namespaces/%s/encodedItems/%s", someBaseUrl,
198+
someEnv, someAppId, someCluster,
199+
someNamespace,
200+
new String(Base64.getEncoder().encode(someKey.getBytes(StandardCharsets.UTF_8)))),
201+
put.getURI().toString());
202+
}
203+
156204
@Test(expected = RuntimeException.class)
157205
public void testUpdateItemWithError() throws Exception {
158206
String someKey = "someKey";
@@ -227,6 +275,27 @@ public void testRemoveItem() throws Exception {
227275
someAppId, someCluster, someNamespace, someKey, someOperator), delete.getURI().toString());
228276
}
229277

278+
@Test
279+
public void testRemoveItemByIllegalKey() throws Exception {
280+
String someKey = "protocol//:host:port";
281+
String someOperator = "someOperator";
282+
283+
final ArgumentCaptor<HttpDelete> request = ArgumentCaptor.forClass(HttpDelete.class);
284+
285+
itemOpenApiService.removeItem(someAppId, someEnv, someCluster, someNamespace, someKey,
286+
someOperator);
287+
288+
verify(httpClient, times(1)).execute(request.capture());
289+
290+
HttpDelete delete = request.getValue();
291+
292+
assertEquals(
293+
String.format("%s/envs/%s/apps/%s/clusters/%s/namespaces/%s/encodedItems/%s?operator=%s",
294+
someBaseUrl, someEnv, someAppId, someCluster, someNamespace,
295+
new String(Base64.getEncoder().encode(someKey.getBytes(StandardCharsets.UTF_8))),
296+
someOperator), delete.getURI().toString());
297+
}
298+
230299
@Test(expected = RuntimeException.class)
231300
public void testRemoveItemWithError() throws Exception {
232301
String someKey = "someKey";
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2022 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.openapi.client.utils;
18+
19+
import static org.junit.Assert.assertFalse;
20+
import static org.junit.Assert.assertTrue;
21+
22+
import com.ctrip.framework.apollo.openapi.utils.UrlUtils;
23+
import org.junit.Test;
24+
25+
/**
26+
* @author huanghousheng
27+
* @since 9/7/22
28+
*/
29+
public class UrlUtilsTest {
30+
31+
@Test
32+
public void testSlash() {
33+
String someKey = "protocol//:host:port";
34+
assertTrue(UrlUtils.hasIllegalChar(someKey));
35+
}
36+
37+
@Test
38+
public void testBackslash() {
39+
String someKey = "a\\c";
40+
assertTrue(UrlUtils.hasIllegalChar(someKey));
41+
}
42+
43+
@Test
44+
public void testNormalKey() {
45+
String someKey = "someKey";
46+
assertFalse(UrlUtils.hasIllegalChar(someKey));
47+
}
48+
49+
@Test
50+
public void testDot() {
51+
String someKey = "a.b";
52+
assertFalse(UrlUtils.hasIllegalChar(someKey));
53+
}
54+
55+
}

apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/ItemController.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.ctrip.framework.apollo.portal.environment.Env;
2727
import com.ctrip.framework.apollo.portal.service.ItemService;
2828
import com.ctrip.framework.apollo.portal.spi.UserService;
29+
import java.nio.charset.StandardCharsets;
30+
import java.util.Base64;
2931
import org.springframework.security.access.prepost.PreAuthorize;
3032
import org.springframework.validation.annotation.Validated;
3133
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -67,6 +69,14 @@ public OpenItemDTO getItem(@PathVariable String appId, @PathVariable String env,
6769
return this.itemOpenApiService.getItem(appId, env, clusterName, namespaceName, key);
6870
}
6971

72+
@GetMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key:.+}")
73+
public OpenItemDTO getItemByEncodedKey(@PathVariable String appId, @PathVariable String env,
74+
@PathVariable String clusterName,
75+
@PathVariable String namespaceName, @PathVariable String key) {
76+
return this.getItem(appId, env, clusterName, namespaceName,
77+
new String(Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8))));
78+
}
79+
7080
@PreAuthorize(value = "@consumerPermissionValidator.hasModifyNamespacePermission(#request, #appId, #namespaceName, #env)")
7181
@PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
7282
public OpenItemDTO createItem(@PathVariable String appId, @PathVariable String env,
@@ -118,6 +128,17 @@ public void updateItem(@PathVariable String appId, @PathVariable String env,
118128
}
119129
}
120130

131+
@PreAuthorize(value = "@consumerPermissionValidator.hasModifyNamespacePermission(#request, #appId, #namespaceName, #env)")
132+
@PutMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key:.+}")
133+
public void updateItemByEncodedKey(@PathVariable String appId, @PathVariable String env,
134+
@PathVariable String clusterName, @PathVariable String namespaceName,
135+
@PathVariable String key, @RequestBody OpenItemDTO item,
136+
@RequestParam(defaultValue = "false") boolean createIfNotExists, HttpServletRequest request) {
137+
this.updateItem(appId, env, clusterName, namespaceName,
138+
new String(Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8))), item,
139+
createIfNotExists, request);
140+
}
141+
121142
@PreAuthorize(value = "@consumerPermissionValidator.hasModifyNamespacePermission(#request, #appId, #namespaceName, #env)")
122143
@DeleteMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{key:.+}")
123144
public void deleteItem(@PathVariable String appId, @PathVariable String env,
@@ -137,6 +158,17 @@ public void deleteItem(@PathVariable String appId, @PathVariable String env,
137158
this.itemOpenApiService.removeItem(appId, env, clusterName, namespaceName, key, operator);
138159
}
139160

161+
@PreAuthorize(value = "@consumerPermissionValidator.hasModifyNamespacePermission(#request, #appId, #namespaceName, #env)")
162+
@DeleteMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key:.+}")
163+
public void deleteItemByEncodedKey(@PathVariable String appId, @PathVariable String env,
164+
@PathVariable String clusterName, @PathVariable String namespaceName,
165+
@PathVariable String key, @RequestParam String operator,
166+
HttpServletRequest request) {
167+
this.deleteItem(appId, env, clusterName, namespaceName,
168+
new String(Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8))), operator,
169+
request);
170+
}
171+
140172
@GetMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
141173
public OpenPageDTO<OpenItemDTO> findItemsByNamespace(@PathVariable String appId, @PathVariable String env,
142174
@PathVariable String clusterName, @PathVariable String namespaceName,

apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO;
2121
import com.ctrip.framework.apollo.portal.environment.Env;
2222
import com.google.common.base.Joiner;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.Base64;
2325
import org.springframework.boot.actuate.health.Health;
2426
import org.springframework.core.ParameterizedTypeReference;
2527
import org.springframework.http.HttpEntity;
@@ -30,8 +32,11 @@
3032
import org.springframework.util.CollectionUtils;
3133
import org.springframework.util.LinkedMultiValueMap;
3234
import org.springframework.util.MultiValueMap;
33-
34-
import java.util.*;
35+
import java.util.Arrays;
36+
import java.util.Collections;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.Set;
3540

3641

3742
@Service
@@ -187,6 +192,13 @@ public ItemDTO loadItem(Env env, String appId, String clusterName, String namesp
187192
ItemDTO.class, appId, clusterName, namespaceName, key);
188193
}
189194

195+
public ItemDTO loadItemByEncodeKey(Env env, String appId, String clusterName, String namespaceName, String key) {
196+
return restTemplate.get(env,
197+
"apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key}",
198+
ItemDTO.class, appId, clusterName, namespaceName,
199+
new String(Base64.getEncoder().encode(key.getBytes(StandardCharsets.UTF_8))));
200+
}
201+
190202
public ItemDTO loadItemById(Env env, long itemId) {
191203
return restTemplate.get(env, "items/{itemId}", ItemDTO.class, itemId);
192204
}

0 commit comments

Comments
 (0)