diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java index 38c9e264b0..9e50e62cda 100644 --- a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java +++ b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java @@ -40,8 +40,8 @@ public class AuthorizationRequest { this.resource = builder.resource; this.identity = builder.identity; this.action = builder.action; - this.context = Collections.unmodifiableMap(builder.context); - this.eventAttributes = Collections.unmodifiableMap(builder.eventAttributes); + this.context = builder.context == null ? null : Collections.unmodifiableMap(builder.context); + this.eventAttributes = builder.context == null ? null : Collections.unmodifiableMap(builder.eventAttributes); } /** diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationResult.java b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationResult.java index acbbbe2382..a3f520c118 100644 --- a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationResult.java +++ b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizationResult.java @@ -21,7 +21,7 @@ package org.apache.nifi.authorization; */ public class AuthorizationResult { - private enum Result { + public enum Result { Approved, Denied, ResourceNotFound diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java index b2b6b3ae06..3721ab4e57 100644 --- a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java +++ b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java @@ -16,6 +16,8 @@ */ package org.apache.nifi.authorization; +import org.apache.nifi.components.PropertyValue; + import java.util.Map; /** @@ -44,5 +46,5 @@ public interface AuthorizerConfigurationContext { * PropertyDescriptor. This method does not substitute default * PropertyDescriptor values, so the value returned will be null if not set */ - String getProperty(String property); + PropertyValue getProperty(String property); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml index 2fef0c42a4..c9a9c0efc4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml @@ -116,5 +116,9 @@ org.apache.commons commons-collections4 + + org.apache.nifi + nifi-expression-language + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java index 946da9649c..3010c921df 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java @@ -16,6 +16,9 @@ */ package org.apache.nifi.authorization; +import org.apache.nifi.attribute.expression.language.StandardPropertyValue; +import org.apache.nifi.components.PropertyValue; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -44,8 +47,8 @@ public class StandardAuthorizerConfigurationContext implements AuthorizerConfigu } @Override - public String getProperty(String property) { - return properties.get(property); + public PropertyValue getProperty(String property) { + return new StandardPropertyValue(properties.get(property), null); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml new file mode 100644 index 0000000000..53f35f412a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml @@ -0,0 +1,94 @@ + + + + 4.0.0 + + org.apache.nifi + nifi-framework + 1.0.0-SNAPSHOT + + nifi-file-authorizer + + + + src/main/resources + + + src/main/xsd + + + + + org.codehaus.mojo + jaxb2-maven-plugin + + + xjc + + xjc + + + org.apache.nifi.authorization.generated + + + + + ${project.build.directory}/generated-sources/jaxb + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + **/authorization/generated/*.java + + + + + + + + org.apache.nifi + nifi-api + + + org.apache.nifi + nifi-utils + + + org.apache.nifi + nifi-properties + + + org.apache.commons + commons-lang3 + + + org.apache.nifi + nifi-framework-authorization + + + commons-codec + commons-codec + test + + + org.apache.nifi + nifi-expression-language + test + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java new file mode 100644 index 0000000000..174e501bea --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java @@ -0,0 +1,279 @@ +/* + * 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.authorization; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.annotation.AuthorizerContext; +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authorization.generated.Authorization; +import org.apache.nifi.authorization.generated.Resource; +import org.apache.nifi.authorization.generated.Resources; +import org.apache.nifi.components.PropertyValue; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.FileUtils; +import org.apache.nifi.util.file.monitor.MD5SumMonitor; +import org.apache.nifi.util.file.monitor.SynchronousFileWatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Provides identity checks and grants authorities. + */ +public class FileAuthorizer implements Authorizer { + + private static final Logger logger = LoggerFactory.getLogger(FileAuthorizer.class); + private static final String READ_CODE = "R"; + private static final String WRITE_CODE = "W"; + private static final String USERS_XSD = "/authorizations.xsd"; + private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authorization.generated"; + private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); + + /** + * Load the JAXBContext. + */ + private static JAXBContext initializeJaxbContext() { + try { + return JAXBContext.newInstance(JAXB_GENERATED_PATH, FileAuthorizer.class.getClassLoader()); + } catch (JAXBException e) { + throw new RuntimeException("Unable to create JAXBContext."); + } + } + + private NiFiProperties properties; + private File authorizationsFile; + private File restoreAuthorizationsFile; + private SynchronousFileWatcher fileWatcher; + private ScheduledExecutorService fileWatcherExecutorService; + + private final AtomicReference>>> authorizations = new AtomicReference<>(); + + @Override + public void initialize(final AuthorizerInitializationContext initializationContext) throws ProviderCreationException { + } + + @Override + public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws ProviderCreationException { + try { + final PropertyValue authorizationsPath = configurationContext.getProperty("Authorizations File"); + if (StringUtils.isBlank(authorizationsPath.getValue())) { + throw new ProviderCreationException("The authorizations file must be specified."); + } + + // get the authorizations file and ensure it exists + authorizationsFile = new File(authorizationsPath.getValue()); + if (!authorizationsFile.exists()) { + throw new ProviderCreationException("The authorizations file must exist."); + } + + final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile(); + + // the restore directory is optional and may be null + final File restoreDirectory = properties.getRestoreDirectory(); + if (restoreDirectory != null) { + // sanity check that restore directory is a directory, creating it if necessary + FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory); + + // check that restore directory is not the same as the primary directory + if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { + throw new ProviderCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ", + authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); + } + + // the restore copy will have same file name, but reside in a different directory + restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName()); + + try { + // sync the primary copy with the restore copy + FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger); + } catch (final IOException | IllegalStateException ioe) { + throw new ProviderCreationException(ioe); + } + } + + final PropertyValue rawReloadInterval = configurationContext.getProperty("Reload Interval"); + + long reloadInterval; + try { + reloadInterval = rawReloadInterval.asTimePeriod(TimeUnit.MILLISECONDS); + } catch (final Exception iae) { + logger.info(String.format("Unable to interpret reload interval '%s'. Using default of 30 seconds.", rawReloadInterval)); + reloadInterval = 30000L; + } + + // reload the authorizations + reload(); + + // watch the file for modifications + fileWatcher = new SynchronousFileWatcher(authorizationsFile.toPath(), new MD5SumMonitor()); + fileWatcherExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(final Runnable r) { + return new Thread(r, "Authorization File Reload Thread"); + } + }); + fileWatcherExecutorService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + try { + if (fileWatcher.checkAndReset()) { + reload(); + } + } catch (final Exception e) { + logger.warn("Unable to reload Authorizations file do to: " + e, e); + } + } + }, reloadInterval, reloadInterval, TimeUnit.MILLISECONDS); + } catch (IOException | ProviderCreationException | SAXException | JAXBException | IllegalStateException e) { + throw new ProviderCreationException(e); + } + + } + + @Override + public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException { + // get the current authorizations + final Map>> currentAuthorizations = authorizations.get(); + + // get the requested resource + final org.apache.nifi.authorization.Resource requestedResource = request.getResource(); + + // get the authorizations for the requested resources + final Map> resourceAuthorizations = currentAuthorizations.get(requestedResource.getIdentifier()); + + // ensure the resource has authorizations + if (resourceAuthorizations == null) { + return AuthorizationResult.resourceNotFound(); + } + + // get the user authorizations + final Set userAuthorizations = resourceAuthorizations.get(request.getIdentity()); + + // ensure the user has authorizations + if (userAuthorizations == null) { + return AuthorizationResult.denied(); + } + + // ensure the appropriate response + if (userAuthorizations.contains(request.getAction())) { + return AuthorizationResult.approved(); + } else { + return AuthorizationResult.denied(); + } + } + + /** + * Reloads the authorized users file. + * + * @throws SAXException Unable to reload the authorized users file + * @throws JAXBException Unable to reload the authorized users file + * @throws IOException Unable to sync file with restore + * @throws IllegalStateException Unable to sync file with restore + */ + private void reload() throws SAXException, JAXBException, IOException, IllegalStateException { + // find the schema + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final Schema schema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); + + // attempt to unmarshal + final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(schema); + final JAXBElement element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Resources.class); + final Resources resources = element.getValue(); + + // new authorizations + final Map>> newAuthorizations = new HashMap<>(); + + // load the new authorizations + for (final Resource authorizedResource : resources.getResource()) { + final String identifier = authorizedResource.getIdentifier(); + + // ensure the entry exists + if (!newAuthorizations.containsKey(identifier)) { + newAuthorizations.put(identifier, new HashMap>()); + } + + // go through each authorization + for (final Authorization authorization : authorizedResource.getAuthorization()) { + final String identity = authorization.getIdentity(); + + // get the authorizations for this resource + final Map> resourceAuthorizations = newAuthorizations.get(identifier); + + // ensure the entry exists + if (!resourceAuthorizations.containsKey(identity)) { + resourceAuthorizations.put(identity, EnumSet.noneOf(RequestAction.class)); + } + + final Set authorizedActions = resourceAuthorizations.get(identity); + final String authorizationCode = authorization.getAction(); + + // updated the actions for this identity + if (authorizationCode.contains(READ_CODE)) { + authorizedActions.add(RequestAction.READ); + } + if (authorizationCode.contains(WRITE_CODE)) { + authorizedActions.add(RequestAction.WRITE); + } + } + } + + // set the new authorizations + authorizations.set(newAuthorizations); + + // if we've copied a the authorizations file to a restore directory synchronize it + if (restoreAuthorizationsFile != null) { + FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger); + } + + logger.info(String.format("Authorizations file loaded at %s", new Date().toString())); + } + + @AuthorizerContext + public void setNiFiProperties(NiFiProperties properties) { + this.properties = properties; + } + + @Override + public void preDestruction() { + if (fileWatcherExecutorService != null) { + fileWatcherExecutorService.shutdown(); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer new file mode 100755 index 0000000000..da9c2ee086 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.authorization.FileAuthorizer diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/xsd/authorizations.xsd b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/xsd/authorizations.xsd new file mode 100644 index 0000000000..dc17265a03 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/xsd/authorizations.xsd @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java new file mode 100644 index 0000000000..359d45baf8 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java @@ -0,0 +1,198 @@ +/* + * 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.authorization; + +import org.apache.nifi.attribute.expression.language.StandardPropertyValue; +import org.apache.nifi.authorization.AuthorizationResult.Result; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authorization.resource.ResourceFactory; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FileAuthorizerTest { + + private static final String EMPTY_AUTHORIZATIONS_CONCISE = + "" + + ""; + + private static final String EMPTY_AUTHORIZATIONS = + "" + + "" + + ""; + + private static final String BAD_SCHEMA_AUTHORIZATIONS = + "" + + "" + + ""; + + private static final String AUTHORIZATIONS = + "" + + "" + + "" + + "" + + "" + + ""; + + private static final String UPDATED_AUTHORIZATIONS = + "" + + "" + + "" + + "" + + "" + + ""; + + private FileAuthorizer authorizer; + private File primary; + private File restore; + + private AuthorizerConfigurationContext configurationContext; + + @Before + public void setup() throws IOException { + // primary authorizations + primary = new File("target/primary/authorizations.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(primary.getParentFile()); + + // restore authorizations + restore = new File("target/restore/authorizations.xml"); + FileUtils.ensureDirectoryExistAndCanAccess(restore.getParentFile()); + + final NiFiProperties properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile()); + + configurationContext = mock(AuthorizerConfigurationContext.class); + when(configurationContext.getProperty(Mockito.eq("Authorizations File"))).thenReturn(new StandardPropertyValue(primary.getPath(), null)); + + authorizer = new FileAuthorizer(); + authorizer.setNiFiProperties(properties); + authorizer.initialize(null); + } + + @After + public void cleanup() throws Exception { + deleteFile(primary); + deleteFile(restore); + } + + @Test + public void testPostConstructionWhenRestoreDoesNotExist() throws Exception { + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + + assertEquals(primary.length(), restore.length()); + } + + @Test(expected = ProviderCreationException.class) + public void testPostConstructionWhenPrimaryDoesNotExist() throws Exception { + writeAuthorizationsFile(restore, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + } + + @Test(expected = ProviderCreationException.class) + public void testPostConstructionWhenPrimaryDifferentThanRestore() throws Exception { + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS); + writeAuthorizationsFile(restore, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + } + + @Test(expected = ProviderCreationException.class) + public void testBadSchema() throws Exception { + writeAuthorizationsFile(primary, BAD_SCHEMA_AUTHORIZATIONS); + authorizer.onConfigured(configurationContext); + } + + @Test + public void testAuthorizedUserAction() throws Exception { + writeAuthorizationsFile(primary, AUTHORIZATIONS); + authorizer.onConfigured(configurationContext); + + final AuthorizationRequest request = new AuthorizationRequest.Builder().resource(ResourceFactory.getFlowResource()).identity("user-1").action(RequestAction.READ).build(); + final AuthorizationResult result = authorizer.authorize(request); + assertTrue(Result.Approved.equals(result.getResult())); + } + + @Test + public void testUnauthorizedUser() throws Exception { + writeAuthorizationsFile(primary, AUTHORIZATIONS); + authorizer.onConfigured(configurationContext); + + final AuthorizationRequest request = new AuthorizationRequest.Builder().resource(ResourceFactory.getFlowResource()).identity("user-2").action(RequestAction.READ).build(); + final AuthorizationResult result = authorizer.authorize(request); + assertFalse(Result.Approved.equals(result.getResult())); + } + + @Test + public void testUnauthorizedAction() throws Exception { + writeAuthorizationsFile(primary, AUTHORIZATIONS); + authorizer.onConfigured(configurationContext); + + final AuthorizationRequest request = new AuthorizationRequest.Builder().resource(ResourceFactory.getFlowResource()).identity("user-1").action(RequestAction.WRITE).build(); + final AuthorizationResult result = authorizer.authorize(request); + assertFalse(Result.Approved.equals(result.getResult())); + } + + @Test + public void testReloadAuthorizations() throws Exception { + writeAuthorizationsFile(primary, AUTHORIZATIONS); + when(configurationContext.getProperty(Mockito.eq("Reload Interval"))).thenReturn(new StandardPropertyValue("1 sec", null)); + authorizer.onConfigured(configurationContext); + + // ensure the user currently does not have write access + final AuthorizationRequest request = new AuthorizationRequest.Builder().resource(ResourceFactory.getFlowResource()).identity("user-1").action(RequestAction.WRITE).build(); + AuthorizationResult result = authorizer.authorize(request); + assertFalse(Result.Approved.equals(result.getResult())); + + // add write access for the user + writeAuthorizationsFile(primary, UPDATED_AUTHORIZATIONS); + + // wait at least one second for the file to be stale + Thread.sleep(4000L); + + // ensure the user does have write access now using the same request + result = authorizer.authorize(request); + assertTrue(Result.Approved.equals(result.getResult())); + } + + private static void writeAuthorizationsFile(final File file, final String content) throws Exception { + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + try (final FileOutputStream fos = new FileOutputStream(file)) { + fos.write(bytes); + } + } + + private static boolean deleteFile(final File file) { + if (file.isDirectory()) { + FileUtils.deleteFilesInDir(file, null, null, true, true); + } + return FileUtils.deleteFile(file, null, 10); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml new file mode 100644 index 0000000000..21815c0727 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizations.xml @@ -0,0 +1,67 @@ + + + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml index 7faf517b74..771a258cfc 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml @@ -37,6 +37,7 @@ nifi-cluster-authorization-provider nifi-user-actions nifi-framework-authorization + nifi-file-authorizer nifi-administration nifi-web nifi-resources diff --git a/nifi-nar-bundles/nifi-framework-bundle/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/pom.xml index d1b14226a0..b4f1c55deb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/pom.xml @@ -88,6 +88,11 @@ nifi-framework-core 1.0.0-SNAPSHOT + + org.apache.nifi + nifi-framework-authorization + 1.0.0-SNAPSHOT + org.apache.nifi nifi-user-actions