This is an automated email from the ASF dual-hosted git repository.
rfellows 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 78a68749e64 NIFI-16250 Treat an unconfigured SECRET_REFERENCE as unset
(#11591)
78a68749e64 is described below
commit 78a68749e64b5c32af53886b10d757aa412467b6
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Aug 25 15:40:37 2026 -0400
NIFI-16250 Treat an unconfigured SECRET_REFERENCE as unset (#11591)
A SECRET property with no secret selected can be stored as a
SECRET_REFERENCE carrying only a provider name. Both helpers relied on
buildSecretKey, which never returns an empty string, so the select
showed an unmatched key like "::Some Provider::" and hasPropertyValue
reported the property as configured.
Both now check fullyQualifiedSecretName and secretName directly.
---
.../property-group-card.component.spec.ts | 38 +++++++++++
.../src/services/value-reference.helper.spec.ts | 37 +++++++++++
.../shared/src/services/value-reference.helper.ts | 20 +++---
.../src/utils/connector-validation.utils.spec.ts | 73 ++++++++++++++++++++++
.../shared/src/utils/connector-validation.utils.ts | 5 ++
5 files changed, 166 insertions(+), 7 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
index da84c658280..a94e0e6bf77 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/components/property-group-card/property-group-card.component.spec.ts
@@ -199,6 +199,23 @@ describe('PropertyGroupCard', () => {
expect(component.hasValue('Password')).toBe(true);
});
+ it('should return false for a SECRET_REFERENCE that names a provider
but selects no secret', async () => {
+ const { component } = await setup({
+ propertyGroup: makeGroup({
+ propertyDescriptors: {
+ Password: { name: 'Password', type: 'SECRET',
required: true }
+ },
+ propertyValues: {
+ Password: {
+ valueType: 'SECRET_REFERENCE',
+ secretProviderName: 'Vault'
+ }
+ }
+ })
+ });
+ expect(component.hasValue('Password')).toBe(false);
+ });
+
it('should return false for a SECRET with only a descriptor default
and no saved reference', async () => {
const { component } = await setup({
propertyGroup: makeGroup({
@@ -424,6 +441,27 @@ describe('PropertyGroupCard', () => {
expect(unsetLabels[0].nativeElement.textContent.trim()).toBe('No
value set');
});
+ it('should show "No value set" rather than the mask for a SECRET with
no secret selected', async () => {
+ const { query } = await setup({
+ propertyGroup: makeGroup({
+ propertyDescriptors: {
+ Password: { name: 'Password', type: 'SECRET',
required: true }
+ },
+ propertyValues: {
+ Password: {
+ valueType: 'SECRET_REFERENCE',
+ secretProviderName: 'Vault'
+ }
+ }
+ })
+ });
+ const content = query('mat-card-content');
+ const unsetLabels = content.queryAll(By.css('.unset'));
+ expect(unsetLabels.length).toBe(1);
+ expect(unsetLabels[0].nativeElement.textContent.trim()).toBe('No
value set');
+
expect(content.nativeElement.textContent).not.toContain('••••••••');
+ });
+
it('should display descriptor defaults when property values are
absent', async () => {
const { query } = await setup({
propertyGroup: makeGroup({
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
index df5894ea5a6..93749aa4ca4 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.spec.ts
@@ -44,5 +44,42 @@ describe('Value Reference Helper', () => {
it('preserves a null BOOLEAN value so callers can use the descriptor
default', () => {
expect(fromValueReference({ value: null, valueType:
'STRING_LITERAL' }, 'BOOLEAN')).toBeNull();
});
+
+ describe('SECRET type', () => {
+ it('should return composite key for populated SECRET_REFERENCE',
() => {
+ const valueRef = {
+ valueType: 'SECRET_REFERENCE' as const,
+ secretProviderId: 'provider-123',
+ secretProviderName: 'My Provider',
+ secretName: 'my-secret',
+ fullyQualifiedSecretName: 'My Provider.group.my-secret'
+ };
+
+ const result = fromValueReference(valueRef, 'SECRET');
+
+ expect(result).toBe('provider-123::My Provider::My
Provider.group.my-secret');
+ });
+
+ it('should return undefined for unconfigured SECRET_REFERENCE with
providerName only', () => {
+ const valueRef = {
+ valueType: 'SECRET_REFERENCE' as const,
+ secretProviderName: 'Local Parameter Provider'
+ };
+
+ const result = fromValueReference(valueRef, 'SECRET');
+
+ expect(result).toBeUndefined();
+ });
+
+ it('should return undefined for SECRET_REFERENCE with all null
fields', () => {
+ const valueRef = {
+ valueType: 'SECRET_REFERENCE' as const
+ };
+
+ const result = fromValueReference(valueRef);
+
+ expect(result).toBeUndefined();
+ });
+ });
});
});
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
index 5b677403167..f78a6495878 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/services/value-reference.helper.ts
@@ -172,13 +172,19 @@ export function fromValueReference(
break;
}
case 'SECRET_REFERENCE':
- // Return composite key to uniquely identify the secret
- // Format: providerId::providerName::fullyQualifiedName
- rawValue = buildSecretKey(
- valueRef.secretProviderId,
- valueRef.secretProviderName,
- valueRef.fullyQualifiedSecretName
- );
+ // If no secret has been selected yet (no FQN and no secret
name),
+ // return undefined so the select shows placeholder instead of
"(no longer available)"
+ if (!valueRef.fullyQualifiedSecretName &&
!valueRef.secretName) {
+ rawValue = undefined;
+ } else {
+ // Return composite key to uniquely identify the secret
+ // Format: providerId::providerName::fullyQualifiedName
+ rawValue = buildSecretKey(
+ valueRef.secretProviderId,
+ valueRef.secretProviderName,
+ valueRef.fullyQualifiedSecretName
+ );
+ }
break;
default:
// Fallback for unknown types - try to get value
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.spec.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.spec.ts
new file mode 100644
index 00000000000..42c58edc8b1
--- /dev/null
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.spec.ts
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+import { hasPropertyValue } from './connector-validation.utils';
+import { ConnectorValueReference } from '../types';
+
+describe('hasPropertyValue', () => {
+ describe('SECRET type', () => {
+ it('should return true for secret reference with value', () => {
+ const valueRef: ConnectorValueReference = {
+ valueType: 'SECRET_REFERENCE',
+ secretName: 'my-secret',
+ secretProviderId: 'provider-1',
+ secretProviderName: 'AWS Secrets Manager',
+ fullyQualifiedSecretName: 'group/my-secret'
+ };
+ expect(hasPropertyValue(valueRef, 'SECRET')).toBe(true);
+ });
+
+ it('should return false for unconfigured secret with providerName
only', () => {
+ const valueRef: ConnectorValueReference = {
+ valueType: 'SECRET_REFERENCE',
+ secretProviderName: 'Local Parameter Provider'
+ };
+ expect(hasPropertyValue(valueRef, 'SECRET')).toBe(false);
+ });
+
+ it('should return false for secret reference with all empty strings',
() => {
+ const valueRef: ConnectorValueReference = {
+ valueType: 'SECRET_REFERENCE',
+ secretProviderId: '',
+ secretProviderName: '',
+ fullyQualifiedSecretName: ''
+ };
+ expect(hasPropertyValue(valueRef, 'SECRET')).toBe(false);
+ });
+
+ it('should return true when secretName is set but
fullyQualifiedSecretName is not', () => {
+ const valueRef: ConnectorValueReference = {
+ valueType: 'SECRET_REFERENCE',
+ secretProviderName: 'My Provider',
+ secretName: 'my-secret'
+ };
+ expect(hasPropertyValue(valueRef, 'SECRET')).toBe(true);
+ });
+
+ it('should return false for cleared secret (STRING_LITERAL with
null)', () => {
+ const valueRef: ConnectorValueReference = {
+ valueType: 'STRING_LITERAL',
+ value: null
+ };
+ expect(hasPropertyValue(valueRef, 'SECRET')).toBe(false);
+ });
+
+ it('should return false for undefined value reference', () => {
+ expect(hasPropertyValue(undefined, 'SECRET')).toBe(false);
+ });
+ });
+});
diff --git
a/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
index 15895d54f7b..18450a9e810 100644
---
a/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
+++
b/nifi-frontend/src/main/frontend/libs/shared/src/utils/connector-validation.utils.ts
@@ -48,6 +48,11 @@ export function hasPropertyValue(
if (valueRef.valueType !== 'SECRET_REFERENCE') {
return false;
}
+ // An unconfigured secret may carry a provider name but no secret
selection,
+ // so it has neither a fullyQualifiedSecretName nor a secretName.
Treat as unset.
+ if (!valueRef.fullyQualifiedSecretName && !valueRef.secretName) {
+ return false;
+ }
const secretKey = buildSecretKey(
valueRef.secretProviderId,
valueRef.secretProviderName,