This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new ea3ad42a2c NIFI-15272 Improved validation for Bitbucket Flow Registry 
Client URL (#10579)
ea3ad42a2c is described below

commit ea3ad42a2c62bdb62b336b7e7db8bff89659b649
Author: dan-s1 <[email protected]>
AuthorDate: Tue Dec 2 12:18:51 2025 -0500

    NIFI-15272 Improved validation for Bitbucket Flow Registry Client URL 
(#10579)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../bitbucket/BitbucketFlowRegistryClient.java     | 25 ++++++-
 .../bitbucket/BitbucketFlowRegistryClientTest.java | 81 ++++++++++++++++++++++
 2 files changed, 105 insertions(+), 1 deletion(-)

diff --git 
a/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClient.java
 
b/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClient.java
index fa56233c1c..dec4c39961 100644
--- 
a/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClient.java
+++ 
b/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/main/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClient.java
@@ -22,6 +22,7 @@ import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.Validator;
 import org.apache.nifi.oauth2.OAuth2AccessTokenProvider;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.registry.flow.FlowRegistryClientConfigurationContext;
@@ -33,6 +34,7 @@ import 
org.apache.nifi.web.client.provider.api.WebClientServiceProvider;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.regex.Pattern;
 
 @Tags({ "atlassian", "bitbucket", "registry", "flow" })
 @CapabilityDescription("Flow Registry Client that uses the Bitbucket REST API 
to version control flows in a Bitbucket Repository.")
@@ -46,10 +48,31 @@ public class BitbucketFlowRegistryClient extends 
AbstractGitFlowRegistryClient {
             .required(true)
             .build();
 
+    private static final Pattern HOST_PATTERN = 
Pattern.compile("(\\[[\\p{XDigit}:.]*[%\\p{Alnum}]*]|[^\\[/?#:]*)");
+    private static final Validator BITBUCKET_API_URL_VALIDATOR = (subject, 
input, validationContext) -> {
+        final ValidationResult.Builder builder = new 
ValidationResult.Builder();
+        builder.subject(subject).input(input);
+
+        if (input == null || input.isBlank()) {
+            builder.explanation("'%s' cannot be 
blank".formatted(subject)).valid(false);
+        } else if (HOST_PATTERN.matcher(input).matches()) {
+            builder.explanation("%s is a valid 
host".formatted(subject)).valid(true);
+        } else {
+            final ValidationResult validationResult = 
StandardValidators.URL_VALIDATOR.validate(subject, input, validationContext);
+            if (validationResult.isValid()) {
+                builder.explanation("%s is a valid 
URL".formatted(subject)).valid(true);
+            } else {
+                builder.explanation("'%s' is neither a host or 
URL".formatted(subject)).valid(false);
+            }
+        }
+
+        return builder.build();
+    };
+
     static final PropertyDescriptor BITBUCKET_API_URL = new 
PropertyDescriptor.Builder()
             .name("Bitbucket API Instance")
             .description("The Bitbucket API host or base URL (for example, 
api.bitbucket.org for Cloud or https://bitbucket.example.com for Data Center)")
-            .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+            .addValidator(BITBUCKET_API_URL_VALIDATOR)
             .defaultValue("api.bitbucket.org")
             .required(true)
             .build();
diff --git 
a/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/test/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClientTest.java
 
b/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/test/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClientTest.java
new file mode 100644
index 0000000000..2cbecb495a
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-atlassian-bundle/nifi-atlassian-extensions/src/test/java/org/apache/nifi/atlassian/bitbucket/BitbucketFlowRegistryClientTest.java
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.nifi.atlassian.bitbucket;
+
+import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
+import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.components.ValidationResult;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class BitbucketFlowRegistryClientTest {
+
+    @Mock
+    private ValidationContext validationContext;
+
+    @ParameterizedTest
+    @MethodSource("apiUrlArgs")
+    void testApiUrl(String input, boolean valid) {
+        when(validationContext.newPropertyValue(input)).thenReturn(new 
StandardPropertyValue(input, null, null));
+        final ValidationResult validationResult = 
BitbucketFlowRegistryClient.BITBUCKET_API_URL.validate(input, 
validationContext);
+
+        if (valid) {
+            assertTrue(validationResult.isValid(), 
validationResult.getExplanation());
+        } else {
+            assertFalse(validationResult.isValid(), 
validationResult.getExplanation());
+        }
+    }
+
+    private static Stream<Arguments> apiUrlArgs() {
+        return Stream.of(
+                Arguments.argumentSet("Valid URL", 
"https://bitbucket.example.com";, true),
+                Arguments.argumentSet("Invalid URL", "https:\\bitbucket", 
false),
+                Arguments.argumentSet("Valid URL with port", 
"http://xxx.xxx.xxx.xxx:7990";, true)
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("apiHostArgs")
+    void testApiHost(String input, boolean valid) {
+        final ValidationResult validationResult = 
BitbucketFlowRegistryClient.BITBUCKET_API_URL.validate(input, 
validationContext);
+
+        if (valid) {
+            assertTrue(validationResult.isValid(), 
validationResult.getExplanation());
+        } else {
+            assertFalse(validationResult.isValid(), 
validationResult.getExplanation());
+        }
+    }
+
+    private static Stream<Arguments> apiHostArgs() {
+        return Stream.of(
+                Arguments.argumentSet("Blank", "", false),
+                Arguments.argumentSet("Valid Host", "api.bitbucket.org", true),
+                Arguments.argumentSet("Invalid Host", "example:com", false)
+        );
+    }
+}

Reply via email to