mirror of https://github.com/apache/nifi.git
NIFI-11747 Refactored Groovy tests in nifi-web-api to Java (and JUnit 5)
This closes #7434 Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
parent
07e797a7c1
commit
7748d9d1e5
|
@ -20,7 +20,6 @@
|
|||
<artifactId>nifi-web</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-api</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<properties>
|
||||
|
@ -404,20 +403,6 @@
|
|||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-mock</artifactId>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* 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.web.api
|
||||
|
||||
import org.apache.nifi.util.NiFiProperties
|
||||
import org.glassfish.jersey.uri.internal.JerseyUriBuilder
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.ws.rs.core.UriBuilderException
|
||||
import javax.ws.rs.core.UriInfo
|
||||
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_CONTEXT_PATH_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_HOST_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_PORT_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_SCHEME_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_CONTEXT_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_HOST_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_PORT_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_PREFIX_HTTP_HEADER
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_PROTO_HTTP_HEADER
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows
|
||||
|
||||
class ApplicationResourceTest {
|
||||
static final String PROXY_CONTEXT_PATH_PROP = NiFiProperties.WEB_PROXY_CONTEXT_PATH
|
||||
static final String ALLOWED_PATH = "/some/context/path"
|
||||
|
||||
class MockApplicationResource extends ApplicationResource {
|
||||
void setHttpServletRequest(HttpServletRequest request) {
|
||||
super.httpServletRequest = request
|
||||
}
|
||||
|
||||
void setUriInfo(UriInfo uriInfo) {
|
||||
super.uriInfo = uriInfo
|
||||
}
|
||||
}
|
||||
|
||||
private ApplicationResource buildApplicationResource() {
|
||||
buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER, FORWARDED_CONTEXT_HTTP_HEADER, PROXY_CONTEXT_PATH_HTTP_HEADER])
|
||||
}
|
||||
|
||||
private ApplicationResource buildApplicationResource(List proxyHeaders) {
|
||||
ApplicationResource resource = new MockApplicationResource()
|
||||
String headerValue = ""
|
||||
HttpServletRequest mockRequest = [getHeader: { String k ->
|
||||
if (proxyHeaders.contains(k)) {
|
||||
headerValue = ALLOWED_PATH
|
||||
} else if ([FORWARDED_PORT_HTTP_HEADER, PROXY_PORT_HTTP_HEADER].contains(k)) {
|
||||
headerValue = "8081"
|
||||
} else if ([FORWARDED_PROTO_HTTP_HEADER, PROXY_SCHEME_HTTP_HEADER].contains(k)) {
|
||||
headerValue = "https"
|
||||
} else if ([PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER].contains(k)) {
|
||||
headerValue = "nifi.apache.org:8081"
|
||||
} else {
|
||||
headerValue = ""
|
||||
}
|
||||
headerValue
|
||||
}, getContextPath: { ->
|
||||
headerValue
|
||||
}, getScheme: { ->
|
||||
"https"
|
||||
}, getServerPort: { ->
|
||||
443
|
||||
}] as HttpServletRequest
|
||||
|
||||
UriInfo mockUriInfo = [getBaseUriBuilder: { ->
|
||||
new JerseyUriBuilder().uri(new URI('https://nifi.apache.org/'))
|
||||
}] as UriInfo
|
||||
|
||||
resource.setHttpServletRequest(mockRequest)
|
||||
resource.setUriInfo(mockUriInfo)
|
||||
resource.properties = new NiFiProperties()
|
||||
|
||||
resource
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldBlockProxyContextPathHeaderIfNotInAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource()
|
||||
assertThrows(UriBuilderException.class, () -> resource.generateResourceUri('actualResource'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowProxyContextPathHeaderIfInAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource()
|
||||
NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
String expectedUri = "https://nifi.apache.org:8081" + ALLOWED_PATH + "/actualResource"
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
|
||||
assertEquals(expectedUri, generatedUri)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowProxyContextPathHeaderIfElementInMultipleAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource()
|
||||
String multipleAllowedPaths = [ALLOWED_PATH, "another/path", "a/third/path"].join(",")
|
||||
NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
String expectedUri = "https://nifi.apache.org:8081" + ALLOWED_PATH + "/actualResource"
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
|
||||
assertEquals(expectedUri, generatedUri)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldBlockForwardedContextHeaderIfNotInAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER])
|
||||
|
||||
assertThrows(UriBuilderException.class, () -> resource.generateResourceUri('actualResource'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldBlockForwardedPrefixHeaderIfNotInAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER])
|
||||
|
||||
assertThrows(UriBuilderException.class, () -> resource.generateResourceUri('actualResource'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedContextHeaderIfInAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER])
|
||||
NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
String expectedUri = "https://nifi.apache.org:8081" + ALLOWED_PATH + "/actualResource"
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
|
||||
assertEquals(expectedUri, generatedUri)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedPrefixHeaderIfInAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER])
|
||||
NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
String expectedUri = "https://nifi.apache.org:8081" + ALLOWED_PATH + "/actualResource"
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
|
||||
assertEquals(expectedUri, generatedUri)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedContextHeaderIfElementInMultipleAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER])
|
||||
String multipleAllowedPaths = [ALLOWED_PATH, "another/path", "a/third/path"].join(",")
|
||||
NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
String expectedUri = "https://nifi.apache.org:8081" + ALLOWED_PATH + "/actualResource"
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
|
||||
assertEquals(expectedUri, generatedUri)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedPrefixHeaderIfElementInMultipleAllowList() throws Exception {
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER])
|
||||
String multipleAllowedPaths = [ALLOWED_PATH, "another/path", "a/third/path"].join(",")
|
||||
NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
String expectedUri = "https://nifi.apache.org:8081" + ALLOWED_PATH + "/actualResource"
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
|
||||
assertEquals(expectedUri, generatedUri)
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
* 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.web.api
|
||||
|
||||
import org.apache.nifi.authorization.AuthorizeAccess
|
||||
import org.apache.nifi.util.NiFiProperties
|
||||
import org.apache.nifi.web.NiFiServiceFacade
|
||||
import org.apache.nifi.web.api.dto.FlowSnippetDTO
|
||||
import org.apache.nifi.web.api.dto.TemplateDTO
|
||||
import org.apache.nifi.web.api.entity.TemplateEntity
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.core.UriInfo
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
||||
class ProcessGroupResourceTest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProcessGroupResourceTest.class)
|
||||
|
||||
@BeforeAll
|
||||
static void setUpOnce() throws Exception {
|
||||
logger.metaClass.methodMissing = { String name, args ->
|
||||
logger.debug("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
|
||||
}
|
||||
}
|
||||
|
||||
/** This test creates a malformed template upload request to exercise error handling and sanitization */
|
||||
@Test
|
||||
void testUploadShouldHandleMalformedTemplate() {
|
||||
// Arrange
|
||||
ProcessGroupResource pgResource = new ProcessGroupResource()
|
||||
|
||||
// Mocking the returned template object to throw a specific exception would be nice
|
||||
final String TEMPLATE_WITH_XSS_PLAIN = "<?xml version=\"1.0\" encoding='><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(JSON.stringify(localstorage));</script><errorResponse test='?>"
|
||||
logger.info("Malformed template XML: ${TEMPLATE_WITH_XSS_PLAIN}")
|
||||
InputStream contentInputStream = new ByteArrayInputStream(TEMPLATE_WITH_XSS_PLAIN.bytes)
|
||||
|
||||
HttpServletRequest mockRequest = [:] as HttpServletRequest
|
||||
UriInfo mockUriInfo = [:] as UriInfo
|
||||
String groupId = "1"
|
||||
|
||||
// Build a malformed template object which can be unmarshalled from XML
|
||||
|
||||
// Act
|
||||
|
||||
// Try to submit the malformed template
|
||||
Response response = pgResource.uploadTemplate(mockRequest, mockUriInfo, groupId, false, contentInputStream)
|
||||
logger.info("Response: ${response}")
|
||||
|
||||
// Assert
|
||||
|
||||
// Assert that the expected error response was returned
|
||||
assertEquals(Response.Status.OK.statusCode, response.status)
|
||||
|
||||
// Assert that the error response is sanitized
|
||||
String responseEntity = response.entity as String
|
||||
logger.info("Error response: ${responseEntity}")
|
||||
assertFalse((responseEntity =~ /<script.*>/).find())
|
||||
}
|
||||
|
||||
/** This test creates a malformed template import request to exercise error handling and sanitization */
|
||||
@Test
|
||||
void testImportShouldHandleMalformedTemplate() {
|
||||
// Arrange
|
||||
ProcessGroupResource pgResource = new ProcessGroupResource()
|
||||
|
||||
// Configure parent fields for write lock process
|
||||
pgResource.properties = [isNode: { -> return false }] as NiFiProperties
|
||||
pgResource.serviceFacade = [
|
||||
authorizeAccess : { AuthorizeAccess a -> },
|
||||
verifyCanAddTemplate: { String gid, String templateName -> },
|
||||
importTemplate : { TemplateDTO template, String gid, Optional<String> seedId ->
|
||||
logger.mock("Called importTemplate;")
|
||||
template
|
||||
}
|
||||
] as NiFiServiceFacade
|
||||
pgResource.templateResource = [
|
||||
populateRemainingTemplateContent: { TemplateDTO td -> }
|
||||
] as TemplateResource
|
||||
|
||||
final String TEMPLATE_WITH_XSS_PLAIN = "<?xml version=\"1.0\" encoding='><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(JSON.stringify(localstorage));</script><errorResponse test='?>"
|
||||
logger.info("Malformed template XML: ${TEMPLATE_WITH_XSS_PLAIN}")
|
||||
|
||||
TemplateDTO mockIAETemplate = [
|
||||
getName : { -> "mockIAETemplate" },
|
||||
getUri : { ->
|
||||
throw new IllegalArgumentException("Expected exception with <script> element")
|
||||
},
|
||||
getSnippet: { -> new FlowSnippetDTO() }
|
||||
] as TemplateDTO
|
||||
|
||||
TemplateDTO mockExceptionTemplate = [
|
||||
getName : { -> "mockExceptionTemplate" },
|
||||
getUri : { ->
|
||||
throw new RuntimeException("Expected exception with <script> element")
|
||||
},
|
||||
getSnippet: { -> new FlowSnippetDTO() }
|
||||
] as TemplateDTO
|
||||
|
||||
TemplateEntity mockIAETemplateEntity = [getTemplate: { ->
|
||||
mockIAETemplate
|
||||
}] as TemplateEntity
|
||||
|
||||
TemplateEntity mockExceptionTemplateEntity = [getTemplate: { ->
|
||||
mockExceptionTemplate
|
||||
}] as TemplateEntity
|
||||
|
||||
// Override the request object and store it for ApplicationResource#withWriteLock
|
||||
HttpServletRequest mockRequest = [getHeader: { String headerName ->
|
||||
logger.mock("Requesting header ${headerName}; returning null")
|
||||
null
|
||||
}] as HttpServletRequest
|
||||
|
||||
// Set the persisted request object so the parent ApplicationResource can use it
|
||||
pgResource.httpServletRequest = mockRequest
|
||||
String groupId = "1"
|
||||
|
||||
// Act
|
||||
List<Response> responses = [mockIAETemplateEntity, mockExceptionTemplateEntity].collect { TemplateEntity te ->
|
||||
// Try to submit the malformed template which throws some kind of exception
|
||||
Response response = pgResource.importTemplate(mockRequest, groupId, te)
|
||||
logger.info("Response: ${response}")
|
||||
response
|
||||
}
|
||||
|
||||
// Assert
|
||||
responses.each { Response r ->
|
||||
// Assert that the expected error response was returned
|
||||
assertEquals(Response.Status.OK.statusCode, r.status)
|
||||
|
||||
// Assert that the error response is sanitized
|
||||
String entity = r.entity as String
|
||||
logger.info("Error response: ${entity}")
|
||||
assertFalse((entity =~ /<script.*>/).find())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* 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.web.api.config
|
||||
|
||||
import com.fasterxml.jackson.core.JsonLocation
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.JsonMappingException
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException
|
||||
import org.apache.nifi.web.api.ProcessGroupResourceTest
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
||||
class JsonContentConversionExceptionMapperTest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProcessGroupResourceTest.class)
|
||||
|
||||
@BeforeAll
|
||||
static void setUpOnce() throws Exception {
|
||||
logger.metaClass.methodMissing = { String name, args ->
|
||||
logger.debug("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldThrowExceptionWithStringPortValue() throws Exception{
|
||||
// Arrange
|
||||
JsonContentConversionExceptionMapper jsonCCEM = new JsonContentConversionExceptionMapper()
|
||||
|
||||
// Using real exception
|
||||
Class<?> instClass = Integer.class
|
||||
def mockParser = [getTokenLocation: { -> return new JsonLocation(null, 100, 1, 1)}] as JsonParser
|
||||
String message = "Some message"
|
||||
String value = "thisIsAnInvalidPort"
|
||||
InvalidFormatException ife = InvalidFormatException.from(mockParser, message, value, instClass)
|
||||
JsonMappingException.wrapWithPath(ife, new JsonMappingException.Reference("RemoteProcessGroupDTO", "proxyPort"))
|
||||
JsonMappingException.wrapWithPath(ife, new JsonMappingException.Reference("RemoteProcessGroupEntity", "component"))
|
||||
|
||||
// Act
|
||||
Response response = jsonCCEM.toResponse(ife)
|
||||
logger.info(response.toString())
|
||||
|
||||
// Assert
|
||||
assertEquals(Response.Status.BAD_REQUEST.statusCode, response.status)
|
||||
String expectedEntity = "The provided proxyPort value 'thisIsAnInvalidPort' is not" +
|
||||
" of required type class java.lang.Integer"
|
||||
assertEquals(expectedEntity, response.entity)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldSanitizeScriptInInput() throws Exception{
|
||||
// Arrange
|
||||
JsonContentConversionExceptionMapper jsonCCEM = new JsonContentConversionExceptionMapper();
|
||||
|
||||
// Using real exception
|
||||
Class<?> instClass = Integer.class
|
||||
def mockParser = [getTokenLocation: { -> return new JsonLocation(null, 100, 1, 1)}] as JsonParser
|
||||
String message = "Some message"
|
||||
String value = "<script>alert(1);</script>"
|
||||
InvalidFormatException ife = InvalidFormatException.from(mockParser, message, value, instClass)
|
||||
JsonMappingException.wrapWithPath(ife, new JsonMappingException.Reference("RemoteProcessGroupDTO", "proxyPort"))
|
||||
JsonMappingException.wrapWithPath(ife, new JsonMappingException.Reference("RemoteProcessGroupEntity", "component"))
|
||||
|
||||
// Act
|
||||
Response response = jsonCCEM.toResponse(ife)
|
||||
logger.info(response.toString())
|
||||
|
||||
// Assert
|
||||
assertEquals(Response.Status.BAD_REQUEST.statusCode, response.status)
|
||||
assertFalse((response.entity =~ /<script.*>/).find())
|
||||
}
|
||||
}
|
|
@ -1,618 +0,0 @@
|
|||
/*
|
||||
* 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.web.dao.impl
|
||||
|
||||
import org.apache.nifi.authorization.*
|
||||
import org.apache.nifi.web.ResourceNotFoundException
|
||||
import org.apache.nifi.web.api.dto.AccessPolicyDTO
|
||||
import org.apache.nifi.web.api.dto.UserDTO
|
||||
import org.apache.nifi.web.api.dto.UserGroupDTO
|
||||
import org.apache.nifi.web.api.entity.TenantEntity
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
|
||||
|
||||
private AbstractPolicyBasedAuthorizer mockAuthorizer() {
|
||||
def authorizer = Mock AbstractPolicyBasedAuthorizer
|
||||
authorizer.getAccessPolicyProvider() >> {
|
||||
callRealMethod();
|
||||
}
|
||||
return authorizer;
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "test non-policy-based authorizer #method throws IllegalStateException"() {
|
||||
when:
|
||||
daoMethod()
|
||||
|
||||
then:
|
||||
def e = thrown(IllegalStateException)
|
||||
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_MANAGED_AUTHORIZER)
|
||||
|
||||
where:
|
||||
method | daoMethod
|
||||
'getAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getAccessPolicy('1') }
|
||||
'getUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUser('1') }
|
||||
'getUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUserGroup('1') }
|
||||
'hasAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasAccessPolicy('1') }
|
||||
'hasUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUser('1') }
|
||||
'hasUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUserGroup('1') }
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "test non-configurable user group provider #method throws IllegalStateException"() {
|
||||
when:
|
||||
daoMethod()
|
||||
|
||||
then:
|
||||
def e = thrown(IllegalStateException)
|
||||
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_USERS)
|
||||
|
||||
where:
|
||||
method | daoMethod
|
||||
'createUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) }
|
||||
'createUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
|
||||
'deleteUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') }
|
||||
'deleteUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') }
|
||||
'updateUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUser(new UserDTO(id: '1', identity: 'a')) }
|
||||
'updateUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "test non-configurable access policy provider #method throws IllegalStateException"() {
|
||||
when:
|
||||
daoMethod()
|
||||
|
||||
then:
|
||||
def e = thrown(IllegalStateException)
|
||||
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_POLICIES)
|
||||
|
||||
where:
|
||||
method | daoMethod
|
||||
'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
|
||||
'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') }
|
||||
'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "HasAccessPolicy: accessPolicy: #accessPolicy"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.hasAccessPolicy('policy-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy('policy-id-1') >> accessPolicy
|
||||
0 * _
|
||||
result == (accessPolicy != null)
|
||||
|
||||
where:
|
||||
accessPolicy | _
|
||||
new AccessPolicy.Builder().identifier('policy-id-1').resource('/fake/resource').addUser('user-id-1').addGroup('user-group-id-1')
|
||||
.action(RequestAction.WRITE).build() | _
|
||||
null | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "CreateAccessPolicy: accessPolicy=#accessPolicy"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
|
||||
users: [new TenantEntity(id: 'user-id-1')] as Set,
|
||||
userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
|
||||
|
||||
when:
|
||||
def result = dao.createAccessPolicy(requestDTO)
|
||||
|
||||
then:
|
||||
noExceptionThrown()
|
||||
|
||||
then:
|
||||
1 * authorizer.doAddAccessPolicy(accessPolicy) >> accessPolicy
|
||||
0 * _
|
||||
result?.equals accessPolicy
|
||||
|
||||
where:
|
||||
accessPolicy | accessPolicies
|
||||
new AccessPolicy.Builder().identifier('policy-id-1').resource('/fake/resource').addUser('user-id-1').addGroup('user-group-id-1')
|
||||
.action(RequestAction.WRITE).build() | [] as Set
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetAccessPolicy: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.getAccessPolicy('policy-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy('policy-id-1') >> accessPolicy
|
||||
0 * _
|
||||
assert result?.equals(accessPolicy)
|
||||
|
||||
where:
|
||||
accessPolicy | _
|
||||
new AccessPolicy.Builder().identifier('policy-id-1').resource('/fake/resource').addUser('user-id-1').addGroup('user-group-id-1')
|
||||
.action(RequestAction.WRITE).build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetAccessPoliciesForUser: access policy contains identifier of missing group"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def group1 = new Group.Builder().identifier("group-id-1").name("Group One").addUser("user-id-1").build()
|
||||
def apBuilder = new AccessPolicy.Builder().resource('/fake/resource').action(RequestAction.WRITE)
|
||||
def ap1 = apBuilder.identifier('policy-id-1').addUser('user-id-1').build()
|
||||
def ap2 = apBuilder.identifier('policy-id-2').clearUsers().addGroup('group-id-1').build()
|
||||
def ap3 = apBuilder.identifier('policy-id-3').clearUsers().clearGroups().addGroup('id-of-missing-group').build()
|
||||
def accessPolicies = new HashSet([ap1, ap2, ap3])
|
||||
|
||||
when:
|
||||
def result = dao.getAccessPoliciesForUser('user-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicies() >> accessPolicies
|
||||
1 * authorizer.getGroup('group-id-1') >> group1
|
||||
1 * authorizer.getGroup('id-of-missing-group') >> null
|
||||
0 * _
|
||||
assert result?.equals(new HashSet<AccessPolicy>([ap1, ap2]))
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetAccessPolicy: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
dao.getAccessPolicy('policy-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy('policy-id-1') >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "UpdateAccessPolicy: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
|
||||
users: [new TenantEntity(id: 'user-id-1')] as Set,
|
||||
userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
|
||||
|
||||
when:
|
||||
def result = dao.updateAccessPolicy(requestDTO)
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy(requestDTO.id) >> accessPolicy
|
||||
1 * authorizer.updateAccessPolicy(accessPolicy) >> accessPolicy
|
||||
0 * _
|
||||
result?.equals(accessPolicy)
|
||||
|
||||
where:
|
||||
accessPolicy | _
|
||||
new AccessPolicy.Builder().identifier('policy-id-1').resource('/fake/resource').addUser('user-id-1').addGroup('user-group-id-1')
|
||||
.action(RequestAction.WRITE).build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "UpdateAccessPolicy: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
|
||||
users: [new TenantEntity(id: 'user-id-1')] as Set,
|
||||
userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
|
||||
|
||||
when:
|
||||
dao.updateAccessPolicy(requestDTO)
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy(requestDTO.id) >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "DeleteAccessPolicy: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.deleteAccessPolicy('policy-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy('policy-id-1') >> accessPolicy
|
||||
1 * authorizer.deleteAccessPolicy(accessPolicy) >> accessPolicy
|
||||
0 * _
|
||||
result?.equals(accessPolicy)
|
||||
|
||||
where:
|
||||
accessPolicy | _
|
||||
new AccessPolicy.Builder().identifier('policy-id-1').resource('/fake/resource').addUser('user-id-1').addGroup('user-group-id-1')
|
||||
.action(RequestAction.WRITE).build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "DeleteAccessPolicy: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
dao.deleteAccessPolicy('policy-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getAccessPolicy('policy-id-1') >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "HasUserGroup: userGroup=#userGroup"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.hasUserGroup('user-group-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup('user-group-id-1') >> userGroup
|
||||
0 * _
|
||||
result == (userGroup != null)
|
||||
|
||||
where:
|
||||
userGroup | _
|
||||
new Group.Builder().identifier('user-group-id-1').name('user-group-id-1').addUser('user-id-1').build() | _
|
||||
null | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "CreateUserGroup: userGroup=#userGroup"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
|
||||
|
||||
when:
|
||||
def result = dao.createUserGroup(requestDTO)
|
||||
|
||||
then:
|
||||
noExceptionThrown()
|
||||
|
||||
then:
|
||||
1 * authorizer.doAddGroup(userGroup) >> userGroup
|
||||
0 * _
|
||||
result?.equals userGroup
|
||||
|
||||
where:
|
||||
userGroup | users | groups
|
||||
new Group.Builder().identifier('user-group-id-1')
|
||||
.name('user-group-id-1').addUser('user-id-1').build() | [] as Set | [] as Set
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetUserGroup: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.getUserGroup('user-group-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup('user-group-id-1') >> userGroup
|
||||
0 * _
|
||||
result?.equals(userGroup)
|
||||
|
||||
where:
|
||||
userGroup | _
|
||||
new Group.Builder().identifier('user-group-id-1').name('user-group-id-1').addUser('user-id-1').build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetUserGroup: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
dao.getUserGroup('user-group-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup('user-group-id-1') >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetUserGroups: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.getUserGroups()
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroups() >> userGroups
|
||||
0 * _
|
||||
result?.equals(userGroups)
|
||||
|
||||
where:
|
||||
userGroups | _
|
||||
[new Group.Builder().identifier('user-group-id-1').name('user-group-id-1').addUser('user-id-1').build()] as Set | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "UpdateUserGroup: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
|
||||
|
||||
when:
|
||||
def result = dao.updateUserGroup(requestDTO)
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup(requestDTO.id) >> userGroup
|
||||
1 * authorizer.doUpdateGroup(userGroup) >> userGroup
|
||||
0 * _
|
||||
result?.equals(userGroup)
|
||||
|
||||
where:
|
||||
userGroup | users | groups
|
||||
new Group.Builder().identifier('user-group-id-1')
|
||||
.name('user-group-id-1').addUser('user-id-1').build() | [] as Set | [] as Set
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "UpdateUserGroup: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
|
||||
|
||||
when:
|
||||
dao.updateUserGroup(requestDTO)
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup(requestDTO.id) >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "DeleteUserGroup: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
authorizer.getAccessPolicyProvider().getAccessPolicies() >> {
|
||||
callRealMethod();
|
||||
}
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.deleteUserGroup('user-group-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup('user-group-id-1') >> userGroup
|
||||
1 * authorizer.deleteGroup(userGroup) >> userGroup
|
||||
1 * authorizer.getAccessPolicies() >> []
|
||||
0 * _
|
||||
assert result?.equals(userGroup)
|
||||
|
||||
where:
|
||||
userGroup | _
|
||||
new Group.Builder().identifier('user-group-id-1').name('user-group-id-1').addUser('user-id-1').build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "DeleteUserGroup: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
dao.deleteUserGroup('user-group-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getGroup('user-group-id-1') >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "HasUser: user=#user"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.hasUser('user-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser('user-id-1') >> user
|
||||
0 * _
|
||||
result == (user != null)
|
||||
|
||||
where:
|
||||
user | _
|
||||
new User.Builder().identifier('user-id-1').identity('user identity').build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "CreateUser: user=#user"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
|
||||
|
||||
when:
|
||||
def result = dao.createUser(requestDTO)
|
||||
|
||||
then:
|
||||
noExceptionThrown()
|
||||
|
||||
then:
|
||||
1 * authorizer.doAddUser(user) >> user
|
||||
0 * _
|
||||
result?.equals user
|
||||
|
||||
where:
|
||||
user | users | groups
|
||||
new User.Builder().identifier('user-id-1')
|
||||
.identity('user identity').build() | [] as Set | [] as Set
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetUser: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.getUser('user-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser('user-id-1') >> user
|
||||
result?.equals(user)
|
||||
0 * _
|
||||
|
||||
where:
|
||||
user | _
|
||||
new User.Builder().identifier('user-id-1').identity('user identity').build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetUser: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
dao.getUser('user-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser('user-id-1') >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "GetUsers: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.getUsers()
|
||||
|
||||
then:
|
||||
1 * authorizer.getUsers() >> users
|
||||
result?.containsAll(users)
|
||||
0 * _
|
||||
|
||||
where:
|
||||
users | _
|
||||
[new User.Builder().identifier('user-id-1').identity('user identity').build()] as Set | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "UpdateUser: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
|
||||
|
||||
when:
|
||||
def result = dao.updateUser(requestDTO)
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser(requestDTO.id) >> user
|
||||
1 * authorizer.doUpdateUser(user) >> user
|
||||
0 * _
|
||||
result?.equals(user)
|
||||
|
||||
where:
|
||||
user | users | groups
|
||||
new User.Builder().identifier('user-id-1')
|
||||
.identity('user identity').build() | [] as Set | [] as Set
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "UpdateUser: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
|
||||
|
||||
when:
|
||||
dao.updateUser(requestDTO)
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser(requestDTO.id) >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "DeleteUser: success"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
def result = dao.deleteUser('user-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser('user-id-1') >> user
|
||||
1 * authorizer.deleteUser(user) >> user
|
||||
1 * authorizer.getAccessPolicies() >> []
|
||||
0 * _
|
||||
result?.equals(user)
|
||||
|
||||
where:
|
||||
user | _
|
||||
new User.Builder().identifier('user-id-1').identity('user identity').build() | _
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "DeleteUser: failure"() {
|
||||
given:
|
||||
def authorizer = mockAuthorizer()
|
||||
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
|
||||
|
||||
when:
|
||||
dao.deleteUser('user-id-1')
|
||||
|
||||
then:
|
||||
1 * authorizer.getUser('user-id-1') >> null
|
||||
0 * _
|
||||
thrown ResourceNotFoundException
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* 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.web.dao.impl
|
||||
|
||||
import org.apache.nifi.authorization.Authorizer
|
||||
import org.apache.nifi.controller.FlowController
|
||||
import org.apache.nifi.controller.flow.FlowManager
|
||||
import org.apache.nifi.controller.serialization.FlowEncodingVersion
|
||||
import org.apache.nifi.controller.service.ControllerServiceProvider
|
||||
import org.apache.nifi.groups.ProcessGroup
|
||||
import org.apache.nifi.web.api.dto.BundleDTO
|
||||
import org.apache.nifi.web.api.dto.ComponentDTO
|
||||
import org.apache.nifi.web.api.dto.DtoFactory
|
||||
import org.apache.nifi.web.api.dto.FlowSnippetDTO
|
||||
import org.apache.nifi.web.api.dto.PositionDTO
|
||||
import org.apache.nifi.web.api.dto.ProcessGroupDTO
|
||||
import org.apache.nifi.web.api.dto.ProcessorConfigDTO
|
||||
import org.apache.nifi.web.api.dto.ProcessorDTO
|
||||
import org.apache.nifi.web.util.SnippetUtils
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
class StandardTemplateDAOSpec extends Specification {
|
||||
|
||||
@Unroll
|
||||
def "test InstantiateTemplate moves and scales templates"() {
|
||||
given:
|
||||
def flowController = Mock FlowController
|
||||
def flowManager = Mock FlowManager
|
||||
flowController.flowManager >> flowManager
|
||||
def snippetUtils = new SnippetUtils()
|
||||
snippetUtils.flowController = flowController
|
||||
def dtoFactory = new DtoFactory()
|
||||
dtoFactory.authorizer = Mock Authorizer
|
||||
dtoFactory.controllerServiceProvider = Mock ControllerServiceProvider
|
||||
snippetUtils.dtoFactory = dtoFactory
|
||||
def standardTemplateDAO = new StandardTemplateDAO()
|
||||
standardTemplateDAO.flowController = flowController
|
||||
standardTemplateDAO.snippetUtils = snippetUtils
|
||||
def templateEncodingVersion = FlowEncodingVersion.parse(encodingVersion);
|
||||
// get the major version, or 0 if no version could be parsed
|
||||
int templateEncodingMajorVersion = templateEncodingVersion != null ? templateEncodingVersion.getMajorVersion() : 0;
|
||||
double factorX = templateEncodingMajorVersion < 1 ? FlowController.DEFAULT_POSITION_SCALE_FACTOR_X : 1.0;
|
||||
double factorY = templateEncodingMajorVersion < 1 ? FlowController.DEFAULT_POSITION_SCALE_FACTOR_Y : 1.0;
|
||||
// get all top-level component starting positions
|
||||
def List<ComponentDTO> components = [snippet.connections + snippet.inputPorts + snippet.outputPorts + snippet.labels + snippet.processGroups + snippet.processGroups +
|
||||
snippet.processors + snippet.funnels + snippet.remoteProcessGroups].flatten()
|
||||
|
||||
// get all starting subcomponent starting positions
|
||||
def List<ComponentDTO> subComponents = org.apache.nifi.util.SnippetUtils.findAllProcessGroups(snippet).collect { ProcessGroupDTO processGroup ->
|
||||
def childSnippet = processGroup.contents
|
||||
childSnippet.connections + childSnippet.inputPorts + childSnippet.outputPorts + childSnippet.labels + childSnippet.processGroups + childSnippet.processGroups +
|
||||
childSnippet.processors + childSnippet.funnels + childSnippet.remoteProcessGroups
|
||||
}.flatten()
|
||||
|
||||
when:
|
||||
def instantiatedTemplate = standardTemplateDAO.instantiateTemplate(rootGroupId, newOriginX, newOriginY, encodingVersion, snippet, idGenerationSeed)
|
||||
|
||||
then:
|
||||
flowManager.getGroup(_) >> { String gId ->
|
||||
def pg = Mock ProcessGroup
|
||||
pg.identifier >> gId
|
||||
pg.inputPorts >> []
|
||||
pg.outputPorts >> []
|
||||
pg.processGroups >> []
|
||||
return pg
|
||||
}
|
||||
flowManager.rootGroupId >> rootGroupId
|
||||
flowManager.instantiateSnippet(*_) >> {}
|
||||
|
||||
def instantiatedComponents = [instantiatedTemplate.connections + instantiatedTemplate.inputPorts + instantiatedTemplate.outputPorts + instantiatedTemplate.labels +
|
||||
instantiatedTemplate.processGroups + instantiatedTemplate.processGroups + instantiatedTemplate.processors + instantiatedTemplate.funnels +
|
||||
instantiatedTemplate.remoteProcessGroups].flatten()
|
||||
components.forEach { component ->
|
||||
def correspondingScaledPosition = instantiatedComponents.find { scaledComponent ->
|
||||
scaledComponent.name.equals(component.name)
|
||||
}.position
|
||||
assert correspondingScaledPosition != null
|
||||
def expectedPosition = calculateMoveAndScalePosition(component.position, oldOriginX, oldOriginY, newOriginX, newOriginY, factorX, factorY)
|
||||
assert correspondingScaledPosition.x == expectedPosition.x
|
||||
assert correspondingScaledPosition.y == expectedPosition.y
|
||||
|
||||
}
|
||||
def instantiatedSubComponents = org.apache.nifi.util.SnippetUtils.findAllProcessGroups(instantiatedTemplate).collect { ProcessGroupDTO processGroup ->
|
||||
def childSnippet = processGroup.contents
|
||||
childSnippet.connections + childSnippet.inputPorts + childSnippet.outputPorts + childSnippet.labels + childSnippet.processGroups + childSnippet.processGroups +
|
||||
childSnippet.processors + childSnippet.funnels + childSnippet.remoteProcessGroups
|
||||
}.flatten()
|
||||
subComponents.forEach { subComponent ->
|
||||
def correspondingScaledPosition = instantiatedSubComponents.find { scaledComponent ->
|
||||
scaledComponent.name.equals(subComponent.name)
|
||||
}.position
|
||||
assert correspondingScaledPosition != null
|
||||
def expectedPosition = calculateScalePosition(subComponent.position, factorX, factorY)
|
||||
assert correspondingScaledPosition.x == expectedPosition.x
|
||||
assert correspondingScaledPosition.y == expectedPosition.y
|
||||
|
||||
}
|
||||
|
||||
where:
|
||||
rootGroupId | oldOriginX | oldOriginY | newOriginX | newOriginY | idGenerationSeed | encodingVersion | snippet
|
||||
'g1' | 0.0 | 0.0 | 5.0 | 5.0 | 'AAAA' | null | new FlowSnippetDTO()
|
||||
'g1' | 10.0 | 10.0 | 5.0 | 5.0 | 'AAAA' | '0.7' | new FlowSnippetDTO(
|
||||
processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0000-c4af042cb155", name: 'proc1', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 10, y: 10))])
|
||||
'g1' | 10.0 | -10.0 | 5.0 | 5.0 | 'AAAA' | null | new FlowSnippetDTO(
|
||||
processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0001-c4af042cb155", name: 'proc2', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 10, y: 10))],
|
||||
processGroups: [
|
||||
new ProcessGroupDTO(id:"c81f6810-0a55-1000-0000-c4af042cb155",
|
||||
name: 'g2',
|
||||
position: new PositionDTO(x: 105, y: -10),
|
||||
contents: new FlowSnippetDTO(processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0002-c4af042cb155", name: 'proc3', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 50, y: 60))]))])
|
||||
'g1' | 10.0 | -10.0 | 5.0 | 5.0 | 'AAAA' | '0.7' | new FlowSnippetDTO(
|
||||
processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0003-c4af042cb155", name: 'proc2', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 10, y: 10))],
|
||||
processGroups: [
|
||||
new ProcessGroupDTO(id:"c81f6810-0a55-1000-0001-c4af042cb155",
|
||||
name: 'g2',
|
||||
position: new PositionDTO(x: 105, y: -10),
|
||||
contents: new FlowSnippetDTO(processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0004-c4af042cb155", name: 'proc3', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 50, y: 60))]))])
|
||||
'g1' | 10.0 | -10.0 | 5.0 | 5.0 | 'AAAA' | '1.0' | new FlowSnippetDTO(
|
||||
processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0005-c4af042cb155", name: 'proc2', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 10, y: 10))],
|
||||
processGroups: [
|
||||
new ProcessGroupDTO(id:"c81f6810-0a55-1000-0003-c4af042cb155",
|
||||
name: 'g2',
|
||||
position: new PositionDTO(x: 105, y: -10),
|
||||
contents: new FlowSnippetDTO(processors: [new ProcessorDTO(id:"c81f6810-0155-1000-0006-c4af042cb155", name: 'proc3', bundle: new BundleDTO("org.apache.nifi", "standard", "1.0"),
|
||||
config: new ProcessorConfigDTO(), position: new PositionDTO(x: 50, y: 60))]))])
|
||||
}
|
||||
|
||||
def PositionDTO calculateMoveAndScalePosition(position, oldOriginX, oldOriginY, newOriginX, newOriginY, factorX, factorY) {
|
||||
new PositionDTO(
|
||||
x: newOriginX + (position.x - oldOriginX) * factorX,
|
||||
y: newOriginY + (position.y - oldOriginY) * factorY)
|
||||
}
|
||||
|
||||
def PositionDTO calculateScalePosition(position, factorX, factorY) {
|
||||
new PositionDTO(
|
||||
x: position.x * factorX,
|
||||
y: position.y * factorY)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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.web.api;
|
||||
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.UriBuilderException;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_CONTEXT_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_HOST_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_PORT_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_PREFIX_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.FORWARDED_PROTO_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_CONTEXT_PATH_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_HOST_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_PORT_HTTP_HEADER;
|
||||
import static org.apache.nifi.web.util.WebUtils.PROXY_SCHEME_HTTP_HEADER;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TestApplicationResource {
|
||||
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 ALLOWED_PATH = "/some/context/path";
|
||||
private static final String FORWARD_SLASH = "/";
|
||||
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 MULTIPLE_ALLOWED_PATHS = String.join(",", ALLOWED_PATH, "another/path", "a/third/path");
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
private MockApplicationResource resource;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp(@Mock UriInfo uriInfo) throws Exception {
|
||||
when(uriInfo.getBaseUriBuilder()).thenReturn(new JerseyUriBuilder().uri(new URI(BASE_URI + FORWARD_SLASH)));
|
||||
when(request.getScheme()).thenReturn("https");
|
||||
|
||||
resource = new MockApplicationResource();
|
||||
resource.setHttpServletRequest(request);
|
||||
resource.setUriInfo(uriInfo);
|
||||
resource.setProperties(new NiFiProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldBlockProxyContextPathHeaderIfNotInAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer());
|
||||
assertThrows(UriBuilderException.class, () -> resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldAllowProxyContextPathHeaderIfInAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer());
|
||||
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, ALLOWED_PATH));
|
||||
|
||||
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldAllowProxyContextPathHeaderIfElementInMultipleAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer());
|
||||
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, MULTIPLE_ALLOWED_PATHS));
|
||||
|
||||
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldBlockForwardedContextHeaderIfNotInAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(FORWARDED_CONTEXT_HTTP_HEADER));
|
||||
assertThrows(UriBuilderException.class, () -> resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldBlockForwardedPrefixHeaderIfNotInAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(FORWARDED_PREFIX_HTTP_HEADER));
|
||||
assertThrows(UriBuilderException.class, () -> resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldAllowForwardedContextHeaderIfInAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(FORWARDED_CONTEXT_HTTP_HEADER));
|
||||
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, ALLOWED_PATH));
|
||||
|
||||
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldAllowForwardedPrefixHeaderIfInAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(FORWARDED_PREFIX_HTTP_HEADER));
|
||||
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, ALLOWED_PATH));
|
||||
|
||||
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldAllowForwardedContextHeaderIfElementInMultipleAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(FORWARDED_CONTEXT_HTTP_HEADER));
|
||||
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, MULTIPLE_ALLOWED_PATHS));
|
||||
|
||||
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateUriShouldAllowForwardedPrefixHeaderIfElementInMultipleAllowList() {
|
||||
when(request.getHeader(anyString())).thenAnswer(new RequestAnswer(FORWARDED_PREFIX_HTTP_HEADER));
|
||||
setNiFiProperties(Collections.singletonMap(PROXY_CONTEXT_PATH_PROP, MULTIPLE_ALLOWED_PATHS));
|
||||
|
||||
assertEquals(EXPECTED_URI, resource.generateResourceUri(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
private void setNiFiProperties(Map<String, String> props) {
|
||||
resource.properties = new NiFiProperties(props);
|
||||
}
|
||||
|
||||
private static class MockApplicationResource extends ApplicationResource {
|
||||
void setHttpServletRequest(HttpServletRequest request) {
|
||||
super.httpServletRequest = request;
|
||||
}
|
||||
|
||||
void setUriInfo(UriInfo uriInfo) {
|
||||
super.uriInfo = uriInfo;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RequestAnswer implements Answer<String> {
|
||||
private final List<String> proxyHeaders;
|
||||
|
||||
public RequestAnswer() {
|
||||
this(FORWARDED_PREFIX_HTTP_HEADER, FORWARDED_CONTEXT_HTTP_HEADER, PROXY_CONTEXT_PATH_HTTP_HEADER);
|
||||
}
|
||||
|
||||
public RequestAnswer(String...proxyHeaders) {
|
||||
this(Arrays.asList(proxyHeaders));
|
||||
}
|
||||
|
||||
public RequestAnswer(List<String> proxyHeaders) {
|
||||
this.proxyHeaders = proxyHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String answer(InvocationOnMock invocationOnMock) {
|
||||
String argument = invocationOnMock.getArgument(0);
|
||||
if(proxyHeaders.contains(argument)) {
|
||||
return ALLOWED_PATH;
|
||||
} else if(Arrays.asList(FORWARDED_PORT_HTTP_HEADER, PROXY_PORT_HTTP_HEADER).contains(argument)) {
|
||||
return "8081";
|
||||
} else if(Arrays.asList(FORWARDED_PROTO_HTTP_HEADER, PROXY_SCHEME_HTTP_HEADER).contains(argument)) {
|
||||
return "https";
|
||||
} else if(Arrays.asList(PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER).contains(argument)) {
|
||||
return "nifi.apache.org:8081";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,47 +18,96 @@ package org.apache.nifi.web.api;
|
|||
|
||||
import org.apache.nifi.flow.VersionedProcessGroup;
|
||||
import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.apache.nifi.web.NiFiServiceFacade;
|
||||
import org.apache.nifi.web.api.dto.FlowSnippetDTO;
|
||||
import org.apache.nifi.web.api.dto.TemplateDTO;
|
||||
import org.apache.nifi.web.api.entity.TemplateEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TestProcessGroupResource {
|
||||
|
||||
@InjectMocks
|
||||
private ProcessGroupResource processGroupResource = new ProcessGroupResource();
|
||||
private ProcessGroupResource processGroupResource;
|
||||
|
||||
@Mock
|
||||
private NiFiServiceFacade serviceFacade;
|
||||
|
||||
@Test
|
||||
public void testExportProcessGroup() {
|
||||
public void testExportProcessGroup(@Mock RegisteredFlowSnapshot versionedFlowSnapshot, @Mock VersionedProcessGroup versionedProcessGroup) {
|
||||
final String groupId = UUID.randomUUID().toString();
|
||||
final RegisteredFlowSnapshot versionedFlowSnapshot = mock(RegisteredFlowSnapshot.class);
|
||||
|
||||
when(serviceFacade.getCurrentFlowSnapshotByGroupId(groupId)).thenReturn(versionedFlowSnapshot);
|
||||
|
||||
final String flowName = "flowname";
|
||||
final VersionedProcessGroup versionedProcessGroup = mock(VersionedProcessGroup.class);
|
||||
when(versionedFlowSnapshot.getFlowContents()).thenReturn(versionedProcessGroup);
|
||||
when(versionedProcessGroup.getName()).thenReturn(flowName);
|
||||
when(versionedProcessGroup.getName()).thenReturn("flowname");
|
||||
|
||||
final Response response = processGroupResource.exportProcessGroup(groupId, false);
|
||||
|
||||
final RegisteredFlowSnapshot resultEntity = (RegisteredFlowSnapshot)response.getEntity();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(versionedFlowSnapshot, resultEntity);
|
||||
try(Response response = processGroupResource.exportProcessGroup(groupId, false)) {
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(versionedFlowSnapshot, response.getEntity());
|
||||
}
|
||||
}
|
||||
|
||||
/** This test creates a malformed template upload request to exercise error handling and sanitization */
|
||||
@Test
|
||||
public void testUploadShouldHandleMalformedTemplate(@Mock HttpServletRequest request, @Mock UriInfo uriInfo) throws Exception {
|
||||
final String templateWithXssPlain = "<?xml version=\"1.0\" encoding='><script xmlns=\"http://www.w3.org/1999/xhtml\">alert(JSON.stringify(localstorage));</script><errorResponse test='?>";
|
||||
Response response = processGroupResource.uploadTemplate(request, uriInfo, "1",
|
||||
false, new ByteArrayInputStream(templateWithXssPlain.getBytes()));
|
||||
|
||||
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
assertFalse(Pattern.compile("<script.*>").matcher(response.getEntity().toString()).find());
|
||||
}
|
||||
|
||||
/** This test creates a malformed template import request to exercise error handling and sanitization */
|
||||
@Test
|
||||
public void testImportShouldHandleMalformedTemplate(@Mock NiFiProperties niFiProperties, @Mock TemplateResource templateResource,
|
||||
@Mock TemplateDTO mockIAETemplate, @Mock TemplateDTO mockExceptionTemplate,
|
||||
@Mock TemplateEntity mockIAETemplateEntity, @Mock TemplateEntity mockExceptionTemplateEntity,
|
||||
@Mock HttpServletRequest mockRequest) {
|
||||
when(niFiProperties.isNode()).thenReturn(false);
|
||||
when(serviceFacade.importTemplate(any(TemplateDTO.class), anyString(), any())).thenAnswer((Answer<TemplateDTO>) invocationOnMock -> invocationOnMock.getArgument(0));
|
||||
when(mockIAETemplate.getName()).thenReturn("mockIAETemplate");
|
||||
when(mockIAETemplate.getUri()).thenThrow(new IllegalArgumentException("Expected exception with <script> element"));
|
||||
when(mockIAETemplate.getSnippet()).thenReturn(new FlowSnippetDTO());
|
||||
when(mockExceptionTemplate.getName()).thenReturn("mockExceptionTemplate");
|
||||
when(mockExceptionTemplate.getUri()).thenThrow(new RuntimeException("Expected exception with <script> element"));
|
||||
when(mockExceptionTemplate.getSnippet()).thenReturn(new FlowSnippetDTO());
|
||||
when(mockIAETemplateEntity.getTemplate()).thenReturn(mockIAETemplate);
|
||||
when(mockExceptionTemplateEntity.getTemplate()).thenReturn(mockExceptionTemplate);
|
||||
|
||||
processGroupResource.properties = niFiProperties;
|
||||
processGroupResource.serviceFacade = serviceFacade;
|
||||
processGroupResource.setTemplateResource(templateResource);
|
||||
processGroupResource.httpServletRequest = mockRequest;
|
||||
|
||||
List<Response> responses = Stream.of(mockIAETemplateEntity, mockExceptionTemplateEntity)
|
||||
.map(templateEntity -> processGroupResource.importTemplate(mockRequest, "1", templateEntity))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
responses.forEach(response -> {
|
||||
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
assertFalse(Pattern.compile("<script.*>").matcher(response.getEntity().toString()).find());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.web.api.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonLocation;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class JsonContentConversionExceptionMapperTest {
|
||||
@Mock
|
||||
private JsonParser mockParser;
|
||||
|
||||
private JsonContentConversionExceptionMapper jsonCCEM;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
when(mockParser.getTokenLocation()).thenReturn(new JsonLocation(null, 100, 1, 1));
|
||||
jsonCCEM = new JsonContentConversionExceptionMapper();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldThrowExceptionWithStringPortValue() {
|
||||
try(Response response = jsonCCEM.toResponse(buildInvalidFormatException("thisIsAnInvalidPort"))) {
|
||||
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
assertEquals("The provided proxyPort value 'thisIsAnInvalidPort' is not" +
|
||||
" of required type class java.lang.Integer", response.getEntity());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldSanitizeScriptInInput() {
|
||||
try(Response response = jsonCCEM.toResponse(buildInvalidFormatException("<script>alert(1);</script>"))) {
|
||||
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
assertFalse(Pattern.compile("<script.*>").matcher(response.getEntity().toString()).find());
|
||||
}
|
||||
}
|
||||
|
||||
private InvalidFormatException buildInvalidFormatException(String value) {
|
||||
InvalidFormatException ife = InvalidFormatException.from(mockParser, "Some message", value, Integer.class);
|
||||
wrapWithPath(ife, new JsonMappingException.Reference("RemoteProcessGroupDTO", "proxyPort"));
|
||||
wrapWithPath(ife, new JsonMappingException.Reference("RemoteProcessGroupEntity", "component"));
|
||||
|
||||
return ife;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue