NIFIREG-385 Make revision feature configurable
NIFIREG-385 Adding comment to revision property in nifi-registry.properties
Signed-off-by: Nathan Gough <thenatog@gmail.com>
This closes #276.
diff --git a/nifi-registry-assembly/pom.xml b/nifi-registry-assembly/pom.xml
index b4c7acd..ec4bc07 100644
--- a/nifi-registry-assembly/pom.xml
+++ b/nifi-registry-assembly/pom.xml
@@ -197,6 +197,9 @@
<nifi.registry.kerberos.spnego.keytab.location />
<nifi.registry.kerberos.spnego.authentication.expiration>12 hours</nifi.registry.kerberos.spnego.authentication.expiration>
+ <!-- nifi.registry.properties: revision management properties -->
+ <nifi.registry.revisions.enabled>false</nifi.registry.revisions.enabled>
+
</properties>
<profiles>
diff --git a/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java b/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
index d3b4a25..4700383 100644
--- a/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
+++ b/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
@@ -87,6 +87,9 @@
public static final String KERBEROS_SERVICE_PRINCIPAL = "nifi.registry.kerberos.service.principal";
public static final String KERBEROS_SERVICE_KEYTAB_LOCATION = "nifi.registry.kerberos.service.keytab.location";
+ // Revision Management Properties
+ public static final String REVISIONS_ENABLED = "nifi.registry.revisions.enabled";
+
// Defaults
public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
public static final String DEFAULT_WAR_DIR = "./lib";
@@ -273,6 +276,10 @@
return extensionDirs;
}
+ public boolean areRevisionsEnabled() {
+ return Boolean.parseBoolean(getPropertyAsTrimmedString(REVISIONS_ENABLED));
+ }
+
/**
* Retrieves all known property keys.
*
diff --git a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
index 1b62023..bf3e09f 100644
--- a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
@@ -99,3 +99,7 @@
nifi.registry.kerberos.spnego.principal=${nifi.registry.kerberos.spnego.principal}
nifi.registry.kerberos.spnego.keytab.location=${nifi.registry.kerberos.spnego.keytab.location}
nifi.registry.kerberos.spnego.authentication.expiration=${nifi.registry.kerberos.spnego.authentication.expiration}
+
+# revision management #
+# This feature should remain disabled until a future NiFi release that supports the revision API changes
+nifi.registry.revisions.enabled=${nifi.registry.revisions.enabled}
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionConfiguration.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionConfiguration.java
index cc0edf9..2b270d6 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionConfiguration.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionConfiguration.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.registry.web.service;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.revision.api.RevisionManager;
import org.apache.nifi.registry.revision.entity.RevisableEntityService;
import org.apache.nifi.registry.revision.entity.StandardRevisableEntityService;
@@ -40,4 +41,11 @@
return new StandardRevisableEntityService(revisionManager);
}
+ @Bean
+ public synchronized RevisionFeature getRevisionFeature(final NiFiRegistryProperties properties) {
+ return () -> {
+ return properties.areRevisionsEnabled();
+ };
+ }
+
}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionFeature.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionFeature.java
new file mode 100644
index 0000000..c39f6d1
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/RevisionFeature.java
@@ -0,0 +1,23 @@
+/*
+ * 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.service;
+
+public interface RevisionFeature {
+
+ boolean isEnabled();
+
+}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/StandardServiceFacade.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/StandardServiceFacade.java
index 7c908e4..f5bfb01 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/StandardServiceFacade.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/service/StandardServiceFacade.java
@@ -105,6 +105,7 @@
private final AuthorizationService authorizationService;
private final AuthorizableLookup authorizableLookup;
private final RevisableEntityService entityService;
+ private final RevisionFeature revisionFeature;
private final PermissionsService permissionsService;
private final LinkService linkService;
@@ -114,6 +115,7 @@
final AuthorizationService authorizationService,
final AuthorizableLookup authorizableLookup,
final RevisableEntityService entityService,
+ final RevisionFeature revisionFeature,
final PermissionsService permissionsService,
final LinkService linkService) {
this.registryService = registryService;
@@ -121,6 +123,7 @@
this.authorizationService = authorizationService;
this.authorizableLookup = authorizableLookup;
this.entityService = entityService;
+ this.revisionFeature = revisionFeature;
this.permissionsService = permissionsService;
this.linkService = linkService;
}
@@ -135,6 +138,7 @@
public Bucket createBucket(final Bucket bucket) {
authorizeBucketsAccess(RequestAction.WRITE);
validateCreationOfRevisableEntity(bucket, BUCKET_ENTITY_TYPE);
+ validateIdentifierNotPresent(bucket, BUCKET_ENTITY_TYPE);
bucket.setIdentifier(UUID.randomUUID().toString());
@@ -231,7 +235,11 @@
authorizeBucketAccess(RequestAction.WRITE, bucketIdentifier);
validateCreationOfRevisableEntity(versionedFlow, VERSIONED_FLOW_ENTITY_TYPE);
- versionedFlow.setIdentifier(UUID.randomUUID().toString());
+ // NOTE: Don't validate that identifier is null...
+ // NiFi has been sending an identifier, so we must maintain backwards compatibility
+ if (versionedFlow.getIdentifier() == null) {
+ versionedFlow.setIdentifier(UUID.randomUUID().toString());
+ }
final VersionedFlow createdFlow = createRevisableEntity(versionedFlow, VERSIONED_FLOW_ENTITY_TYPE, currentUserIdentity(),
() -> registryService.createFlow(bucketIdentifier, versionedFlow));
@@ -804,6 +812,7 @@
verifyAuthorizerSupportsConfigurableUserGroups();
authorizeTenantsAccess(RequestAction.WRITE);
validateCreationOfRevisableEntity(user, USER_ENTITY_TYPE);
+ validateIdentifierNotPresent(user, USER_ENTITY_TYPE);
user.setIdentifier(UUID.randomUUID().toString());
return createRevisableEntity(user, USER_ENTITY_TYPE, currentUserIdentity(), () -> authorizationService.createUser(user));
@@ -854,6 +863,7 @@
verifyAuthorizerSupportsConfigurableUserGroups();
authorizeTenantsAccess(RequestAction.WRITE);
validateCreationOfRevisableEntity(userGroup, USER_GROUP_ENTITY_TYPE);
+ validateIdentifierNotPresent(userGroup, USER_GROUP_ENTITY_TYPE);
userGroup.setIdentifier(UUID.randomUUID().toString());
return createRevisableEntity(userGroup, USER_GROUP_ENTITY_TYPE, currentUserIdentity(),
@@ -907,6 +917,7 @@
verifyAuthorizerSupportsConfigurablePolicies();
authorizePoliciesAccess(RequestAction.WRITE);
validateCreationOfRevisableEntity(accessPolicy, ACCESS_POLICY_ENTITY_TYPE);
+ validateIdentifierNotPresent(accessPolicy, ACCESS_POLICY_ENTITY_TYPE);
accessPolicy.setIdentifier(UUID.randomUUID().toString());
return createRevisableEntity(accessPolicy, ACCESS_POLICY_ENTITY_TYPE, currentUserIdentity(),
@@ -1111,15 +1122,20 @@
authorizationService.authorize(tenantsAuthorizable, actionType);
}
+ // ---------------------- Revision Helper Methods -------------------------------------
+
private void validateCreationOfRevisableEntity(final RevisableEntity entity, final String entityTypeName) {
if (entity == null) {
throw new IllegalArgumentException(entityTypeName + " cannot be null");
}
- if (entity.getIdentifier() != null) {
- throw new IllegalArgumentException(entityTypeName + " identifier cannot be specified when creating a new "
- + entityTypeName.toLowerCase() + ".");
+
+ // skip checking revision if feature is disabled
+ if (!revisionFeature.isEnabled()) {
+ return;
}
+ // NOT: restore identifier check here when we no longer needs backwards compatibility
+
if (entity.getRevision() == null
|| entity.getRevision().getVersion() == null
|| entity.getRevision().getVersion().longValue() != 0) {
@@ -1127,11 +1143,29 @@
}
}
+ /**
+ * NOTE: This logic should be moved back to validateCreationOfRevisableEntity once we no longer need to maintain
+ * backwards compatibility (i.e. on a major release like 1.0.0).
+ *
+ * Currently NiFi has been sending an identifier when creating a flow, so we need to continue to allow that.
+ */
+ private void validateIdentifierNotPresent(final RevisableEntity entity, final String entityTypeName) {
+ if (entity.getIdentifier() != null) {
+ throw new IllegalArgumentException(entityTypeName + " identifier cannot be specified when creating a new "
+ + entityTypeName.toLowerCase() + ".");
+ }
+ }
+
private void validateUpdateOfRevisableEntity(final RevisableEntity entity, final String entityTypeName) {
if (entity == null) {
throw new IllegalArgumentException(entityTypeName + " cannot be null");
}
+ // skip checking revision if feature is disabled
+ if (!revisionFeature.isEnabled()) {
+ return;
+ }
+
if (entity.getRevision() == null || entity.getRevision().getVersion() == null) {
throw new IllegalArgumentException("Revision info must be specified.");
}
@@ -1142,6 +1176,11 @@
throw new IllegalArgumentException(entityTypeName + " identifier is required");
}
+ // skip checking revision if feature is disabled
+ if (!revisionFeature.isEnabled()) {
+ return;
+ }
+
if (revision == null || revision.getVersion() == null) {
throw new IllegalArgumentException("Revision info must be specified.");
}
@@ -1149,31 +1188,48 @@
private <T extends RevisableEntity> T createRevisableEntity(final T requestEntity, final String entityTypeName,
final String creatorIdentity, final Supplier<T> createEntity) {
- try {
- return entityService.create(requestEntity, creatorIdentity, createEntity);
- } catch (InvalidRevisionException e) {
- final String msg = String.format(INVALID_REVISION_MSG, entityTypeName, "create", requestEntity.getIdentifier());
- throw new InvalidRevisionException(msg, e);
+
+ // skip using the entity service if revision feature is disabled
+ if (!revisionFeature.isEnabled()) {
+ return createEntity.get();
+ } else {
+ try {
+ return entityService.create(requestEntity, creatorIdentity, createEntity);
+ } catch (InvalidRevisionException e) {
+ final String msg = String.format(INVALID_REVISION_MSG, entityTypeName, "create", requestEntity.getIdentifier());
+ throw new InvalidRevisionException(msg, e);
+ }
}
}
private <T extends RevisableEntity> T updateRevisableEntity(final T requestEntity, final String entityTypeName,
final String updaterIdentity, final Supplier<T> updateEntity) {
- try {
- return entityService.update(requestEntity, updaterIdentity, updateEntity);
- } catch (InvalidRevisionException e) {
- final String msg = String.format(INVALID_REVISION_MSG, entityTypeName, "update", requestEntity.getIdentifier());
- throw new InvalidRevisionException(msg, e);
+
+ // skip using the entity service if revision feature is disabled
+ if (!revisionFeature.isEnabled()) {
+ return updateEntity.get();
+ } else {
+ try {
+ return entityService.update(requestEntity, updaterIdentity, updateEntity);
+ } catch (InvalidRevisionException e) {
+ final String msg = String.format(INVALID_REVISION_MSG, entityTypeName, "update", requestEntity.getIdentifier());
+ throw new InvalidRevisionException(msg, e);
+ }
}
}
private <T extends RevisableEntity> T deleteRevisableEntity(final String entityIdentifier, final String entityTypeName,
final RevisionInfo revisionInfo, final Supplier<T> deleteEntity) {
- try {
- return entityService.delete(entityIdentifier, revisionInfo, deleteEntity);
- } catch (InvalidRevisionException e) {
- final String msg = String.format(INVALID_REVISION_MSG, entityTypeName, "delete", entityIdentifier);
- throw new InvalidRevisionException(msg, e);
+ // skip using the entity service if revision feature is disabled
+ if (!revisionFeature.isEnabled()) {
+ return deleteEntity.get();
+ } else {
+ try {
+ return entityService.delete(entityIdentifier, revisionInfo, deleteEntity);
+ } catch (InvalidRevisionException e) {
+ final String msg = String.format(INVALID_REVISION_MSG, entityTypeName, "delete", entityIdentifier);
+ throw new InvalidRevisionException(msg, e);
+ }
}
}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/NoRevisionsIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/NoRevisionsIT.java
new file mode 100644
index 0000000..e93b522
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/NoRevisionsIT.java
@@ -0,0 +1,139 @@
+/*
+ * 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.bucket.Bucket;
+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.junit.Test;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+
+import java.util.UUID;
+
+import static org.apache.nifi.registry.web.api.IntegrationTestUtils.assertBucketsEqual;
+import static org.apache.nifi.registry.web.api.IntegrationTestUtils.assertFlowSnapshotsEqual;
+import static org.apache.nifi.registry.web.api.IntegrationTestUtils.assertFlowsEqual;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class NoRevisionsIT extends UnsecuredNoRevisionsITBase {
+
+ @Test
+ public void testNoRevisions() {
+ // Create a bucket...
+
+ final Bucket bucket = new Bucket();
+ bucket.setName("Integration Test Bucket");
+ bucket.setDescription("A bucket created by an integration test.");
+
+ final Bucket createdBucket = client
+ .target(createURL("buckets"))
+ .request()
+ .post(Entity.entity(bucket, MediaType.APPLICATION_JSON), Bucket.class);
+
+ assertBucketsEqual(bucket, createdBucket, false);
+ assertNotNull(createdBucket.getIdentifier());
+
+ // Update bucket...
+
+ createdBucket.setName("Renamed Bucket");
+ createdBucket.setDescription("This bucket has been updated by an integration test.");
+
+ final Bucket updatedBucket = client
+ .target(createURL("buckets/" + createdBucket.getIdentifier()))
+ .request()
+ .put(Entity.entity(createdBucket, MediaType.APPLICATION_JSON), Bucket.class);
+
+ assertBucketsEqual(updatedBucket, createdBucket, true);
+
+ // Create a flow...
+
+ final VersionedFlow flow = new VersionedFlow();
+ flow.setIdentifier(UUID.randomUUID().toString()); // Simulate NiFi sending an identifier
+ flow.setBucketIdentifier(createdBucket.getIdentifier());
+ flow.setName("Test Flow");
+ flow.setDescription("This is a flow created by an integration test.");
+
+ final VersionedFlow createdFlow = client
+ .target(createURL("buckets/{bucketId}/flows"))
+ .resolveTemplate("bucketId", flow.getBucketIdentifier())
+ .request()
+ .post(Entity.entity(flow, MediaType.APPLICATION_JSON), VersionedFlow.class);
+
+ assertFlowsEqual(flow, createdFlow, false);
+ assertNotNull(createdFlow.getIdentifier());
+
+ // Update flow...
+
+ createdFlow.setName("Renamed Flow");
+ createdFlow.setDescription("This flow has been updated by an integration test.");
+
+ final VersionedFlow updatedFlow = client
+ .target(createURL("buckets/{bucketId}/flows/{flowId}"))
+ .resolveTemplate("bucketId",flow.getBucketIdentifier())
+ .resolveTemplate("flowId", createdFlow.getIdentifier())
+ .request()
+ .put(Entity.entity(createdFlow, MediaType.APPLICATION_JSON), VersionedFlow.class);
+
+ assertTrue(updatedFlow.getModifiedTimestamp() > createdFlow.getModifiedTimestamp());
+
+ // Create a version of a flow...
+
+ final VersionedFlowSnapshotMetadata flowSnapshotMetadata = new VersionedFlowSnapshotMetadata();
+ flowSnapshotMetadata.setVersion(1);
+ flowSnapshotMetadata.setBucketIdentifier(createdFlow.getBucketIdentifier());
+ flowSnapshotMetadata.setFlowIdentifier(createdFlow.getIdentifier());
+ flowSnapshotMetadata.setComments("This is snapshot 1, created by an integration test.");
+
+ final VersionedFlowSnapshot flowSnapshot = new VersionedFlowSnapshot();
+ flowSnapshot.setSnapshotMetadata(flowSnapshotMetadata);
+ flowSnapshot.setFlowContents(new VersionedProcessGroup()); // an empty root process group
+
+ final VersionedFlowSnapshot createdFlowSnapshot = client
+ .target(createURL("buckets/{bucketId}/flows/{flowId}/versions"))
+ .resolveTemplate("bucketId", flowSnapshotMetadata.getBucketIdentifier())
+ .resolveTemplate("flowId", flowSnapshotMetadata.getFlowIdentifier())
+ .request()
+ .post(Entity.entity(flowSnapshot, MediaType.APPLICATION_JSON), VersionedFlowSnapshot.class);
+
+ assertFlowSnapshotsEqual(flowSnapshot, createdFlowSnapshot, false);
+
+ // Delete flow...
+
+ final VersionedFlow deletedFlow = client
+ .target(createURL("buckets/{bucketId}/flows/{flowId}"))
+ .resolveTemplate("bucketId", createdFlow.getBucketIdentifier())
+ .resolveTemplate("flowId", createdFlow.getIdentifier())
+ .request()
+ .delete(VersionedFlow.class);
+
+ assertNotNull(deletedFlow);
+
+ // Delete bucket...
+
+ final Bucket deletedBucket = client
+ .target(createURL("buckets/" + createdBucket.getIdentifier()))
+ .request()
+ .delete(Bucket.class);
+
+ assertNotNull(deletedBucket);
+ }
+}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNoRevisionsITBase.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNoRevisionsITBase.java
new file mode 100644
index 0000000..cabe25e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNoRevisionsITBase.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=ITUnsecuredNoRevisions")
+@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:db/clearDB.sql")
+public class UnsecuredNoRevisionsITBase extends IntegrationTestBase {
+
+ // Tests cases defined in subclasses
+
+}
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecuredNoRevisions.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecuredNoRevisions.properties
new file mode 100644
index 0000000..5e16e04
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/application-ITUnsecuredNoRevisions.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-no-revisions/nifi-registry.properties
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/db-flow-storage/nifi-registry.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/db-flow-storage/nifi-registry.properties
index dd50ae6..dd13679 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/db-flow-storage/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/db-flow-storage/nifi-registry.properties
@@ -25,4 +25,7 @@
nifi.registry.extensions.working.directory=./target/work/extensions
# database properties
-nifi.registry.db.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
\ No newline at end of file
+nifi.registry.db.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# enabled revision checking #
+nifi.registry.revisions.enabled=true
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-file/nifi-registry.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-file/nifi-registry.properties
index 408f44d..fa2ad4c 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-file/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-file/nifi-registry.properties
@@ -28,3 +28,6 @@
# providers properties #
nifi.registry.providers.configuration.file=./target/test-classes/conf/providers.xml
+
+# enabled revision checking #
+nifi.registry.revisions.enabled=true
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties
index 3d5c122..79a41e3 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties
@@ -34,3 +34,6 @@
nifi.registry.kerberos.spnego.authentication.expiration=12 hours
nifi.registry.kerberos.spnego.principal=HTTP/localhost@LOCALHOST
nifi.registry.kerberos.spnego.keytab.location=/path/to/keytab
+
+# enabled revision checking #
+nifi.registry.revisions.enabled=true
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry.properties
index 1b46ac2..b3512f3 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry.properties
@@ -30,3 +30,6 @@
# providers properties #
nifi.registry.providers.configuration.file=./target/test-classes/conf/providers.xml
+
+# enabled revision checking #
+nifi.registry.revisions.enabled=true
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured-no-revisions/nifi-registry.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured-no-revisions/nifi-registry.properties
new file mode 100644
index 0000000..f87062c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured-no-revisions/nifi-registry.properties
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+# web properties #
+nifi.registry.web.http.host=localhost
+
+# providers properties #
+nifi.registry.providers.configuration.file=./target/test-classes/conf/providers.xml
+
+# extensions working dir #
+nifi.registry.extensions.working.directory=./target/work/extensions
+
+# database properties
+nifi.registry.db.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# disable revision checking #
+nifi.registry.revisions.enabled=false
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured/nifi-registry.properties b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured/nifi-registry.properties
index 70ca5e3..451abc0 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/conf/unsecured/nifi-registry.properties
@@ -25,4 +25,7 @@
nifi.registry.extensions.working.directory=./target/work/extensions
# database properties
-nifi.registry.db.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
\ No newline at end of file
+nifi.registry.db.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# enabled revision checking #
+nifi.registry.revisions.enabled=true
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html
index b7c37e4..b2aff5a 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.html
@@ -74,7 +74,7 @@
<div *ngIf="nfRegistryService.currentUser.identity && nfRegistryService.perspective !== 'login' && nfRegistryService.perspective !== 'not-found'" fxLayout="column" fxLayoutAlign="space-around end" class="push-right-sm">
<div id="current-user" matTooltip="{{nfRegistryService.currentUser.identity}}">{{nfRegistryService.currentUser.identity}}</div>
<a id="logout-link-container" *ngIf="nfRegistryService.currentUser.canLogout" class="link" (click)="logout()">logout</a>
- <a id="logout-link-container" *ngIf="!nfRegistryService.currentUser.canLogout && nfRegistryService.currentUser.anonymous && nfRegistryService.currentUser.loginSupported" class="link" (click)="login()">login</a>
+ <a id="login-link-container" *ngIf="!nfRegistryService.currentUser.canLogout && nfRegistryService.currentUser.anonymous && nfRegistryService.currentUser.loginSupported" class="link" (click)="login()">login</a>
</div>
<div id="nifi-registry-documentation" *ngIf="nfRegistryService.perspective !== 'login'" class="pad-right-sm">
<a matTooltip="Help" href="{{nfRegistryService.documentation.link}}" target="_blank"><i class="fa fa-question-circle help-icon" aria-hidden="true"></i></a>
diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
index 88ea7df..50ecdf4 100644
--- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
+++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/theming/_structureElements.scss
@@ -113,7 +113,8 @@
color: $grey5;
}
-#logout-link-container {
+#logout-link-container,
+#login-link-container {
font-size: 12px;
text-transform: uppercase;
}