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:10 UTC

[11/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-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
new file mode 100644
index 0000000..543ea87
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
@@ -0,0 +1,651 @@
+/*
+ * 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.web.api;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.SecureLdapTestApiApplication;
+import org.apache.nifi.registry.authorization.AccessPolicy;
+import org.apache.nifi.registry.authorization.AccessPolicySummary;
+import org.apache.nifi.registry.authorization.CurrentUser;
+import org.apache.nifi.registry.authorization.Permissions;
+import org.apache.nifi.registry.authorization.Tenant;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.extension.ExtensionManager;
+import org.apache.nifi.registry.properties.AESSensitivePropertyProvider;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.properties.SensitivePropertyProvider;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.AuthorizerFactory;
+import org.apache.nifi.registry.security.crypto.BootstrapFileCryptoKeyProvider;
+import org.apache.nifi.registry.security.crypto.CryptoKeyProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Profile;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Deploy the Web API Application using an embedded Jetty Server for local integration testing, with the follow characteristics:
+ *
+ * - A NiFiRegistryProperties has to be explicitly provided to the ApplicationContext using a profile unique to this test suite.
+ * - A NiFiRegistryClientConfig has been configured to create a client capable of completing one-way TLS
+ * - The database is embed H2 using volatile (in-memory) persistence
+ * - Custom SQL is clearing the DB before each test method by default, unless method overrides this behavior
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+        classes = SecureLdapTestApiApplication.class,
+        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+        properties = "spring.profiles.include=ITSecureLdap")
+@Import(SecureITClientConfiguration.class)
+@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:db/clearDB.sql")
+public class SecureLdapIT extends IntegrationTestBase {
+
+    private static final String tokenLoginPath = "access/token/login";
+    private static final String tokenIdentityProviderPath = "access/token/identity-provider";
+
+    @TestConfiguration
+    @Profile("ITSecureLdap")
+    public static class LdapTestConfiguration {
+
+        static AuthorizerFactory authorizerFactory;
+
+        @Primary
+        @Bean
+        @DependsOn({"directoryServer"}) // Can't load LdapUserGroupProvider until the embedded LDAP server, which creates the "directoryServer" bean, is running
+        public static Authorizer getAuthorizer(@Autowired NiFiRegistryProperties properties, ExtensionManager extensionManager) throws Exception {
+            if (authorizerFactory == null) {
+                authorizerFactory = new AuthorizerFactory(properties, extensionManager, sensitivePropertyProvider());
+            }
+            return authorizerFactory.getAuthorizer();
+        }
+
+        @Primary
+        @Bean
+        public static SensitivePropertyProvider sensitivePropertyProvider() throws Exception {
+            return new AESSensitivePropertyProvider(getNiFiRegistryMasterKeyProvider().getKey());
+        }
+
+        private static CryptoKeyProvider getNiFiRegistryMasterKeyProvider() {
+            return new BootstrapFileCryptoKeyProvider("src/test/resources/conf/secure-ldap/bootstrap.conf");
+        }
+
+    }
+
+    private String adminAuthToken;
+    private List<AccessPolicy> beforeTestAccessPoliciesSnapshot;
+
+    @Before
+    public void setup() {
+        final String basicAuthCredentials = encodeCredentialsForBasicAuth("nifiadmin", "password");
+        final String token = client
+                .target(createURL(tokenIdentityProviderPath))
+                .request()
+                .header("Authorization", "Basic " + basicAuthCredentials)
+                .post(null, String.class);
+        adminAuthToken = token;
+
+        beforeTestAccessPoliciesSnapshot = createAccessPoliciesSnapshot();
+    }
+
+    @After
+    public void cleanup() {
+        restoreAccessPoliciesSnapshot(beforeTestAccessPoliciesSnapshot);
+    }
+
+    @Test
+    public void testTokenGenerationAndAccessStatus() throws Exception {
+
+        // Note: this test intentionally does not use the token generated
+        // for nifiadmin by the @Before method
+
+        // Given: the client and server have been configured correctly for LDAP authentication
+        String expectedJwtPayloadJson = "{" +
+                "\"sub\":\"nobel\"," +
+                "\"preferred_username\":\"nobel\"," +
+                "\"iss\":\"LdapIdentityProvider\"" +
+                "}";
+        String expectedAccessStatusJson = "{" +
+                "\"identity\":\"nobel\"," +
+                "\"anonymous\":false" +
+                "}";
+
+        // When: the /access/token/login endpoint is queried
+        final String basicAuthCredentials = encodeCredentialsForBasicAuth("nobel", "password");
+        final Response tokenResponse = client
+                .target(createURL(tokenIdentityProviderPath))
+                .request()
+                .header("Authorization", "Basic " + basicAuthCredentials)
+                .post(null, Response.class);
+
+        // Then: the server returns 200 OK with an access token
+        assertEquals(201, tokenResponse.getStatus());
+        String token = tokenResponse.readEntity(String.class);
+        assertTrue(StringUtils.isNotEmpty(token));
+        String[] jwtParts = token.split("\\.");
+        assertEquals(3, jwtParts.length);
+        String jwtPayload = new String(Base64.getDecoder().decode(jwtParts[1]), "UTF-8");
+        JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false);
+
+        // When: the token is returned in the Authorization header
+        final Response accessResponse = client
+                .target(createURL("access"))
+                .request()
+                .header("Authorization", "Bearer " + token)
+                .get(Response.class);
+
+        // Then: the server acknowledges the client has access
+        assertEquals(200, accessResponse.getStatus());
+        String accessStatus = accessResponse.readEntity(String.class);
+        JSONAssert.assertEquals(expectedAccessStatusJson, accessStatus, false);
+
+    }
+
+    @Test
+    public void testTokenGenerationWithIdentityProvider() throws Exception {
+
+        // Given: the client and server have been configured correctly for LDAP authentication
+        String expectedJwtPayloadJson = "{" +
+                "\"sub\":\"nobel\"," +
+                "\"preferred_username\":\"nobel\"," +
+                "\"iss\":\"LdapIdentityProvider\"," +
+                "\"aud\":\"LdapIdentityProvider\"" +
+                "}";
+        String expectedAccessStatusJson = "{" +
+                "\"identity\":\"nobel\"," +
+                "\"anonymous\":false" +
+                "}";
+
+        // When: the /access/token/identity-provider endpoint is queried
+        final String basicAuthCredentials = encodeCredentialsForBasicAuth("nobel", "password");
+        final Response tokenResponse = client
+                .target(createURL(tokenIdentityProviderPath))
+                .request()
+                .header("Authorization", "Basic " + basicAuthCredentials)
+                .post(null, Response.class);
+
+        // Then: the server returns 200 OK with an access token
+        assertEquals(201, tokenResponse.getStatus());
+        String token = tokenResponse.readEntity(String.class);
+        assertTrue(StringUtils.isNotEmpty(token));
+        String[] jwtParts = token.split("\\.");
+        assertEquals(3, jwtParts.length);
+        String jwtPayload = new String(Base64.getDecoder().decode(jwtParts[1]), "UTF-8");
+        JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false);
+
+        // When: the token is returned in the Authorization header
+        final Response accessResponse = client
+                .target(createURL("access"))
+                .request()
+                .header("Authorization", "Bearer " + token)
+                .get(Response.class);
+
+        // Then: the server acknowledges the client has access
+        assertEquals(200, accessResponse.getStatus());
+        String accessStatus = accessResponse.readEntity(String.class);
+        JSONAssert.assertEquals(expectedAccessStatusJson, accessStatus, false);
+
+    }
+
+    @Test
+    public void testGetCurrentUserFailsForAnonymous() throws Exception {
+
+        // Given: the client is connected to an unsecured NiFi Registry
+
+        // When: the /access endpoint is queried with no credentials
+        final Response response = client
+                .target(createURL("/access"))
+                .request()
+                .get(Response.class);
+
+        // Then: the server returns a 200 OK with the expected current user
+        assertEquals(401, response.getStatus());
+
+    }
+
+    @Test
+    public void testGetCurrentUser() throws Exception {
+
+        // Given: the client is connected to an unsecured NiFi Registry
+        String expectedJson = "{" +
+                "\"identity\":\"nifiadmin\"," +
+                "\"anonymous\":false," +
+                "\"resourcePermissions\":{" +
+                "\"anyTopLevelResource\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                "\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                "\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                "\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}" +
+                "}";
+
+        // When: the /access endpoint is queried using a JWT for the nifiadmin LDAP user
+        final Response response = client
+                .target(createURL("/access"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(Response.class);
+
+        // Then: the server returns a 200 OK with the expected current user
+        assertEquals(200, response.getStatus());
+        String actualJson = response.readEntity(String.class);
+        JSONAssert.assertEquals(expectedJson, actualJson, false);
+
+    }
+
+    @Test
+    public void testUsers() throws Exception {
+
+        // Given: the client and server have been configured correctly for LDAP authentication
+        String expectedJson = "[" +
+                "{\"identity\":\"nifiadmin\",\"userGroups\":[],\"configurable\":false," +
+                    "\"resourcePermissions\":{" +
+                    "\"anyTopLevelResource\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                    "\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                    "\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                    "\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
+                    "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}}," +
+                "{\"identity\":\"euler\",\"userGroups\":[{\"identity\":\"mathematicians\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"euclid\",\"userGroups\":[{\"identity\":\"mathematicians\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"boyle\",\"userGroups\":[{\"identity\":\"chemists\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"newton\",\"userGroups\":[{\"identity\":\"scientists\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"riemann\",\"userGroups\":[{\"identity\":\"mathematicians\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"gauss\",\"userGroups\":[{\"identity\":\"mathematicians\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"galileo\",\"userGroups\":[{\"identity\":\"scientists\"},{\"identity\":\"italians\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"nobel\",\"userGroups\":[{\"identity\":\"chemists\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"pasteur\",\"userGroups\":[{\"identity\":\"chemists\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"tesla\",\"userGroups\":[{\"identity\":\"scientists\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"nogroup\",\"userGroups\":[],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"einstein\",\"userGroups\":[{\"identity\":\"scientists\"}],\"accessPolicies\":[],\"configurable\":false}," +
+                "{\"identity\":\"curie\",\"userGroups\":[{\"identity\":\"chemists\"}],\"accessPolicies\":[],\"configurable\":false}]";
+
+        // When: the /tenants/users endpoint is queried
+        final String usersJson = client
+                .target(createURL("tenants/users"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(String.class);
+
+        // Then: the server returns a list of all users (see test-ldap-data.ldif)
+        JSONAssert.assertEquals(expectedJson, usersJson, false);
+    }
+
+    @Test
+    public void testUserGroups() throws Exception {
+
+        // Given: the client and server have been configured correctly for LDAP authentication
+        String expectedJson = "[" +
+                "{" +
+                    "\"identity\":\"chemists\"," +
+                    "\"users\":[{\"identity\":\"pasteur\"},{\"identity\":\"boyle\"},{\"identity\":\"curie\"},{\"identity\":\"nobel\"}]," +
+                    "\"accessPolicies\":[]," +
+                    "\"configurable\":false" +
+                "}," +
+                "{" +
+                    "\"identity\":\"mathematicians\"," +
+                    "\"users\":[{\"identity\":\"gauss\"},{\"identity\":\"euclid\"},{\"identity\":\"riemann\"},{\"identity\":\"euler\"}]," +
+                    "\"accessPolicies\":[]," +
+                    "\"configurable\":false" +
+                "}," +
+                "{" +
+                    "\"identity\":\"scientists\"," +
+                    "\"users\":[{\"identity\":\"einstein\"},{\"identity\":\"tesla\"},{\"identity\":\"newton\"},{\"identity\":\"galileo\"}]," +
+                    "\"accessPolicies\":[]," +
+                    "\"configurable\":false" +
+                "}," +
+                "{" +
+                    "\"identity\":\"italians\"," +
+                    "\"users\":[{\"identity\":\"galileo\"}]," +
+                    "\"accessPolicies\":[]," +
+                    "\"configurable\":false" +
+                "}]";
+
+        // When: the /tenants/users endpoint is queried
+        final String groupsJson = client
+                .target(createURL("tenants/user-groups"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(String.class);
+
+        // Then: the server returns a list of all users (see test-ldap-data.ldif)
+        JSONAssert.assertEquals(expectedJson, groupsJson, false);
+    }
+
+    @Test
+    public void testCreateTenantFails() throws Exception {
+
+        // Given: the server has been configured with the LdapUserGroupProvider, which is non-configurable,
+        //   and: the client wants to create a tenant
+        Tenant tenant = new Tenant();
+        tenant.setIdentity("new_tenant");
+
+        // When: the POST /tenants/users endpoint is accessed
+        final Response createUserResponse = client
+                .target(createURL("tenants/users"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .post(Entity.entity(tenant, MediaType.APPLICATION_JSON_TYPE), Response.class);
+
+        // Then: an error is returned
+        assertEquals(409, createUserResponse.getStatus());
+
+        // When: the POST /tenants/users endpoint is accessed
+        final Response createUserGroupResponse = client
+                .target(createURL("tenants/user-groups"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .post(Entity.entity(tenant, MediaType.APPLICATION_JSON_TYPE), Response.class);
+
+        // Then: an error is returned because the UserGroupProvider is non-configurable
+        assertEquals(409, createUserGroupResponse.getStatus());
+    }
+
+    @Test
+    public void testAccessPolicyCreation() throws Exception {
+
+        // Given: the server has been configured with an initial admin "nifiadmin" and a user with no accessPolicies "nobel"
+        String nobelId = getTenantIdentifierByIdentity("nobel");
+        String chemistsId = getTenantIdentifierByIdentity("chemists"); // a group containing user "nobel"
+
+        final String basicAuthCredentials = encodeCredentialsForBasicAuth("nobel", "password");
+        final String nobelAuthToken = client
+                .target(createURL(tokenIdentityProviderPath))
+                .request()
+                .header("Authorization", "Basic " + basicAuthCredentials)
+                .post(null, String.class);
+
+        // When: user nobel re-checks top-level permissions
+        final CurrentUser currentUser = client
+                .target(createURL("/access"))
+                .request()
+                .header("Authorization", "Bearer " + nobelAuthToken)
+                .get(CurrentUser.class);
+
+        // Then: 200 OK is returned indicating user has access to no top-level resources
+        assertEquals(new Permissions(), currentUser.getResourcePermissions().getBuckets());
+        assertEquals(new Permissions(), currentUser.getResourcePermissions().getTenants());
+        assertEquals(new Permissions(), currentUser.getResourcePermissions().getPolicies());
+        assertEquals(new Permissions(), currentUser.getResourcePermissions().getProxy());
+
+        // When: nifiadmin creates a bucket
+        final Bucket bucket = new Bucket();
+        bucket.setName("Integration Test Bucket");
+        bucket.setDescription("A bucket created by an integration test.");
+        Response adminCreatesBucketResponse = client
+                .target(createURL("buckets"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .post(Entity.entity(bucket, MediaType.APPLICATION_JSON), Response.class);
+
+        // Then: the server returns a 200 OK
+        assertEquals(200, adminCreatesBucketResponse.getStatus());
+        Bucket createdBucket = adminCreatesBucketResponse.readEntity(Bucket.class);
+
+
+        // When: user nobel initial queries /buckets
+        final Bucket[] buckets1 = client
+                .target(createURL("buckets"))
+                .request()
+                .header("Authorization", "Bearer " + nobelAuthToken)
+                .get(Bucket[].class);
+
+        // Then: an empty list is returned (nobel has no read access yet)
+        assertNotNull(buckets1);
+        assertEquals(0, buckets1.length);
+
+
+        // When: nifiadmin grants read access on createdBucket to 'chemists' a group containing nobel
+        AccessPolicy readPolicy = new AccessPolicy();
+        readPolicy.setResource("/buckets/" + createdBucket.getIdentifier());
+        readPolicy.setAction("read");
+        readPolicy.addUserGroups(Arrays.asList(new Tenant(chemistsId, "chemists")));
+        Response adminGrantsReadAccessResponse = client
+                .target(createURL("policies"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .post(Entity.entity(readPolicy, MediaType.APPLICATION_JSON), Response.class);
+
+        // Then: the server returns a 201 Created
+        assertEquals(201, adminGrantsReadAccessResponse.getStatus());
+
+
+        // When: nifiadmin tries to list all buckets
+        final Bucket[] adminBuckets = client
+                .target(createURL("buckets"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(Bucket[].class);
+
+        // Then: the full list is returned (verifies that per-bucket access policies are additive to base /buckets policy)
+        assertNotNull(adminBuckets);
+        assertEquals(1, adminBuckets.length);
+        assertEquals(createdBucket.getIdentifier(), adminBuckets[0].getIdentifier());
+        assertEquals(new Permissions().withCanRead(true).withCanWrite(true).withCanDelete(true), adminBuckets[0].getPermissions());
+
+
+        // When: user nobel re-queries /buckets
+        final Bucket[] buckets2 = client
+                .target(createURL("buckets"))
+                .request()
+                .header("Authorization", "Bearer " + nobelAuthToken)
+                .get(Bucket[].class);
+
+        // Then: the created bucket is now present
+        assertNotNull(buckets2);
+        assertEquals(1, buckets2.length);
+        assertEquals(createdBucket.getIdentifier(), buckets2[0].getIdentifier());
+        assertEquals(new Permissions().withCanRead(true), buckets2[0].getPermissions());
+
+
+        // When: nifiadmin grants write access on createdBucket to user 'nobel'
+        AccessPolicy writePolicy = new AccessPolicy();
+        writePolicy.setResource("/buckets/" + createdBucket.getIdentifier());
+        writePolicy.setAction("write");
+        writePolicy.addUsers(Arrays.asList(new Tenant(nobelId, "nobel")));
+        Response adminGrantsWriteAccessResponse = client
+                .target(createURL("policies"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .post(Entity.entity(writePolicy, MediaType.APPLICATION_JSON), Response.class);
+
+        // Then: the server returns a 201 Created
+        assertEquals(201, adminGrantsWriteAccessResponse.getStatus());
+
+
+        // When: user nobel re-queries /buckets
+        final Bucket[] buckets3 = client
+                .target(createURL("buckets"))
+                .request()
+                .header("Authorization", "Bearer " + nobelAuthToken)
+                .get(Bucket[].class);
+
+        // Then: the authorizedActions are updated
+        assertNotNull(buckets3);
+        assertEquals(1, buckets3.length);
+        assertEquals(createdBucket.getIdentifier(), buckets3[0].getIdentifier());
+        assertEquals(new Permissions().withCanRead(true).withCanWrite(true), buckets3[0].getPermissions());
+
+    }
+
+    /** A helper method to lookup identifiers for tenant identities using the REST API
+     *
+     * @param tenantIdentity - the identity to lookup
+     * @return A string containing the identifier of the tenant, or null if the tenant identity is not found.
+     */
+    private String getTenantIdentifierByIdentity(String tenantIdentity) {
+
+        final Tenant[] users = client
+                .target(createURL("tenants/users"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(Tenant[].class);
+
+        final Tenant[] groups = client
+                .target(createURL("tenants/user-groups"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(Tenant[].class);
+
+        final Tenant matchedTenant = Stream.concat(Arrays.stream(users), Arrays.stream(groups))
+                .filter(tenant -> tenant.getIdentity().equalsIgnoreCase(tenantIdentity))
+                .findFirst()
+                .orElse(null);
+
+        return matchedTenant != null ? matchedTenant.getIdentifier() : null;
+    }
+
+    /** A helper method to lookup access policies
+     *
+     * @return A string containing the identifier of the policy, or null if the policy identity is not found.
+     */
+    private AccessPolicy getPolicyByResourceAction(String action, String resource) {
+
+        final AccessPolicySummary[] policies = client
+                .target(createURL("policies"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(AccessPolicySummary[].class);
+
+        final AccessPolicySummary matchedPolicy = Arrays.stream(policies)
+                .filter(p -> p.getAction().equalsIgnoreCase(action) && p.getResource().equalsIgnoreCase(resource))
+                .findFirst()
+                .orElse(null);
+
+        if (matchedPolicy == null) {
+            return null;
+        }
+
+        String policyId = matchedPolicy.getIdentifier();
+
+        final AccessPolicy policy = client
+                .target(createURL("policies/" + policyId))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(AccessPolicy.class);
+
+        return policy;
+    }
+
+    private List<AccessPolicy> createAccessPoliciesSnapshot() {
+
+        final AccessPolicySummary[] policySummaries = client
+                .target(createURL("policies"))
+                .request()
+                .header("Authorization", "Bearer " + adminAuthToken)
+                .get(AccessPolicySummary[].class);
+
+        final List<AccessPolicy> policies = new ArrayList<>(policySummaries.length);
+        for (AccessPolicySummary s : policySummaries) {
+            AccessPolicy policy = client
+                    .target(createURL("policies/" + s.getIdentifier()))
+                    .request()
+                    .header("Authorization", "Bearer " + adminAuthToken)
+                    .get(AccessPolicy.class);
+            policies.add(policy);
+        }
+
+        return policies;
+    }
+
+    private void restoreAccessPoliciesSnapshot(List<AccessPolicy> accessPoliciesSnapshot) {
+
+        List<AccessPolicy> currentAccessPolicies = createAccessPoliciesSnapshot();
+
+        Set<String> policiesToRestore = accessPoliciesSnapshot.stream()
+                .map(AccessPolicy::getIdentifier)
+                .collect(Collectors.toSet());
+
+        Set<String> policiesToDelete = currentAccessPolicies.stream()
+                .filter(p -> !policiesToRestore.contains(p.getIdentifier()))
+                .map(AccessPolicy::getIdentifier)
+                .collect(Collectors.toSet());
+
+        for (AccessPolicy originalPolicy : accessPoliciesSnapshot) {
+
+            Response getCurrentPolicy = client
+                    .target(createURL("policies/" + originalPolicy.getIdentifier()))
+                    .request()
+                    .header("Authorization", "Bearer " + adminAuthToken)
+                    .get(Response.class);
+
+            if (getCurrentPolicy.getStatus() == 200) {
+                // update policy to match original
+                client.target(createURL("policies/" + originalPolicy.getIdentifier()))
+                        .request()
+                        .header("Authorization", "Bearer " + adminAuthToken)
+                        .put(Entity.entity(originalPolicy, MediaType.APPLICATION_JSON));
+            } else {
+                // post the original policy
+                client.target(createURL("policies"))
+                        .request()
+                        .header("Authorization", "Bearer " + adminAuthToken)
+                        .post(Entity.entity(originalPolicy, MediaType.APPLICATION_JSON));
+            }
+
+        }
+
+        for (String id : policiesToDelete) {
+            try {
+                client.target(createURL("policies/" + id))
+                        .request()
+                        .header("Authorization", "Bearer " + adminAuthToken)
+                        .delete();
+            } catch (Exception e) {
+                // do nothing
+            }
+        }
+
+    }
+
+    private static Form encodeCredentialsForURLFormParams(String username, String password) {
+        return new Form()
+                .param("username", username)
+                .param("password", password);
+    }
+
+    private static String encodeCredentialsForBasicAuth(String username, String password) {
+        final String credentials = username + ":" + password;
+        final String base64credentials =  new String(Base64.getEncoder().encode(credentials.getBytes(Charset.forName("UTF-8"))));
+        return base64credentials;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
new file mode 100644
index 0000000..cb14b90
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
@@ -0,0 +1,217 @@
+/*
+ * 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.web.api;
+
+import org.apache.nifi.registry.NiFiRegistryTestApiApplication;
+import org.apache.nifi.registry.authorization.Permissions;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.client.BucketClient;
+import org.apache.nifi.registry.client.FlowClient;
+import org.apache.nifi.registry.client.FlowSnapshotClient;
+import org.apache.nifi.registry.client.NiFiRegistryClient;
+import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.UserClient;
+import org.apache.nifi.registry.client.impl.JerseyNiFiRegistryClient;
+import org.apache.nifi.registry.flow.VersionedFlow;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+import org.apache.nifi.registry.authorization.CurrentUser;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.ws.rs.ForbiddenException;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+        classes = NiFiRegistryTestApiApplication.class,
+        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+        properties = "spring.profiles.include=ITSecureFile")
+@Import(SecureITClientConfiguration.class)
+@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = {"classpath:db/clearDB.sql", "classpath:db/FlowsIT.sql"})
+public class SecureNiFiRegistryClientIT extends IntegrationTestBase {
+
+    static final Logger LOGGER = LoggerFactory.getLogger(SecureNiFiRegistryClientIT.class);
+
+    private NiFiRegistryClient client;
+
+    @Before
+    public void setup() {
+        final String baseUrl = createBaseURL();
+        LOGGER.info("Using base url = " + baseUrl);
+
+        final NiFiRegistryClientConfig clientConfig = createClientConfig(baseUrl);
+        Assert.assertNotNull(clientConfig);
+
+        final NiFiRegistryClient client = new JerseyNiFiRegistryClient.Builder()
+                .config(clientConfig)
+                .build();
+        Assert.assertNotNull(client);
+        this.client = client;
+    }
+
+    @After
+    public void teardown() {
+        try {
+            client.close();
+        } catch (Exception e) {
+
+        }
+    }
+
+    @Test
+    public void testGetAccessStatus() throws IOException, NiFiRegistryException {
+        final UserClient userClient = client.getUserClient();
+        final CurrentUser currentUser = userClient.getAccessStatus();
+        Assert.assertEquals("CN=user1, OU=nifi", currentUser.getIdentity());
+        Assert.assertFalse(currentUser.isAnonymous());
+        Assert.assertNotNull(currentUser.getResourcePermissions());
+        Permissions fullAccess = new Permissions().withCanRead(true).withCanWrite(true).withCanDelete(true);
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getAnyTopLevelResource());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getBuckets());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getTenants());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getPolicies());
+        Assert.assertEquals(new Permissions().withCanWrite(true), currentUser.getResourcePermissions().getProxy());
+    }
+
+    @Test
+    public void testCrudOperations() throws IOException, NiFiRegistryException {
+        final Bucket bucket = new Bucket();
+        bucket.setName("Bucket 1 " + System.currentTimeMillis());
+        bucket.setDescription("This is bucket 1");
+
+        final BucketClient bucketClient = client.getBucketClient();
+        final Bucket createdBucket = bucketClient.create(bucket);
+        Assert.assertNotNull(createdBucket);
+        Assert.assertNotNull(createdBucket.getIdentifier());
+
+        final List<Bucket> buckets = bucketClient.getAll();
+        Assert.assertEquals(4, buckets.size());
+
+        final VersionedFlow flow = new VersionedFlow();
+        flow.setBucketIdentifier(createdBucket.getIdentifier());
+        flow.setName("Flow 1 - " + System.currentTimeMillis());
+
+        final FlowClient flowClient = client.getFlowClient();
+        final VersionedFlow createdFlow = flowClient.create(flow);
+        Assert.assertNotNull(createdFlow);
+        Assert.assertNotNull(createdFlow.getIdentifier());
+
+        final VersionedFlowSnapshotMetadata snapshotMetadata = new VersionedFlowSnapshotMetadata();
+        snapshotMetadata.setBucketIdentifier(createdFlow.getBucketIdentifier());
+        snapshotMetadata.setFlowIdentifier(createdFlow.getIdentifier());
+        snapshotMetadata.setVersion(1);
+        snapshotMetadata.setComments("This is snapshot #1");
+
+        final VersionedProcessGroup rootProcessGroup = new VersionedProcessGroup();
+        rootProcessGroup.setIdentifier("root-pg");
+        rootProcessGroup.setName("Root Process Group");
+
+        final VersionedFlowSnapshot snapshot = new VersionedFlowSnapshot();
+        snapshot.setSnapshotMetadata(snapshotMetadata);
+        snapshot.setFlowContents(rootProcessGroup);
+
+        final FlowSnapshotClient snapshotClient = client.getFlowSnapshotClient();
+        final VersionedFlowSnapshot createdSnapshot = snapshotClient.create(snapshot);
+        Assert.assertNotNull(createdSnapshot);
+        Assert.assertEquals("CN=user1, OU=nifi", createdSnapshot.getSnapshotMetadata().getAuthor());
+    }
+
+    @Test
+    public void testGetAccessStatusWithProxiedEntity() throws IOException, NiFiRegistryException {
+        final String proxiedEntity = "user2";
+        final UserClient userClient = client.getUserClient(proxiedEntity);
+        final CurrentUser status = userClient.getAccessStatus();
+        Assert.assertEquals("user2", status.getIdentity());
+        Assert.assertFalse(status.isAnonymous());
+    }
+
+    @Test
+    public void testCreatedBucketWithProxiedEntity() throws IOException, NiFiRegistryException {
+        final String proxiedEntity = "user2";
+        final BucketClient bucketClient = client.getBucketClient(proxiedEntity);
+
+        final Bucket bucket = new Bucket();
+        bucket.setName("Bucket 1");
+        bucket.setDescription("This is bucket 1");
+
+        try {
+            bucketClient.create(bucket);
+            Assert.fail("Shouldn't have been able to create a bucket");
+        } catch (Exception e) {
+
+        }
+    }
+
+    @Test
+    public void testDirectFlowAccess() throws IOException {
+        // this user shouldn't have access to anything
+        final String proxiedEntity = "CN=no-access, OU=nifi";
+
+        final FlowClient proxiedFlowClient = client.getFlowClient(proxiedEntity);
+        final FlowSnapshotClient proxiedFlowSnapshotClient = client.getFlowSnapshotClient(proxiedEntity);
+
+        try {
+            proxiedFlowClient.get("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.getLatest("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.getLatestMetadata("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.get("1", 1);
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.getSnapshotMetadata("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredITBase.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredITBase.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredITBase.java
new file mode 100644
index 0000000..a0c981b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredITBase.java
@@ -0,0 +1,42 @@
+/*
+ * 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.web.api;
+
+import org.apache.nifi.registry.NiFiRegistryTestApiApplication;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * Deploy the Web API Application using an embedded Jetty Server for local integration testing, with the follow characteristics:
+ *
+ * - A NiFiRegistryProperties has to be explicitly provided to the ApplicationContext using a profile unique to this test suite.
+ * - The database is embed H2 using volatile (in-memory) persistence
+ * - Custom SQL is clearing the DB before each test method by default, unless method overrides this behavior
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+        classes = NiFiRegistryTestApiApplication.class,
+        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+        properties = "spring.profiles.include=ITUnsecured")
+@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:db/clearDB.sql")
+public class UnsecuredITBase extends IntegrationTestBase {
+
+    // Tests cases defined in subclasses
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
new file mode 100644
index 0000000..2410234
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
@@ -0,0 +1,403 @@
+/*
+ * 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.web.api;
+
+import org.apache.nifi.registry.authorization.CurrentUser;
+import org.apache.nifi.registry.authorization.Permissions;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.client.BucketClient;
+import org.apache.nifi.registry.client.FlowClient;
+import org.apache.nifi.registry.client.FlowSnapshotClient;
+import org.apache.nifi.registry.client.ItemsClient;
+import org.apache.nifi.registry.client.NiFiRegistryClient;
+import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.UserClient;
+import org.apache.nifi.registry.client.impl.JerseyNiFiRegistryClient;
+import org.apache.nifi.registry.diff.VersionedFlowDifference;
+import org.apache.nifi.registry.field.Fields;
+import org.apache.nifi.registry.flow.VersionedFlow;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+import org.apache.nifi.registry.flow.VersionedProcessor;
+import org.apache.nifi.registry.flow.VersionedPropertyDescriptor;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test all basic functionality of JerseyNiFiRegistryClient.
+ */
+public class UnsecuredNiFiRegistryClientIT extends UnsecuredITBase {
+
+    static final Logger LOGGER = LoggerFactory.getLogger(UnsecuredNiFiRegistryClientIT.class);
+
+    private NiFiRegistryClient client;
+
+    @Before
+    public void setup() {
+        final String baseUrl = createBaseURL();
+        LOGGER.info("Using base url = " + baseUrl);
+
+        final NiFiRegistryClientConfig clientConfig = new NiFiRegistryClientConfig.Builder()
+                .baseUrl(baseUrl)
+                .build();
+
+        Assert.assertNotNull(clientConfig);
+
+        final NiFiRegistryClient client = new JerseyNiFiRegistryClient.Builder()
+                .config(clientConfig)
+                .build();
+
+        Assert.assertNotNull(client);
+        this.client = client;
+    }
+
+    @After
+    public void teardown() {
+        try {
+            client.close();
+        } catch (Exception e) {
+
+        }
+    }
+
+    @Test
+    public void testGetAccessStatus() throws IOException, NiFiRegistryException {
+        final UserClient userClient = client.getUserClient();
+        final CurrentUser currentUser = userClient.getAccessStatus();
+        Assert.assertEquals("anonymous", currentUser.getIdentity());
+        Assert.assertTrue(currentUser.isAnonymous());
+        Assert.assertNotNull(currentUser.getResourcePermissions());
+        Permissions fullAccess = new Permissions().withCanRead(true).withCanWrite(true).withCanDelete(true);
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getAnyTopLevelResource());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getBuckets());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getTenants());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getPolicies());
+        Assert.assertEquals(fullAccess, currentUser.getResourcePermissions().getProxy());
+    }
+
+    @Test
+    public void testNiFiRegistryClient() throws IOException, NiFiRegistryException {
+        // ---------------------- TEST BUCKETS --------------------------//
+
+        final BucketClient bucketClient = client.getBucketClient();
+
+        // create buckets
+        final int numBuckets = 10;
+        final List<Bucket> createdBuckets = new ArrayList<>();
+
+        for (int i=0; i < numBuckets; i++) {
+            final Bucket createdBucket = createBucket(bucketClient, i);
+            LOGGER.info("Created bucket # " + i + " with id " + createdBucket.getIdentifier());
+            createdBuckets.add(createdBucket);
+        }
+
+        // get each bucket
+        for (final Bucket bucket : createdBuckets) {
+            final Bucket retrievedBucket = bucketClient.get(bucket.getIdentifier());
+            Assert.assertNotNull(retrievedBucket);
+            LOGGER.info("Retrieved bucket " + retrievedBucket.getIdentifier());
+        }
+
+        //final Bucket nonExistentBucket = bucketClient.get("does-not-exist");
+        //Assert.assertNull(nonExistentBucket);
+
+        // get bucket fields
+        final Fields bucketFields = bucketClient.getFields();
+        Assert.assertNotNull(bucketFields);
+        LOGGER.info("Retrieved bucket fields, size = " + bucketFields.getFields().size());
+        Assert.assertTrue(bucketFields.getFields().size() > 0);
+
+        // get all buckets
+        final List<Bucket> allBuckets = bucketClient.getAll();
+        LOGGER.info("Retrieved buckets, size = " + allBuckets.size());
+        Assert.assertEquals(numBuckets, allBuckets.size());
+        allBuckets.stream().forEach(b -> System.out.println("Retrieve bucket " + b.getIdentifier()));
+
+        // update each bucket
+        for (final Bucket bucket : createdBuckets) {
+            final Bucket bucketUpdate = new Bucket();
+            bucketUpdate.setIdentifier(bucket.getIdentifier());
+            bucketUpdate.setDescription(bucket.getDescription() + " UPDATE");
+
+            final Bucket updatedBucket = bucketClient.update(bucketUpdate);
+            Assert.assertNotNull(updatedBucket);
+            LOGGER.info("Updated bucket " + updatedBucket.getIdentifier());
+        }
+
+        // ---------------------- TEST FLOWS --------------------------//
+
+        final FlowClient flowClient = client.getFlowClient();
+
+        // create flows
+        final Bucket flowsBucket = createdBuckets.get(0);
+
+        final VersionedFlow flow1 = createFlow(flowClient, flowsBucket, 1);
+        LOGGER.info("Created flow # 1 with id " + flow1.getIdentifier());
+
+        final VersionedFlow flow2 = createFlow(flowClient, flowsBucket, 2);
+        LOGGER.info("Created flow # 2 with id " + flow2.getIdentifier());
+
+        // get flow
+        final VersionedFlow retrievedFlow1 = flowClient.get(flowsBucket.getIdentifier(), flow1.getIdentifier());
+        Assert.assertNotNull(retrievedFlow1);
+        LOGGER.info("Retrieved flow # 1 with id " + retrievedFlow1.getIdentifier());
+
+        final VersionedFlow retrievedFlow2 = flowClient.get(flowsBucket.getIdentifier(), flow2.getIdentifier());
+        Assert.assertNotNull(retrievedFlow2);
+        LOGGER.info("Retrieved flow # 2 with id " + retrievedFlow2.getIdentifier());
+
+        // get flow without bucket
+        final VersionedFlow retrievedFlow1WithoutBucket = flowClient.get(flow1.getIdentifier());
+        Assert.assertNotNull(retrievedFlow1WithoutBucket);
+        Assert.assertEquals(flow1.getIdentifier(), retrievedFlow1WithoutBucket.getIdentifier());
+        LOGGER.info("Retrieved flow # 1 without bucket id, with id " + retrievedFlow1WithoutBucket.getIdentifier());
+
+        // update flows
+        final VersionedFlow flow1Update = new VersionedFlow();
+        flow1Update.setIdentifier(flow1.getIdentifier());
+        flow1Update.setName(flow1.getName() + " UPDATED");
+
+        final VersionedFlow updatedFlow1 = flowClient.update(flowsBucket.getIdentifier(), flow1Update);
+        Assert.assertNotNull(updatedFlow1);
+        LOGGER.info("Updated flow # 1 with id " + updatedFlow1.getIdentifier());
+
+        // get flow fields
+        final Fields flowFields = flowClient.getFields();
+        Assert.assertNotNull(flowFields);
+        LOGGER.info("Retrieved flow fields, size = " + flowFields.getFields().size());
+        Assert.assertTrue(flowFields.getFields().size() > 0);
+
+        // get flows in bucket
+        final List<VersionedFlow> flowsInBucket = flowClient.getByBucket(flowsBucket.getIdentifier());
+        Assert.assertNotNull(flowsInBucket);
+        Assert.assertEquals(2, flowsInBucket.size());
+        flowsInBucket.stream().forEach(f -> LOGGER.info("Flow in bucket, flow id " + f.getIdentifier()));
+
+        // ---------------------- TEST SNAPSHOTS --------------------------//
+
+        final FlowSnapshotClient snapshotClient = client.getFlowSnapshotClient();
+
+        // create snapshots
+        final VersionedFlow snapshotFlow = flow1;
+
+        final VersionedFlowSnapshot snapshot1 = createSnapshot(snapshotClient, snapshotFlow, 1);
+        LOGGER.info("Created snapshot # 1 with version " + snapshot1.getSnapshotMetadata().getVersion());
+
+        final VersionedFlowSnapshot snapshot2 = createSnapshot(snapshotClient, snapshotFlow, 2);
+        LOGGER.info("Created snapshot # 2 with version " + snapshot2.getSnapshotMetadata().getVersion());
+
+        // get snapshot
+        final VersionedFlowSnapshot retrievedSnapshot1 = snapshotClient.get(snapshotFlow.getBucketIdentifier(), snapshotFlow.getIdentifier(), 1);
+        Assert.assertNotNull(retrievedSnapshot1);
+        Assert.assertFalse(retrievedSnapshot1.isLatest());
+        LOGGER.info("Retrieved snapshot # 1 with version " + retrievedSnapshot1.getSnapshotMetadata().getVersion());
+
+        final VersionedFlowSnapshot retrievedSnapshot2 = snapshotClient.get(snapshotFlow.getBucketIdentifier(), snapshotFlow.getIdentifier(), 2);
+        Assert.assertNotNull(retrievedSnapshot2);
+        Assert.assertTrue(retrievedSnapshot2.isLatest());
+        LOGGER.info("Retrieved snapshot # 2 with version " + retrievedSnapshot2.getSnapshotMetadata().getVersion());
+
+        // get snapshot without bucket
+        final VersionedFlowSnapshot retrievedSnapshot1WithoutBucket = snapshotClient.get(snapshotFlow.getIdentifier(), 1);
+        Assert.assertNotNull(retrievedSnapshot1WithoutBucket);
+        Assert.assertFalse(retrievedSnapshot1WithoutBucket.isLatest());
+        Assert.assertEquals(snapshotFlow.getIdentifier(), retrievedSnapshot1WithoutBucket.getSnapshotMetadata().getFlowIdentifier());
+        Assert.assertEquals(1, retrievedSnapshot1WithoutBucket.getSnapshotMetadata().getVersion());
+        LOGGER.info("Retrieved snapshot # 1 without using bucket id, with version " + retrievedSnapshot1WithoutBucket.getSnapshotMetadata().getVersion());
+
+        // get latest
+        final VersionedFlowSnapshot retrievedSnapshotLatest = snapshotClient.getLatest(snapshotFlow.getBucketIdentifier(), snapshotFlow.getIdentifier());
+        Assert.assertNotNull(retrievedSnapshotLatest);
+        Assert.assertEquals(snapshot2.getSnapshotMetadata().getVersion(), retrievedSnapshotLatest.getSnapshotMetadata().getVersion());
+        Assert.assertTrue(retrievedSnapshotLatest.isLatest());
+        LOGGER.info("Retrieved latest snapshot with version " + retrievedSnapshotLatest.getSnapshotMetadata().getVersion());
+
+        // get latest without bucket
+        final VersionedFlowSnapshot retrievedSnapshotLatestWithoutBucket = snapshotClient.getLatest(snapshotFlow.getIdentifier());
+        Assert.assertNotNull(retrievedSnapshotLatestWithoutBucket);
+        Assert.assertEquals(snapshot2.getSnapshotMetadata().getVersion(), retrievedSnapshotLatestWithoutBucket.getSnapshotMetadata().getVersion());
+        Assert.assertTrue(retrievedSnapshotLatestWithoutBucket.isLatest());
+        LOGGER.info("Retrieved latest snapshot without bucket, with version " + retrievedSnapshotLatestWithoutBucket.getSnapshotMetadata().getVersion());
+
+        // get metadata
+        final List<VersionedFlowSnapshotMetadata> retrievedMetadata = snapshotClient.getSnapshotMetadata(snapshotFlow.getBucketIdentifier(), snapshotFlow.getIdentifier());
+        Assert.assertNotNull(retrievedMetadata);
+        Assert.assertEquals(2, retrievedMetadata.size());
+        Assert.assertEquals(2, retrievedMetadata.get(0).getVersion());
+        Assert.assertEquals(1, retrievedMetadata.get(1).getVersion());
+        retrievedMetadata.stream().forEach(s -> LOGGER.info("Retrieved snapshot metadata " + s.getVersion()));
+
+        // get metadata without bucket
+        final List<VersionedFlowSnapshotMetadata> retrievedMetadataWithoutBucket = snapshotClient.getSnapshotMetadata(snapshotFlow.getIdentifier());
+        Assert.assertNotNull(retrievedMetadataWithoutBucket);
+        Assert.assertEquals(2, retrievedMetadataWithoutBucket.size());
+        Assert.assertEquals(2, retrievedMetadataWithoutBucket.get(0).getVersion());
+        Assert.assertEquals(1, retrievedMetadataWithoutBucket.get(1).getVersion());
+        retrievedMetadataWithoutBucket.stream().forEach(s -> LOGGER.info("Retrieved snapshot metadata " + s.getVersion()));
+
+        // get latest metadata
+        final VersionedFlowSnapshotMetadata latestMetadata = snapshotClient.getLatestMetadata(snapshotFlow.getBucketIdentifier(), snapshotFlow.getIdentifier());
+        Assert.assertNotNull(latestMetadata);
+        Assert.assertEquals(2, latestMetadata.getVersion());
+
+        // get latest metadata that doesn't exist
+        try {
+            snapshotClient.getLatestMetadata(snapshotFlow.getBucketIdentifier(), "DOES-NOT-EXIST");
+            Assert.fail("Should have thrown exception");
+        } catch (NiFiRegistryException nfe) {
+            Assert.assertEquals("Error retrieving latest snapshot metadata: The specified flow ID does not exist in this bucket.", nfe.getMessage());
+        }
+
+        // get latest metadata without bucket
+        final VersionedFlowSnapshotMetadata latestMetadataWithoutBucket = snapshotClient.getLatestMetadata(snapshotFlow.getIdentifier());
+        Assert.assertNotNull(latestMetadataWithoutBucket);
+        Assert.assertEquals(snapshotFlow.getIdentifier(), latestMetadataWithoutBucket.getFlowIdentifier());
+        Assert.assertEquals(2, latestMetadataWithoutBucket.getVersion());
+
+        // ---------------------- TEST ITEMS --------------------------//
+
+        final ItemsClient itemsClient = client.getItemsClient();
+
+        // get fields
+        final Fields itemFields = itemsClient.getFields();
+        Assert.assertNotNull(itemFields.getFields());
+        Assert.assertTrue(itemFields.getFields().size() > 0);
+
+        // get all items
+        final List<BucketItem> allItems = itemsClient.getAll();
+        Assert.assertEquals(2, allItems.size());
+        allItems.stream().forEach(i -> Assert.assertNotNull(i.getBucketName()));
+        allItems.stream().forEach(i -> LOGGER.info("All items, item " + i.getIdentifier()));
+
+        // get items for bucket
+        final List<BucketItem> bucketItems = itemsClient.getByBucket(flowsBucket.getIdentifier());
+        Assert.assertEquals(2, bucketItems.size());
+        allItems.stream().forEach(i -> Assert.assertNotNull(i.getBucketName()));
+        bucketItems.stream().forEach(i -> LOGGER.info("Items in bucket, item " + i.getIdentifier()));
+
+        // ----------------------- TEST DIFF ---------------------------//
+
+        final VersionedFlowSnapshot snapshot3 = buildSnapshot(snapshotFlow, 3);
+        final VersionedProcessGroup newlyAddedPG = new VersionedProcessGroup();
+        newlyAddedPG.setIdentifier("new-pg");
+        newlyAddedPG.setName("NEW Process Group");
+        snapshot3.getFlowContents().getProcessGroups().add(newlyAddedPG);
+        snapshotClient.create(snapshot3);
+
+        VersionedFlowDifference diff = flowClient.diff(snapshotFlow.getBucketIdentifier(), snapshotFlow.getIdentifier(), 3, 2);
+        Assert.assertNotNull(diff);
+        Assert.assertEquals(1, diff.getComponentDifferenceGroups().size());
+
+        // ---------------------- DELETE DATA --------------------------//
+
+        final VersionedFlow deletedFlow1 = flowClient.delete(flowsBucket.getIdentifier(), flow1.getIdentifier());
+        Assert.assertNotNull(deletedFlow1);
+        LOGGER.info("Deleted flow " + deletedFlow1.getIdentifier());
+
+        final VersionedFlow deletedFlow2 = flowClient.delete(flowsBucket.getIdentifier(), flow2.getIdentifier());
+        Assert.assertNotNull(deletedFlow2);
+        LOGGER.info("Deleted flow " + deletedFlow2.getIdentifier());
+
+        // delete each bucket
+        for (final Bucket bucket : createdBuckets) {
+            final Bucket deletedBucket = bucketClient.delete(bucket.getIdentifier());
+            Assert.assertNotNull(deletedBucket);
+            LOGGER.info("Deleted bucket " + deletedBucket.getIdentifier());
+        }
+        Assert.assertEquals(0, bucketClient.getAll().size());
+
+        LOGGER.info("!!! SUCCESS !!!");
+
+    }
+
+    private static Bucket createBucket(BucketClient bucketClient, int num) throws IOException, NiFiRegistryException {
+        final Bucket bucket = new Bucket();
+        bucket.setName("Bucket #" + num);
+        bucket.setDescription("This is bucket #" + num);
+        return bucketClient.create(bucket);
+    }
+
+    private static VersionedFlow createFlow(FlowClient client, Bucket bucket, int num) throws IOException, NiFiRegistryException {
+        final VersionedFlow versionedFlow = new VersionedFlow();
+        versionedFlow.setName(bucket.getName() + " Flow #" + num);
+        versionedFlow.setDescription("This is " + bucket.getName() + " flow #" + num);
+        versionedFlow.setBucketIdentifier(bucket.getIdentifier());
+        return client.create(versionedFlow);
+    }
+
+    private static VersionedFlowSnapshot buildSnapshot(VersionedFlow flow, int num) {
+        final VersionedFlowSnapshotMetadata snapshotMetadata = new VersionedFlowSnapshotMetadata();
+        snapshotMetadata.setBucketIdentifier(flow.getBucketIdentifier());
+        snapshotMetadata.setFlowIdentifier(flow.getIdentifier());
+        snapshotMetadata.setVersion(num);
+        snapshotMetadata.setComments("This is snapshot #" + num);
+
+        final VersionedProcessGroup rootProcessGroup = new VersionedProcessGroup();
+        rootProcessGroup.setIdentifier("root-pg");
+        rootProcessGroup.setName("Root Process Group");
+
+        final VersionedProcessGroup subProcessGroup = new VersionedProcessGroup();
+        subProcessGroup.setIdentifier("sub-pg");
+        subProcessGroup.setName("Sub Process Group");
+        rootProcessGroup.getProcessGroups().add(subProcessGroup);
+
+        final Map<String,String> processorProperties = new HashMap<>();
+        processorProperties.put("Prop 1", "Val 1");
+        processorProperties.put("Prop 2", "Val 2");
+
+        final Map<String, VersionedPropertyDescriptor> propertyDescriptors = new HashMap<>();
+
+        final VersionedProcessor processor1 = new VersionedProcessor();
+        processor1.setIdentifier("p1");
+        processor1.setName("Processor 1");
+        processor1.setProperties(processorProperties);
+        processor1.setPropertyDescriptors(propertyDescriptors);
+
+        final VersionedProcessor processor2 = new VersionedProcessor();
+        processor2.setIdentifier("p2");
+        processor2.setName("Processor 2");
+        processor2.setProperties(processorProperties);
+        processor2.setPropertyDescriptors(propertyDescriptors);
+
+        subProcessGroup.getProcessors().add(processor1);
+        subProcessGroup.getProcessors().add(processor2);
+
+        final VersionedFlowSnapshot snapshot = new VersionedFlowSnapshot();
+        snapshot.setSnapshotMetadata(snapshotMetadata);
+        snapshot.setFlowContents(rootProcessGroup);
+        return snapshot;
+    }
+
+    private static VersionedFlowSnapshot createSnapshot(FlowSnapshotClient client, VersionedFlow flow, int num) throws IOException, NiFiRegistryException {
+        final VersionedFlowSnapshot snapshot = buildSnapshot(flow, num);
+
+        return client.create(snapshot);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/link/TestLinkService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/link/TestLinkService.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/link/TestLinkService.java
new file mode 100644
index 0000000..bfc9a46
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/link/TestLinkService.java
@@ -0,0 +1,124 @@
+/*
+ * 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.web.link;
+
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.flow.VersionedFlow;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestLinkService {
+
+    private LinkService linkService;
+
+    private List<Bucket> buckets;
+    private List<VersionedFlow> flows;
+    private List<VersionedFlowSnapshotMetadata> snapshots;
+    private List<BucketItem> items;
+
+    @Before
+    public void setup() {
+        linkService = new LinkService();
+
+        // setup buckets
+        final Bucket bucket1 = new Bucket();
+        bucket1.setIdentifier("b1");
+        bucket1.setName("Bucket 1");
+
+        final Bucket bucket2 = new Bucket();
+        bucket2.setIdentifier("b2");
+        bucket2.setName("Bucket 2");
+
+        buckets = new ArrayList<>();
+        buckets.add(bucket1);
+        buckets.add(bucket2);
+
+        // setup flows
+        final VersionedFlow flow1 = new VersionedFlow();
+        flow1.setIdentifier("f1");
+        flow1.setName("Flow 1");
+        flow1.setBucketIdentifier(bucket1.getIdentifier());
+
+        final VersionedFlow flow2 = new VersionedFlow();
+        flow2.setIdentifier("f2");
+        flow2.setName("Flow 2");
+        flow2.setBucketIdentifier(bucket1.getIdentifier());
+
+        flows = new ArrayList<>();
+        flows.add(flow1);
+        flows.add(flow2);
+
+        //setup snapshots
+        final VersionedFlowSnapshotMetadata snapshotMetadata1 = new VersionedFlowSnapshotMetadata();
+        snapshotMetadata1.setFlowIdentifier(flow1.getIdentifier());
+        snapshotMetadata1.setVersion(1);
+        snapshotMetadata1.setBucketIdentifier(bucket1.getIdentifier());
+
+        final VersionedFlowSnapshotMetadata snapshotMetadata2 = new VersionedFlowSnapshotMetadata();
+        snapshotMetadata2.setFlowIdentifier(flow1.getIdentifier());
+        snapshotMetadata2.setVersion(2);
+        snapshotMetadata2.setBucketIdentifier(bucket1.getIdentifier());
+
+        snapshots = new ArrayList<>();
+        snapshots.add(snapshotMetadata1);
+        snapshots.add(snapshotMetadata2);
+
+        // setup items
+        items = new ArrayList<>();
+        items.add(flow1);
+        items.add(flow2);
+    }
+
+    @Test
+    public void testPopulateBucketLinks() {
+        buckets.stream().forEach(b -> Assert.assertNull(b.getLink()));
+        linkService.populateBucketLinks(buckets);
+        buckets.stream().forEach(b -> Assert.assertEquals(
+                "buckets/" + b.getIdentifier(), b.getLink().getUri().toString()));
+    }
+
+    @Test
+    public void testPopulateFlowLinks() {
+        flows.stream().forEach(f -> Assert.assertNull(f.getLink()));
+        linkService.populateFlowLinks(flows);
+        flows.stream().forEach(f -> Assert.assertEquals(
+                "buckets/" + f.getBucketIdentifier() + "/flows/" + f.getIdentifier(), f.getLink().getUri().toString()));
+    }
+
+    @Test
+    public void testPopulateSnapshotLinks() {
+        snapshots.stream().forEach(s -> Assert.assertNull(s.getLink()));
+        linkService.populateSnapshotLinks(snapshots);
+        snapshots.stream().forEach(s -> Assert.assertEquals(
+                "buckets/" + s.getBucketIdentifier() + "/flows/" + s.getFlowIdentifier() + "/versions/" + s.getVersion(), s.getLink().getUri().toString()));
+    }
+
+    @Test
+    public void testPopulateItemLinks() {
+        items.stream().forEach(i -> Assert.assertNull(i.getLink()));
+        linkService.populateItemLinks(items);
+        items.stream().forEach(i -> Assert.assertEquals(
+                "buckets/" + i.getBucketIdentifier() + "/flows/" + i.getIdentifier(), i.getLink().getUri().toString()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureFile.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureFile.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureFile.properties
new file mode 100644
index 0000000..3ea5398
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureFile.properties
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+
+
+# Properties for Spring Boot integration tests
+# Documentation for common Spring Boot application properties can be found at:
+# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+
+
+# Custom (non-standard to Spring Boot) properties
+nifi.registry.properties.file: src/test/resources/conf/secure-file/nifi-registry.properties
+nifi.registry.client.properties.file: src/test/resources/conf/secure-file/nifi-registry-client.properties
+
+
+# Embedded Server SSL Context Config
+server.ssl.client-auth: need
+server.ssl.key-store: ./target/test-classes/keys/localhost-ks.jks
+server.ssl.key-store-password: localhostKeystorePassword
+server.ssl.key-password: localhostKeystorePassword
+server.ssl.protocol: TLS
+server.ssl.trust-store: ./target/test-classes/keys/localhost-ts.jks
+server.ssl.trust-store-password: localhostTruststorePassword

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties
new file mode 100644
index 0000000..6ce3665
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+
+
+# Properties for Spring Boot integration tests
+# Documentation for common Spring Boot application properties can be found at:
+# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+
+
+# Custom (non-standard to Spring Boot) properties
+nifi.registry.properties.file: src/test/resources/conf/secure-kerberos/nifi-registry.properties
+nifi.registry.client.properties.file: src/test/resources/conf/secure-kerberos/nifi-registry-client.properties
+
+
+# Embedded Server SSL Context Config
+#server.ssl.client-auth: need  # LDAP-configured server does not require two-way TLS
+server.ssl.key-store: ./target/test-classes/keys/localhost-ks.jks
+server.ssl.key-store-password: localhostKeystorePassword
+server.ssl.key-password: localhostKeystorePassword
+server.ssl.protocol: TLS
+server.ssl.trust-store: ./target/test-classes/keys/localhost-ts.jks
+server.ssl.trust-store-password: localhostTruststorePassword

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureLdap.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureLdap.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureLdap.properties
new file mode 100644
index 0000000..ffcc43e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITSecureLdap.properties
@@ -0,0 +1,48 @@
+#
+# 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.
+#
+
+
+# Properties for Spring Boot integration tests
+# Documentation for common Spring Boot application properties can be found at:
+# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+
+
+# Custom (non-standard to Spring Boot) properties
+nifi.registry.properties.file: src/test/resources/conf/secure-ldap/nifi-registry.properties
+nifi.registry.client.properties.file: src/test/resources/conf/secure-ldap/nifi-registry-client.properties
+
+
+# Embedded Server SSL Context Config
+#server.ssl.client-auth: need  # LDAP-configured server does not require two-way TLS
+server.ssl.key-store: ./target/test-classes/keys/localhost-ks.jks
+server.ssl.key-store-password: localhostKeystorePassword
+server.ssl.key-password: localhostKeystorePassword
+server.ssl.protocol: TLS
+server.ssl.trust-store: ./target/test-classes/keys/localhost-ts.jks
+server.ssl.trust-store-password: localhostTruststorePassword
+
+# Embedded LDAP Config
+spring.ldap.embedded.base-dn: dc=example,dc=com
+spring.ldap.embedded.credential.username: cn=read-only-admin,dc=example,dc=com
+spring.ldap.embedded.credential.password: password
+spring.ldap.embedded.ldif: classpath:conf/secure-ldap/test-ldap-data.ldif
+spring.ldap.embedded.port: 8389
+spring.ldap.embedded.validation.enabled: false
+
+# Additional Logging Config
+logging.level.org.springframework.security.ldap: DEBUG
+logging.level.org.springframework.ldap: DEBUG
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecured.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecured.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecured.properties
new file mode 100644
index 0000000..bcd338c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecured.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+# Integration Test Profile for running an unsecured NiFi Registry instance
+
+# Custom (non-standard to Spring Boot) properties
+nifi.registry.properties.file = src/test/resources/conf/unsecured/nifi-registry.properties

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/application.properties
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/application.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application.properties
new file mode 100644
index 0000000..efa0290
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application.properties
@@ -0,0 +1,25 @@
+#
+# 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.
+#
+
+# Properties for Spring Boot integration tests
+# Documentation for commoon Spring Boot application properties can be found at:
+# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+
+# These verbose log levels can be enabled locally for dev testing, but disable them in the repo to minimize travis logs.
+#logging.level.org.springframework.core.io.support: DEBUG
+#logging.level.org.springframework.context.annotation: DEBUG
+#logging.level.org.springframework.web: DEBUG

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/banner.txt
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/banner.txt b/nifi-registry-core/nifi-registry-web-api/src/test/resources/banner.txt
new file mode 100644
index 0000000..2f54644
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/banner.txt
@@ -0,0 +1,8 @@
+
+  Apache NiFi   _     _
+ _ __ ___  __ _(_)___| |_ _ __ _   _
+| '__/ _ \/ _` | / __| __| '__| | | |
+| | |  __/ (_| | \__ \ |_| |  | |_| |
+|_|  \___|\__, |_|___/\__|_|   \__, |
+==========|___/================|___/=
+               Integration Test

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/providers.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/providers.xml b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/providers.xml
new file mode 100644
index 0000000..fd002be
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/providers.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ 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.
+  -->
+<providers>
+
+    <flowPersistenceProvider>
+        <class>org.apache.nifi.registry.provider.flow.FileSystemFlowPersistenceProvider</class>
+        <property name="Flow Storage Directory">./target/test-classes/flow_storage</property>
+    </flowPersistenceProvider>
+
+</providers>
\ No newline at end of file