NIFI-13606 Corrected Custom UI loading in reverse proxy deployments

This closes #9130

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Matt Gilman 2024-07-31 10:10:04 -04:00 committed by exceptionfactory
parent 2542927b53
commit 6c930626dc
No known key found for this signature in database
6 changed files with 52 additions and 21 deletions

View File

@ -154,6 +154,21 @@ public abstract class ApplicationResource {
return buildResourceUri(uriBuilder.replacePath(ROOT_PATH).build()); return buildResourceUri(uriBuilder.replacePath(ROOT_PATH).build());
} }
/**
* Generate a URI to an external UI.
*
* @param pathSegments path segments for the external UI
* @return the full external UI
*/
protected String generateExternalUiUri(final String... pathSegments) {
final RequestUriBuilder builder = RequestUriBuilder.fromHttpServletRequest(httpServletRequest, properties.getAllowedContextPathsAsList());
final String path = String.join("/", pathSegments);
builder.path(path);
return builder.build().toString();
}
private URI buildResourceUri(final String... path) { private URI buildResourceUri(final String... path) {
final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
return buildResourceUri(uriBuilder.segment(path).build()); return buildResourceUri(uriBuilder.segment(path).build());

View File

@ -156,7 +156,7 @@ public class ControllerServiceResource extends ApplicationResource {
final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(controllerService.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion()); final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(controllerService.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion());
for (final UiExtension uiExtension : uiExtensions) { for (final UiExtension uiExtension : uiExtensions) {
if (UiExtensionType.ControllerServiceConfiguration.equals(uiExtension.getExtensionType())) { if (UiExtensionType.ControllerServiceConfiguration.equals(uiExtension.getExtensionType())) {
controllerService.setCustomUiUrl(uiExtension.getContextPath() + "/configure"); controllerService.setCustomUiUrl(generateExternalUiUri(uiExtension.getContextPath(), "configure"));
} }
} }
} }

View File

@ -217,7 +217,7 @@ public class ParameterProviderResource extends AbstractParameterResource {
final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(parameterProvider.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion()); final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(parameterProvider.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion());
for (final UiExtension uiExtension : uiExtensions) { for (final UiExtension uiExtension : uiExtensions) {
if (UiExtensionType.ParameterProviderConfiguration.equals(uiExtension.getExtensionType())) { if (UiExtensionType.ParameterProviderConfiguration.equals(uiExtension.getExtensionType())) {
parameterProvider.setCustomUiUrl(uiExtension.getContextPath() + "/configure"); parameterProvider.setCustomUiUrl(generateExternalUiUri(uiExtension.getContextPath(), "configure"));
} }
} }
} }

View File

