Skip to content

Commit 555e20d

Browse files
Fix URL validation to allow domains starting with 'fd', 'fc', and 'fe80'
Co-authored-by: chirag-madlani <[email protected]>
1 parent 9144699 commit 555e20d

File tree

2 files changed

+122
-2
lines changed

2 files changed

+122
-2
lines changed

openmetadata-service/src/main/java/org/openmetadata/service/util/URLValidator.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class URLValidator {
3232
private static final List<String> ALLOWED_SCHEMES = Arrays.asList("http", "https");
3333
private static final Pattern PRIVATE_IP_PATTERN =
3434
Pattern.compile(
35-
"^(127\\.|10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|169\\.254\\.|::1|[fF][cCdD]|[fF][eE][80-9a-fA-F]:).*");
35+
"^(127\\.|10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|169\\.254\\.|\\[?::1\\]?|\\[?[fF][cCdD][0-9a-fA-F]{0,2}:|\\[?[fF][eE][80-9a-bA-B][0-9a-fA-F]:).*");
3636

3737
public static void validateURL(String urlString) {
3838
if (urlString == null || urlString.trim().isEmpty()) {
@@ -64,6 +64,11 @@ public static void validateURL(String urlString) {
6464
throw new BadRequestException("URL scheme not allowed: " + protocol);
6565
}
6666

67-
return url.getHost().toLowerCase();
67+
String host = url.getHost();
68+
if (host == null || host.trim().isEmpty()) {
69+
throw new BadRequestException("URL must have a valid host");
70+
}
71+
72+
return host.toLowerCase();
6873
}
6974
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2024 Collate.
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
14+
package org.openmetadata.service.util;
15+
16+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
17+
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
19+
import jakarta.ws.rs.BadRequestException;
20+
import org.junit.jupiter.api.Test;
21+
22+
class URLValidatorTest {
23+
24+
@Test
25+
void testValidPublicUrls() {
26+
assertDoesNotThrow(() -> URLValidator.validateURL("https://example.com"));
27+
assertDoesNotThrow(() -> URLValidator.validateURL("http://example.com"));
28+
assertDoesNotThrow(
29+
() ->
30+
URLValidator.validateURL(
31+
"https://dev-96705996-admin.okta.com/.well-known/openid-configuration"));
32+
assertDoesNotThrow(() -> URLValidator.validateURL("https://api.github.com/repos"));
33+
}
34+
35+
@Test
36+
void testValidOktaUrlsWithFdPrefix() {
37+
assertDoesNotThrow(
38+
() -> URLValidator.validateURL("https://fdxxx.okta.com/.well-known/openid-configuration"));
39+
assertDoesNotThrow(
40+
() -> URLValidator.validateURL("https://fd123.okta.com/.well-known/openid-configuration"));
41+
assertDoesNotThrow(
42+
() ->
43+
URLValidator.validateURL("https://fd-test.okta.com/.well-known/openid-configuration"));
44+
assertDoesNotThrow(() -> URLValidator.validateURL("https://fddomain.example.com/api"));
45+
}
46+
47+
@Test
48+
void testValidUrlsWithFcPrefix() {
49+
assertDoesNotThrow(() -> URLValidator.validateURL("https://fcdomain.com/api"));
50+
assertDoesNotThrow(() -> URLValidator.validateURL("https://fc123.example.com"));
51+
}
52+
53+
@Test
54+
void testValidUrlsWithFe80Prefix() {
55+
assertDoesNotThrow(() -> URLValidator.validateURL("https://fe80-test.com/api"));
56+
assertDoesNotThrow(() -> URLValidator.validateURL("https://fe80domain.example.com"));
57+
}
58+
59+
@Test
60+
void testPrivateIpv4AddressesBlocked() {
61+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://127.0.0.1"));
62+
assertThrows(
63+
BadRequestException.class, () -> URLValidator.validateURL("http://127.0.0.1:8080/api"));
64+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://10.0.0.1"));
65+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://10.1.2.3:80"));
66+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://172.16.0.1"));
67+
assertThrows(
68+
BadRequestException.class, () -> URLValidator.validateURL("http://172.31.255.255"));
69+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://192.168.1.1"));
70+
assertThrows(
71+
BadRequestException.class, () -> URLValidator.validateURL("http://192.168.0.1:3000"));
72+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://169.254.1.1"));
73+
assertThrows(
74+
BadRequestException.class, () -> URLValidator.validateURL("http://169.254.169.254"));
75+
}
76+
77+
@Test
78+
void testPrivateIpv6AddressesBlocked() {
79+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[::1]"));
80+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[::1]:8080"));
81+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fc00::]"));
82+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fc00::1]"));
83+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fcaa::1]"));
84+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fd00::]"));
85+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fd00::1]"));
86+
assertThrows(
87+
BadRequestException.class, () -> URLValidator.validateURL("http://[fd12:3456:789a::]"));
88+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fe80::]"));
89+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fe80::1]"));
90+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[fe8a::1]"));
91+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://[feb0::1]"));
92+
}
93+
94+
@Test
95+
void testInvalidUrlSchemes() {
96+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("ftp://example.com"));
97+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("file:///etc/passwd"));
98+
assertThrows(
99+
BadRequestException.class, () -> URLValidator.validateURL("javascript:alert('xss')"));
100+
}
101+
102+
@Test
103+
void testEmptyAndNullUrls() {
104+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL(null));
105+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL(""));
106+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL(" "));
107+
}
108+
109+
@Test
110+
void testMalformedUrls() {
111+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("not a url"));
112+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("http://"));
113+
assertThrows(BadRequestException.class, () -> URLValidator.validateURL("://example.com"));
114+
}
115+
}

0 commit comments

Comments
 (0)