You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:29 UTC

[30/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring project structure to better isolate extensions

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizations.xsd
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizations.xsd b/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizations.xsd
new file mode 100644
index 0000000..2c8f805
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizations.xsd
@@ -0,0 +1,87 @@
+<?xml version="1.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.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:complexType name="Policy">
+        <xs:sequence>
+            <xs:element name="group" minOccurs="0" maxOccurs="unbounded" >
+                <xs:complexType>
+                    <xs:attribute name="identifier">
+                        <xs:simpleType>
+                            <xs:restriction base="xs:string">
+                                <xs:minLength value="1"/>
+                                <xs:pattern value=".*[^\s].*"/>
+                            </xs:restriction>
+                        </xs:simpleType>
+                    </xs:attribute>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="user" minOccurs="0" maxOccurs="unbounded" >
+                <xs:complexType>
+                    <xs:attribute name="identifier">
+                        <xs:simpleType>
+                            <xs:restriction base="xs:string">
+                                <xs:minLength value="1"/>
+                                <xs:pattern value=".*[^\s].*"/>
+                            </xs:restriction>
+                        </xs:simpleType>
+                    </xs:attribute>
+                </xs:complexType>
+            </xs:element>
+        </xs:sequence>
+        <xs:attribute name="identifier">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:minLength value="1"/>
+                    <xs:pattern value=".*[^\s].*"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+        <xs:attribute name="resource">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:minLength value="1"/>
+                    <xs:pattern value=".*[^\s].*"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+        <xs:attribute name="action">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:enumeration value="R"/>
+                    <xs:enumeration value="W"/>
+                    <xs:enumeration value="D"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="Policies">
+        <xs:sequence>
+            <xs:element name="policy" type="Policy" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- top-level authorizations element -->
+    <xs:element name="authorizations">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="policies" type="Policies" minOccurs="0" maxOccurs="1" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizers.xsd
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizers.xsd b/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizers.xsd
new file mode 100644
index 0000000..ed2a293
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/authorizers.xsd
@@ -0,0 +1,68 @@
+<?xml version="1.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.
+  -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <!-- user group providers type -->
+    <xs:complexType name="UserGroupProvider">
+        <xs:sequence>
+            <xs:element name="identifier" type="xs:string"/>
+            <xs:element name="class" type="xs:string"/>
+            <xs:element name="property" type="Prop" minOccurs="0" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- access policy provider type -->
+    <xs:complexType name="AccessPolicyProvider">
+        <xs:sequence>
+            <xs:element name="identifier" type="xs:string"/>
+            <xs:element name="class" type="xs:string"/>
+            <xs:element name="property" type="Prop" minOccurs="0" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- authorizers type -->
+    <xs:complexType name="Authorizer">
+        <xs:sequence>
+            <xs:element name="identifier" type="xs:string"/>
+            <xs:element name="class" type="xs:string"/>
+            <xs:element name="classpath" type="xs:string" minOccurs="0"/>
+            <xs:element name="property" type="Prop" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Name/Value properties-->
+    <xs:complexType name="Prop">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="xs:string"></xs:attribute>
+                <xs:attribute name="encryption" type="xs:string"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <!-- authorizers -->
+    <xs:element name="authorizers">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="userGroupProvider" type="UserGroupProvider" minOccurs="0" maxOccurs="unbounded"/>
+                <xs:element name="accessPolicyProvider" type="AccessPolicyProvider" minOccurs="0" maxOccurs="unbounded"/>
+                <xs:element name="authorizer" type="Authorizer" minOccurs="0" maxOccurs="unbounded"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/xsd/identity-providers.xsd
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/xsd/identity-providers.xsd b/nifi-registry-core/nifi-registry-framework/src/main/xsd/identity-providers.xsd
new file mode 100644
index 0000000..bcca014
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/identity-providers.xsd
@@ -0,0 +1,50 @@
+<?xml version="1.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.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <!-- role -->
+    <xs:complexType name="Provider">
+        <xs:sequence>
+            <xs:element name="identifier" type="NonEmptyStringType"/>
+            <xs:element name="class" type="NonEmptyStringType"/>
+            <xs:element name="property" type="Property" minOccurs="0" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Name/Value properties-->
+    <xs:complexType name="Property">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="NonEmptyStringType"/>
+                <xs:attribute name="encryption" type="xs:string"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="NonEmptyStringType">
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- login identity provider -->
+    <xs:element name="identityProviders">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="provider" type="Provider" minOccurs="0" maxOccurs="unbounded"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd b/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
new file mode 100644
index 0000000..ce82dcc
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
@@ -0,0 +1,51 @@
+<?xml version="1.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.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <!-- Provider type -->
+    <xs:complexType name="Provider">
+        <xs:sequence>
+            <xs:element name="class" type="NonEmptyStringType"/>
+            <xs:element name="property" type="Property" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Name/Value properties-->
+    <xs:complexType name="Property">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="NonEmptyStringType"></xs:attribute>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="NonEmptyStringType">
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- providers -->
+    <xs:element name="providers">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="flowPersistenceProvider" type="Provider" minOccurs="1" maxOccurs="1" />
+                <xs:element name="eventHookProvider" type="Provider" minOccurs="0" maxOccurs="unbounded" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/xsd/tenants.xsd
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/xsd/tenants.xsd b/nifi-registry-core/nifi-registry-framework/src/main/xsd/tenants.xsd
new file mode 100644
index 0000000..c1193c3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/tenants.xsd
@@ -0,0 +1,96 @@
+<?xml version="1.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.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <!-- group -->
+    <xs:complexType name="Group">
+        <xs:sequence>
+            <xs:element name="user" minOccurs="0" maxOccurs="unbounded" >
+                <xs:complexType>
+                    <xs:attribute name="identifier">
+                        <xs:simpleType>
+                            <xs:restriction base="xs:string">
+                                <xs:minLength value="1"/>
+                                <xs:pattern value=".*[^\s].*"/>
+                            </xs:restriction>
+                        </xs:simpleType>
+                    </xs:attribute>
+                </xs:complexType>
+            </xs:element>
+        </xs:sequence>
+        <xs:attribute name="identifier">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:minLength value="1"/>
+                    <xs:pattern value=".*[^\s].*"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+        <xs:attribute name="name">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:minLength value="1"/>
+                    <xs:pattern value=".*[^\s].*"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+    </xs:complexType>
+
+    <!-- groups -->
+    <xs:complexType name="Groups">
+        <xs:sequence>
+            <xs:element name="group" type="Group" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- user -->
+    <xs:complexType name="User">
+        <xs:attribute name="identifier">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:minLength value="1"/>
+                    <xs:pattern value=".*[^\s].*"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+        <xs:attribute name="identity">
+            <xs:simpleType>
+                <xs:restriction base="xs:string">
+                    <xs:minLength value="1"/>
+                    <xs:pattern value=".*[^\s].*"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
+    </xs:complexType>
+
+    <!-- users -->
+    <xs:complexType name="Users">
+        <xs:sequence>
+            <xs:element name="user" type="User" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- top-level authorizations element -->
+    <xs:element name="tenants">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="groups" type="Groups" minOccurs="0" maxOccurs="1" />
+                <xs:element name="users" type="Users" minOccurs="0" maxOccurs="1" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
new file mode 100644
index 0000000..932d483
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/security/authorization/AuthorizerFactorySpec.groovy
@@ -0,0 +1,129 @@
+/*
+ * 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.registry.security.authorization
+
+import org.apache.nifi.registry.extension.ExtensionManager
+import org.apache.nifi.registry.properties.NiFiRegistryProperties
+import org.apache.nifi.registry.security.authorization.file.FileAccessPolicyProvider
+import org.apache.nifi.registry.security.authorization.file.FileUserGroupProvider
+import org.apache.nifi.registry.security.authorization.resource.ResourceFactory
+import spock.lang.Specification
+
+class AuthorizerFactorySpec extends Specification {
+
+    def mockProperties = Mock(NiFiRegistryProperties)
+    def mockExtensionManager = Mock(ExtensionManager)
+
+    AuthorizerFactory authorizerFactory
+
+    // runs before every feature method
+    def setup() {
+        mockExtensionManager.getExtensionClassLoader(_) >> this.getClass().getClassLoader()
+        mockProperties.getPropertyKeys() >> new HashSet<String>() // Called by IdentityMappingUtil.getIdentityMappings()
+
+        authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null)
+    }
+
+    // runs after every feature method
+    def cleanup() {
+        authorizerFactory = null
+    }
+
+    // runs before the first feature method
+    def setupSpec() {}
+
+    // runs after the last feature method
+    def cleanupSpec() {}
+
+    def "create default authorizer"() {
+
+        setup: "properties indicate nifi-registry is unsecured"
+        mockProperties.getProperty(NiFiRegistryProperties.WEB_HTTPS_PORT) >> ""
+
+        when: "getAuthorizer() is first called"
+        def authorizer = authorizerFactory.getAuthorizer()
+
+        then: "the default authorizer is returned"
+        authorizer != null
+
+        and: "any authorization request made to that authorizer is approved"
+        def authorizationResult = authorizer.authorize(getTestAuthorizationRequest())
+        authorizationResult.result == AuthorizationResult.Result.Approved
+
+    }
+
+    def "create file-backed authorizer"() {
+
+        setup:
+        setMockPropsAuthorizersConfig("src/test/resources/security/authorizers-good-file-providers.xml", "managed-authorizer")
+
+        when: "getAuthorizer() is first called"
+        def authorizer = authorizerFactory.getAuthorizer()
+
+        then: "an authorizer is returned with the expected providers"
+        authorizer != null
+        authorizer instanceof ManagedAuthorizer
+        def apProvider = ((ManagedAuthorizer) authorizer).getAccessPolicyProvider()
+        apProvider instanceof ConfigurableAccessPolicyProvider
+        def ugProvider = ((ConfigurableAccessPolicyProvider) apProvider).getUserGroupProvider()
+        ugProvider instanceof ConfigurableUserGroupProvider
+
+    }
+
+    def "invalid authorizer configuration fails"() {
+
+        when: "a bad configuration is provided and getAuthorizer() is called"
+        setMockPropsAuthorizersConfig(authorizersConfigFile, selectedAuthorizer)
+        authorizerFactory = new AuthorizerFactory(mockProperties, mockExtensionManager, null)
+        authorizerFactory.getAuthorizer()
+
+        then: "expect an exception"
+        def e = thrown AuthorizerFactoryException
+        e.message =~ expectedExceptionMessage || e.getCause().getMessage() =~ expectedExceptionMessage
+
+        where:
+        authorizersConfigFile                                                    | selectedAuthorizer        | expectedExceptionMessage
+        "src/test/resources/security/authorizers-good-file-providers.xml"        | ""                        | "When running securely, the authorizer identifier must be specified in the nifi-registry.properties file."
+        "src/test/resources/security/authorizers-good-file-providers.xml"        | "non-existent-authorizer" | "The specified authorizer 'non-existent-authorizer' could not be found."
+        "src/test/resources/security/authorizers-bad-ug-provider-ids.xml"        | "managed-authorizer"      | "Duplicate User Group Provider identifier in Authorizers configuration"
+        "src/test/resources/security/authorizers-bad-ap-provider-ids.xml"        | "managed-authorizer"      | "Duplicate Access Policy Provider identifier in Authorizers configuration"
+        "src/test/resources/security/authorizers-bad-authorizer-ids.xml"         | "managed-authorizer"      | "Duplicate Authorizer identifier in Authorizers configuration"
+        "src/test/resources/security/authorizers-bad-composite.xml"              | "managed-authorizer"      | "Duplicate provider in Composite User Group Provider configuration"
+        "src/test/resources/security/authorizers-bad-configurable-composite.xml" | "managed-authorizer"      | "Duplicate provider in Composite Configurable User Group Provider configuration"
+
+    }
+
+    // Helper methods
+
+    private void setMockPropsAuthorizersConfig(String filePath, String authorizer = "managed-authorizer") {
+        mockProperties.getProperty(NiFiRegistryProperties.WEB_HTTPS_PORT) >> "443"
+        mockProperties.getSslPort() >> 443 // required to be non-null to create authorizer
+        mockProperties.getProperty(NiFiRegistryProperties.SECURITY_AUTHORIZERS_CONFIGURATION_FILE) >> filePath
+        mockProperties.getAuthorizersConfigurationFile() >> new File(filePath)
+        mockProperties.getProperty(NiFiRegistryProperties.SECURITY_AUTHORIZER) >> authorizer
+    }
+
+    private static AuthorizationRequest getTestAuthorizationRequest() {
+        return new AuthorizationRequest.Builder()
+                .resource(ResourceFactory.getBucketsResource())
+                .action(RequestAction.WRITE)
+                .accessAttempt(false)
+                .anonymous(true)
+                .build()
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
new file mode 100644
index 0000000..149ec36
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/groovy/org/apache/nifi/registry/service/AuthorizationServiceSpec.groovy
@@ -0,0 +1,615 @@
+/*
+ * 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.registry.service
+
+import org.apache.nifi.registry.authorization.AccessPolicy
+import org.apache.nifi.registry.authorization.User
+import org.apache.nifi.registry.authorization.UserGroup
+import org.apache.nifi.registry.bucket.Bucket
+import org.apache.nifi.registry.security.authorization.AccessPolicy as AuthAccessPolicy
+import org.apache.nifi.registry.security.authorization.AuthorizableLookup
+import org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider
+import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider
+import org.apache.nifi.registry.security.authorization.Group
+import org.apache.nifi.registry.security.authorization.RequestAction
+import org.apache.nifi.registry.security.authorization.StandardManagedAuthorizer
+import org.apache.nifi.registry.security.authorization.User as AuthUser
+import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException
+import org.apache.nifi.registry.security.authorization.resource.Authorizable
+import org.apache.nifi.registry.security.authorization.resource.ResourceType
+import spock.lang.Specification
+
+class AuthorizationServiceSpec extends Specification {
+
+    def registryService = Mock(RegistryService)
+    def authorizableLookup = Mock(AuthorizableLookup)
+    def userGroupProvider = Mock(ConfigurableUserGroupProvider)
+    def accessPolicyProvider = Mock(ConfigurableAccessPolicyProvider)
+
+    AuthorizationService authorizationService
+
+    def setup() {
+        accessPolicyProvider.getUserGroupProvider() >> userGroupProvider
+        def authorizer = new StandardManagedAuthorizer(accessPolicyProvider, userGroupProvider)
+        authorizationService = new AuthorizationService(authorizableLookup, authorizer, registryService)
+    }
+
+    // ----- User tests -------------------------------------------------------
+
+    def "create user"() {
+
+        setup:
+        userGroupProvider.addUser(!null as AuthUser) >> {
+            AuthUser u -> new AuthUser.Builder().identifier(u.identifier).identity(u.identity).build()
+        }
+        userGroupProvider.getGroups() >> new HashSet<Group>()  // needed for converting user to DTO
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()  // needed for converting user to DTO
+
+        when: "new user is created successfully"
+        def user = new User(null, "username")
+        User createdUser = authorizationService.createUser(user)
+
+        then: "created user has been assigned an identifier"
+        with(createdUser) {
+            identifier != null
+            identity == "username"
+        }
+
+    }
+
+    def "list users"() {
+
+        setup:
+        userGroupProvider.getUsers() >> [
+                new AuthUser.Builder().identifier("user1").identity("username1").build(),
+                new AuthUser.Builder().identifier("user2").identity("username2").build(),
+                new AuthUser.Builder().identifier("user3").identity("username3").build(),
+        ]
+        userGroupProvider.getGroups() >> new HashSet<Group>()
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+        when: "list of users is queried"
+        def users = authorizationService.getUsers()
+
+        then: "users are successfully returned as list of DTO objects"
+        users != null
+        users.size() == 3
+        with(users[0]) {
+            identifier == "user1"
+            identity == "username1"
+        }
+        with(users[1]) {
+            identifier == "user2"
+            identity == "username2"
+        }
+        with(users[2]) {
+            identifier == "user3"
+            identity == "username3"
+        }
+
+    }
+
+    def "get user"() {
+
+        setup:
+        userGroupProvider.getGroups() >> new HashSet<Group>()
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+
+        when: "get user for existing user identifier"
+        userGroupProvider.getUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build()
+        def user1 = authorizationService.getUser("userId")
+
+        then: "user is returned converted to DTO"
+        with(user1) {
+            identifier == "userId"
+            identity == "username"
+        }
+
+
+        when: "get user for non-existent user identifier"
+        userGroupProvider.getUser("nonExistentUserId") >> null
+        userGroupProvider.getGroup("nonExistentUserId") >> null
+        def user2 = authorizationService.getUser("nonExistentUserId")
+
+        then: "no user is returned"
+        user2 == null
+
+    }
+
+    def "update user"() {
+
+        setup:
+        userGroupProvider.updateUser(!null as AuthUser) >> {
+            AuthUser u -> new AuthUser.Builder().identifier(u.identifier).identity(u.identity).build()
+        }
+        userGroupProvider.getGroups() >> new HashSet<Group>()
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+
+        when: "user is updated"
+        def user = authorizationService.updateUser(new User("userId", "username"))
+
+        then: "updated user is returned"
+        with(user) {
+            identifier == "userId"
+            identity == "username"
+        }
+
+    }
+
+    def "delete user"() {
+
+        setup:
+        userGroupProvider.getUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build()
+        userGroupProvider.deleteUser("userId") >> new AuthUser.Builder().identifier("userId").identity("username").build()
+        userGroupProvider.getGroups() >> new HashSet<Group>()
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+
+        when: "user is deleted"
+        def user = authorizationService.deleteUser("userId")
+
+        then: "deleted user is returned converted to DTO"
+        with(user) {
+            identifier == "userId"
+            identity == "username"
+        }
+
+    }
+
+    // ----- User Group tests -------------------------------------------------
+
+    def "create user group"() {
+
+        setup:
+        userGroupProvider.addGroup(!null as Group) >> {
+            Group g -> new Group.Builder().identifier(g.identifier).name(g.name).build()
+        }
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()  // needed for converting to DTO
+
+        when: "new group is created successfully"
+        def group = new UserGroup(null, "groupName")
+        UserGroup createdGroup = authorizationService.createUserGroup(group)
+
+        then: "created group has been assigned an identifier"
+        with(createdGroup) {
+            identifier != null
+            identity == "groupName"
+        }
+
+    }
+
+    def "list user groups"() {
+
+        setup:
+        userGroupProvider.getGroups() >> [
+                new Group.Builder().identifier("groupId1").name("groupName1").build(),
+                new Group.Builder().identifier("groupId2").name("groupName2").build(),
+                new Group.Builder().identifier("groupId3").name("groupName3").build(),
+        ]
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+        when: "list of groups is queried"
+        def groups = authorizationService.getUserGroups()
+
+        then: "groups are successfully returned as list of DTO objects"
+        groups != null
+        groups.size() == 3
+        with(groups[0]) {
+            identifier == "groupId1"
+            identity == "groupName1"
+        }
+        with(groups[1]) {
+            identifier == "groupId2"
+            identity == "groupName2"
+        }
+        with(groups[2]) {
+            identifier == "groupId3"
+            identity == "groupName3"
+        }
+
+    }
+
+    def "get user group"() {
+
+        setup:
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+
+        when: "get group for existing user identifier"
+        userGroupProvider.getGroup("groupId") >> new Group.Builder().identifier("groupId").name ("groupName").build()
+        def g1 = authorizationService.getUserGroup("groupId")
+
+        then: "group is returned converted to DTO"
+        with(g1) {
+            identifier == "groupId"
+            identity == "groupName"
+        }
+
+
+        when: "get group for non-existent group identifier"
+        userGroupProvider.getUser("nonExistentId") >> null
+        userGroupProvider.getGroup("nonExistentId") >> null
+        def g2 = authorizationService.getUserGroup("nonExistentId")
+
+        then: "no group is returned"
+        g2 == null
+
+    }
+
+    def "update user group"() {
+
+        setup:
+        userGroupProvider.updateGroup(!null as Group) >> {
+            Group g -> new Group.Builder().identifier(g.identifier).name(g.name).build()
+        }
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+
+        when: "group is updated"
+        def group = authorizationService.updateUserGroup(new UserGroup("id", "name"))
+
+        then: "updated group is returned converted to DTO"
+        with(group) {
+            identifier == "id"
+            identity == "name"
+        }
+
+    }
+
+    def "delete user group"() {
+
+        setup:
+        userGroupProvider.getGroup("id") >> new Group.Builder().identifier("id").name("name").build()
+        userGroupProvider.deleteGroup("id") >> new Group.Builder().identifier("id").name("name").build()
+        accessPolicyProvider.getAccessPolicies() >> new HashSet<AccessPolicy>()
+
+
+        when: "group is deleted"
+        def group = authorizationService.deleteUserGroup("id")
+
+        then: "deleted user is returned"
+        with(group) {
+            identifier == "id"
+            identity == "name"
+        }
+
+    }
+
+    // ----- Access Policy tests ----------------------------------------------
+
+    def "create access policy"() {
+
+        setup:
+        accessPolicyProvider.addAccessPolicy(!null as AuthAccessPolicy) >> {
+            AuthAccessPolicy p -> new AuthAccessPolicy.Builder()
+                    .identifier(p.identifier)
+                    .resource(p.resource)
+                    .action(p.action)
+                    .addGroups(p.groups)
+                    .addUsers(p.users)
+                    .build()
+        }
+        accessPolicyProvider.isConfigurable(_ as AuthAccessPolicy) >> true
+
+
+        when: "new access policy is created successfully"
+        def createdPolicy = authorizationService.createAccessPolicy(new AccessPolicy([resource: "/resource", action: "read"]))
+
+        then: "created policy has been assigned an identifier"
+        with(createdPolicy) {
+            identifier != null
+            resource == "/resource"
+            action == "read"
+            configurable == true
+        }
+
+    }
+
+    def "list access policies"() {
+
+        setup:
+        accessPolicyProvider.getAccessPolicies() >> [
+                new AuthAccessPolicy.Builder().identifier("ap1").resource("r1").action(RequestAction.READ).build(),
+                new AuthAccessPolicy.Builder().identifier("ap2").resource("r2").action(RequestAction.WRITE).build()
+        ]
+
+        when: "list access polices is queried"
+        def policies = authorizationService.getAccessPolicies()
+
+        then: "access policies are successfully returned as list of DTO objects"
+        policies != null
+        policies.size() == 2
+        with(policies[0]) {
+            identifier == "ap1"
+            resource == "r1"
+            action == RequestAction.READ.toString()
+        }
+        with(policies[1]) {
+            identifier == "ap2"
+            resource == "r2"
+            action == RequestAction.WRITE.toString()
+        }
+
+    }
+
+    def "get access policy"() {
+
+        when: "get policy for existing identifier"
+        accessPolicyProvider.getAccessPolicy("id") >> new AuthAccessPolicy.Builder()
+                .identifier("id")
+                .resource("/resource")
+                .action(RequestAction.READ)
+                .build()
+        def p1 = authorizationService.getAccessPolicy("id")
+
+        then: "policy is returned converted to DTO"
+        with(p1) {
+            identifier == "id"
+            resource == "/resource"
+            action == RequestAction.READ.toString()
+        }
+
+
+        when: "get policy for non-existent identifier"
+        accessPolicyProvider.getAccessPolicy("nonExistentId") >> null
+        def p2 = authorizationService.getAccessPolicy("nonExistentId")
+
+        then: "no policy is returned"
+        p2 == null
+
+    }
+
+
+    def "update access policy"() {
+
+        setup:
+        def users = [
+                "user1": "alice",
+                "user2": "bob",
+                "user3": "charlie" ]
+        def groups = [
+                "group1": "users",
+                "group2": "devs",
+                "group3": "admins" ]
+        def policies = [
+                "policy1": [
+                        "resource": "/resource1",
+                        "action": "read",
+                        "users": [ "user1" ],
+                        "groups": []
+                ]
+        ]
+        def mapDtoUser = { String id -> new User(id, users[id])}
+        def mapDtoGroup = { String id -> new UserGroup(id, groups[id])}
+        def mapAuthUser = { String id -> new AuthUser.Builder().identifier(id).identity(users[id]).build() }
+        def mapAuthGroup = { String id -> new Group.Builder().identifier(id).name(groups[id]).build() }
+        def mapAuthAccessPolicy = {
+            String id -> return new AuthAccessPolicy.Builder()
+                    .identifier(id)
+                    .resource(policies[id]["resource"] as String)
+                    .action(RequestAction.valueOfValue(policies[id]["action"] as String))
+                    .addUsers(policies[id]["users"] as Set<String>)
+                    .addGroups(policies[id]["groups"] as Set<String>)
+                    .build()
+        }
+        userGroupProvider.getUser(!null as String) >> { String id -> users.containsKey(id) ? mapAuthUser(id) : null }
+        userGroupProvider.getGroup(!null as String) >> { String id -> groups.containsKey(id) ? mapAuthGroup(id) : null }
+        userGroupProvider.getUsers() >> {
+            def authUsers = []
+            users.each{ k, v -> authUsers.add(new AuthUser.Builder().identifier(k).identity(v).build()) }
+            return authUsers
+        }
+        userGroupProvider.getGroups() >> {
+            def authGroups = []
+            users.each{ k, v -> authGroups.add(new Group.Builder().identifier(k).name(v).build()) }
+            return authGroups
+        }
+        accessPolicyProvider.getAccessPolicy(!null as String) >> { String id -> policies.containsKey(id) ? mapAuthAccessPolicy(id) : null }
+        accessPolicyProvider.updateAccessPolicy(!null as AuthAccessPolicy) >> {
+            AuthAccessPolicy p -> new AuthAccessPolicy.Builder()
+                    .identifier(p.identifier)
+                    .resource(p.resource)
+                    .action(p.action)
+                    .addGroups(p.groups)
+                    .addUsers(p.users)
+                    .build()
+        }
+        accessPolicyProvider.isConfigurable(_ as AuthAccessPolicy) >> true
+
+
+        when: "policy is updated"
+        def policy = new AccessPolicy([identifier: "policy1", resource: "/resource1", action: "read"])
+        policy.addUsers([mapDtoUser("user1"), mapDtoUser("user2")])
+        policy.addUserGroups([mapDtoGroup("group1")])
+        def p1 = authorizationService.updateAccessPolicy(policy)
+
+        then: "updated group is returned converted to DTO"
+        p1 != null
+        p1.users.size() == 2
+        def sortedUsers = p1.users.sort{it.identifier}
+        with(sortedUsers[0]) {
+            identifier == "user1"
+            identity == "alice"
+        }
+        with(sortedUsers[1]) {
+            identifier == "user2"
+            identity == "bob"
+        }
+        p1.userGroups.size() == 1
+        with(p1.userGroups[0]) {
+            identifier == "group1"
+            identity == "users"
+        }
+
+
+        when: "attempt to change policy resource and action"
+        def p2 = authorizationService.updateAccessPolicy(new AccessPolicy([identifier: "policy1", resource: "/newResource", action: "write"]))
+
+        then: "resource and action are unchanged"
+        with(p2) {
+            identifier == "policy1"
+            resource == "/resource1"
+            action == "read"
+        }
+
+    }
+
+    def "delete access policy"() {
+
+        setup:
+        userGroupProvider.getGroups() >> new HashSet<Group>()
+        userGroupProvider.getUsers() >> new HashSet<AuthUser>()
+        accessPolicyProvider.getAccessPolicy("id") >> {
+            String id -> new AuthAccessPolicy.Builder()
+                    .identifier("id")
+                    .resource("/resource")
+                    .action(RequestAction.READ)
+                    .addGroups(new HashSet<String>())
+                    .addUsers(new HashSet<String>())
+                    .build()
+        }
+        accessPolicyProvider.deleteAccessPolicy(!null as String) >> {
+            String id -> new AuthAccessPolicy.Builder()
+                    .identifier(id)
+                    .resource("/resource")
+                    .action(RequestAction.READ)
+                    .addGroups(new HashSet<String>())
+                    .addUsers(new HashSet<String>())
+                    .build()
+        }
+
+        when: "access policy is deleted"
+        def policy = authorizationService.deleteAccessPolicy("id")
+
+        then: "deleted policy is returned"
+        with(policy) {
+            identifier == "id"
+            resource == "/resource"
+            action == RequestAction.READ.toString()
+        }
+
+    }
+
+    // ----- Resource tests ---------------------------------------------------
+
+    def "get resources"() {
+
+        setup:
+        def buckets = [
+                "b1": [
+                        "name": "Bucket #1",
+                        "description": "An initial bucket for testing",
+                        "createdTimestamp": 1
+                ],
+                "b2": [
+                        "name": "Bucket #2",
+                        "description": "A second bucket for testing",
+                        "createdTimestamp": 2
+                ],
+        ]
+        def mapBucket = {
+            String id -> new Bucket([
+                    identifier: id,
+                    name: buckets[id]["name"] as String,
+                    description: buckets[id]["description"] as String]) }
+
+        registryService.getBuckets() >> {[ mapBucket("b1"), mapBucket("b2") ]}
+
+        when:
+        def resources = authorizationService.getResources()
+
+        then:
+        resources != null
+        resources.size() == 8
+        def sortedResources = resources.sort{it.identifier}
+        sortedResources[0].identifier == "/actuator"
+        sortedResources[1].identifier == "/buckets"
+        sortedResources[2].identifier == "/buckets/b1"
+        sortedResources[3].identifier == "/buckets/b2"
+        sortedResources[4].identifier == "/policies"
+        sortedResources[5].identifier == "/proxy"
+        sortedResources[6].identifier == "/swagger"
+        sortedResources[7].identifier == "/tenants"
+
+    }
+
+    def "get authorized resources"() {
+
+        setup:
+        def buckets = [
+                "b1": [
+                        "name": "Bucket #1",
+                        "description": "An initial bucket for testing",
+                        "createdTimestamp": 1
+                ],
+                "b2": [
+                        "name": "Bucket #2",
+                        "description": "A second bucket for testing",
+                        "createdTimestamp": 2
+                ],
+        ]
+        def mapBucket = {
+            String id -> new Bucket([
+                    identifier: id,
+                    name: buckets[id]["name"] as String,
+                    description: buckets[id]["description"] as String]) }
+
+        registryService.getBuckets() >> {[ mapBucket("b1"), mapBucket("b2") ]}
+
+        def authorized = Mock(Authorizable)
+        authorized.authorize(_, _, _) >> { return }
+        def denied = Mock(Authorizable)
+        denied.authorize(_, _, _) >> { throw new AccessDeniedException("") }
+
+        authorizableLookup.getAuthorizableByResource("/actuator")   >> denied
+        authorizableLookup.getAuthorizableByResource("/buckets")    >> authorized
+        authorizableLookup.getAuthorizableByResource("/buckets/b1") >> authorized
+        authorizableLookup.getAuthorizableByResource("/buckets/b2") >> denied
+        authorizableLookup.getAuthorizableByResource("/policies")   >> authorized
+        authorizableLookup.getAuthorizableByResource("/proxy")      >> denied
+        authorizableLookup.getAuthorizableByResource("/swagger")    >> denied
+        authorizableLookup.getAuthorizableByResource("/tenants")    >> authorized
+
+
+        when:
+        def resources = authorizationService.getAuthorizedResources(RequestAction.READ)
+
+        then:
+        resources != null
+        resources.size() == 4
+        def sortedResources = resources.sort{it.identifier}
+        sortedResources[0].identifier == "/buckets"
+        sortedResources[1].identifier == "/buckets/b1"
+        sortedResources[2].identifier == "/policies"
+        sortedResources[3].identifier == "/tenants"
+
+
+        when:
+        def filteredResources = authorizationService.getAuthorizedResources(RequestAction.READ, ResourceType.Bucket)
+
+        then:
+        filteredResources != null
+        filteredResources.size() == 2
+        def sortedFilteredResources = filteredResources.sort{it.identifier}
+        sortedFilteredResources[0].identifier == "/buckets"
+        sortedFilteredResources[1].identifier == "/buckets/b1"
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseBaseTest.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseBaseTest.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseBaseTest.java
new file mode 100644
index 0000000..02b04c0
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseBaseTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.registry.db;
+
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = DatabaseTestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
+public abstract class DatabaseBaseTest {
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java
new file mode 100644
index 0000000..0ce3812
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java
@@ -0,0 +1,52 @@
+/*
+ * 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.registry.db;
+
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.mockito.Mockito;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+
+/**
+ * Sets up the application context for database repository tests.
+ *
+ * The @SpringBootTest annotation on the repository tests will find this class by working up the package hierarchy.
+ * This class must be in the "db" package in order to find the entities in "db.entity" and repositories in "db.repository".
+ *
+ * The DataSourceFactory is excluded so that Spring Boot will load an in-memory H2 database.
+ */
+@SpringBootApplication
+@ComponentScan(
+        excludeFilters = {
+                @ComponentScan.Filter(
+                        type = FilterType.ASSIGNABLE_TYPE,
+                        value = DataSourceFactory.class)
+        })
+public class DatabaseTestApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(DatabaseTestApplication.class, args);
+    }
+
+    @Bean
+    public NiFiRegistryProperties createNiFiRegistryProperties() {
+        return Mockito.mock(NiFiRegistryProperties.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseKeyService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseKeyService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseKeyService.java
new file mode 100644
index 0000000..d0ab56a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseKeyService.java
@@ -0,0 +1,76 @@
+/*
+ * 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.registry.db;
+
+import org.apache.nifi.registry.security.key.Key;
+import org.apache.nifi.registry.security.key.KeyService;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class TestDatabaseKeyService extends DatabaseBaseTest {
+
+    @Autowired
+    private KeyService keyService;
+
+    @Test
+    public void testGetKeyByIdWhenExists() {
+        final Key existingKey = keyService.getKey("1");
+        assertNotNull(existingKey);
+        assertEquals("1", existingKey.getId());
+        assertEquals("unit_test_tenant_identity", existingKey.getIdentity());
+        assertEquals("0123456789abcdef", existingKey.getKey());
+    }
+
+    @Test
+    public void testGetKeyByIdWhenDoesNotExist() {
+        final Key existingKey = keyService.getKey("2");
+        assertNull(existingKey);
+    }
+
+    @Test
+    public void testGetOrCreateKeyWhenExists() {
+        final Key existingKey = keyService.getOrCreateKey("unit_test_tenant_identity");
+        assertNotNull(existingKey);
+        assertEquals("1", existingKey.getId());
+        assertEquals("unit_test_tenant_identity", existingKey.getIdentity());
+        assertEquals("0123456789abcdef", existingKey.getKey());
+    }
+
+    @Test
+    public void testGetOrCreateKeyWhenDoesNotExist() {
+        final Key createdKey = keyService.getOrCreateKey("does-not-exist");
+        assertNotNull(createdKey);
+        assertNotNull(createdKey.getId());
+        assertEquals("does-not-exist", createdKey.getIdentity());
+        assertNotNull(createdKey.getKey());
+    }
+
+    @Test
+    public void testDeleteKeyWhenExists() {
+        final Key existingKey = keyService.getKey("1");
+        assertNotNull(existingKey);
+
+        keyService.deleteKey(existingKey.getIdentity());
+
+        final Key deletedKey = keyService.getKey("1");
+        assertNull(deletedKey);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
new file mode 100644
index 0000000..35ba757
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
@@ -0,0 +1,386 @@
+/*
+ * 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.registry.db;
+
+import org.apache.nifi.registry.db.entity.BucketEntity;
+import org.apache.nifi.registry.db.entity.BucketItemEntity;
+import org.apache.nifi.registry.db.entity.BucketItemEntityType;
+import org.apache.nifi.registry.db.entity.FlowEntity;
+import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
+import org.apache.nifi.registry.service.MetadataService;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class TestDatabaseMetadataService extends DatabaseBaseTest {
+
+    @Autowired
+    private MetadataService metadataService;
+
+    //----------------- Buckets ---------------------------------
+
+    @Test
+    public void testCreateAndGetBucket() {
+        final BucketEntity b = new BucketEntity();
+        b.setId("testBucketId");
+        b.setName("testBucketName");
+        b.setDescription("testBucketDesc");
+        b.setCreated(new Date());
+
+        metadataService.createBucket(b);
+
+        final BucketEntity createdBucket = metadataService.getBucketById(b.getId());
+        assertNotNull(createdBucket);
+        assertEquals(b.getId(), createdBucket.getId());
+        assertEquals(b.getName(), createdBucket.getName());
+        assertEquals(b.getDescription(), createdBucket.getDescription());
+        assertEquals(b.getCreated(), createdBucket.getCreated());
+    }
+
+    @Test
+    public void testGetBucketDoesNotExist() {
+        final BucketEntity bucket = metadataService.getBucketById("does-not-exist");
+        assertNull(bucket);
+    }
+
+    @Test
+    public void testGetBucketsByName() {
+        final List<BucketEntity> buckets = metadataService.getBucketsByName("Bucket 1");
+        assertNotNull(buckets);
+        assertEquals(1, buckets.size());
+        assertEquals("Bucket 1", buckets.get(0).getName());
+    }
+
+    @Test
+    public void testGetBucketsByNameNoneFound() {
+        final List<BucketEntity> buckets = metadataService.getBucketsByName("Bucket XYZ");
+        assertNotNull(buckets);
+        assertEquals(0, buckets.size());
+    }
+
+    @Test
+    public void testUpdateBucket() {
+        final BucketEntity bucket = metadataService.getBucketById("1");
+        assertNotNull(bucket);
+
+        final String updatedName = bucket.getName() + " UPDATED";
+        final String updatedDesc = bucket.getDescription() + "DESC";
+
+        bucket.setName(updatedName);
+        bucket.setDescription(updatedDesc);
+
+        metadataService.updateBucket(bucket);
+
+        final BucketEntity updatedBucket = metadataService.getBucketById(bucket.getId());
+        assertNotNull(updatedName);
+        assertEquals(updatedName, updatedBucket.getName());
+        assertEquals(updatedDesc, updatedBucket.getDescription());
+    }
+
+    @Test
+    public void testDeleteBucketNoChildren() {
+        final BucketEntity bucket = metadataService.getBucketById("6");
+        assertNotNull(bucket);
+
+        metadataService.deleteBucket(bucket);
+
+        final BucketEntity deletedBucket = metadataService.getBucketById("6");
+        assertNull(deletedBucket);
+    }
+
+    @Test
+    public void testDeleteBucketWithChildren() {
+        final BucketEntity bucket = metadataService.getBucketById("1");
+        assertNotNull(bucket);
+
+        metadataService.deleteBucket(bucket);
+
+        final BucketEntity deletedBucket = metadataService.getBucketById("1");
+        assertNull(deletedBucket);
+    }
+
+    @Test
+    public void testGetBucketsForIds() {
+        final List<BucketEntity> buckets = metadataService.getBuckets(new HashSet<>(Arrays.asList("1", "2")));
+        assertNotNull(buckets);
+        assertEquals(2, buckets.size());
+        assertEquals("1", buckets.get(0).getId());
+        assertEquals("2", buckets.get(1).getId());
+    }
+
+    @Test
+    public void testGetAllBuckets() {
+        final List<BucketEntity> buckets = metadataService.getAllBuckets();
+        assertNotNull(buckets);
+        assertEquals(6, buckets.size());
+    }
+
+    //----------------- BucketItems ---------------------------------
+
+    @Test
+    public void testGetBucketItemsForBucket() {
+        final BucketEntity bucket = metadataService.getBucketById("1");
+        assertNotNull(bucket);
+
+        final List<BucketItemEntity> items = metadataService.getBucketItems(bucket.getId());
+        assertNotNull(items);
+        assertEquals(2, items.size());
+
+        items.stream().forEach(i -> assertNotNull(i.getBucketName()));
+    }
+
+    @Test
+    public void testGetBucketItemsForBuckets() {
+        final List<BucketItemEntity> items = metadataService.getBucketItems(new HashSet<>(Arrays.asList("1", "2")));
+        assertNotNull(items);
+        assertEquals(3, items.size());
+
+        items.stream().forEach(i -> assertNotNull(i.getBucketName()));
+    }
+
+    @Test
+    public void testGetItemsWithCounts() {
+        final List<BucketItemEntity> items = metadataService.getBucketItems(new HashSet<>(Arrays.asList("1", "2")));
+        assertNotNull(items);
+
+        // 3 items across all buckets
+        assertEquals(3, items.size());
+
+        final BucketItemEntity item1 = items.stream().filter(i -> i.getId().equals("1")).findFirst().orElse(null);
+        assertNotNull(item1);
+        assertEquals(BucketItemEntityType.FLOW, item1.getType());
+
+        final FlowEntity flowEntity = (FlowEntity) item1;
+        assertEquals(3, flowEntity.getSnapshotCount());
+
+        items.stream().forEach(i -> assertNotNull(i.getBucketName()));
+    }
+
+    @Test
+    public void testGetItemsWithCountsFilteredByBuckets() {
+        final List<BucketItemEntity> items = metadataService.getBucketItems(Collections.singleton("1"));
+        assertNotNull(items);
+
+        // only 2 items in bucket 1
+        assertEquals(2, items.size());
+
+        final BucketItemEntity item1 = items.stream().filter(i -> i.getId().equals("1")).findFirst().orElse(null);
+        assertNotNull(item1);
+        assertEquals(BucketItemEntityType.FLOW, item1.getType());
+
+        final FlowEntity flowEntity = (FlowEntity) item1;
+        assertEquals(3, flowEntity.getSnapshotCount());
+
+        items.stream().forEach(i -> assertNotNull(i.getBucketName()));
+    }
+
+    //----------------- Flows ---------------------------------
+
+    @Test
+    public void testGetFlowByIdWhenExists() {
+        final FlowEntity flow = metadataService.getFlowById("1");
+        assertNotNull(flow);
+        assertEquals("1", flow.getId());
+        assertEquals("1", flow.getBucketId());
+    }
+
+    @Test
+    public void testGetFlowByIdWhenDoesNotExist() {
+        final FlowEntity flow = metadataService.getFlowById("does-not-exist");
+        assertNull(flow);
+    }
+
+    @Test
+    public void testCreateFlow() {
+        final String bucketId = "1";
+
+        final FlowEntity flow = new FlowEntity();
+        flow.setId(UUID.randomUUID().toString());
+        flow.setBucketId(bucketId);
+        flow.setName("Test Flow 1");
+        flow.setDescription("Description for Test Flow 1");
+        flow.setCreated(new Date());
+        flow.setModified(new Date());
+        flow.setType(BucketItemEntityType.FLOW);
+
+        metadataService.createFlow(flow);
+
+        final FlowEntity createdFlow = metadataService.getFlowById(flow.getId());
+        assertNotNull(flow);
+        assertEquals(flow.getId(), createdFlow.getId());
+        assertEquals(flow.getBucketId(), createdFlow.getBucketId());
+        assertEquals(flow.getName(), createdFlow.getName());
+        assertEquals(flow.getDescription(), createdFlow.getDescription());
+        assertEquals(flow.getCreated(), createdFlow.getCreated());
+        assertEquals(flow.getModified(), createdFlow.getModified());
+        assertEquals(flow.getType(), createdFlow.getType());
+    }
+
+    @Test
+    public void testGetFlowByIdWithSnapshotCount() {
+       final FlowEntity flowEntity = metadataService.getFlowByIdWithSnapshotCounts("1");
+        assertNotNull(flowEntity);
+        assertEquals(3, flowEntity.getSnapshotCount());
+    }
+
+    @Test
+    public void testGetFlowsByBucket() {
+        final BucketEntity bucketEntity = metadataService.getBucketById("1");
+        final List<FlowEntity> flows = metadataService.getFlowsByBucket(bucketEntity.getId());
+        assertEquals(2, flows.size());
+
+        final FlowEntity flowEntity = flows.stream().filter(f -> f.getId().equals("1")).findFirst().orElse(null);
+        assertNotNull(flowEntity);
+        assertEquals(3, flowEntity.getSnapshotCount());
+    }
+
+    @Test
+    public void testGetFlowsByName() {
+        final List<FlowEntity> flows = metadataService.getFlowsByName("Flow 1");
+        assertNotNull(flows);
+        assertEquals(2, flows.size());
+        assertEquals("Flow 1", flows.get(0).getName());
+        assertEquals("Flow 1", flows.get(1).getName());
+    }
+
+    @Test
+    public void testGetFlowsByNameByBucket() {
+        final List<FlowEntity> flows = metadataService.getFlowsByName("2","Flow 1");
+        assertNotNull(flows);
+        assertEquals(1, flows.size());
+        assertEquals("Flow 1", flows.get(0).getName());
+        assertEquals("2", flows.get(0).getBucketId());
+    }
+
+    @Test
+    public void testUpdateFlow() {
+        final FlowEntity flow = metadataService.getFlowById("1");
+        assertNotNull(flow);
+
+        final Date originalModified = flow.getModified();
+
+        flow.setName(flow.getName() + " UPDATED");
+        flow.setDescription(flow.getDescription() + " UPDATED");
+
+        metadataService.updateFlow(flow);
+
+        final FlowEntity updatedFlow = metadataService.getFlowById( "1");
+        assertNotNull(flow);
+        assertEquals(flow.getName(), updatedFlow.getName());
+        assertEquals(flow.getDescription(), updatedFlow.getDescription());
+        assertEquals(flow.getModified().getTime(), updatedFlow.getModified().getTime());
+        assertTrue(updatedFlow.getModified().getTime() > originalModified.getTime());
+    }
+
+    @Test
+    public void testDeleteFlowWithSnapshots() {
+        final FlowEntity flow = metadataService.getFlowById( "1");
+        assertNotNull(flow);
+
+        metadataService.deleteFlow(flow);
+
+        final FlowEntity deletedFlow = metadataService.getFlowById("1");
+        assertNull(deletedFlow);
+    }
+
+    //----------------- FlowSnapshots ---------------------------------
+
+    @Test
+    public void testGetFlowSnapshot() {
+        final FlowSnapshotEntity entity = metadataService.getFlowSnapshot( "1", 1);
+        assertNotNull(entity);
+        assertEquals("1", entity.getFlowId());
+        assertEquals(1, entity.getVersion().intValue());
+    }
+
+    @Test
+    public void testGetFlowSnapshotDoesNotExist() {
+        final FlowSnapshotEntity entity = metadataService.getFlowSnapshot( "DOES-NOT-EXIST", 1);
+        assertNull(entity);
+    }
+
+    @Test
+    public void testCreateFlowSnapshot() {
+        final FlowSnapshotEntity flowSnapshot = new FlowSnapshotEntity();
+        flowSnapshot.setFlowId("1");
+        flowSnapshot.setVersion(4);
+        flowSnapshot.setCreated(new Date());
+        flowSnapshot.setCreatedBy("test-user");
+        flowSnapshot.setComments("Comments");
+
+        metadataService.createFlowSnapshot(flowSnapshot);
+
+        final FlowSnapshotEntity createdFlowSnapshot = metadataService.getFlowSnapshot(flowSnapshot.getFlowId(), flowSnapshot.getVersion());
+        assertNotNull(createdFlowSnapshot);
+        assertEquals(flowSnapshot.getFlowId(), createdFlowSnapshot.getFlowId());
+        assertEquals(flowSnapshot.getVersion(), createdFlowSnapshot.getVersion());
+        assertEquals(flowSnapshot.getComments(), createdFlowSnapshot.getComments());
+        assertEquals(flowSnapshot.getCreated(), createdFlowSnapshot.getCreated());
+        assertEquals(flowSnapshot.getCreatedBy(), createdFlowSnapshot.getCreatedBy());
+    }
+
+    @Test
+    public void testGetLatestSnapshot() {
+        final FlowSnapshotEntity latest = metadataService.getLatestSnapshot("1");
+        assertNotNull(latest);
+        assertEquals("1", latest.getFlowId());
+        assertEquals(3, latest.getVersion().intValue());
+    }
+
+    @Test
+    public void testGetLatestSnapshotDoesNotExist() {
+        final FlowSnapshotEntity latest = metadataService.getLatestSnapshot("DOES-NOT-EXIST");
+        assertNull(latest);
+    }
+
+    @Test
+    public void testGetFlowSnapshots() {
+        final List<FlowSnapshotEntity> flowSnapshots = metadataService.getSnapshots( "1");
+        assertNotNull(flowSnapshots);
+        assertEquals(3, flowSnapshots.size());
+    }
+
+    @Test
+    public void testGetFlowSnapshotsNoneFound() {
+        final List<FlowSnapshotEntity> flowSnapshots = metadataService.getSnapshots( "2");
+        assertNotNull(flowSnapshots);
+        assertEquals(0, flowSnapshots.size());
+    }
+
+    @Test
+    public void testDeleteFlowSnapshot() {
+        final FlowSnapshotEntity entity = metadataService.getFlowSnapshot( "1", 1);
+        assertNotNull(entity);
+
+        metadataService.deleteFlowSnapshot(entity);
+
+        final FlowSnapshotEntity deletedEntity = metadataService.getFlowSnapshot( "1", 1);
+        assertNull(deletedEntity);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
new file mode 100644
index 0000000..df37e8e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
@@ -0,0 +1,141 @@
+/*
+ * 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.registry.db.migration;
+
+import org.apache.nifi.registry.db.entity.BucketItemEntityType;
+import org.flywaydb.core.Flyway;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test running the legacy Flyway migrations against an in-memory H2 and then using the LegacyDatabaseService to
+ * retrieve data. Purposely not using Spring test annotations here to avoid interfering with the normal DB context/flyway.
+ */
+public class TestLegacyDatabaseService {
+
+    private DataSource dataSource;
+    private JdbcTemplate jdbcTemplate;
+    private Flyway flyway;
+
+    private BucketEntityV1 bucketEntityV1;
+    private FlowEntityV1 flowEntityV1;
+    private FlowSnapshotEntityV1 flowSnapshotEntityV1;
+
+    @Before
+    public void setup() {
+        dataSource = DataSourceBuilder.create()
+                .url("jdbc:h2:mem:legacydb")
+                .driverClassName("org.h2.Driver")
+                .build();
+
+        jdbcTemplate = new JdbcTemplate(dataSource);
+
+        flyway = new Flyway();
+        flyway.setDataSource(dataSource);
+        flyway.setLocations("db/original");
+        flyway.migrate();
+
+        bucketEntityV1 = new BucketEntityV1();
+        bucketEntityV1.setId("1");
+        bucketEntityV1.setName("Bucket1");
+        bucketEntityV1.setDescription("This is bucket 1");
+        bucketEntityV1.setCreated(new Date());
+
+        jdbcTemplate.update("INSERT INTO bucket (ID, NAME, DESCRIPTION, CREATED) VALUES (?, ?, ?, ?)",
+                bucketEntityV1.getId(),
+                bucketEntityV1.getName(),
+                bucketEntityV1.getDescription(),
+                bucketEntityV1.getCreated());
+
+        flowEntityV1 = new FlowEntityV1();
+        flowEntityV1.setId("1");
+        flowEntityV1.setBucketId(bucketEntityV1.getId());
+        flowEntityV1.setName("Flow1");
+        flowEntityV1.setDescription("This is flow1");
+        flowEntityV1.setCreated(new Date());
+        flowEntityV1.setModified(new Date());
+
+        jdbcTemplate.update("INSERT INTO bucket_item (ID, NAME, DESCRIPTION, CREATED, MODIFIED, ITEM_TYPE, BUCKET_ID) VALUES (?, ?, ?, ?, ?, ?, ?)",
+                flowEntityV1.getId(),
+                flowEntityV1.getName(),
+                flowEntityV1.getDescription(),
+                flowEntityV1.getCreated(),
+                flowEntityV1.getModified(),
+                BucketItemEntityType.FLOW.toString(),
+                flowEntityV1.getBucketId());
+
+        jdbcTemplate.update("INSERT INTO flow (ID) VALUES (?)", flowEntityV1.getId());
+
+        flowSnapshotEntityV1 = new FlowSnapshotEntityV1();
+        flowSnapshotEntityV1.setFlowId(flowEntityV1.getId());
+        flowSnapshotEntityV1.setVersion(1);
+        flowSnapshotEntityV1.setComments("This is v1");
+        flowSnapshotEntityV1.setCreated(new Date());
+        flowSnapshotEntityV1.setCreatedBy("user1");
+
+        jdbcTemplate.update("INSERT INTO flow_snapshot (FLOW_ID, VERSION, CREATED, CREATED_BY, COMMENTS) VALUES (?, ?, ?, ?, ?)",
+                flowSnapshotEntityV1.getFlowId(),
+                flowSnapshotEntityV1.getVersion(),
+                flowSnapshotEntityV1.getCreated(),
+                flowSnapshotEntityV1.getCreatedBy(),
+                flowSnapshotEntityV1.getComments());
+    }
+
+    @Test
+    public void testGetLegacyData() {
+        final LegacyDatabaseService service = new LegacyDatabaseService(dataSource);
+
+        final List<BucketEntityV1> buckets = service.getAllBuckets();
+        assertEquals(1, buckets.size());
+
+        final BucketEntityV1 b = buckets.stream().findFirst().get();
+        assertEquals(bucketEntityV1.getId(), b.getId());
+        assertEquals(bucketEntityV1.getName(), b.getName());
+        assertEquals(bucketEntityV1.getDescription(), b.getDescription());
+        assertEquals(bucketEntityV1.getCreated(), b.getCreated());
+
+        final List<FlowEntityV1> flows = service.getAllFlows();
+        assertEquals(1, flows.size());
+
+        final FlowEntityV1 f = flows.stream().findFirst().get();
+        assertEquals(flowEntityV1.getId(), f.getId());
+        assertEquals(flowEntityV1.getName(), f.getName());
+        assertEquals(flowEntityV1.getDescription(), f.getDescription());
+        assertEquals(flowEntityV1.getCreated(), f.getCreated());
+        assertEquals(flowEntityV1.getModified(), f.getModified());
+        assertEquals(flowEntityV1.getBucketId(), f.getBucketId());
+
+        final List<FlowSnapshotEntityV1> flowSnapshots = service.getAllFlowSnapshots();
+        assertEquals(1, flowSnapshots.size());
+
+        final FlowSnapshotEntityV1 fs = flowSnapshots.stream().findFirst().get();
+        assertEquals(flowSnapshotEntityV1.getFlowId(), fs.getFlowId());
+        assertEquals(flowSnapshotEntityV1.getVersion(), fs.getVersion());
+        assertEquals(flowSnapshotEntityV1.getComments(), fs.getComments());
+        assertEquals(flowSnapshotEntityV1.getCreatedBy(), fs.getCreatedBy());
+        assertEquals(flowSnapshotEntityV1.getCreated(), fs.getCreated());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyEntityMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyEntityMapper.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyEntityMapper.java
new file mode 100644
index 0000000..3de297f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyEntityMapper.java
@@ -0,0 +1,79 @@
+/*
+ * 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.registry.db.migration;
+
+import org.apache.nifi.registry.db.entity.BucketEntity;
+import org.apache.nifi.registry.db.entity.FlowEntity;
+import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
+import org.junit.Test;
+
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TestLegacyEntityMapper {
+
+    @Test
+    public void testMapLegacyEntities() {
+        final BucketEntityV1 bucketEntityV1 = new BucketEntityV1();
+        bucketEntityV1.setId("1");
+        bucketEntityV1.setName("Bucket1");
+        bucketEntityV1.setDescription("This is bucket 1");
+        bucketEntityV1.setCreated(new Date());
+
+        final BucketEntity bucketEntity = LegacyEntityMapper.createBucketEntity(bucketEntityV1);
+        assertNotNull(bucketEntity);
+        assertEquals(bucketEntityV1.getId(), bucketEntity.getId());
+        assertEquals(bucketEntityV1.getName(), bucketEntity.getName());
+        assertEquals(bucketEntityV1.getDescription(), bucketEntity.getDescription());
+        assertEquals(bucketEntityV1.getCreated(), bucketEntity.getCreated());
+
+        final FlowEntityV1 flowEntityV1 = new FlowEntityV1();
+        flowEntityV1.setId("1");
+        flowEntityV1.setBucketId(bucketEntityV1.getId());
+        flowEntityV1.setName("Flow1");
+        flowEntityV1.setDescription("This is flow1");
+        flowEntityV1.setCreated(new Date());
+        flowEntityV1.setModified(new Date());
+
+        final FlowEntity flowEntity = LegacyEntityMapper.createFlowEntity(flowEntityV1);
+        assertNotNull(flowEntity);
+        assertEquals(flowEntityV1.getId(), flowEntity.getId());
+        assertEquals(flowEntityV1.getBucketId(), flowEntity.getBucketId());
+        assertEquals(flowEntityV1.getName(), flowEntity.getName());
+        assertEquals(flowEntityV1.getDescription(), flowEntity.getDescription());
+        assertEquals(flowEntityV1.getCreated(), flowEntity.getCreated());
+        assertEquals(flowEntityV1.getModified(), flowEntity.getModified());
+
+        final FlowSnapshotEntityV1 flowSnapshotEntityV1 = new FlowSnapshotEntityV1();
+        flowSnapshotEntityV1.setFlowId(flowEntityV1.getId());
+        flowSnapshotEntityV1.setVersion(1);
+        flowSnapshotEntityV1.setComments("This is v1");
+        flowSnapshotEntityV1.setCreated(new Date());
+        flowSnapshotEntityV1.setCreatedBy("user1");
+
+        final FlowSnapshotEntity flowSnapshotEntity = LegacyEntityMapper.createFlowSnapshotEntity(flowSnapshotEntityV1);
+        assertNotNull(flowSnapshotEntity);
+        assertEquals(flowSnapshotEntityV1.getFlowId(), flowSnapshotEntity.getFlowId());
+        assertEquals(flowSnapshotEntityV1.getVersion(), flowSnapshotEntity.getVersion());
+        assertEquals(flowSnapshotEntityV1.getComments(), flowSnapshotEntity.getComments());
+        assertEquals(flowSnapshotEntityV1.getCreatedBy(), flowSnapshotEntity.getCreatedBy());
+        assertEquals(flowSnapshotEntityV1.getCreated(), flowSnapshotEntity.getCreated());
+    }
+
+}