@ -165,21 +165,15 @@ public class ProcessorResource extends ApplicationResource {
// get the config details and see if there is a custom ui for this processor type // get the config details and see if there is a custom ui for this processor type
ProcessorConfigDTO config = processor.getConfig(); ProcessorConfigDTO config = processor.getConfig();
if (config != null) { if (config != null) {
// consider legacy custom ui fist final BundleDTO bundle = processor.getBundle();
String customUiUrl = servletContext.getInitParameter(processor.getType());
if (StringUtils.isNotBlank(customUiUrl)) {
config.setCustomUiUrl(customUiUrl);
} else {
final BundleDTO bundle = processor.getBundle();
// see if this processor has any ui extensions // see if this processor has any ui extensions
final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions"); final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions");
if (uiExtensionMapping.hasUiExtension(processor.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion())) { if (uiExtensionMapping.hasUiExtension(processor.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion())) {
final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(processor.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion()); final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(processor.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion());
for (final UiExtension uiExtension : uiExtensions) { for (final UiExtension uiExtension : uiExtensions) {
if (UiExtensionType.ProcessorConfiguration.equals(uiExtension.getExtensionType())) { if (UiExtensionType.ProcessorConfiguration.equals(uiExtension.getExtensionType())) {
config.setCustomUiUrl(uiExtension.getContextPath() + "/configure"); config.setCustomUiUrl(generateExternalUiUri(uiExtension.getContextPath(), "configure"));
}
} }
} }
} }

View File

@ -149,7 +149,7 @@ public class ReportingTaskResource extends ApplicationResource {
final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(reportingTask.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion()); final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(reportingTask.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion());
for (final UiExtension uiExtension : uiExtensions) { for (final UiExtension uiExtension : uiExtensions) {
if (UiExtensionType.ReportingTaskConfiguration.equals(uiExtension.getExtensionType())) { if (UiExtensionType.ReportingTaskConfiguration.equals(uiExtension.getExtensionType())) {
reportingTask.setCustomUiUrl(uiExtension.getContextPath() + "/configure"); reportingTask.setCustomUiUrl(generateExternalUiUri(uiExtension.getContextPath(), "configure"));
} }
} }
} }

View File

@ -38,16 +38,21 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class TestApplicationResource { public class TestApplicationResource {
private static final String PROXY_CONTEXT_PATH_PROP = NiFiProperties.WEB_PROXY_CONTEXT_PATH; private static final String PROXY_CONTEXT_PATH_PROP = NiFiProperties.WEB_PROXY_CONTEXT_PATH;
private static final String BASE_URI = "https://nifi.apache.org"; private static final String SCHEME = "https";
private static final String HOST = "nifi.apache.org";
private static final int PORT = 8081;
private static final String BASE_URI = SCHEME + "://" + HOST;
private static final String ALLOWED_PATH = "/some/context/path"; private static final String ALLOWED_PATH = "/some/context/path";
private static final String FORWARD_SLASH = "/"; private static final String FORWARD_SLASH = "/";
private static final String CUSTOM_UI_PATH = "/my-custom-ui-1.0.0";
private static final String ACTUAL_RESOURCE = "actualResource"; private static final String ACTUAL_RESOURCE = "actualResource";
private static final String EXPECTED_URI = BASE_URI + ":8081" + ALLOWED_PATH + FORWARD_SLASH + ACTUAL_RESOURCE; private static final String EXPECTED_URI = BASE_URI + ":" + PORT + ALLOWED_PATH + FORWARD_SLASH + ACTUAL_RESOURCE;
private static final String MULTIPLE_ALLOWED_PATHS = String.join(",", ALLOWED_PATH, "another/path", "a/third/path"); private static final String MULTIPLE_ALLOWED_PATHS = String.join(",", ALLOWED_PATH, "another/path", "a/third/path");
@Mock @Mock
@ -57,8 +62,12 @@ public class TestApplicationResource {
@BeforeEach @BeforeEach
public void setUp(@Mock UriInfo uriInfo) throws Exception { public void setUp(@Mock UriInfo uriInfo) throws Exception {
when(uriInfo.getBaseUriBuilder()).thenReturn(new JerseyUriBuilder().uri(new URI(BASE_URI + FORWARD_SLASH))); // this stubbing is lenient because it is unnecessary in some tests
when(request.getScheme()).thenReturn("https"); lenient().when(uriInfo.getBaseUriBuilder()).thenReturn(new JerseyUriBuilder().uri(new URI(BASE_URI + FORWARD_SLASH)));
when(request.getScheme()).thenReturn(SCHEME);
when(request.getServerName()).thenReturn(HOST);
when(request.getServerPort()).thenReturn(PORT);
resource = new MockApplicationResource(); resource = new MockApplicationResource();
resource.setHttpServletRequest(request); resource.setHttpServletRequest(request);
@ -132,6 +141,19 @@ public class TestApplicationResource {
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE)); assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
} }
@Test
public void testGenerateExternalUiUri() {
assertEquals(SCHEME + "://" + HOST + ":" + PORT + CUSTOM_UI_PATH, resource.generateExternalUiUri(CUSTOM_UI_PATH));
}
@Test
public void testGenerateExternalUiUriWithProxy() {
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(ProxyHeader.FORWARDED_CONTEXT.getHeader()));
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, ALLOWED_PATH));
assertEquals(SCHEME + "://" + HOST + ":" + PORT + ALLOWED_PATH + CUSTOM_UI_PATH, resource.generateExternalUiUri(CUSTOM_UI_PATH));
}
private void setNiFiProperties(Map<String, String> props) { private void setNiFiProperties(Map<String, String> props) {
resource.properties = new NiFiProperties(props); resource.properties = new NiFiProperties(props);
} }