mirror of https://github.com/apache/nifi.git
NIFI-1553:
- Implementing a file based authorizer. - Providing an example authorizations files. - Address comments from PR. - This closes #330
This commit is contained in:
parent
3f4ac3156c
commit
5de40ccec3
|
@ -40,8 +40,8 @@ public class AuthorizationRequest {
|
||||||
this.resource = builder.resource;
|
this.resource = builder.resource;
|
||||||
this.identity = builder.identity;
|
this.identity = builder.identity;
|
||||||
this.action = builder.action;
|
this.action = builder.action;
|
||||||
this.context = Collections.unmodifiableMap(builder.context);
|
this.context = builder.context == null ? null : Collections.unmodifiableMap(builder.context);
|
||||||
this.eventAttributes = Collections.unmodifiableMap(builder.eventAttributes);
|
this.eventAttributes = builder.context == null ? null : Collections.unmodifiableMap(builder.eventAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,7 @@ package org.apache.nifi.authorization;
|
||||||
*/
|
*/
|
||||||
public class AuthorizationResult {
|
public class AuthorizationResult {
|
||||||
|
|
||||||
private enum Result {
|
public enum Result {
|
||||||
Approved,
|
Approved,
|
||||||
Denied,
|
Denied,
|
||||||
ResourceNotFound
|
ResourceNotFound
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.authorization;
|
package org.apache.nifi.authorization;
|
||||||
|
|
||||||
|
import org.apache.nifi.components.PropertyValue;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,5 +46,5 @@ public interface AuthorizerConfigurationContext {
|
||||||
* PropertyDescriptor. This method does not substitute default
|
* PropertyDescriptor. This method does not substitute default
|
||||||
* PropertyDescriptor values, so the value returned will be null if not set
|
* PropertyDescriptor values, so the value returned will be null if not set
|
||||||
*/
|
*/
|
||||||
String getProperty(String property);
|
PropertyValue getProperty(String property);
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,5 +116,9 @@
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-collections4</artifactId>
|
<artifactId>commons-collections4</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-expression-language</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.authorization;
|
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.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -44,8 +47,8 @@ public class StandardAuthorizerConfigurationContext implements AuthorizerConfigu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProperty(String property) {
|
public PropertyValue getProperty(String property) {
|
||||||
return properties.get(property);
|
return new StandardPropertyValue(properties.get(property), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-framework</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>nifi-file-authorizer</artifactId>
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/xsd</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>jaxb2-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>xjc</id>
|
||||||
|
<goals>
|
||||||
|
<goal>xjc</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<packageName>org.apache.nifi.authorization.generated</packageName>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/generated-sources/jaxb</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>**/authorization/generated/*.java</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-utils</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-properties</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-framework-authorization</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-expression-language</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -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<Map<String, Map<String, Set<RequestAction>>>> 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<String, Map<String, Set<RequestAction>>> 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<String, Set<RequestAction>> resourceAuthorizations = currentAuthorizations.get(requestedResource.getIdentifier());
|
||||||
|
|
||||||
|
// ensure the resource has authorizations
|
||||||
|
if (resourceAuthorizations == null) {
|
||||||
|
return AuthorizationResult.resourceNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the user authorizations
|
||||||
|
final Set<RequestAction> 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<Resources> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Resources.class);
|
||||||
|
final Resources resources = element.getValue();
|
||||||
|
|
||||||
|
// new authorizations
|
||||||
|
final Map<String, Map<String, Set<RequestAction>>> 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<String, Set<RequestAction>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// go through each authorization
|
||||||
|
for (final Authorization authorization : authorizedResource.getAuthorization()) {
|
||||||
|
final String identity = authorization.getIdentity();
|
||||||
|
|
||||||
|
// get the authorizations for this resource
|
||||||
|
final Map<String, Set<RequestAction>> resourceAuthorizations = newAuthorizations.get(identifier);
|
||||||
|
|
||||||
|
// ensure the entry exists
|
||||||
|
if (!resourceAuthorizations.containsKey(identity)) {
|
||||||
|
resourceAuthorizations.put(identity, EnumSet.noneOf(RequestAction.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<RequestAction> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<!-- authorization -->
|
||||||
|
<xs:complexType name="Authorization">
|
||||||
|
<xs:attribute name="identity">
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:minLength value="1"/>
|
||||||
|
<xs:pattern value=".*[^\s].*"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="action">
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:enumeration value="R"/>
|
||||||
|
<xs:enumeration value="W"/>
|
||||||
|
<xs:enumeration value="RW"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<!-- resource -->
|
||||||
|
<xs:complexType name="Resource">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="authorization" type="Authorization" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="identifier">
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:minLength value="1"/>
|
||||||
|
<xs:pattern value=".*[^\s].*"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<!-- resources -->
|
||||||
|
<xs:element name="resources">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="resource" type="Resource" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
|
@ -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 =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||||
|
+ "<resources/>";
|
||||||
|
|
||||||
|
private static final String EMPTY_AUTHORIZATIONS =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||||
|
+ "<resources>"
|
||||||
|
+ "</resources>";
|
||||||
|
|
||||||
|
private static final String BAD_SCHEMA_AUTHORIZATIONS =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||||
|
+ "<resource>"
|
||||||
|
+ "</resource>";
|
||||||
|
|
||||||
|
private static final String AUTHORIZATIONS =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||||
|
+ "<resources>"
|
||||||
|
+ "<resource identifier=\"/flow\">"
|
||||||
|
+ "<authorization identity=\"user-1\" action=\"R\"/>"
|
||||||
|
+ "</resource>"
|
||||||
|
+ "</resources>";
|
||||||
|
|
||||||
|
private static final String UPDATED_AUTHORIZATIONS =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||||
|
+ "<resources>"
|
||||||
|
+ "<resource identifier=\"/flow\">"
|
||||||
|
+ "<authorization identity=\"user-1\" action=\"RW\"/>"
|
||||||
|
+ "</resource>"
|
||||||
|
+ "</resources>";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
This file lists all authorizations for this NiFi instance. Refer to the properties file and authorizers.xml for configuration details.
|
||||||
|
|
||||||
|
Available resources:
|
||||||
|
/flow - READ - allows user/entity to load the UI and see the flow structure
|
||||||
|
- WRITE - NA
|
||||||
|
/resource - READ - allows user/entity to retrieve the available resources
|
||||||
|
- WRITE - NA
|
||||||
|
/system - READ - allows user/entity to retrieve system level diagnostics (CPU load, disk utilization, etc)
|
||||||
|
- WRITE - NA
|
||||||
|
/controller - READ - allows user/entity to retrieve configuration details for the controller (controller bulletins, thread pool, reporting tasks, etc)
|
||||||
|
- WRITE - allows user/entity to modify configuration details for the controller
|
||||||
|
/provenance - READ - allows user/entity to perform provenance requests. results will be filtered based on access to provenance data per component
|
||||||
|
- WRITE - NA
|
||||||
|
/token - READ - NA
|
||||||
|
- WRITE - allows user/entity to create a token for access the REST API
|
||||||
|
/site-to-site - READ - allows user/entity to retrieve configuration details for performing site to site data transfers with this NiFi
|
||||||
|
- WRITE - NA
|
||||||
|
/proxy - READ - NA
|
||||||
|
- WRITE - allows user/entity to create a proxy request on behalf of another user
|
||||||
|
/process-groups/{id} - READ - allows user/entity to retrieve configuration details for the process group and all descendant components without explicit access policies
|
||||||
|
- WRITE - allows user/entity to create/update/delete configuration details for the process group and all descendant components without explicit access policies
|
||||||
|
/processors/{id} - READ - allows user/entity to retrieve configuration details for the processor overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the processor overriding any inherited authorizations from an ancestor process group
|
||||||
|
/input-ports/{id} - READ - allows user/entity to retrieve configuration details for the input port overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the input port overriding any inherited authorizations from an ancestor process group
|
||||||
|
/output-ports/{id} - READ - allows user/entity to retrieve configuration details for the output port overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the output port overriding any inherited authorizations from an ancestor process group
|
||||||
|
/labels/{id} - READ - allows user/entity to retrieve configuration details for the label overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the label overriding any inherited authorizations from an ancestor process group
|
||||||
|
/connections/{id} - READ - allows user/entity to retrieve configuration details for the connection overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the label overriding any inherited authorizations from an ancestor process group
|
||||||
|
/remote-process-groups/{id} - READ - allows user/entity to retrieve configuration details for the remote process group overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the remote process group overriding any inherited authorizations from an ancestor process group
|
||||||
|
/templates/{id} - READ - allows user/entity to retrieve configuration details for the template overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to create/update/delete the template overriding any inherited authorizations from an ancestor process group
|
||||||
|
/controller-services/{id} - READ - allows user/entity to retrieve configuration details for the controller service overriding any inherited authorizations from an ancestor process group
|
||||||
|
- WRITE - allows user/entity to update/delete the controller service overriding any inherited authorizations from an ancestor process group
|
||||||
|
/reporting-tasks/{id} - READ - allows user/entity to retrieve configuration details for the reporting tasks overriding any inherited authorizations from the controller
|
||||||
|
- WRITE - allows user/entity to create/update/delete the reporting tasks overriding any inherited authorizations from the controller
|
||||||
|
/{type}/{id}/provenance - READ - allows user/entity to view provenance data from the underlying component
|
||||||
|
- WRITE - NA
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<!--
|
||||||
|
<resource identifier="/flow">
|
||||||
|
<authorization identity="user-identity-1" action="R"></authorization>
|
||||||
|
<authorization identity="user-identity-2" action="W"></authorization>
|
||||||
|
<authorization identity="user-identity-3" action="RW"></authorization>
|
||||||
|
</resource>
|
||||||
|
-->
|
||||||
|
</resources>
|
|
@ -37,6 +37,7 @@
|
||||||
<module>nifi-cluster-authorization-provider</module>
|
<module>nifi-cluster-authorization-provider</module>
|
||||||
<module>nifi-user-actions</module>
|
<module>nifi-user-actions</module>
|
||||||
<module>nifi-framework-authorization</module>
|
<module>nifi-framework-authorization</module>
|
||||||
|
<module>nifi-file-authorizer</module>
|
||||||
<module>nifi-administration</module>
|
<module>nifi-administration</module>
|
||||||
<module>nifi-web</module>
|
<module>nifi-web</module>
|
||||||
<module>nifi-resources</module>
|
<module>nifi-resources</module>
|
||||||
|
|
|
@ -88,6 +88,11 @@
|
||||||
<artifactId>nifi-framework-core</artifactId>
|
<artifactId>nifi-framework-core</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-framework-authorization</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.nifi</groupId>
|
<groupId>org.apache.nifi</groupId>
|
||||||
<artifactId>nifi-user-actions</artifactId>
|
<artifactId>nifi-user-actions</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue