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:
dan-s1 2023-06-26 17:09:57 +00:00 committed by exceptionfactory
parent 07e797a7c1
commit 7748d9d1e5
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
10 changed files with 327 additions and 2374 deletions

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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())
}
}
}

View File

@ -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())
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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 "";
}
}
}
}

View File

@ -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());
});
}
}

View File

@ -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;
}
}