26
26
import org .apache .zookeeper .KeeperException ;
27
27
import org .apache .zookeeper .data .Id ;
28
28
import org .apache .zookeeper .server .ServerCnxn ;
29
+ import org .slf4j .Logger ;
30
+ import org .slf4j .LoggerFactory ;
29
31
30
32
public class IPAuthenticationProvider implements AuthenticationProvider {
33
+ private static final Logger LOG = LoggerFactory .getLogger (IPAuthenticationProvider .class );
31
34
public static final String X_FORWARDED_FOR_HEADER_NAME = "X-Forwarded-For" ;
32
35
33
36
public static final String USE_X_FORWARDED_FOR_KEY = "zookeeper.IPAuthenticationProvider.usexforwardedfor" ;
37
+ private static final int IPV6_BYTE_LENGTH = 16 ; // IPv6 address is 128 bits = 16 bytes
38
+ private static final int IPV6_SEGMENT_COUNT = 8 ; // IPv6 address has 8 segments
39
+ private static final int IPV6_SEGMENT_HEX_LENGTH = 4 ; // Each segment has up to 4 hex digits
40
+
34
41
35
42
public String getScheme () {
36
43
return "ip" ;
@@ -55,9 +62,16 @@ public List<Id> handleAuthentication(HttpServletRequest request, byte[] authData
55
62
// This is a bit weird but we need to return the address and the number of
56
63
// bytes (to distinguish between IPv4 and IPv6
57
64
private byte [] addr2Bytes (String addr ) {
58
- byte [] b = v4addr2Bytes (addr );
59
- // TODO Write the v6addr2Bytes
60
- return b ;
65
+ if (addr .contains (":" )) {
66
+ LOG .info ("Attempting to parse as IPv6..." );
67
+ return v6addr2Bytes (addr );
68
+ } else if (addr .contains ("." )) {
69
+ LOG .info ("Attempting to parse as IPv4..." );
70
+ return v4addr2Bytes (addr );
71
+ } else {
72
+ LOG .info ("Input string does not resemble an IPv4 or IPv6 address: {}" , addr );
73
+ return null ;
74
+ }
61
75
}
62
76
63
77
private byte [] v4addr2Bytes (String addr ) {
@@ -81,6 +95,132 @@ private byte[] v4addr2Bytes(String addr) {
81
95
return b ;
82
96
}
83
97
98
+ /**
99
+ * Validates an IPv6 address string and converts it into a byte array.
100
+ *
101
+ * @param ipv6Addr The IPv6 address string to validate.
102
+ * @return A byte array representing the IPv6 address if valid, or null if the address
103
+ * is invalid or cannot be parsed.
104
+ */
105
+ public static byte [] v6addr2Bytes (String ipv6Addr ) {
106
+ if (ipv6Addr == null || ipv6Addr .trim ().isEmpty ()) {
107
+ LOG .info ("Input IPv6 address cannot be null or empty." );
108
+ return null ;
109
+ }
110
+
111
+ // Check for multiple '::' which is invalid
112
+ if (ipv6Addr .indexOf ("::" ) != ipv6Addr .lastIndexOf ("::" )) {
113
+ LOG .info ("IPv6 address contains multiple '::' which is invalid: {}" , ipv6Addr );
114
+ return null ;
115
+ }
116
+
117
+ // Split the address by "::" to handle zero compression, -1 to keep trailing empty strings
118
+ String [] parts = ipv6Addr .split ("::" , -1 );
119
+
120
+ String [] segments1 = new String [0 ];
121
+ String [] segments2 = new String [0 ];
122
+
123
+ // Case 1: No "::" (full address)
124
+ if (parts .length == 1 ) {
125
+ segments1 = parts [0 ].split (":" );
126
+ if (segments1 .length != IPV6_SEGMENT_COUNT ) {
127
+ LOG .info ("IPv6 address without '::' must have " + IPV6_SEGMENT_COUNT + " segments: {}" , ipv6Addr );
128
+ return null ;
129
+ }
130
+ } else if (parts .length == 2 ) {
131
+ // Case 2: "::" is present
132
+ // Handle cases like "::1" or "1::"
133
+ if (!parts [0 ].isEmpty ()) {
134
+ segments1 = parts [0 ].split (":" );
135
+ }
136
+ if (!parts [1 ].isEmpty ()) {
137
+ segments2 = parts [1 ].split (":" );
138
+ }
139
+
140
+ // Check if the total number of explicit segments exceeds 8
141
+ if (segments1 .length + segments2 .length >= IPV6_SEGMENT_COUNT ) {
142
+ LOG .info ("Too many segments in IPv6 address with '::': {}" , ipv6Addr );
143
+ return null ;
144
+ }
145
+ } else {
146
+ // Case 3: Invalid number of parts after splitting by "::" (should be 1 or 2)
147
+ LOG .info ("Invalid IPv6 address format (unexpected '::' split result): {}" , ipv6Addr );
148
+ return null ;
149
+ }
150
+
151
+ List <Byte > byteList = new ArrayList <>();
152
+
153
+ try {
154
+ // Process segments before "::"
155
+ for (String segment : segments1 ) {
156
+ if (isInvalidSegment (segment )) {
157
+ LOG .info ("Invalid IPv6 segment: '{}' in address: {}" , segment , ipv6Addr );
158
+ return null ;
159
+ }
160
+ int value = Integer .parseInt (segment , 16 );
161
+ byteList .add ((byte ) ((value >> 8 ) & 0xFF ));
162
+ byteList .add ((byte ) (value & 0xFF ));
163
+ }
164
+
165
+ // Add zero segments for "::" compression
166
+ int missingSegments = IPV6_SEGMENT_COUNT - (segments1 .length + segments2 .length );
167
+ for (int i = 0 ; i < missingSegments ; i ++) {
168
+ byteList .add ((byte ) 0x00 );
169
+ byteList .add ((byte ) 0x00 );
170
+ }
171
+
172
+ // Process segments after "::"
173
+ for (String segment : segments2 ) {
174
+ if (isInvalidSegment (segment )) {
175
+ LOG .info ("Invalid IPv6 segment: '{}' in address: {}" , segment , ipv6Addr );
176
+ return null ;
177
+ }
178
+ int value = Integer .parseInt (segment , 16 );
179
+ byteList .add ((byte ) ((value >> 8 ) & 0xFF ));
180
+ byteList .add ((byte ) (value & 0xFF ));
181
+ }
182
+
183
+ } catch (NumberFormatException e ) {
184
+ // 3. Catch NumberFormatException if String cannot be parsed
185
+ LOG .info ("Invalid hexadecimal format in IPv6 address: {} - {}" , ipv6Addr , e .getMessage ());
186
+ return null ;
187
+ }
188
+
189
+ // 4. Return null if address in out of bounds (i.e., not exactly 16 bytes)
190
+ if (byteList .size () != IPV6_BYTE_LENGTH ) {
191
+ LOG .info ("Parsed IPv6 address byte length is incorrect. Expected " + IPV6_BYTE_LENGTH + ", got {}: {}" , byteList .size (), ipv6Addr );
192
+ return null ;
193
+ }
194
+
195
+ // Convert List<Byte> to byte[]
196
+ byte [] result = new byte [IPV6_BYTE_LENGTH ];
197
+ for (int i = 0 ; i < byteList .size (); i ++) {
198
+ result [i ] = byteList .get (i );
199
+ }
200
+
201
+ return result ;
202
+ }
203
+
204
+ /**
205
+ * Checks if a single IPv6 segment is valid.
206
+ * A valid segment is a non-empty string of 1 to 4 hexadecimal characters.
207
+ *
208
+ * @param segment The segment string to validate.
209
+ * @return true if the segment is valid, false otherwise.
210
+ */
211
+ private static boolean isInvalidSegment (String segment ) {
212
+ if (segment == null || segment .isEmpty () || segment .length () > IPV6_SEGMENT_HEX_LENGTH ) {
213
+ return true ;
214
+ }
215
+ // Check if all characters are valid hexadecimal digits
216
+ for (char c : segment .toCharArray ()) {
217
+ if (!Character .isDigit (c ) && (c < 'a' || c > 'f' ) && (c < 'A' || c > 'F' )) {
218
+ return true ;
219
+ }
220
+ }
221
+ return false ;
222
+ }
223
+
84
224
private void mask (byte [] b , int bits ) {
85
225
int start = bits / 8 ;
86
226
int startMask = (1 << (8 - (bits % 8 ))) - 1 ;
@@ -93,6 +233,7 @@ private void mask(byte[] b, int bits) {
93
233
}
94
234
95
235
public boolean matches (String id , String aclExpr ) {
236
+ LOG .trace ("id: '{}' aclExpr: {}" , id , aclExpr );
96
237
String [] parts = aclExpr .split ("/" , 2 );
97
238
byte [] aclAddr = addr2Bytes (parts [0 ]);
98
239
if (aclAddr == null ) {
@@ -112,11 +253,13 @@ public boolean matches(String id, String aclExpr) {
112
253
mask (aclAddr , bits );
113
254
byte [] remoteAddr = addr2Bytes (id );
114
255
if (remoteAddr == null ) {
256
+ LOG .info ("Address is null" );
115
257
return false ;
116
258
}
117
259
mask (remoteAddr , bits );
118
260
for (int i = 0 ; i < remoteAddr .length ; i ++) {
119
261
if (remoteAddr [i ] != aclAddr [i ]) {
262
+ LOG .info ("ACL Expr and id are not identical" );
120
263
return false ;
121
264
}
122
265
}
0 commit comments