NIFIREG-9: Initial Auth Implementation

Authentication and authorization enforcement for web API, largely based on NiFi.
This commit adds interfaces, framework, and file-based authorizer providers (file access policy provider, file user group provider).
Authentication of identities is currently based on certificates in two-way SSL (HTTPS). Alternative identity strategies (user&pass, JWT) will
be added later building upon the foundation in this commit.

As part of this feature, some changes were made to the RegistryService interface and the providers it utilizes.

This closes #14.

Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
Kevin Doran 2017-10-02 22:40:26 -04:00 коммит произвёл Bryan Bende
Родитель a87d42ee98
Коммит 785cb81ff0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A0DDA9ED50711C39
145 изменённых файлов: 16468 добавлений и 637 удалений

Просмотреть файл

@ -95,6 +95,11 @@
<artifactId>nifi-registry-runtime</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-security-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-web-ui</artifactId>
@ -135,8 +140,8 @@
<nifi.registry.security.truststore />
<nifi.registry.security.truststoreType />
<nifi.registry.security.truststorePasswd />
<nifi.registry.security.needClientAuth />
<nifi.registry.security.authorized.users>./conf/authorized-users.xml</nifi.registry.security.authorized.users>
<nifi.registry.security.authorizers.configuration.file>./conf/authorizers.xml</nifi.registry.security.authorizers.configuration.file><nifi.registry.security.needClientAuth />
<nifi.registry.security.authorizer>managed-authorizer</nifi.registry.security.authorizer>
<!-- nifi-registry.properties: provider properties -->
<nifi.registry.providers.configuration.file>./conf/providers.xml</nifi.registry.providers.configuration.file>

Просмотреть файл

@ -34,5 +34,9 @@
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>

Просмотреть файл

@ -0,0 +1,72 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Access policy details, including the users and user groups to which the policy applies.
*/
@ApiModel("accessPolicy")
public class AccessPolicy extends AccessPolicySummary {
private Set<Tenant> users;
private Set<Tenant> userGroups;
@ApiModelProperty(value = "The set of user IDs associated with this access policy.")
public Set<Tenant> getUsers() {
return users;
}
public void setUsers(Set<Tenant> users) {
this.users = users;
}
public void addUsers(Collection<? extends Tenant> users) {
if (users != null) {
if (this.users == null) {
this.users = new HashSet<>();
}
this.users.addAll(users);
}
}
@ApiModelProperty(value = "The set of user group IDs associated with this access policy.")
public Set<Tenant> getUserGroups() {
return userGroups;
}
public void setUserGroups(Set<Tenant> userGroups) {
this.userGroups = userGroups;
}
public void addUserGroups(Collection<? extends Tenant> userGroups) {
if (userGroups != null) {
if (this.userGroups == null) {
this.userGroups = new HashSet<>();
}
this.userGroups.addAll(userGroups);
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Access policy summary of which actions ("read', "write") are allowable for a specified web resource.
*/
@ApiModel("accessPolicySummary")
public class AccessPolicySummary {
private String identifier;
private String resource;
private String action;
private Boolean configurable;
@ApiModelProperty("The id of the policy. Set by server at creation time.")
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
@ApiModelProperty("The resource for this access policy.")
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
}
@ApiModelProperty(
value = "The action associated with this access policy.",
allowableValues = "READ, WRITE"
)
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
@ApiModelProperty("Indicates if this access policy is configurable, based on which authorizer has been configured to manage it.")
public Boolean getConfigurable() {
return configurable;
}
public void setConfigurable(Boolean configurable) {
this.configurable = configurable;
}
}

Просмотреть файл

@ -0,0 +1,69 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("accessStatus")
public class AccessStatus {
public static enum Status {
UNKNOWN,
ACTIVE
}
private String identity;
private Status status;
private String message;
@ApiModelProperty(
value = "The user identity.",
readOnly = true
)
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
@ApiModelProperty(
value = "The user access status.",
readOnly = true
)
public String getStatus() {
return status.toString();
}
public void setStatus(String status) {
this.status = Status.valueOf(status);
}
@ApiModelProperty(
value = "Additional details about the user access status.",
readOnly = true
)
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

Просмотреть файл

@ -0,0 +1,56 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("resource")
public class Resource {
private String identifier;
private String name;
/**
* The name of the resource.
*
* @return The name of the resource
*/
@ApiModelProperty("The name of the resource.")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* The identifier of the resource.
*
* @return The identifier of the resource
*/
@ApiModelProperty("The identifier of the resource.")
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
}

Просмотреть файл

@ -0,0 +1,61 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* A tenant of this NiFi Registry
*/
@ApiModel("tenant")
public class Tenant {
private String identifier;
private String identity;
public Tenant() {}
public Tenant(String identifier, String identity) {
this.identifier = identifier;
this.identity = identity;
}
/**
* @return tenant's unique identifier
*/
@ApiModelProperty(value = "The computer-generated identifier of the tenant.")
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
/**
* @return tenant's identity
*/
@ApiModelProperty(value = "The identity provider's identity of the tenant.")
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
}

Просмотреть файл

@ -0,0 +1,78 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@ApiModel("user")
public class User extends Tenant {
private Set<Tenant> userGroups;
private Set<AccessPolicySummary> accessPolicies;
public User(String identifier, String identity) {
super(identifier, identity);
}
@ApiModelProperty(
value = "The groups to which the user belongs. This field is read only.",
readOnly = true
)
public Set<Tenant> getUserGroups() {
return userGroups;
}
public void setUserGroups(Set<Tenant> userGroups) {
this.userGroups = userGroups;
}
public void addUserGroups(Collection<? extends Tenant> userGroups) {
if (userGroups != null) {
if (this.userGroups == null) {
this.userGroups = new HashSet<>();
}
this.userGroups.addAll(userGroups);
}
}
@ApiModelProperty(
value = "The access policies granted to this user. This field is read only",
readOnly = true
)
public Set<AccessPolicySummary> getAccessPolicies() {
return accessPolicies;
}
public void setAccessPolicies(Set<AccessPolicySummary> accessPolicies) {
this.accessPolicies = accessPolicies;
}
public void addAccessPolicies(Collection<? extends AccessPolicySummary> accessPolicies) {
if (accessPolicies != null) {
if (this.accessPolicies == null) {
this.accessPolicies = new HashSet<>();
}
this.accessPolicies.addAll(accessPolicies);
}
}
}

Просмотреть файл

@ -0,0 +1,83 @@
/*
* 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.model.authorization;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* A user group, used to apply a single set of authorization policies to a group of users.
*/
@ApiModel("userGroup")
public class UserGroup extends Tenant {
private Set<Tenant> users;
private Set<AccessPolicySummary> accessPolicies;
public UserGroup(String identifier, String identity) {
super(identifier, identity);
}
/**
* @return The users that belong to this user group.
*/
@ApiModelProperty(value = "The users that belong to this user group.")
public Set<Tenant> getUsers() {
return users;
}
public void setUsers(Set<Tenant> users) {
this.users = users;
}
public void addUsers(Collection<? extends Tenant> users) {
if (users != null) {
if (this.users == null) {
this.users = new HashSet<>();
}
this.users.addAll(users);
}
}
/**
* @return The access policies set for this user group
*/
@ApiModelProperty(
value = "The access policies granted to this user group.",
readOnly = true
)
public Set<AccessPolicySummary> getAccessPolicies() {
return accessPolicies;
}
public void setAccessPolicies(Set<AccessPolicySummary> accessPolicies) {
this.accessPolicies = accessPolicies;
}
public void addAccessPolicies(Collection<? extends AccessPolicySummary> accessPolicies) {
if (accessPolicies != null) {
if (this.accessPolicies == null) {
this.accessPolicies = new HashSet<>();
}
this.accessPolicies.addAll(accessPolicies);
}
}
}

Просмотреть файл

@ -39,7 +39,7 @@
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<id>current</id>
<id>providers</id>
<goals>
<goal>xjc</goal>
</goals>
@ -47,13 +47,45 @@
<packageName>org.apache.nifi.registry.provider.generated</packageName>
</configuration>
</execution>
<execution>
<id>authorizers</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<packageName>org.apache.nifi.registry.authorization.generated</packageName>
<clearOutputDir>false</clearOutputDir>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<excludes>**/provider/generated/*.java,</excludes>
<excludes>**/authorization/generated/*.java,**/provider/generated/*.java,</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<goals>
<goal>addTestSources</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
@ -80,12 +112,28 @@
<artifactId>nifi-registry-properties</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-security-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope> <!-- will be in lib sir -->
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-security-api-impl</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
@ -139,5 +187,23 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.0-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Просмотреть файл

@ -0,0 +1,75 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.resource.Authorizable;
public interface AuthorizableLookup {
/**
* Get the authorizable for retrieving resources.
*
* @return authorizable
*/
Authorizable getResourcesAuthorizable();
/**
* Get the authorizable for all tenants.
*
* Get the {@link Authorizable} that represents the resource of users and user groups.
* @return authorizable
*/
Authorizable getTenantsAuthorizable();
/**
* Get the authorizable for all access policies.
*
* @return authorizable
*/
Authorizable getPoliciesAuthorizable();
/**
* Get the authorizable for all Buckets.
*
* @return authorizable
*/
Authorizable getBucketsAuthorizable();
/**
* Get the authorizable for the Bucket with the bucket id.
*
* @param bucketIdentifier bucket id
* @return authorizable
*/
Authorizable getBucketAuthorizable(String bucketIdentifier);
/**
* Get the authorizable for the policy of the specified resource.
*
* @param resource resource
* @return authorizable
*/
Authorizable getAccessPolicyByResource(String resource);
/**
* Get the authorizable of the specified resource.
*
* @param resource resource
* @return authorizable
*/
Authorizable getAuthorizableByResource(final String resource);
}

Просмотреть файл

@ -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.
*/
package org.apache.nifi.registry.authorization;
public interface AuthorizeAccess {
void authorize(AuthorizableLookup lookup);
}

Просмотреть файл

@ -0,0 +1,75 @@
/*
* 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.authorization;
public final class AuthorizerCapabilityDetection {
public static boolean isManagedAuthorizer(final Authorizer authorizer) {
return authorizer instanceof ManagedAuthorizer;
}
public static boolean isConfigurableAccessPolicyProvider(final Authorizer authorizer) {
if (!isManagedAuthorizer(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
return managedAuthorizer.getAccessPolicyProvider() instanceof ConfigurableAccessPolicyProvider;
}
public static boolean isConfigurableUserGroupProvider(final Authorizer authorizer) {
if (!isManagedAuthorizer(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final AccessPolicyProvider accessPolicyProvider = managedAuthorizer.getAccessPolicyProvider();
return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider;
}
public static boolean isUserConfigurable(final Authorizer authorizer, final User user) {
if (!isConfigurableUserGroupProvider(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
return configurableUserGroupProvider.isConfigurable(user);
}
public static boolean isGroupConfigurable(final Authorizer authorizer, final Group group) {
if (!isConfigurableUserGroupProvider(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
return configurableUserGroupProvider.isConfigurable(group);
}
public static boolean isAccessPolicyConfigurable(final Authorizer authorizer, final AccessPolicy accessPolicy) {
if (!isConfigurableAccessPolicyProvider(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
return configurableAccessPolicyProvider.isConfigurable(accessPolicy);
}
private AuthorizerCapabilityDetection() {}
}

Просмотреть файл

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization;
public interface AuthorizerFactory {
/**
* Initialize the factory.
*
* @throws AuthorizerFactoryException if an error occurs during initialization
*/
void initialize() throws AuthorizerFactoryException;
/**
* @return the configured Authorizer
*/
Authorizer getAuthorizer() throws AuthorizerFactoryException;
}

Просмотреть файл

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization;
public class AuthorizerFactoryException extends RuntimeException {
public AuthorizerFactoryException(String message) {
super(message);
}
public AuthorizerFactoryException(String message, Throwable cause) {
super(message, cause);
}
public AuthorizerFactoryException(Throwable cause) {
super(cause);
}
}

Просмотреть файл

@ -0,0 +1,197 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.util.PropertyValue;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import java.util.HashSet;
import java.util.Set;
public class CompositeConfigurableUserGroupProvider extends CompositeUserGroupProvider implements ConfigurableUserGroupProvider {
static final String PROP_CONFIGURABLE_USER_GROUP_PROVIDER = "Configurable User Group Provider";
private UserGroupProviderLookup userGroupProviderLookup;
private ConfigurableUserGroupProvider configurableUserGroupProvider;
public CompositeConfigurableUserGroupProvider() {
super(true);
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
// initialize the CompositeUserGroupProvider
super.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final PropertyValue configurableUserGroupProviderKey = configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER);
if (!configurableUserGroupProviderKey.isSet()) {
throw new AuthorizerCreationException("The Configurable User Group Provider must be set.");
}
final UserGroupProvider userGroupProvider = userGroupProviderLookup.getUserGroupProvider(configurableUserGroupProviderKey.getValue());
if (userGroupProvider == null) {
throw new AuthorizerCreationException(String.format("Unable to locate the Configurable User Group Provider: %s", configurableUserGroupProviderKey));
}
if (!(userGroupProvider instanceof ConfigurableUserGroupProvider)) {
throw new AuthorizerCreationException(String.format("The Configurable User Group Provider is not configurable: %s", configurableUserGroupProviderKey));
}
configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
// configure the CompositeUserGroupProvider
super.onConfigured(configurationContext);
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
return configurableUserGroupProvider.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
configurableUserGroupProvider.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
configurableUserGroupProvider.checkInheritability(proposedFingerprint);
}
@Override
public User addUser(User user) throws AuthorizationAccessException {
return configurableUserGroupProvider.addUser(user);
}
@Override
public boolean isConfigurable(User user) {
return configurableUserGroupProvider.isConfigurable(user);
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
return configurableUserGroupProvider.updateUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
return configurableUserGroupProvider.deleteUser(user);
}
@Override
public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
return configurableUserGroupProvider.deleteUser(userIdentifier);
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
return configurableUserGroupProvider.addGroup(group);
}
@Override
public boolean isConfigurable(Group group) {
return configurableUserGroupProvider.isConfigurable(group);
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
return configurableUserGroupProvider.updateGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
return configurableUserGroupProvider.deleteGroup(group);
}
@Override
public Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
return configurableUserGroupProvider.deleteGroup(groupIdentifier);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
final Set<User> users = new HashSet<>(configurableUserGroupProvider.getUsers());
users.addAll(super.getUsers());
return users;
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
User user = configurableUserGroupProvider.getUser(identifier);
if (user == null) {
user = super.getUser(identifier);
}
return user;
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
User user = configurableUserGroupProvider.getUserByIdentity(identity);
if (user == null) {
user = super.getUserByIdentity(identity);
}
return user;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
final Set<Group> groups = new HashSet<>(configurableUserGroupProvider.getGroups());
groups.addAll(super.getGroups());
return groups;
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
Group group = configurableUserGroupProvider.getGroup(identifier);
if (group == null) {
group = super.getGroup(identifier);
}
return group;
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
UserAndGroups userAndGroups = configurableUserGroupProvider.getUserAndGroups(identity);
if (userAndGroups.getUser() == null) {
userAndGroups = super.getUserAndGroups(identity);
}
return userAndGroups;
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
super.preDestruction();
}
}

Просмотреть файл

@ -0,0 +1,177 @@
/*
* 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.authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CompositeUserGroupProvider implements UserGroupProvider {
static final String PROP_USER_GROUP_PROVIDER_PREFIX = "User Group Provider ";
static final Pattern USER_GROUP_PROVIDER_PATTERN = Pattern.compile(PROP_USER_GROUP_PROVIDER_PREFIX + "\\S+");
private final boolean allowEmptyProviderList;
private UserGroupProviderLookup userGroupProviderLookup;
private List<UserGroupProvider> userGroupProviders = new ArrayList<>(); // order matters
public CompositeUserGroupProvider() {
this(false);
}
public CompositeUserGroupProvider(boolean allowEmptyProviderList) {
this.allowEmptyProviderList = allowEmptyProviderList;
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
Matcher matcher = USER_GROUP_PROVIDER_PATTERN.matcher(entry.getKey());
if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
final String userGroupProviderKey = entry.getValue();
final UserGroupProvider userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderKey);
if (userGroupProvider == null) {
throw new AuthorizerCreationException(String.format("Unable to locate the configured User Group Provider: %s", userGroupProviderKey));
}
userGroupProviders.add(userGroupProvider);
}
}
if (!allowEmptyProviderList && userGroupProviders.isEmpty()) {
throw new AuthorizerCreationException("At least one User Group Provider must be configured.");
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
final Set<User> users = new HashSet<>();
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
users.addAll(userGroupProvider.getUsers());
}
return users;
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
User user = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
user = userGroupProvider.getUser(identifier);
if (user != null) {
break;
}
}
return user;
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
User user = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
user = userGroupProvider.getUserByIdentity(identity);
if (user != null) {
break;
}
}
return user;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
final Set<Group> groups = new HashSet<>();
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
groups.addAll(userGroupProvider.getGroups());
}
return groups;
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
Group group = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
group = userGroupProvider.getGroup(identifier);
if (group != null) {
break;
}
}
return group;
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
UserAndGroups userAndGroups = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
userAndGroups = userGroupProvider.getUserAndGroups(identity);
if (userAndGroups.getUser() != null) {
break;
}
}
if (userAndGroups == null) {
// per API, returning non null with null user/groups
return new UserAndGroups() {
@Override
public User getUser() {
return null;
}
@Override
public Set<Group> getGroups() {
return null;
}
};
} else {
// a delegated provider contained a matching user
return userAndGroups;
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
}

Просмотреть файл

@ -0,0 +1,218 @@
/*
* 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.authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.authorization.resource.Authorizable;
import org.apache.nifi.registry.authorization.resource.ResourceFactory;
import org.apache.nifi.registry.authorization.resource.ResourceType;
import org.apache.nifi.registry.authorization.resource.AccessPolicyAuthorizable;
import org.apache.nifi.registry.exception.ResourceNotFoundException;
// TODO, make this spring-wired bean
public class StandardAuthorizableLookup implements AuthorizableLookup {
private static final Authorizable TENANTS_AUTHORIZABLE = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return null;
}
@Override
public Resource getResource() {
return ResourceFactory.getTenantResource();
}
};
private static final Authorizable POLICIES_AUTHORIZABLE = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return null;
}
@Override
public Resource getResource() {
return ResourceFactory.getPoliciesResource();
}
};
private static final Authorizable RESOURCES_AUTHORIZABLE = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return null;
}
@Override
public Resource getResource() {
return ResourceFactory.getResourceResource();
}
};
private static final Authorizable BUCKETS_AUTHORIZABLE = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return null;
}
@Override
public Resource getResource() {
return ResourceFactory.getBucketsResource();
}
};
@Override
public Authorizable getResourcesAuthorizable() {
return RESOURCES_AUTHORIZABLE;
}
@Override
public Authorizable getTenantsAuthorizable() {
return TENANTS_AUTHORIZABLE;
}
@Override
public Authorizable getPoliciesAuthorizable() {
return POLICIES_AUTHORIZABLE;
}
@Override
public Authorizable getBucketsAuthorizable() {
return BUCKETS_AUTHORIZABLE;
}
@Override
public Authorizable getBucketAuthorizable(String bucketIdentifier) {
return new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return getBucketsAuthorizable();
}
@Override
public Resource getResource() {
return ResourceFactory.getBucketResource(bucketIdentifier, null);
}
};
}
@Override
public Authorizable getAccessPolicyByResource(final String resource) {
try {
return new AccessPolicyAuthorizable(getAuthorizableByResource(resource));
} catch (final ResourceNotFoundException e) {
// the underlying component has been removed or resource is invalid... require /policies permissions
return POLICIES_AUTHORIZABLE;
}
}
@Override
public Authorizable getAuthorizableByResource(String resource) {
// parse the resource type
ResourceType resourceType = null;
for (ResourceType type : ResourceType.values()) {
if (resource.equals(type.getValue()) || resource.startsWith(type.getValue() + "/")) {
resourceType = type;
}
}
if (resourceType == null) {
throw new ResourceNotFoundException("Unrecognized resource: " + resource);
}
// if this is a policy resource, there should be another resource type
if (ResourceType.Policy.equals(resourceType)) {
final ResourceType primaryResourceType = resourceType;
// get the resource type
resource = StringUtils.substringAfter(resource, resourceType.getValue());
for (ResourceType type : ResourceType.values()) {
if (resource.equals(type.getValue()) || resource.startsWith(type.getValue() + "/")) {
resourceType = type;
}
}
if (resourceType == null) {
throw new ResourceNotFoundException("Unrecognized resource: " + resource);
}
return new AccessPolicyAuthorizable(getAccessPolicy(resourceType, resource));
} else {
return getAccessPolicy(resourceType, resource);
}
}
private Authorizable getAccessPolicy(final ResourceType resourceType, final String resource) {
final String slashComponentId = StringUtils.substringAfter(resource, resourceType.getValue());
if (slashComponentId.startsWith("/")) {
return getAccessPolicyByResource(resourceType, slashComponentId.substring(1));
} else {
return getAccessPolicyByResource(resourceType);
}
}
private Authorizable getAccessPolicyByResource(final ResourceType resourceType, final String childResourceId) {
Authorizable authorizable = null;
switch (resourceType) {
case Bucket:
authorizable = getBucketAuthorizable(childResourceId);
}
if (authorizable == null) {
throw new IllegalArgumentException("An unexpected type of resource in this policy " + resourceType.getValue());
}
return authorizable;
}
private Authorizable getAccessPolicyByResource(final ResourceType resourceType) {
Authorizable authorizable = null;
switch (resourceType) {
case Bucket:
authorizable = getBucketsAuthorizable();
break;
case Policy:
authorizable = getPoliciesAuthorizable();
break;
case Resource:
authorizable = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return null;
}
@Override
public Resource getResource() {
return ResourceFactory.getResourceResource();
}
};
break;
case Tenant:
authorizable = getTenantsAuthorizable();
break;
}
if (authorizable == null) {
throw new IllegalArgumentException("An unexpected type of resource in this policy " + resourceType.getValue());
}
return authorizable;
}
}

Просмотреть файл

@ -0,0 +1,796 @@
/*
* 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.authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.authorization.generated.Authorizers;
import org.apache.nifi.registry.authorization.generated.Prop;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.provider.StandardProviderFactory;
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.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This implementation of AuthorizerFactory in NiFi Registry is based on a combination of
* NiFi's AuthorizerFactory and AuthorizerFactoryBean.
*/
public class StandardAuthorizerFactory implements AuthorizerFactory, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup{
private static final Logger logger = LoggerFactory.getLogger(StandardProviderFactory.class);
private static final String AUTHORIZERS_XSD = "/authorizers.xsd";
private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.authorization.generated";
private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
/**
* Load the JAXBContext.
*/
private static JAXBContext initializeJaxbContext() {
try {
return JAXBContext.newInstance(JAXB_GENERATED_PATH, StandardAuthorizerFactory.class.getClassLoader());
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext.", e);
}
}
private final NiFiRegistryProperties properties;
private Authorizer authorizer;
private final Map<String, UserGroupProvider> userGroupProviders = new HashMap<>();
private final Map<String, AccessPolicyProvider> accessPolicyProviders = new HashMap<>();
private final Map<String, Authorizer> authorizers = new HashMap<>();
public StandardAuthorizerFactory(final NiFiRegistryProperties properties) {
this.properties = properties;
if (this.properties == null) {
throw new IllegalStateException("NiFiRegistryProperties cannot be null");
}
}
/***** UserGroupProviderLookup *****/
@Override
public UserGroupProvider getUserGroupProvider(String identifier) {
return userGroupProviders.get(identifier);
}
/***** AccessPolicyProviderLookup *****/
@Override
public AccessPolicyProvider getAccessPolicyProvider(String identifier) {
return accessPolicyProviders.get(identifier);
}
/***** AuthorizerLookup *****/
@Override
public Authorizer getAuthorizer(String identifier) {
return authorizers.get(identifier);
}
/***** AuthorizerFactory *****/
@Override
public void initialize() throws AuthorizerFactoryException {
// if (authorizerHolder.get() == null) {
// final File authorizersConfigFile = properties.getAuthorizersConfigurationFile();
// if (authorizersConfigFile.exists()) {
// try {
// // find the schema
// final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// final Schema schema = schemaFactory.newSchema(StandardProviderFactory.class.getResource(AUTHORIZERS_XSD));
//
// // attempt to unmarshal
// final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
// unmarshaller.setSchema(schema);
//
// // set the holder for later use
// final JAXBElement<Authorizers> element = unmarshaller.unmarshal(new StreamSource(authorizersConfigFile), Authorizers.class);
// authorizerHolder.set(element.getValue());
// } catch (SAXException | JAXBException e) {
// throw new AuthorizerFactoryException("Unable to load the authorizer configuration file at: " + authorizersConfigFile.getAbsolutePath(), e);
// }
// } else {
// throw new AuthorizerFactoryException("Unable to find the providers configuration file at " + authorizersConfigFile.getAbsolutePath());
// }
// }
}
@Override
public Authorizer getAuthorizer() throws AuthorizerFactoryException {
if (authorizer == null) {
if (properties.getSslPort() == null) {
// use a default authorizer... only allowable when running not securely
authorizer = createDefaultAuthorizer();
} else {
// look up the authorizer to use
final String authorizerIdentifier = properties.getProperty(NiFiRegistryProperties.SECURITY_AUTHORIZER);
// ensure the authorizer class name was specified
if (StringUtils.isBlank(authorizerIdentifier)) {
throw new AuthorizerFactoryException("When running securely, the authorizer identifier must be specified in the nifi properties file.");
} else {
try {
final Authorizers authorizerConfiguration = loadAuthorizersConfiguration();
// create each user group provider
for (final org.apache.nifi.registry.authorization.generated.UserGroupProvider userGroupProvider : authorizerConfiguration.getUserGroupProvider()) {
userGroupProviders.put(userGroupProvider.getIdentifier(), createUserGroupProvider(userGroupProvider.getIdentifier(), userGroupProvider.getClazz()));
}
// configure each user group provider
for (final org.apache.nifi.registry.authorization.generated.UserGroupProvider provider : authorizerConfiguration.getUserGroupProvider()) {
final UserGroupProvider instance = userGroupProviders.get(provider.getIdentifier());
instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
}
// create each access policy provider
for (final org.apache.nifi.registry.authorization.generated.AccessPolicyProvider accessPolicyProvider : authorizerConfiguration.getAccessPolicyProvider()) {
accessPolicyProviders.put(accessPolicyProvider.getIdentifier(), createAccessPolicyProvider(accessPolicyProvider.getIdentifier(), accessPolicyProvider.getClazz()));
}
// configure each access policy provider
for (final org.apache.nifi.registry.authorization.generated.AccessPolicyProvider provider : authorizerConfiguration.getAccessPolicyProvider()) {
final AccessPolicyProvider instance = accessPolicyProviders.get(provider.getIdentifier());
instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
}
// create each authorizer
for (final org.apache.nifi.registry.authorization.generated.Authorizer authorizer : authorizerConfiguration.getAuthorizer()) {
authorizers.put(authorizer.getIdentifier(), createAuthorizer(authorizer.getIdentifier(), authorizer.getClazz(), authorizer.getClasspath()));
}
// configure each authorizer
for (final org.apache.nifi.registry.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) {
final Authorizer instance = authorizers.get(provider.getIdentifier());
instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
}
// get the authorizer instance
authorizer = getAuthorizer(authorizerIdentifier);
// ensure it was found
if (authorizer == null) {
throw new AuthorizerFactoryException(String.format("The specified authorizer '%s' could not be found.", authorizerIdentifier));
}
} catch (Exception e) {
throw new AuthorizerFactoryException("Failed to construct Authorizer.", e);
}
}
}
}
return authorizer;
}
private Authorizers loadAuthorizersConfiguration() throws Exception {
final File authorizersConfigurationFile = properties.getAuthorizersConfigurationFile();
// load the authorizers from the specified file
if (authorizersConfigurationFile.exists()) {
try {
// find the schema
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = schemaFactory.newSchema(Authorizers.class.getResource(AUTHORIZERS_XSD));
// attempt to unmarshal
final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(schema);
final JAXBElement<Authorizers> element = unmarshaller.unmarshal(new StreamSource(authorizersConfigurationFile), Authorizers.class);
return element.getValue();
} catch (SAXException | JAXBException e) {
throw new Exception("Unable to load the authorizer configuration file at: " + authorizersConfigurationFile.getAbsolutePath(), e);
}
} else {
throw new Exception("Unable to find the authorizer configuration file at " + authorizersConfigurationFile.getAbsolutePath());
}
}
private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Prop> properties) {
final Map<String, String> authorizerProperties = new HashMap<>();
for (final Prop property : properties) {
authorizerProperties.put(property.getName(), property.getValue());
}
return new StandardAuthorizerConfigurationContext(identifier, authorizerProperties);
}
private UserGroupProvider createUserGroupProvider(final String identifier, final String userGroupProviderClassName) throws Exception {
final UserGroupProvider instance;
// attempt to load the class
Class<?> rawUserGroupProviderClass = Class.forName(userGroupProviderClassName);
Class<? extends UserGroupProvider> userGroupProviderClass = rawUserGroupProviderClass.asSubclass(UserGroupProvider.class);
// otherwise create a new instance
Constructor constructor = userGroupProviderClass.getConstructor();
instance = (UserGroupProvider) constructor.newInstance();
// method injection
performMethodInjection(instance, userGroupProviderClass);
// field injection
performFieldInjection(instance, userGroupProviderClass);
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
return instance;
}
private AccessPolicyProvider createAccessPolicyProvider(final String identifier, final String accessPolicyProviderClassName) throws Exception {
final AccessPolicyProvider instance;
// attempt to load the class
Class<?> rawAccessPolicyProviderClass = Class.forName(accessPolicyProviderClassName);
Class<? extends AccessPolicyProvider> accessPolicyClass = rawAccessPolicyProviderClass.asSubclass(AccessPolicyProvider.class);
// otherwise create a new instance
Constructor constructor = accessPolicyClass.getConstructor();
instance = (AccessPolicyProvider) constructor.newInstance();
// method injection
performMethodInjection(instance, accessPolicyClass);
// field injection
performFieldInjection(instance, accessPolicyClass);
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
return instance;
}
private Authorizer createAuthorizer(final String identifier, final String authorizerClassName, final String classpathResources) throws Exception {
final Authorizer instance;
// attempt to load the class
Class<?> rawAuthorizerClass = Class.forName(authorizerClassName);
Class<? extends Authorizer> authorizerClass = rawAuthorizerClass.asSubclass(Authorizer.class);
// otherwise create a new instance
Constructor constructor = authorizerClass.getConstructor();
instance = (Authorizer) constructor.newInstance();
// method injection
performMethodInjection(instance, authorizerClass);
// field injection
performFieldInjection(instance, authorizerClass);
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
// TODO, implement and test loading additional resources from the classpath for custom authorizer impls.
// if (StringUtils.isNotEmpty(classpathResources)) {
// URL[] urls = ClassLoaderUtils.getURLsForClasspath(classpathResources, null, true);
// authorizerClassLoader = new URLClassLoader(urls, authorizerClassLoader);
// }
return installIntegrityChecks(instance);
}
private void performMethodInjection(final Object instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
for (final Method method : authorizerClass.getMethods()) {
if (method.isAnnotationPresent(AuthorizerContext.class)) {
// make the method accessible
final boolean isAccessible = method.isAccessible();
method.setAccessible(true);
try {
final Class<?>[] argumentTypes = method.getParameterTypes();
// look for setters (single argument)
if (argumentTypes.length == 1) {
final Class<?> argumentType = argumentTypes[0];
// look for well known types
if (NiFiRegistryProperties.class.isAssignableFrom(argumentType)) {
// nifi properties injection
method.invoke(instance, properties);
}
}
} finally {
method.setAccessible(isAccessible);
}
}
}
final Class parentClass = authorizerClass.getSuperclass();
if (parentClass != null && Authorizer.class.isAssignableFrom(parentClass)) {
performMethodInjection(instance, parentClass);
}
}
private void performFieldInjection(final Object instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException {
for (final Field field : authorizerClass.getDeclaredFields()) {
if (field.isAnnotationPresent(AuthorizerContext.class)) {
// make the method accessible
final boolean isAccessible = field.isAccessible();
field.setAccessible(true);
try {
// get the type
final Class<?> fieldType = field.getType();
// only consider this field if it isn't set yet
if (field.get(instance) == null) {
// look for well known types
if (NiFiRegistryProperties.class.isAssignableFrom(fieldType)) {
// nifi properties injection
field.set(instance, properties);
}
}
} finally {
field.setAccessible(isAccessible);
}
}
}
final Class parentClass = authorizerClass.getSuperclass();
if (parentClass != null && Authorizer.class.isAssignableFrom(parentClass)) {
performFieldInjection(instance, parentClass);
}
}
/**
* @return a default Authorizer to use when running unsecurely with no authorizer configured
*/
private Authorizer createDefaultAuthorizer() {
return new Authorizer() {
@Override
public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
return AuthorizationResult.approved();
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) {
if (baseAuthorizer instanceof ManagedAuthorizer) {
final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
return new ManagedAuthorizer() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
return baseManagedAuthorizer.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
baseManagedAuthorizer.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
baseManagedAuthorizer.checkInheritability(proposedFingerprint);
}
@Override
public AccessPolicyProvider getAccessPolicyProvider() {
final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
return new ConfigurableAccessPolicyProvider() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
}
@Override
public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) {
throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
}
return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
}
@Override
public boolean isConfigurable(AccessPolicy accessPolicy) {
return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(baseConfigurableAccessPolicyProvider.getAccessPolicy(accessPolicyIdentifier))) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getAccessPolicies();
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
}
@Override
public UserGroupProvider getUserGroupProvider() {
final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider();
if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
return new ConfigurableUserGroupProvider() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
}
@Override
public User addUser(User user) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
return baseConfigurableUserGroupProvider.addUser(user);
}
@Override
public boolean isConfigurable(User user) {
return baseConfigurableUserGroupProvider.isConfigurable(user);
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.updateUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteUser(user);
}
@Override
public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getUser(userIdentifier))) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteUser(userIdentifier);
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
return baseConfigurableUserGroupProvider.addGroup(group);
}
@Override
public boolean isConfigurable(Group group) {
return baseConfigurableUserGroupProvider.isConfigurable(group);
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.updateGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteGroup(group);
}
@Override
public Group deleteGroup(String groupId) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getGroup(groupId))) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteGroup(groupId);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUsers();
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUser(identifier);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getGroups();
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getGroup(identifier);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
baseConfigurableUserGroupProvider.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseConfigurableUserGroupProvider.onConfigured(configurationContext);
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseConfigurableUserGroupProvider.preDestruction();
}
};
} else {
return baseUserGroupProvider;
}
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
baseConfigurableAccessPolicyProvider.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseConfigurableAccessPolicyProvider.preDestruction();
}
};
} else {
return baseAccessPolicyProvider;
}
}
@Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final AuthorizationResult result = baseAuthorizer.authorize(request);
// audit the authorization request
audit(baseAuthorizer, request, result);
return result;
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
baseManagedAuthorizer.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseManagedAuthorizer.onConfigured(configurationContext);
final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider();
// ensure that only one policy per resource-action exists
for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
if (policyExists(accessPolicyProvider, accessPolicy)) {
throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
}
}
// ensure that only one group exists per identity
for (User user : userGroupProvider.getUsers()) {
if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
}
}
// ensure that only one group exists per identity
for (Group group : userGroupProvider.getGroups()) {
if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) {
throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
}
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseManagedAuthorizer.preDestruction();
}
};
} else {
return new Authorizer() {
@Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final AuthorizationResult result = baseAuthorizer.authorize(request);
// audit the authorization request
audit(baseAuthorizer, request, result);
return result;
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
baseAuthorizer.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseAuthorizer.onConfigured(configurationContext);
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseAuthorizer.preDestruction();
}
};
}
}
private static void audit(final Authorizer authorizer, final AuthorizationRequest request, final AuthorizationResult result) {
// audit when...
// 1 - the authorizer supports auditing
// 2 - the request is an access attempt
// 3 - the result is either approved/denied, when resource is not found a subsequent request may be following with the parent resource
if (authorizer instanceof AuthorizationAuditor && request.isAccessAttempt() && !AuthorizationResult.Result.ResourceNotFound.equals(result.getResult())) {
((AuthorizationAuditor) authorizer).auditAccessAttempt(request, result);
}
}
/**
* Checks if another policy exists with the same resource and action as the given policy.
*
* @param checkAccessPolicy an access policy being checked
* @return true if another access policy exists with the same resource and action, false otherwise
*/
private static boolean policyExists(final AccessPolicyProvider accessPolicyProvider, final AccessPolicy checkAccessPolicy) {
for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier())
&& accessPolicy.getResource().equals(checkAccessPolicy.getResource())
&& accessPolicy.getAction().equals(checkAccessPolicy.getAction())) {
return true;
}
}
return false;
}
/**
* Checks if another user exists with the same identity.
*
* @param identifier identity of the user
* @param identity identity of the user
* @return true if another user exists with the same identity, false otherwise
*/
private static boolean tenantExists(final UserGroupProvider userGroupProvider, final String identifier, final String identity) {
for (User user : userGroupProvider.getUsers()) {
if (!user.getIdentifier().equals(identifier)
&& user.getIdentity().equals(identity)) {
return true;
}
}
for (Group group : userGroupProvider.getGroups()) {
if (!group.getIdentifier().equals(identifier)
&& group.getName().equals(identity)) {
return true;
}
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,122 @@
/*
* 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.authorization.resource;
import org.apache.nifi.registry.authorization.exception.AccessDeniedException;
import org.apache.nifi.registry.authorization.AuthorizationResult;
import org.apache.nifi.registry.authorization.AuthorizationResult.Result;
import org.apache.nifi.registry.authorization.Authorizer;
import org.apache.nifi.registry.authorization.RequestAction;
import org.apache.nifi.registry.authorization.Resource;
import org.apache.nifi.registry.authorization.user.NiFiUser;
import java.util.Map;
/**
* Authorizable for policies of an Authorizable.
*/
public class AccessPolicyAuthorizable implements Authorizable, EnforcePolicyPermissionsThroughBaseResource {
private static final Authorizable POLICIES_AUTHORIZABLE = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return null;
}
@Override
public Resource getResource() {
return ResourceFactory.getPoliciesResource();
}
};
final Authorizable authorizable;
public AccessPolicyAuthorizable(Authorizable authorizable) {
this.authorizable = authorizable;
}
@Override
public Authorizable getBaseAuthorizable() {
return authorizable;
}
@Override
public Authorizable getParentAuthorizable() {
final Authorizable effectiveAuthorizable = getEffectiveAuthorizable();
if (effectiveAuthorizable.getParentAuthorizable() == null) {
return POLICIES_AUTHORIZABLE;
} else {
return new AccessPolicyAuthorizable(effectiveAuthorizable.getParentAuthorizable());
}
}
@Override
public Resource getResource() {
return ResourceFactory.getPolicyResource(getEffectiveAuthorizable().getResource());
}
private Authorizable getEffectiveAuthorizable() {
// possibly consider the base resource if the authorizable uses it to enforce policy permissions
if (authorizable instanceof EnforcePolicyPermissionsThroughBaseResource) {
final Authorizable baseAuthorizable = ((EnforcePolicyPermissionsThroughBaseResource) authorizable).getBaseAuthorizable();
// if the base authorizable is for a policy, we don't want to use the base otherwise it would keep unwinding and would eventually
// evaluate to the policy of the component and not the policy of the policies for the component
if (baseAuthorizable instanceof AccessPolicyAuthorizable) {
return authorizable;
} else {
return baseAuthorizable;
}
} else {
return authorizable;
}
}
@Override
public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
if (user == null) {
throw new AccessDeniedException("Unknown user.");
}
final AuthorizationResult resourceResult = Authorizable.super.checkAuthorization(authorizer, action, user, resourceContext);
// if we're denied from the resource try inheriting
if (Result.Denied.equals(resourceResult.getResult())) {
return getParentAuthorizable().checkAuthorization(authorizer, action, user, resourceContext);
} else {
return resourceResult;
}
}
@Override
public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
if (user == null) {
throw new AccessDeniedException("Unknown user.");
}
try {
Authorizable.super.authorize(authorizer, action, user, resourceContext);
} catch (final AccessDeniedException resourceDenied) {
// if we're denied from the resource try inheriting
try {
getParentAuthorizable().authorize(authorizer, action, user, resourceContext);
} catch (final AccessDeniedException policiesDenied) {
throw resourceDenied;
}
}
}
}

Просмотреть файл

@ -0,0 +1,300 @@
/*
* 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.authorization.resource;
import org.apache.nifi.registry.authorization.AuthorizationResult.Result;
import org.apache.nifi.registry.authorization.exception.AccessDeniedException;
import org.apache.nifi.registry.authorization.user.NiFiUser;
import org.apache.nifi.registry.authorization.AuthorizationAuditor;
import org.apache.nifi.registry.authorization.AuthorizationRequest;
import org.apache.nifi.registry.authorization.AuthorizationResult;
import org.apache.nifi.registry.authorization.Authorizer;
import org.apache.nifi.registry.authorization.RequestAction;
import org.apache.nifi.registry.authorization.Resource;
import org.apache.nifi.registry.authorization.UserContextKeys;
import java.util.HashMap;
import java.util.Map;
public interface Authorizable {
/**
* The parent for this Authorizable. May be null.
*
* @return the parent authorizable or null
*/
Authorizable getParentAuthorizable();
/**
* The Resource for this Authorizable.
*
* @return the resource
*/
Resource getResource();
/**
* The originally requested resource for this Authorizable. Because policies are inherited, if a resource
* does not have a policy, this Authorizable may represent a parent resource and this method will return
* the originally requested resource.
*
* @return the originally requested resource
*/
default Resource getRequestedResource() {
return getResource();
}
/**
* Returns whether the current user is authorized for the specified action on the specified resource. This
* method does not imply the user is directly attempting to access the specified resource. If the user is
* attempting a direct access use Authorizable.authorize().
*
* @param authorizer authorizer
* @param action action
* @return is authorized
*/
default boolean isAuthorized(Authorizer authorizer, RequestAction action, NiFiUser user) {
return Result.Approved.equals(checkAuthorization(authorizer, action, user).getResult());
}
/**
* Returns the result of an authorization request for the specified user for the specified action on the specified
* resource. This method does not imply the user is directly attempting to access the specified resource. If the user is
* attempting a direct access use Authorizable.authorize().
*
* @param authorizer authorizer
* @param action action
* @param user user
* @return is authorized
*/
default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
if (user == null) {
return AuthorizationResult.denied("Unknown user.");
}
final Map<String,String> userContext;
if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final Resource resource = getResource();
final Resource requestedResource = getRequestedResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(false)
.action(action)
.resource(resource)
.requestedResource(requestedResource)
.resourceContext(resourceContext)
.userContext(userContext)
.explanationSupplier(() -> {
// build the safe explanation
final StringBuilder safeDescription = new StringBuilder("Unable to ");
if (RequestAction.READ.equals(action)) {
safeDescription.append("view ");
} else {
safeDescription.append("modify ");
}
safeDescription.append(resource.getSafeDescription()).append(".");
return safeDescription.toString();
})
.build();
// perform the authorization
final AuthorizationResult result = authorizer.authorize(request);
// verify the results
if (Result.ResourceNotFound.equals(result.getResult())) {
final Authorizable parent = getParentAuthorizable();
if (parent == null) {
return AuthorizationResult.denied("No applicable policies could be found.");
} else {
// create a custom authorizable to override the safe description but still defer to the parent authorizable
final Authorizable parentProxy = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return parent.getParentAuthorizable();
}
@Override
public Resource getRequestedResource() {
return requestedResource;
}
@Override
public Resource getResource() {
final Resource parentResource = parent.getResource();
return new Resource() {
@Override
public String getIdentifier() {
return parentResource.getIdentifier();
}
@Override
public String getName() {
return parentResource.getName();
}
@Override
public String getSafeDescription() {
return resource.getSafeDescription();
}
};
}
};
return parentProxy.checkAuthorization(authorizer, action, user, resourceContext);
}
} else {
return result;
}
}
/**
* Returns the result of an authorization request for the specified user for the specified action on the specified
* resource. This method does not imply the user is directly attempting to access the specified resource. If the user is
* attempting a direct access use Authorizable.authorize().
*
* @param authorizer authorizer
* @param action action
* @param user user
* @return is authorized
*/
default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user) {
return checkAuthorization(authorizer, action, user, null);
}
/**
* Authorizes the current user for the specified action on the specified resource. This method does imply the user is
* directly accessing the specified resource.
*
* @param authorizer authorizer
* @param action action
* @param user user
* @param resourceContext resource context
*/
default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
if (user == null) {
throw new AccessDeniedException("Unknown user.");
}
final Map<String,String> userContext;
if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
userContext = new HashMap<>();
userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
} else {
userContext = null;
}
final Resource resource = getResource();
final Resource requestedResource = getRequestedResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)
.resource(resource)
.requestedResource(requestedResource)
.resourceContext(resourceContext)
.userContext(userContext)
.explanationSupplier(() -> {
// build the safe explanation
final StringBuilder safeDescription = new StringBuilder("Unable to ");
if (RequestAction.READ.equals(action)) {
safeDescription.append("view ");
} else {
safeDescription.append("modify ");
}
safeDescription.append(resource.getSafeDescription()).append(".");
return safeDescription.toString();
})
.build();
final AuthorizationResult result = authorizer.authorize(request);
if (Result.ResourceNotFound.equals(result.getResult())) {
final Authorizable parent = getParentAuthorizable();
if (parent == null) {
final AuthorizationResult failure = AuthorizationResult.denied("No applicable policies could be found.");
// audit authorization request
if (authorizer instanceof AuthorizationAuditor) {
((AuthorizationAuditor) authorizer).auditAccessAttempt(request, failure);
}
// denied
throw new AccessDeniedException(failure.getExplanation());
} else {
// create a custom authorizable to override the safe description but still defer to the parent authorizable
final Authorizable parentProxy = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
return parent.getParentAuthorizable();
}
@Override
public Resource getRequestedResource() {
return requestedResource;
}
@Override
public Resource getResource() {
final Resource parentResource = parent.getResource();
return new Resource() {
@Override
public String getIdentifier() {
return parentResource.getIdentifier();
}
@Override
public String getName() {
return parentResource.getName();
}
@Override
public String getSafeDescription() {
return resource.getSafeDescription();
}
};
}
};
parentProxy.authorize(authorizer, action, user, resourceContext);
}
} else if (Result.Denied.equals(result.getResult())) {
throw new AccessDeniedException(result.getExplanation());
}
}
/**
* Authorizes the current user for the specified action on the specified resource. This method does imply the user is
* directly accessing the specified resource.
*
* @param authorizer authorizer
* @param action action
* @param user user
*/
default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user) throws AccessDeniedException {
authorize(authorizer, action, user, null);
}
}

Просмотреть файл

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization.resource;
/**
* Defers permissions on policies to the policies of the base authorizable. Required because we don't
* want to change the enforcement of the policies on the authorizable. For example...
*
* if a user has permissions to /policies/input-ports/1234 then they have permissions to the following
*
* - the policy for /buckets/1234 -> /policies/buckets/1234
* - the policy for /policies/buckets/1234 -> /policies/policies/buckets/1234
*/
public interface EnforcePolicyPermissionsThroughBaseResource {
/**
* Returns the base authorizable. Cannot be null.
*
* @return base authorizable
*/
Authorizable getBaseAuthorizable();
}

Просмотреть файл

@ -0,0 +1,261 @@
/*
* 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.authorization.resource;
import org.apache.nifi.registry.authorization.Resource;
import java.util.Objects;
public final class ResourceFactory {
private final static Resource BUCKETS_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return ResourceType.Bucket.getValue();
}
@Override
public String getName() {
return "Buckets";
}
@Override
public String getSafeDescription() {
return "buckets";
}
};
private final static Resource POLICY_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return ResourceType.Policy.getValue();
}
@Override
public String getName() {
return "Policies for ";
}
@Override
public String getSafeDescription() {
return "the policies for ";
}
};
private final static Resource PROXY_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return ResourceType.Proxy.getValue();
}
@Override
public String getName() {
return "Proxy User Requests";
}
@Override
public String getSafeDescription() {
return "proxy requests on behalf of users";
}
};
private final static Resource RESOURCE_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return ResourceType.Resource.getValue();
}
@Override
public String getName() {
return "NiFi Resources";
}
@Override
public String getSafeDescription() {
return "resources";
}
};
private final static Resource TENANT_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return ResourceType.Tenant.getValue();
}
@Override
public String getName() {
return "Tenant";
}
@Override
public String getSafeDescription() {
return "users/user groups";
}
};
private final static Resource POLICIES_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return "/policies";
}
@Override
public String getName() {
return "Access Policies";
}
@Override
public String getSafeDescription() {
return "policies";
}
};
/**
* Gets the Resource for proxying a user request.
*
* @return The resource for proxying a user request
*/
public static Resource getProxyResource() {
return PROXY_RESOURCE;
}
/**
* Gets the Resource for detailing all available NiFi Resources.
*
* @return The Resource resource
*/
public static Resource getResourceResource() {
return RESOURCE_RESOURCE;
}
/**
* Gets the Resource for accessing Tenants which includes creating, modifying, and deleting Users and UserGroups.
*
* @return The Resource for accessing Tenants
*/
public static Resource getTenantResource() {
return TENANT_RESOURCE;
}
/**
* Gets the {@link Resource} for accessing access policies.
* @return The policies resource
*/
public static Resource getPoliciesResource() {
return POLICIES_RESOURCE;
}
/**
* Gets the {@link Resource} for accessing buckets.
* @return The buckets resource
*/
public static Resource getBucketsResource() {
return BUCKETS_RESOURCE;
}
/**
* Gets the {@link Resource} for accessing buckets.
* @return The buckets resource
*/
public static Resource getBucketResource(String bucketIdentifier, String bucketName) {
return getChildResource(ResourceType.Bucket, bucketIdentifier, bucketName);
}
/**
* Gets a Resource for accessing a resources's policies.
*
* @param resource The resource being accessed
* @return The resource
*/
public static Resource getPolicyResource(final Resource resource) {
Objects.requireNonNull(resource, "The resource type must be specified.");
return new Resource() {
@Override
public String getIdentifier() {
return String.format("%s%s", POLICY_RESOURCE.getIdentifier(), resource.getIdentifier());
}
@Override
public String getName() {
return POLICY_RESOURCE.getName() + resource.getName();
}
@Override
public String getSafeDescription() {
return POLICY_RESOURCE.getSafeDescription() + resource.getSafeDescription();
}
};
}
/**
* Get a Resource object for any object that has a base type and an identifier, ie:
* /buckets/{uuid}
*
* @param parentResourceType - Required, the base resource type
* @param childIdentifier - Required, the identity of this sub resource
* @param name - Optional, the name of the subresource
* @return A resource for this object
*/
public static Resource getChildResource(final ResourceType parentResourceType, final String childIdentifier, final String name) {
Objects.requireNonNull(parentResourceType, "The base resource type must be specified.");
Objects.requireNonNull(childIdentifier, "The child identifier identifier must be specified.");
return new Resource() {
@Override
public String getIdentifier() {
return String.format("%s/%s", parentResourceType.getValue(), childIdentifier);
}
@Override
public String getName() {
return name;
}
@Override
public String getSafeDescription() {
final StringBuilder safeDescription = new StringBuilder();
switch (parentResourceType) {
case Bucket:
safeDescription.append("Bucket");
break;
case Policy:
safeDescription.append("Policy");
break;
case Tenant:
safeDescription.append("Tenant");
break;
default:
safeDescription.append("Unknown resource type");
break;
}
safeDescription.append(" with ID ");
safeDescription.append(childIdentifier);
return safeDescription.toString();
}
};
}
/**
* Prevent outside instantiation.
*/
private ResourceFactory() {}
}

Просмотреть файл

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization.resource;
public enum ResourceType {
Bucket("/buckets"),
Policy("/policies"),
Proxy("/proxy"),
Resource("/resources"),
Tenant("/tenants");
final String value;
private ResourceType(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static ResourceType valueOfValue(final String rawValue) {
ResourceType type = null;
for (final ResourceType rt : values()) {
if (rt.getValue().equals(rawValue)) {
type = rt;
break;
}
}
if (type == null) {
throw new IllegalArgumentException("Unknown resource type value " + rawValue);
}
return type;
}
}

Просмотреть файл

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization.user;
import java.util.Set;
/**
* A representation of a NiFi user that has logged into the application
*/
public interface NiFiUser {
/**
* @return the unique identity of this user
*/
String getIdentity();
/**
* @return the groups that this user belongs to if this nifi is configured to load user groups, null otherwise.
*/
Set<String> getGroups();
/**
* @return the next user in the proxied entities chain, or <code>null</code> if no more users exist in the chain.
*/
NiFiUser getChain();
/**
* @return <code>true</code> if the user is the unauthenticated Anonymous user
*/
boolean isAnonymous();
/**
* @return the address of the client that made the request which created this user
*/
String getClientAddress();
}

Просмотреть файл

@ -0,0 +1,91 @@
/*
* 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.authorization.user;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/**
* User details for a NiFi user.
*/
public class NiFiUserDetails implements UserDetails {
private final NiFiUser user;
/**
* Creates a new NiFiUserDetails.
*
* @param user user
*/
public NiFiUserDetails(NiFiUser user) {
this.user = user;
}
/**
* Get the user for this UserDetails.
*
* @return user
*/
public NiFiUser getNiFiUser() {
return user;
}
/**
* Returns the authorities that this NiFi user has.
*
* @return authorities
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.EMPTY_SET;
}
@Override
public String getPassword() {
return StringUtils.EMPTY;
}
@Override
public String getUsername() {
return user.getIdentity();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

Просмотреть файл

@ -0,0 +1,91 @@
/*
* 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.authorization.user;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.ArrayList;
import java.util.List;
/**
* Utility methods for retrieving information about the current application user.
*
*/
public final class NiFiUserUtils {
/**
* Returns the current NiFiUser or null if the current user is not a NiFiUser.
*
* @return user
*/
public static NiFiUser getNiFiUser() {
NiFiUser user = null;
// obtain the principal in the current authentication
final SecurityContext context = SecurityContextHolder.getContext();
final Authentication authentication = context.getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof NiFiUserDetails) {
user = ((NiFiUserDetails) principal).getNiFiUser();
}
}
return user;
}
public static String getNiFiUserIdentity() {
// get the nifi user to extract the username
NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user == null) {
return "unknown";
} else {
return user.getIdentity();
}
}
/**
* Builds the proxy chain for the specified user.
*
* @param user The current user
* @return The proxy chain for that user in List form
*/
public static List<String> buildProxiedEntitiesChain(final NiFiUser user) {
// calculate the dn chain
final List<String> proxyChain = new ArrayList<>();
// build the dn chain
NiFiUser chainedUser = user;
while (chainedUser != null) {
// add the entry for this user
if (chainedUser.isAnonymous()) {
// use an empty string to represent an anonymous user in the proxy entities chain
proxyChain.add(StringUtils.EMPTY);
} else {
proxyChain.add(chainedUser.getIdentity());
}
// go to the next user in the chain
chainedUser = chainedUser.getChain();
}
return proxyChain;
}
}

Просмотреть файл

@ -0,0 +1,189 @@
/*
* 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.authorization.user;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
/**
* An implementation of NiFiUser.
*/
public class StandardNiFiUser implements NiFiUser {
public static final String ANONYMOUS_IDENTITY = "anonymous";
public static final StandardNiFiUser ANONYMOUS = new Builder().identity(ANONYMOUS_IDENTITY).anonymous(true).build();
private final String identity;
private final Set<String> groups;
private final NiFiUser chain;
private final String clientAddress;
private final boolean isAnonymous;
private StandardNiFiUser(final Builder builder) {
this.identity = builder.identity;
this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
this.chain = builder.chain;
this.clientAddress = builder.clientAddress;
this.isAnonymous = builder.isAnonymous;
}
/**
* This static builder allows the chain and clientAddress to be populated without allowing calling code to provide a non-anonymous identity of the anonymous user.
*
* @param chain the proxied entities in {@see NiFiUser} form
* @param clientAddress the address the request originated from
* @return an anonymous user instance with the identity "anonymous"
*/
public static StandardNiFiUser populateAnonymousUser(NiFiUser chain, String clientAddress) {
return new Builder().identity(ANONYMOUS_IDENTITY).chain(chain).clientAddress(clientAddress).anonymous(true).build();
}
@Override
public String getIdentity() {
return identity;
}
@Override
public Set<String> getGroups() {
return groups;
}
@Override
public NiFiUser getChain() {
return chain;
}
@Override
public boolean isAnonymous() {
return isAnonymous;
}
@Override
public String getClientAddress() {
return clientAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof NiFiUser)) {
return false;
}
final NiFiUser other = (NiFiUser) obj;
return Objects.equals(this.identity, other.getIdentity());
}
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + Objects.hashCode(this.identity);
return hash;
}
@Override
public String toString() {
final String formattedGroups;
if (groups == null) {
formattedGroups = "none";
} else {
formattedGroups = StringUtils.join(groups, ", ");
}
return String.format("identity[%s], groups[%s]", getIdentity(), formattedGroups);
}
/**
* Builder for a StandardNiFiUser
*/
public static class Builder {
private String identity;
private Set<String> groups;
private NiFiUser chain;
private String clientAddress;
private boolean isAnonymous = false;
/**
* Sets the identity.
*
* @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
* @return the builder
*/
public Builder identity(final String identity) {
this.identity = identity;
return this;
}
/**
* Sets the groups.
*
* @param groups the user groups
* @return the builder
*/
public Builder groups(final Set<String> groups) {
this.groups = groups;
return this;
}
/**
* Sets the chain.
*
* @param chain the proxy chain that leads to this users
* @return the builder
*/
public Builder chain(final NiFiUser chain) {
this.chain = chain;
return this;
}
/**
* Sets the client address.
*
* @param clientAddress the source address of the request
* @return the builder
*/
public Builder clientAddress(final String clientAddress) {
this.clientAddress = clientAddress;
return this;
}
/**
* Sets whether this user is the canonical "anonymous" user
*
* @param isAnonymous true to represent the canonical "anonymous" user
* @return the builder
*/
private Builder anonymous(final boolean isAnonymous) {
this.isAnonymous = isAnonymous;
return this;
}
/**
* @return builds a StandardNiFiUser from the current state of the builder
*/
public StandardNiFiUser build() {
return new StandardNiFiUser(this);
}
}
}

Просмотреть файл

@ -42,6 +42,7 @@ import javax.persistence.metamodel.Metamodel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -166,6 +167,10 @@ public class DatabaseMetadataService implements MetadataService {
return buckets;
}
private Set<BucketEntity> getBuckets(Set<String> bucketIds) {
return new HashSet<>(bucketRepository.findByIdIn(bucketIds));
}
// ------------------------------------------------------------------------------------
@Override
@ -218,6 +223,18 @@ public class DatabaseMetadataService implements MetadataService {
}
}
@Override
public List<BucketItemEntity> getBucketItems(final QueryParameters params, final Set<String> bucketIds) {
Set<BucketEntity> filterBuckets = getBuckets(bucketIds);
if (params.getNumRows() != null && params.getPageNum() != null) {
return getPagedBucketItems(params, filterBuckets);
} else if (params.getSortParameters() != null && params.getSortParameters().size() > 0) {
return getSortedBucketItems(params, filterBuckets);
} else {
return getBucketItems(filterBuckets);
}
}
private List<BucketItemEntity> getBucketItems(final BucketEntity bucket) {
final List<BucketItemEntity> bucketItems = new ArrayList<>();
for (BucketItemEntity item : itemRepository.findByBucket(bucket)) {
@ -244,6 +261,32 @@ public class DatabaseMetadataService implements MetadataService {
return items;
}
private List<BucketItemEntity> getBucketItems(final Set<BucketEntity> buckets) {
final List<BucketItemEntity> bucketItems = new ArrayList<>();
for (BucketItemEntity item : itemRepository.findByBucketIn(buckets)) {
bucketItems.add(item);
}
return bucketItems;
}
private List<BucketItemEntity> getPagedBucketItems(final QueryParameters params, final Set<BucketEntity> buckets) {
final Pageable pageable = getPageRequest(params);
final List<BucketItemEntity> items = new ArrayList<>();
for (BucketItemEntity item : itemRepository.findByBucketIn(buckets, pageable)) {
items.add(item);
}
return items;
}
private List<BucketItemEntity> getSortedBucketItems(final QueryParameters params, final Set<BucketEntity> buckets) {
final Sort sort = getSort(params);
final List<BucketItemEntity> items = new ArrayList<>();
for (BucketItemEntity item : itemRepository.findByBucketIn(buckets, sort)) {
items.add(item);
}
return items;
}
// ------------------------------------------------------------------------------------
@Override
@ -254,8 +297,14 @@ public class DatabaseMetadataService implements MetadataService {
}
@Override
public FlowEntity getFlowById(final String flowIdentifier) {
return flowRepository.findOne(flowIdentifier);
public FlowEntity getFlowById(final String bucketIdentifier, final String flowIdentifier) {
FlowEntity flow = flowRepository.findOne(flowIdentifier);
if (flow.getBucket() == null || !bucketIdentifier.equals(flow.getBucket().getId())) {
return null;
}
return flow;
}
@Override
@ -285,12 +334,18 @@ public class DatabaseMetadataService implements MetadataService {
@Override
public List<FlowEntity> getFlows(final QueryParameters params) {
return getFlows(params, null);
}
@Override
public List<FlowEntity> getFlows(final QueryParameters params, Set<String> bucketIds) {
Set<BucketEntity> filterBuckets = getBuckets(bucketIds);
if (params.getNumRows() != null && params.getPageNum() != null) {
return getPagedFlows(params);
return getPagedFlows(params, filterBuckets);
} else if (params.getSortParameters() != null && params.getSortParameters().size() > 0) {
return getSortedFlows(params);
return getSortedFlows(params, filterBuckets);
} else {
return getAllFlows();
return getAllFlows(filterBuckets);
}
}
@ -320,6 +375,20 @@ public class DatabaseMetadataService implements MetadataService {
return flows;
}
private List<FlowEntity> getAllFlows(Set<BucketEntity> buckets) {
return flowRepository.findByBucketIn(buckets);
}
private List<FlowEntity> getPagedFlows(final QueryParameters params, Set<BucketEntity> buckets) {
final Pageable pageable = getPageRequest(params);
return flowRepository.findByBucketIn(buckets, pageable);
}
private List<FlowEntity> getSortedFlows(final QueryParameters params, Set<BucketEntity> buckets) {
final Sort sort = getSort(params);
return flowRepository.findByBucketIn(buckets, sort);
}
// ------------------------------------------------------------------------------------
@Override
@ -329,9 +398,18 @@ public class DatabaseMetadataService implements MetadataService {
}
@Override
public FlowSnapshotEntity getFlowSnapshot(final String flowIdentifier, final Integer version) {
public FlowSnapshotEntity getFlowSnapshot(final String bucketIdentifier, final String flowIdentifier, final Integer version) {
final FlowSnapshotEntityKey key = new FlowSnapshotEntityKey(flowIdentifier, version);
return flowSnapshotRepository.findOne(key);
FlowSnapshotEntity flowSnapshot = flowSnapshotRepository.findOne(key);
if (flowSnapshot == null
|| flowSnapshot.getFlow() == null
|| flowSnapshot.getFlow().getBucket() == null
|| !bucketIdentifier.equals(flowSnapshot.getFlow().getBucket().getId())) {
return null;
}
return flowSnapshot;
}
@Override

Просмотреть файл

@ -24,6 +24,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Set;
/**
* Repository for BucketItems that exposes only the methods from PagingAndSortingRepository.
@ -81,4 +82,30 @@ public interface BucketItemRepository extends Repository<BucketItemEntity,String
*/
List<BucketItemEntity> findByBucket(BucketEntity bucket, Pageable pageable);
/**
* Find all items by buckets.
*
* @param buckets the buckets to find items for
* @return the list of items for the buckets
*/
List<BucketItemEntity> findByBucketIn(Set<BucketEntity> buckets);
/**
* Find all items by buckets with sorting.
*
* @param buckets the buckets to find items for
* @param sort the sort params
* @return the list of items for the buckets
*/
List<BucketItemEntity> findByBucketIn(Set<BucketEntity> buckets, Sort sort);
/**
* Find all items by buckets with paging/sorting.
*
* @param buckets the buckets to find items for
* @param pageable the pageable params
* @return the list of items for the buckets based on the pageable params
*/
List<BucketItemEntity> findByBucketIn(Set<BucketEntity> buckets, Pageable pageable);
}

Просмотреть файл

@ -20,6 +20,7 @@ import org.apache.nifi.registry.db.entity.BucketEntity;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
import java.util.Set;
/**
* Spring Data Repository for BucketEntity.
@ -28,4 +29,6 @@ public interface BucketRepository extends PagingAndSortingRepository<BucketEntit
List<BucketEntity> findByNameIgnoreCase(String name);
List<BucketEntity> findByIdIn(Set<String> id);
}

Просмотреть файл

@ -16,10 +16,14 @@
*/
package org.apache.nifi.registry.db.repository;
import org.apache.nifi.registry.db.entity.BucketEntity;
import org.apache.nifi.registry.db.entity.FlowEntity;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
import java.util.Set;
/**
* Spring Data Repository for FlowEntity.
@ -28,4 +32,30 @@ public interface FlowRepository extends PagingAndSortingRepository<FlowEntity,St
List<FlowEntity> findByNameIgnoreCase(String name);
/**
* Find all flows by buckets.
*
* @param buckets the buckets to find items for
* @return the list of items for the buckets
*/
List<FlowEntity> findByBucketIn(Set<BucketEntity> buckets);
/**
* Find all flows by buckets with sorting.
*
* @param buckets the buckets to find flows for
* @param sort the sort params
* @return the list of flows for the buckets
*/
List<FlowEntity> findByBucketIn(Set<BucketEntity> buckets, Sort sort);
/**
* Find all flows by buckets with paging/sorting.
*
* @param buckets the buckets to find flows for
* @param pageable the pageable params
* @return the list of flows for the buckets based on the pageable params
*/
List<FlowEntity> findByBucketIn(Set<BucketEntity> buckets, Pageable pageable);
}

Просмотреть файл

@ -0,0 +1,39 @@
/*
* 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.exception;
/**
*
*/
public class AdministrationException extends RuntimeException {
public AdministrationException(Throwable cause) {
super(cause);
}
public AdministrationException(String message, Throwable cause) {
super(message, cause);
}
public AdministrationException(String message) {
super(message);
}
public AdministrationException() {
}
}

Просмотреть файл

@ -0,0 +1,695 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.service;
import org.apache.nifi.registry.authorization.AccessPolicyProvider;
import org.apache.nifi.registry.authorization.AccessPolicyProviderInitializationContext;
import org.apache.nifi.registry.authorization.AuthorizableLookup;
import org.apache.nifi.registry.authorization.exception.AccessDeniedException;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.AuthorizeAccess;
import org.apache.nifi.registry.authorization.Authorizer;
import org.apache.nifi.registry.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.registry.authorization.ConfigurableAccessPolicyProvider;
import org.apache.nifi.registry.authorization.ConfigurableUserGroupProvider;
import org.apache.nifi.registry.authorization.Group;
import org.apache.nifi.registry.authorization.ManagedAuthorizer;
import org.apache.nifi.registry.authorization.RequestAction;
import org.apache.nifi.registry.authorization.UserAndGroups;
import org.apache.nifi.registry.authorization.UserGroupProvider;
import org.apache.nifi.registry.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.registry.authorization.resource.ResourceFactory;
import org.apache.nifi.registry.authorization.resource.ResourceType;
import org.apache.nifi.registry.authorization.user.NiFiUserUtils;
import org.apache.nifi.registry.bucket.Bucket;
import org.apache.nifi.registry.model.authorization.AccessPolicy;
import org.apache.nifi.registry.model.authorization.AccessPolicySummary;
import org.apache.nifi.registry.model.authorization.Resource;
import org.apache.nifi.registry.model.authorization.Tenant;
import org.apache.nifi.registry.model.authorization.User;
import org.apache.nifi.registry.model.authorization.UserGroup;
import org.apache.nifi.registry.service.params.QueryParameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@Service
public class AuthorizationService {
public static final String MSG_NON_MANAGED_AUTHORIZER = "This NiFi Registry is not configured to internally manage users, groups, or policies. Please contact your system administrator.";
public static final String MSG_NON_CONFIGURABLE_POLICIES = "This NiFi Registry is not configured to allow configurable policies. Please contact your system administrator.";
public static final String MSG_NON_CONFIGURABLE_USERS = "This NiFi Registry is not configured to allow configurable users and groups. Please contact your system administrator.";
private AuthorizableLookup authorizableLookup;
private Authorizer authorizer;
private RegistryService registryService;
private UserGroupProvider userGroupProvider;
private AccessPolicyProvider accessPolicyProvider;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
@Autowired
public AuthorizationService(
final AuthorizableLookup authorizableLookup,
final Authorizer authorizer,
final RegistryService registryService) {
this.authorizableLookup = authorizableLookup;
this.authorizer = authorizer;
this.registryService = registryService;
if (AuthorizerCapabilityDetection.isManagedAuthorizer(this.authorizer)) {
this.accessPolicyProvider = ((ManagedAuthorizer) authorizer).getAccessPolicyProvider();
} else {
this.accessPolicyProvider = createExceptionThrowingAccessPolicyProvider();
}
this.userGroupProvider = accessPolicyProvider.getUserGroupProvider();
}
// ---------------------- Authorization methods -------------------------------------
public void authorizeAccess(final AuthorizeAccess authorizeAccess) {
authorizeAccess.authorize(authorizableLookup);
}
// ---------------------- Tenant methods --------------------------------------------
public Tenant getTenant(String identifier) {
this.readLock.lock();
try {
org.apache.nifi.registry.authorization.User user = userGroupProvider.getUser(identifier);
if (user != null) {
return tenantToDTO(user);
} else {
org.apache.nifi.registry.authorization.Group group = userGroupProvider.getGroup(identifier);
return tenantToDTO(group);
}
} finally {
this.readLock.unlock();
}
}
// ---------------------- User methods ----------------------------------------------
public User createUser(User user) {
verifyUserGroupProviderIsConfigurable();
writeLock.lock();
try {
final org.apache.nifi.registry.authorization.User createdUser =
((ConfigurableUserGroupProvider) userGroupProvider).addUser(userFromDTO(user));
return userToDTO(createdUser);
} finally {
writeLock.unlock();
}
}
public List<User> getUsers() {
this.readLock.lock();
try {
return userGroupProvider.getUsers().stream().map(this::userToDTO).collect(Collectors.toList());
} finally {
this.readLock.unlock();
}
}
public User getUser(String identifier) {
this.readLock.lock();
try {
return userToDTO(userGroupProvider.getUser(identifier));
} finally {
this.readLock.unlock();
}
}
public User updateUser(User user) {
verifyUserGroupProviderIsConfigurable();
this.writeLock.lock();
try {
final org.apache.nifi.registry.authorization.User updatedUser =
((ConfigurableUserGroupProvider) userGroupProvider).updateUser(userFromDTO(user));
return userToDTO(updatedUser);
} finally {
this.writeLock.unlock();
}
}
public User deleteUser(String identifier) {
verifyUserGroupProviderIsConfigurable();
this.writeLock.lock();
try {
User deletedUserDTO = getUser(identifier);
((ConfigurableUserGroupProvider) userGroupProvider).deleteUser(identifier);
return deletedUserDTO;
} finally {
this.writeLock.unlock();
}
}
// ---------------------- User Group methods --------------------------------------
public UserGroup createUserGroup(UserGroup userGroup) {
verifyUserGroupProviderIsConfigurable();
writeLock.lock();
try {
final org.apache.nifi.registry.authorization.Group createdGroup =
((ConfigurableUserGroupProvider) userGroupProvider).addGroup(userGroupFromDTO(userGroup));
return userGroupToDTO(createdGroup);
} finally {
writeLock.unlock();
}
}
public List<UserGroup> getUserGroups() {
this.readLock.lock();
try {
return userGroupProvider.getGroups().stream().map(this::userGroupToDTO).collect(Collectors.toList());
} finally {
this.readLock.unlock();
}
}
public List<UserGroup> getUserGroupsForUser(String userIdentifier) {
this.readLock.lock();
try {
return userGroupProvider.getGroups()
.stream()
.filter(group -> group.getUsers().contains(userIdentifier))
.map(this::userGroupToDTO)
.collect(Collectors.toList());
} finally {
this.readLock.unlock();
}
}
public UserGroup getUserGroup(String identifier) {
this.readLock.lock();
try {
return userGroupToDTO(userGroupProvider.getGroup(identifier));
} finally {
this.readLock.unlock();
}
}
public UserGroup updateUserGroup(UserGroup userGroup) {
verifyUserGroupProviderIsConfigurable();
writeLock.lock();
try {
final org.apache.nifi.registry.authorization.Group updatedGroup =
((ConfigurableUserGroupProvider) userGroupProvider).updateGroup(userGroupFromDTO(userGroup));
return userGroupToDTO(updatedGroup);
} finally {
writeLock.unlock();
}
}
public UserGroup deleteUserGroup(String identifier) {
verifyUserGroupProviderIsConfigurable();
writeLock.lock();
try {
final UserGroup userGroupDTO = getUserGroup(identifier);
((ConfigurableUserGroupProvider) userGroupProvider).deleteGroup(identifier);
return userGroupDTO;
} finally {
writeLock.unlock();
}
}
// ---------------------- Access Policy methods ----------------------------------------
public AccessPolicy createAccessPolicy(AccessPolicy accessPolicy) {
verifyAccessPolicyProviderIsConfigurable();
writeLock.lock();
try {
org.apache.nifi.registry.authorization.AccessPolicy createdAccessPolicy =
((ConfigurableAccessPolicyProvider) accessPolicyProvider).addAccessPolicy(accessPolicyFromDTO(accessPolicy));
return accessPolicyToDTO(createdAccessPolicy);
} finally {
writeLock.unlock();
}
}
public AccessPolicy getAccessPolicy(String identifier) {
readLock.lock();
try {
return accessPolicyToDTO(accessPolicyProvider.getAccessPolicy(identifier));
} finally {
readLock.unlock();
}
}
public AccessPolicy getAccessPolicy(String resource, RequestAction action) {
readLock.lock();
try {
return accessPolicyToDTO(accessPolicyProvider.getAccessPolicy(resource, action));
} finally {
readLock.unlock();
}
}
public List<AccessPolicy> getAccessPolicies() {
readLock.lock();
try {
return accessPolicyProvider.getAccessPolicies().stream().map(this::accessPolicyToDTO).collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public List<AccessPolicySummary> getAccessPolicySummaries() {
readLock.lock();
try {
return accessPolicyProvider.getAccessPolicies().stream().map(this::accessPolicyToSummaryDTO).collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public List<AccessPolicy> getAccessPoliciesForUser(String userIdentifier) {
readLock.lock();
try {
return accessPolicyProvider.getAccessPolicies().stream()
.filter(accessPolicy -> accessPolicy.getUsers().contains(userIdentifier))
.map(this::accessPolicyToDTO)
.collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public List<AccessPolicySummary> getAccessPolicySummariesForUser(String userIdentifier) {
readLock.lock();
try {
return accessPolicyProvider.getAccessPolicies().stream()
.filter(accessPolicy -> accessPolicy.getUsers().contains(userIdentifier))
.map(this::accessPolicyToSummaryDTO)
.collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public List<AccessPolicySummary> getAccessPolicySummariesForUserGroup(String userGroupIdentifier) {
readLock.lock();
try {
return accessPolicyProvider.getAccessPolicies().stream()
.filter(accessPolicy -> accessPolicy.getGroups().contains(userGroupIdentifier))
.map(this::accessPolicyToSummaryDTO)
.collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) {
verifyAccessPolicyProviderIsConfigurable();
writeLock.lock();
try {
// Don't allow changing action or resource of existing policy (should only be adding/removing users/groups)
org.apache.nifi.registry.authorization.AccessPolicy currentAccessPolicy =
accessPolicyProvider.getAccessPolicy(accessPolicy.getIdentifier());
accessPolicy.setResource(currentAccessPolicy.getResource());
accessPolicy.setAction(currentAccessPolicy.getAction().toString());
org.apache.nifi.registry.authorization.AccessPolicy updateAccessPolicy =
((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(accessPolicyFromDTO(accessPolicy));
return accessPolicyToDTO(updateAccessPolicy);
} finally {
writeLock.unlock();
}
}
public AccessPolicy deleteAccessPolicy(String identifier) {
verifyAccessPolicyProviderIsConfigurable();
writeLock.lock();
try {
AccessPolicy deletedAccessPolicyDTO = getAccessPolicy(identifier);
((ConfigurableAccessPolicyProvider) accessPolicyProvider).deleteAccessPolicy(identifier);
return deletedAccessPolicyDTO;
} finally {
writeLock.unlock();
}
}
// ---------------------- Resource Lookup methods --------------------------------------
public List<Resource> getAuthorizedResources(RequestAction actionType, ResourceType resourceType) {
final List<Resource> authorizedResources =
getAllAuthorizableResources()
.stream()
.filter(resource -> {
String resourceId = resource.getIdentifier();
if (resourceType != null) {
if (!resourceId.startsWith(resourceType.getValue())) {
return false;
}
}
try {
authorizableLookup
.getAuthorizableByResource(resource.getIdentifier())
.authorize(authorizer, actionType, NiFiUserUtils.getNiFiUser());
} catch (AccessDeniedException e) {
return false;
}
return true;
})
.map(AuthorizationService::resourceToDTO)
.collect(Collectors.toList());
return authorizedResources;
}
public List<Resource> getAuthorizedResources(RequestAction actionType) {
return getAuthorizedResources(actionType, null);
}
public List<Resource> getResources() {
final List<Resource> dtoResources =
getAllAuthorizableResources()
.stream()
.map(AuthorizationService::resourceToDTO)
.collect(Collectors.toList());
return dtoResources;
}
// ---------------------- Private Helper methods --------------------------------------
private void verifyUserGroupProviderIsConfigurable() {
if (!(userGroupProvider instanceof ConfigurableUserGroupProvider)) {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
private void verifyAccessPolicyProviderIsConfigurable() {
if (!(accessPolicyProvider instanceof ConfigurableAccessPolicyProvider)) {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
}
}
private List<org.apache.nifi.registry.authorization.Resource> getAllAuthorizableResources() {
final List<org.apache.nifi.registry.authorization.Resource> resources = new ArrayList<>();
resources.add(ResourceFactory.getPoliciesResource());
resources.add(ResourceFactory.getTenantResource());
resources.add(ResourceFactory.getProxyResource());
resources.add(ResourceFactory.getResourceResource());
// add all buckets
resources.add(ResourceFactory.getBucketsResource());
for (final Bucket bucket : registryService.getBuckets(QueryParameters.EMPTY_PARAMETERS)) {
resources.add(ResourceFactory.getChildResource(ResourceType.Bucket, bucket.getIdentifier(), bucket.getName()));
}
return resources;
}
private org.apache.nifi.registry.model.authorization.User userToDTO(
final org.apache.nifi.registry.authorization.User user) {
if (user == null) {
return null;
}
String userIdentifier = user.getIdentifier();
Collection<UserGroup> userGroups = getUserGroupsForUser(userIdentifier);
Collection<AccessPolicySummary> accessPolicySummaries = getAccessPolicySummariesForUser(userIdentifier);
return userToDTO(user, userGroups, accessPolicySummaries);
}
private org.apache.nifi.registry.model.authorization.UserGroup userGroupToDTO(
final org.apache.nifi.registry.authorization.Group userGroup) {
if (userGroup == null) {
return null;
}
Collection<Tenant> userTenants = userGroup.getUsers() != null
? userGroup.getUsers().stream().map(this::getTenant).collect(Collectors.toSet()) : null;
Collection<AccessPolicySummary> accessPolicySummaries = getAccessPolicySummariesForUserGroup(userGroup.getIdentifier());
return userGroupToDTO(userGroup, userTenants, accessPolicySummaries);
}
private org.apache.nifi.registry.model.authorization.AccessPolicy accessPolicyToDTO(
final org.apache.nifi.registry.authorization.AccessPolicy accessPolicy) {
if (accessPolicy == null) {
return null;
}
Collection<Tenant> users = accessPolicy.getUsers() != null
? accessPolicy.getUsers().stream().map(this::getTenant).collect(Collectors.toList()) : null;
Collection<Tenant> userGroups = accessPolicy.getGroups() != null
? accessPolicy.getGroups().stream().map(this::getTenant).collect(Collectors.toList()) : null;
Boolean isConfigurable = AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy);
return accessPolicyToDTO(accessPolicy, userGroups, users, isConfigurable);
}
private org.apache.nifi.registry.model.authorization.AccessPolicySummary accessPolicyToSummaryDTO(
final org.apache.nifi.registry.authorization.AccessPolicy accessPolicy) {
if (accessPolicy == null) {
return null;
}
Boolean isConfigurable = AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy);
final AccessPolicySummary accessPolicySummaryDTO = new AccessPolicySummary();
accessPolicySummaryDTO.setIdentifier(accessPolicy.getIdentifier());
accessPolicySummaryDTO.setAction(accessPolicy.getAction().toString());
accessPolicySummaryDTO.setResource(accessPolicy.getResource());
accessPolicySummaryDTO.setConfigurable(isConfigurable);
return accessPolicySummaryDTO;
}
private static Resource resourceToDTO(org.apache.nifi.registry.authorization.Resource resource) {
if (resource == null) {
return null;
}
Resource resourceDto = new Resource();
resourceDto.setIdentifier(resource.getIdentifier());
resourceDto.setName(resource.getName());
return resourceDto;
}
private static Tenant tenantToDTO(org.apache.nifi.registry.authorization.User user) {
if (user == null) {
return null;
}
return new Tenant(user.getIdentifier(), user.getIdentity());
}
private static Tenant tenantToDTO(org.apache.nifi.registry.authorization.Group group) {
if (group == null) {
return null;
}
return new Tenant(group.getIdentifier(), group.getName());
}
private static org.apache.nifi.registry.authorization.User userFromDTO(
final org.apache.nifi.registry.model.authorization.User userDTO) {
if (userDTO == null) {
return null;
}
return new org.apache.nifi.registry.authorization.User.Builder()
.identifier(userDTO.getIdentifier() != null ? userDTO.getIdentifier() : UUID.randomUUID().toString())
.identity(userDTO.getIdentity())
.build();
}
private static org.apache.nifi.registry.model.authorization.User userToDTO(
final org.apache.nifi.registry.authorization.User user,
final Collection<? extends Tenant> userGroups,
final Collection<? extends AccessPolicySummary> accessPolicies) {
if (user == null) {
return null;
}
User userDTO = new User(user.getIdentifier(), user.getIdentity());
userDTO.addUserGroups(userGroups);
userDTO.addAccessPolicies(accessPolicies);
return userDTO;
}
private static org.apache.nifi.registry.authorization.Group userGroupFromDTO(
final org.apache.nifi.registry.model.authorization.UserGroup userGroupDTO) {
if (userGroupDTO == null) {
return null;
}
org.apache.nifi.registry.authorization.Group.Builder groupBuilder = new org.apache.nifi.registry.authorization.Group.Builder()
.identifier(userGroupDTO.getIdentifier() != null ? userGroupDTO.getIdentifier() : UUID.randomUUID().toString())
.name(userGroupDTO.getIdentity());
Set<Tenant> users = userGroupDTO.getUsers();
if (users != null) {
groupBuilder.addUsers(users.stream().map(Tenant::getIdentifier).collect(Collectors.toSet()));
}
return groupBuilder.build();
}
private static org.apache.nifi.registry.model.authorization.UserGroup userGroupToDTO(
final org.apache.nifi.registry.authorization.Group userGroup,
final Collection<? extends Tenant> users,
final Collection<? extends AccessPolicySummary> accessPolicies) {
if (userGroup == null) {
return null;
}
UserGroup userGroupDTO = new UserGroup(userGroup.getIdentifier(), userGroup.getName());
userGroupDTO.addUsers(users);
userGroupDTO.addAccessPolicies(accessPolicies);
return userGroupDTO;
}
private static org.apache.nifi.registry.authorization.AccessPolicy accessPolicyFromDTO(
final org.apache.nifi.registry.model.authorization.AccessPolicy accessPolicyDTO) {
org.apache.nifi.registry.authorization.AccessPolicy.Builder accessPolicyBuilder =
new org.apache.nifi.registry.authorization.AccessPolicy.Builder()
.identifier(accessPolicyDTO.getIdentifier() != null ? accessPolicyDTO.getIdentifier() : UUID.randomUUID().toString())
.resource(accessPolicyDTO.getResource())
.action(RequestAction.valueOfValue(accessPolicyDTO.getAction()));
Set<Tenant> dtoUsers = accessPolicyDTO.getUsers();
if (accessPolicyDTO.getUsers() != null) {
accessPolicyBuilder.addUsers(dtoUsers.stream().map(Tenant::getIdentifier).collect(Collectors.toSet()));
}
Set<Tenant> dtoUserGroups = accessPolicyDTO.getUserGroups();
if (dtoUserGroups != null) {
accessPolicyBuilder.addGroups(dtoUserGroups.stream().map(Tenant::getIdentifier).collect(Collectors.toSet()));
}
return accessPolicyBuilder.build();
}
private static org.apache.nifi.registry.model.authorization.AccessPolicy accessPolicyToDTO(
final org.apache.nifi.registry.authorization.AccessPolicy accessPolicy,
final Collection<? extends Tenant> userGroups,
final Collection<? extends Tenant> users,
final Boolean isConfigurable) {
if (accessPolicy == null) {
return null;
}
final AccessPolicy accessPolicyDTO = new AccessPolicy();
accessPolicyDTO.setIdentifier(accessPolicy.getIdentifier());
accessPolicyDTO.setAction(accessPolicy.getAction().toString());
accessPolicyDTO.setResource(accessPolicy.getResource());
accessPolicyDTO.setConfigurable(isConfigurable);
accessPolicyDTO.addUsers(users);
accessPolicyDTO.addUserGroups(userGroups);
return accessPolicyDTO;
}
private static AccessPolicyProvider createExceptionThrowingAccessPolicyProvider() {
return new AccessPolicyProvider() {
@Override
public Set<org.apache.nifi.registry.authorization.AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public org.apache.nifi.registry.authorization.AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public org.apache.nifi.registry.authorization.AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public UserGroupProvider getUserGroupProvider() {
return new UserGroupProvider() {
@Override
public Set<org.apache.nifi.registry.authorization.User> getUsers() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public org.apache.nifi.registry.authorization.User getUser(String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public org.apache.nifi.registry.authorization.User getUserByIdentity(String identity) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
}

Просмотреть файл

@ -95,6 +95,15 @@ public interface MetadataService {
*/
List<BucketItemEntity> getBucketItems(QueryParameters queryParameters, BucketEntity bucket);
/**
* Retrieves items for the given buckets.
*
* @param bucketIds the ids of buckets to retrieve items for
* @param queryParameters the parameters for retrieving the items, or null
* @return the set of items for the bucket
*/
List<BucketItemEntity> getBucketItems(QueryParameters queryParameters, Set<String> bucketIds);
/**
* Creates a versioned flow in the given bucket.
*
@ -107,10 +116,11 @@ public interface MetadataService {
/**
* Retrieves the versioned flow with the given id.
*
* @param bucketIdentifier the identifier of the bucket storing the flow
* @param flowIdentifier the identifier of the flow to retrieve
* @return the versioned flow with the given id, or null if no flow with the given id exists
*/
FlowEntity getFlowById(String flowIdentifier);
FlowEntity getFlowById(String bucketIdentifier, String flowIdentifier);
/**
* Retrieves the versioned flows with the given name. The name comparison must be case-insensitive.
@ -143,6 +153,15 @@ public interface MetadataService {
*/
List<FlowEntity> getFlows(QueryParameters queryParameters);
/**
* Retrieves items for the given buckets.
*
* @param bucketIds the ids of buckets to retrieve items for
* @param queryParameters the parameters for retrieving the items, or null
* @return the set of items for the bucket
*/
List<FlowEntity> getFlows(QueryParameters queryParameters, Set<String> bucketIds);
/**
* Creates a versioned flow snapshot.
*
@ -155,11 +174,12 @@ public interface MetadataService {
/**
* Retrieves the snapshot for the given flow identifier and snapshot version.
*
* @param bucketIdentifier the identifier of the bucket storign the flow
* @param flowIdentifier the identifier of the flow the snapshot belongs to
* @param version the version of the snapshot
* @return the versioned flow snapshot for the given flow identifier and version, or null if none exists
*/
FlowSnapshotEntity getFlowSnapshot(String flowIdentifier, Integer version);
FlowSnapshotEntity getFlowSnapshot(String bucketIdentifier, String flowIdentifier, Integer version);
/**
* Deletes the flow snapshot.

Просмотреть файл

@ -261,6 +261,21 @@ public class RegistryService {
}
}
public List<BucketItem> getBucketItems(final QueryParameters queryParameters, final Set<String> bucketIdentifiers) {
if (bucketIdentifiers == null || bucketIdentifiers.isEmpty()) {
throw new IllegalArgumentException("Bucket Identifiers cannot be null or empty");
}
readLock.lock();
try {
final List<BucketItem> bucketItems = new ArrayList<>();
metadataService.getBucketItems(queryParameters, bucketIdentifiers).stream().forEach(b -> addBucketItem(bucketItems, b));
return bucketItems;
} finally {
readLock.unlock();
}
}
private void addBucketItem(final List<BucketItem> bucketItems, final BucketItemEntity itemEntity) {
if (itemEntity instanceof FlowEntity) {
final FlowEntity flowEntity = (FlowEntity) itemEntity;
@ -326,14 +341,14 @@ public class RegistryService {
}
}
public VersionedFlow getFlow(final String flowIdentifier, final boolean verbose) {
public VersionedFlow getFlow(final String bucketIdentifier, final String flowIdentifier, final boolean verbose) {
if (StringUtils.isBlank(flowIdentifier)) {
throw new IllegalArgumentException("Flow Identifier cannot be null or blank");
}
readLock.lock();
try {
final FlowEntity flowEntity = metadataService.getFlowById(flowIdentifier);
final FlowEntity flowEntity = metadataService.getFlowById(bucketIdentifier, flowIdentifier);
if (flowEntity == null) {
throw new ResourceNotFoundException("VersionedFlow does not exist for identifier: " + flowIdentifier);
}
@ -375,6 +390,17 @@ public class RegistryService {
}
}
public List<VersionedFlow> getFlows(final QueryParameters queryParameters, final Set<String> bucketIds) {
readLock.lock();
try {
// return non-verbose set of all flows
final List<FlowEntity> flows = metadataService.getFlows(queryParameters, bucketIds);
return flows.stream().map(f -> DataModelMapper.map(f, false)).collect(Collectors.toList());
} finally {
readLock.unlock();
}
}
public VersionedFlow updateFlow(final VersionedFlow versionedFlow) {
if (versionedFlow == null) {
throw new IllegalArgumentException("VersionedFlow cannot be null");
@ -384,10 +410,14 @@ public class RegistryService {
throw new IllegalArgumentException("VersionedFlow identifier cannot be null or blank");
}
if (StringUtils.isBlank(versionedFlow.getBucketIdentifier())) {
throw new IllegalArgumentException("VersionedFlow bucket identifier cannot be null or blank");
}
writeLock.lock();
try {
// ensure a flow with the given id exists
final FlowEntity existingFlow = metadataService.getFlowById(versionedFlow.getIdentifier());
final FlowEntity existingFlow = metadataService.getFlowById(versionedFlow.getBucketIdentifier(), versionedFlow.getIdentifier());
if (existingFlow == null) {
throw new ResourceNotFoundException("VersionedFlow does not exist for identifier: " + versionedFlow.getIdentifier());
}
@ -422,7 +452,10 @@ public class RegistryService {
}
}
public VersionedFlow deleteFlow(final String flowIdentifier) {
public VersionedFlow deleteFlow(final String bucketIdentifier, final String flowIdentifier) {
if (StringUtils.isBlank(bucketIdentifier)) {
throw new IllegalArgumentException("Bucket Identifier cannot be null or blank");
}
if (StringUtils.isBlank(flowIdentifier)) {
throw new IllegalArgumentException("Flow Identifier cannot be null or blank");
}
@ -430,7 +463,7 @@ public class RegistryService {
writeLock.lock();
try {
// ensure the flow exists
final FlowEntity existingFlow = metadataService.getFlowById(flowIdentifier);
final FlowEntity existingFlow = metadataService.getFlowById(bucketIdentifier, flowIdentifier);
if (existingFlow == null) {
throw new ResourceNotFoundException("VersionedFlow does not exist for identifier: " + flowIdentifier);
}
@ -472,7 +505,7 @@ public class RegistryService {
}
// ensure the flow exists
final FlowEntity existingFlow = metadataService.getFlowById(snapshotMetadata.getFlowIdentifier());
final FlowEntity existingFlow = metadataService.getFlowById(snapshotMetadata.getBucketIdentifier(), snapshotMetadata.getFlowIdentifier());
if (existingFlow == null) {
throw new ResourceNotFoundException("VersionedFlow does not exist for identifier: " + snapshotMetadata.getFlowIdentifier());
}
@ -518,9 +551,13 @@ public class RegistryService {
}
}
public VersionedFlowSnapshot getFlowSnapshot(final String flowIdentifier, final Integer version) {
public VersionedFlowSnapshot getFlowSnapshot(final String bucketIdentifier, final String flowIdentifier, final Integer version) {
if (StringUtils.isBlank(bucketIdentifier)) {
throw new IllegalArgumentException("Bucket identifier cannot be null or blank");
}
if (StringUtils.isBlank(flowIdentifier)) {
throw new IllegalArgumentException("Flow Identifier cannot be null or blank");
throw new IllegalArgumentException("Flow identifier cannot be null or blank");
}
if (version == null) {
@ -530,17 +567,13 @@ public class RegistryService {
readLock.lock();
try {
// ensure the snapshot exists
final FlowSnapshotEntity snapshotEntity = metadataService.getFlowSnapshot(flowIdentifier, version);
final FlowSnapshotEntity snapshotEntity = metadataService.getFlowSnapshot(bucketIdentifier, flowIdentifier, version);
if (snapshotEntity == null) {
throw new ResourceNotFoundException("VersionedFlowSnapshot does not exist for flow " + flowIdentifier + " and version " + version);
}
final FlowEntity flow = snapshotEntity.getFlow();
final String flowId = flow.getId();
final String bucketId = flow.getBucket().getId();
// get the serialized bytes of the snapshot
final byte[] serializedSnapshot = flowPersistenceProvider.getSnapshot(bucketId, flowId, version);
final byte[] serializedSnapshot = flowPersistenceProvider.getSnapshot(bucketIdentifier, flowIdentifier, version);
if (serializedSnapshot == null || serializedSnapshot.length == 0) {
throw new IllegalStateException("No serialized content found for snapshot with flow identifier "
@ -554,9 +587,13 @@ public class RegistryService {
}
}
public VersionedFlowSnapshotMetadata deleteFlowSnapshot(final String flowIdentifier, final Integer version) {
public VersionedFlowSnapshotMetadata deleteFlowSnapshot(final String bucketIdentifier, final String flowIdentifier, final Integer version) {
if (StringUtils.isBlank(bucketIdentifier)) {
throw new IllegalArgumentException("Bucket identifier cannot be null or blank");
}
if (StringUtils.isBlank(flowIdentifier)) {
throw new IllegalArgumentException("Flow Identifier cannot be null or blank");
throw new IllegalArgumentException("Flow identifier cannot be null or blank");
}
if (version == null) {
@ -566,18 +603,14 @@ public class RegistryService {
writeLock.lock();
try {
// ensure the snapshot exists
final FlowSnapshotEntity snapshotEntity = metadataService.getFlowSnapshot(flowIdentifier, version);
final FlowSnapshotEntity snapshotEntity = metadataService.getFlowSnapshot(bucketIdentifier, flowIdentifier, version);
if (snapshotEntity == null) {
throw new ResourceNotFoundException("VersionedFlowSnapshot does not exist for flow "
+ flowIdentifier + " and version " + version);
}
final FlowEntity flow = snapshotEntity.getFlow();
final String flowId = flow.getId();
final String bucketId = flow.getBucket().getId();
// delete the content of the snapshot
flowPersistenceProvider.deleteSnapshot(bucketId, flowId, version);
flowPersistenceProvider.deleteSnapshot(bucketIdentifier, flowIdentifier, version);
// delete the snapshot itself
metadataService.deleteFlowSnapshot(snapshotEntity);

Просмотреть файл

@ -26,6 +26,8 @@ import java.util.List;
*/
public class QueryParameters {
public static final QueryParameters EMPTY_PARAMETERS = new QueryParameters.Builder().build();
private final Integer pageNum;
private final Integer numRows;

Просмотреть файл

@ -0,0 +1,67 @@
<?xml version="1.0"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- user group providers type -->
<xs:complexType name="UserGroupProvider">
<xs:sequence>
<xs:element name="identifier" type="xs:string"/>
<xs:element name="class" type="xs:string"/>
<xs:element name="property" type="Prop" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<!-- access policy provider type -->
<xs:complexType name="AccessPolicyProvider">
<xs:sequence>
<xs:element name="identifier" type="xs:string"/>
<xs:element name="class" type="xs:string"/>
<xs:element name="property" type="Prop" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<!-- authorizers type -->
<xs:complexType name="Authorizer">
<xs:sequence>
<xs:element name="identifier" type="xs:string"/>
<xs:element name="class" type="xs:string"/>
<xs:element name="classpath" type="xs:string" minOccurs="0"/>
<xs:element name="property" type="Prop" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- Name/Value properties-->
<xs:complexType name="Prop">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string"></xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<!-- authorizers -->
<xs:element name="authorizers">
<xs:complexType>
<xs:sequence>
<xs:element name="userGroupProvider" type="UserGroupProvider" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="accessPolicyProvider" type="AccessPolicyProvider" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="authorizer" type="Authorizer" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Просмотреть файл

@ -356,8 +356,8 @@ public class TestRegistryService {
@Test(expected = ResourceNotFoundException.class)
public void testGetFlowDoesNotExist() {
when(metadataService.getFlowById(any(String.class))).thenReturn(null);
registryService.getFlow("flow1", false);
when(metadataService.getFlowById(any(String.class), any(String.class))).thenReturn(null);
registryService.getFlow("bucket1","flow1", false);
}
@Test
@ -376,9 +376,9 @@ public class TestRegistryService {
flowEntity.setModified(new Date());
flowEntity.setBucket(existingBucket);
when(metadataService.getFlowById(flowEntity.getId())).thenReturn(flowEntity);
when(metadataService.getFlowById(existingBucket.getId(), flowEntity.getId())).thenReturn(flowEntity);
final VersionedFlow versionedFlow = registryService.getFlow(flowEntity.getId(), false);
final VersionedFlow versionedFlow = registryService.getFlow(existingBucket.getId(), flowEntity.getId(), false);
assertNotNull(versionedFlow);
assertEquals(flowEntity.getId(), versionedFlow.getIdentifier());
assertEquals(flowEntity.getName(), versionedFlow.getName());
@ -475,9 +475,10 @@ public class TestRegistryService {
@Test(expected = ResourceNotFoundException.class)
public void testUpdateFlowDoesNotExist() {
final VersionedFlow versionedFlow = new VersionedFlow();
versionedFlow.setBucketIdentifier("b1");
versionedFlow.setIdentifier("flow1");
when(metadataService.getFlowById(versionedFlow.getIdentifier())).thenReturn(null);
when(metadataService.getFlowById(versionedFlow.getBucketIdentifier(), versionedFlow.getIdentifier())).thenReturn(null);
registryService.updateFlow(versionedFlow);
}
@ -498,7 +499,7 @@ public class TestRegistryService {
flowToUpdate.setModified(new Date());
flowToUpdate.setBucket(existingBucket);
when(metadataService.getFlowById(flowToUpdate.getId())).thenReturn(flowToUpdate);
when(metadataService.getFlowById(existingBucket.getId(), flowToUpdate.getId())).thenReturn(flowToUpdate);
final FlowEntity otherFlow = new FlowEntity();
otherFlow.setId("flow2");
@ -512,6 +513,7 @@ public class TestRegistryService {
final VersionedFlow versionedFlow = new VersionedFlow();
versionedFlow.setIdentifier(flowToUpdate.getId());
versionedFlow.setBucketIdentifier(existingBucket.getId());
versionedFlow.setName(otherFlow.getName());
registryService.updateFlow(versionedFlow);
@ -533,12 +535,13 @@ public class TestRegistryService {
flowToUpdate.setModified(new Date());
flowToUpdate.setBucket(existingBucket);
when(metadataService.getFlowById(flowToUpdate.getId())).thenReturn(flowToUpdate);
when(metadataService.getFlowById(existingBucket.getId(), flowToUpdate.getId())).thenReturn(flowToUpdate);
when(metadataService.getFlowsByName(flowToUpdate.getName())).thenReturn(Collections.singletonList(flowToUpdate));
doAnswer(updateFlowAnswer()).when(metadataService).updateFlow(any(FlowEntity.class));
final VersionedFlow versionedFlow = new VersionedFlow();
versionedFlow.setBucketIdentifier(flowToUpdate.getBucket().getId());
versionedFlow.setIdentifier(flowToUpdate.getId());
versionedFlow.setName("New Flow Name");
versionedFlow.setDescription("This is a new description");
@ -560,8 +563,8 @@ public class TestRegistryService {
@Test(expected = ResourceNotFoundException.class)
public void testDeleteFlowDoesNotExist() {
when(metadataService.getFlowById(any(String.class))).thenReturn(null);
registryService.deleteFlow("flow1");
when(metadataService.getFlowById(any(String.class), any(String.class))).thenReturn(null);
registryService.deleteFlow("b1", "flow1");
}
@Test
@ -580,10 +583,10 @@ public class TestRegistryService {
flowToDelete.setModified(new Date());
flowToDelete.setBucket(existingBucket);
when(metadataService.getFlowById(flowToDelete.getId())).thenReturn(flowToDelete);
when(metadataService.getFlowById(existingBucket.getId(), flowToDelete.getId())).thenReturn(flowToDelete);
when(metadataService.getFlowsByName(flowToDelete.getName())).thenReturn(Collections.singletonList(flowToDelete));
final VersionedFlow deletedFlow = registryService.deleteFlow(flowToDelete.getId());
final VersionedFlow deletedFlow = registryService.deleteFlow(existingBucket.getId(), flowToDelete.getId());
assertNotNull(deletedFlow);
assertEquals(flowToDelete.getId(), deletedFlow.getIdentifier());
@ -662,7 +665,7 @@ public class TestRegistryService {
when(metadataService.getBucketById(existingBucket.getId())).thenReturn(existingBucket);
when(metadataService.getFlowById(snapshot.getSnapshotMetadata().getFlowIdentifier())).thenReturn(null);
when(metadataService.getFlowById(existingBucket.getId(), snapshot.getSnapshotMetadata().getFlowIdentifier())).thenReturn(null);
registryService.createFlowSnapshot(snapshot);
}
@ -701,7 +704,7 @@ public class TestRegistryService {
existingFlow.setSnapshots(Collections.singleton(existingSnapshot));
when(metadataService.getFlowById(existingFlow.getId())).thenReturn(existingFlow);
when(metadataService.getFlowById(existingBucket.getId(), existingFlow.getId())).thenReturn(existingFlow);
registryService.createFlowSnapshot(snapshot);
}
@ -740,7 +743,7 @@ public class TestRegistryService {
existingFlow.setSnapshots(Collections.singleton(existingSnapshot));
when(metadataService.getFlowById(existingFlow.getId())).thenReturn(existingFlow);
when(metadataService.getFlowById(existingBucket.getId(), existingFlow.getId())).thenReturn(existingFlow);
// set the version to something that is not the next one-up version
snapshot.getSnapshotMetadata().setVersion(100);
@ -768,7 +771,7 @@ public class TestRegistryService {
existingFlow.setModified(new Date());
existingFlow.setBucket(existingBucket);
when(metadataService.getFlowById(existingFlow.getId())).thenReturn(existingFlow);
when(metadataService.getFlowById(existingBucket.getId(), existingFlow.getId())).thenReturn(existingFlow);
final VersionedFlowSnapshot createdSnapshot = registryService.createFlowSnapshot(snapshot);
assertNotNull(createdSnapshot);
@ -799,7 +802,7 @@ public class TestRegistryService {
existingFlow.setModified(new Date());
existingFlow.setBucket(existingBucket);
when(metadataService.getFlowById(existingFlow.getId())).thenReturn(existingFlow);
when(metadataService.getFlowById(existingBucket.getId(), existingFlow.getId())).thenReturn(existingFlow);
// set the first version to something other than 1
snapshot.getSnapshotMetadata().setVersion(100);
@ -808,18 +811,20 @@ public class TestRegistryService {
@Test(expected = ResourceNotFoundException.class)
public void testGetSnapshotDoesNotExistInMetadataProvider() {
final String bucketId = "b1";
final String flowId = "flow1";
final Integer version = 1;
when(metadataService.getFlowSnapshot(flowId, version)).thenReturn(null);
registryService.getFlowSnapshot(flowId, version);
when(metadataService.getFlowSnapshot(bucketId, flowId, version)).thenReturn(null);
registryService.getFlowSnapshot(bucketId, flowId, version);
}
@Test(expected = IllegalStateException.class)
public void testGetSnapshotDoesNotExistInPersistenceProvider() {
final FlowSnapshotEntity existingSnapshot = createFlowSnapshotEntity();
final FlowSnapshotEntityKey key = existingSnapshot.getId();
final String bucketId = existingSnapshot.getFlow().getBucket().getId();
when(metadataService.getFlowSnapshot(key.getFlowId(), key.getVersion())).thenReturn(existingSnapshot);
when(metadataService.getFlowSnapshot(bucketId, key.getFlowId(), key.getVersion())).thenReturn(existingSnapshot);
when(flowPersistenceProvider.getSnapshot(
existingSnapshot.getFlow().getBucket().getId(),
@ -827,15 +832,16 @@ public class TestRegistryService {
existingSnapshot.getId().getVersion()
)).thenReturn(null);
registryService.getFlowSnapshot(existingSnapshot.getFlow().getId(), existingSnapshot.getId().getVersion());
registryService.getFlowSnapshot(bucketId, existingSnapshot.getFlow().getId(), existingSnapshot.getId().getVersion());
}
@Test
public void testGetSnapshotExists() {
final FlowSnapshotEntity existingSnapshot = createFlowSnapshotEntity();
final FlowSnapshotEntityKey key = existingSnapshot.getId();
final String bucketId = existingSnapshot.getFlow().getBucket().getId();
when(metadataService.getFlowSnapshot(key.getFlowId(), key.getVersion()))
when(metadataService.getFlowSnapshot(bucketId, key.getFlowId(), key.getVersion()))
.thenReturn(existingSnapshot);
// return a non-null, non-zero-length array so something gets passed to the serializer
@ -849,27 +855,29 @@ public class TestRegistryService {
when(snapshotSerializer.deserialize(any(InputStream.class))).thenReturn(snapshotToDeserialize);
final VersionedFlowSnapshot returnedSnapshot = registryService.getFlowSnapshot(
existingSnapshot.getFlow().getId(), existingSnapshot.getId().getVersion());
bucketId, existingSnapshot.getFlow().getId(), existingSnapshot.getId().getVersion());
assertNotNull(returnedSnapshot);
}
@Test(expected = ResourceNotFoundException.class)
public void testDeleteSnapshotDoesNotExist() {
final String bucketId = "b1";
final String flowId = "flow1";
final Integer version = 1;
when(metadataService.getFlowSnapshot(flowId, version)).thenReturn(null);
registryService.deleteFlowSnapshot(flowId, version);
when(metadataService.getFlowSnapshot(bucketId, flowId, version)).thenReturn(null);
registryService.deleteFlowSnapshot(bucketId, flowId, version);
}
@Test
public void testDeleteSnapshotExists() {
final FlowSnapshotEntity existingSnapshot = createFlowSnapshotEntity();
final FlowSnapshotEntityKey key = existingSnapshot.getId();
final String bucketId = existingSnapshot.getFlow().getBucket().getId();
when(metadataService.getFlowSnapshot(key.getFlowId(), key.getVersion()))
when(metadataService.getFlowSnapshot(bucketId, key.getFlowId(), key.getVersion()))
.thenReturn(existingSnapshot);
final VersionedFlowSnapshotMetadata deletedSnapshot = registryService.deleteFlowSnapshot(key.getFlowId(), key.getVersion());
final VersionedFlowSnapshotMetadata deletedSnapshot = registryService.deleteFlowSnapshot(bucketId, key.getFlowId(), key.getVersion());
assertNotNull(deletedSnapshot);
assertEquals(existingSnapshot.getId().getFlowId(), deletedSnapshot.getFlowIdentifier());

Просмотреть файл

@ -29,11 +29,6 @@
<artifactId>nifi-registry-properties</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>

Просмотреть файл

@ -17,7 +17,11 @@
package org.apache.nifi.registry.properties;
import java.io.File;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,7 +47,11 @@ public class NiFiRegistryProperties extends Properties {
public static final String SECURITY_TRUSTSTORE_TYPE = "nifi.registry.security.truststoreType";
public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.registry.security.truststorePasswd";
public static final String SECURITY_NEED_CLIENT_AUTH = "nifi.registry.security.needClientAuth";
public static final String SECURITY_AUTHORIZED_USERS = "nifi.registry.security.authorized.users";
public static final String SECURITY_AUTHORIZERS_CONFIGURATION_FILE = "nifi.registry.security.authorizers.configuration.file";
public static final String SECURITY_AUTHORIZER = "nifi.registry.security.authorizer";
public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.registry.security.identity.mapping.pattern.";
public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.registry.security.identity.mapping.value.";
public static final String PROVIDERS_CONFIGURATION_FILE = "nifi.registry.providers.configuration.file";
@ -54,6 +62,7 @@ public class NiFiRegistryProperties extends Properties {
public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
public static final String DEFAULT_WAR_DIR = "./lib";
public static final String DEFAULT_PROVIDERS_CONFIGURATION_FILE = "./conf/providers.xml";
public static final String DEFAULT_SECURITY_AUTHORIZERS_CONFIGURATION_FILE = "./conf/authorizers.xml";
public int getWebThreads() {
int webThreads = 200;
@ -142,14 +151,6 @@ public class NiFiRegistryProperties extends Properties {
return new File(getProperty(WEB_WORKING_DIR, DEFAULT_WEB_WORKING_DIR));
}
public File getAuthorizedUsersFile() {
final String authorizedUsersFile = getProperty(SECURITY_AUTHORIZED_USERS);
if (StringUtils.isBlank(authorizedUsersFile)) {
return null;
}
return new File(authorizedUsersFile);
}
public File getProvidersConfigurationFile() {
final String value = getProperty(PROVIDERS_CONFIGURATION_FILE);
if (StringUtils.isBlank(value)) {
@ -167,4 +168,28 @@ public class NiFiRegistryProperties extends Properties {
return getProperty(DATABASE_URL_APPEND);
}
public File getAuthorizersConfigurationFile() {
final String value = getProperty(SECURITY_AUTHORIZERS_CONFIGURATION_FILE);
if (StringUtils.isBlank(value)) {
return new File(DEFAULT_SECURITY_AUTHORIZERS_CONFIGURATION_FILE);
} else {
return new File(value);
}
}
/**
* Retrieves all known property keys.
*
* @return all known property keys
*/
public Set<String> getPropertyKeys() {
Set<String> propertyNames = new HashSet<>();
Enumeration e = this.propertyNames();
for (; e.hasMoreElements(); ){
propertyNames.add((String) e.nextElement());
}
return propertyNames;
}
}

Просмотреть файл

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.properties.util;
import java.util.regex.Pattern;
/**
* Holder to pass around the key, pattern, and replacement from an identity mapping in NiFiProperties.
*/
public class IdentityMapping {
private final String key;
private final Pattern pattern;
private final String replacementValue;
public IdentityMapping(String key, Pattern pattern, String replacementValue) {
this.key = key;
this.pattern = pattern;
this.replacementValue = replacementValue;
}
public String getKey() {
return key;
}
public Pattern getPattern() {
return pattern;
}
public String getReplacementValue() {
return replacementValue;
}
}

Просмотреть файл

@ -0,0 +1,145 @@
/*
* 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.properties.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IdentityMappingUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(IdentityMappingUtil.class);
private static final Pattern backReferencePattern = Pattern.compile("\\$(\\d+)");
/**
* Builds the identity mappings from NiFiRegistryProperties.
*
* @param properties the NiFiRegistryProperties instance
* @return a list of identity mappings
*/
public static List<IdentityMapping> getIdentityMappings(final NiFiRegistryProperties properties) {
final List<IdentityMapping> mappings = new ArrayList<>();
// go through each property
for (String propertyName : properties.getPropertyKeys()) {
if (StringUtils.startsWith(propertyName, NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) {
final String key = StringUtils.substringAfter(propertyName, NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX);
final String identityPattern = properties.getProperty(propertyName);
if (StringUtils.isBlank(identityPattern)) {
LOGGER.warn("Identity Mapping property {} was found, but was empty", new Object[]{propertyName});
continue;
}
final String identityValueProperty = NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key;
final String identityValue = properties.getProperty(identityValueProperty);
if (StringUtils.isBlank(identityValue)) {
LOGGER.warn("Identity Mapping property {} was found, but corresponding value {} was not found",
new Object[]{propertyName, identityValueProperty});
continue;
}
final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue);
mappings.add(identityMapping);
LOGGER.debug("Found Identity Mapping with key = {}, pattern = {}, value = {}",
new Object[] {key, identityPattern, identityValue});
}
}
// sort the list by the key so users can control the ordering in nifi.properties
Collections.sort(mappings, new Comparator<IdentityMapping>() {
@Override
public int compare(IdentityMapping m1, IdentityMapping m2) {
return m1.getKey().compareTo(m2.getKey());
}
});
return mappings;
}
/**
* Checks the given identity against each provided mapping and performs the mapping using the first one that matches.
* If none match then the identity is returned as is.
*
* @param identity the identity to map
* @param mappings the mappings
* @return the mapped identity, or the same identity if no mappings matched
*/
public static String mapIdentity(final String identity, List<IdentityMapping> mappings) {
for (IdentityMapping mapping : mappings) {
Matcher m = mapping.getPattern().matcher(identity);
if (m.matches()) {
final String pattern = mapping.getPattern().pattern();
final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
return identity.replaceAll(pattern, replacementValue);
}
}
return identity;
}
// If we find a back reference that is not valid, then we will treat it as a literal string. For example, if we have 3 capturing
// groups and the Replacement Value has the value is "I owe $8 to him", then we want to treat the $8 as a literal "$8", rather
// than attempting to use it as a back reference.
private static String escapeLiteralBackReferences(final String unescaped, final int numCapturingGroups) {
if (numCapturingGroups == 0) {
return unescaped;
}
String value = unescaped;
final Matcher backRefMatcher = backReferencePattern.matcher(value);
while (backRefMatcher.find()) {
final String backRefNum = backRefMatcher.group(1);
if (backRefNum.startsWith("0")) {
continue;
}
final int originalBackRefIndex = Integer.parseInt(backRefNum);
int backRefIndex = originalBackRefIndex;
// if we have a replacement value like $123, and we have less than 123 capturing groups, then
// we want to truncate the 3 and use capturing group 12; if we have less than 12 capturing groups,
// then we want to truncate the 2 and use capturing group 1; if we don't have a capturing group then
// we want to truncate the 1 and get 0.
while (backRefIndex > numCapturingGroups && backRefIndex >= 10) {
backRefIndex /= 10;
}
if (backRefIndex > numCapturingGroups) {
final StringBuilder sb = new StringBuilder(value.length() + 1);
final int groupStart = backRefMatcher.start(1);
sb.append(value.substring(0, groupStart - 1));
sb.append("\\");
sb.append(value.substring(groupStart - 1));
value = sb.toString();
}
}
return value;
}
}

Просмотреть файл

@ -25,11 +25,5 @@
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Просмотреть файл

@ -1,20 +0,0 @@
<?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.
-->
<users>
<!--
<user dn="[user dn]"></user>
-->
</users>

Просмотреть файл

@ -0,0 +1,141 @@
<?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 the userGroupProviders, accessPolicyProviders, and authorizers to use when running securely. In order
to use a specific authorizer it must be configured here and its identifier must be specified in the nifi.properties file.
If the authorizer is a managedAuthorizer, it may need to be configured with an accessPolicyProvider and an userGroupProvider.
This file allows for configuration of them, but they must be configured in order:
...
all userGroupProviders
all accessPolicyProviders
all Authorizers
...
-->
<authorizers>
<!--
The FileUserGroupProvider will provide support for managing users and groups which is backed by a file
on the local file system.
- Users File - The file where the FileUserGroupProvider will store users and groups.
- Initial User Identity [unique key] - The identity of a users and systems to seed the Users File. The name of
each property must be unique, for example: "Initial User Identity A", "Initial User Identity B",
"Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities,
so the values should be the unmapped identities (i.e. full DN from a certificate).
-->
<userGroupProvider>
<identifier>file-user-group-provider</identifier>
<class>org.apache.nifi.registry.authorization.file.FileUserGroupProvider</class>
<property name="Users File">./conf/users.xml</property>
<property name="Initial User Identity 1"><!--CN=abc, OU=xyz--></property>
</userGroupProvider>
<!--
The CompositeUserGroupProvider will provide support for retrieving users and groups from multiple sources.
- User Group Provider [unique key] - The identifier of user group providers to load from. The name of
each property must be unique, for example: "User Group Provider A", "User Group Provider B",
"User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"
NOTE: Any identity mapping rules specified in nifi.properties are not applied in this implementation. This behavior
would need to be applied by the base implementation.
-->
<!-- To enable the composite-user-group-provider remove 2 lines. This is 1 of 2.
<userGroupProvider>
<identifier>composite-user-group-provider</identifier>
<class>org.apache.nifi.authorization.CompositeUserGroupProvider</class>
<property name="User Group Provider 1"></property>
</userGroupProvider>
To enable the composite-user-group-provider remove 2 lines. This is 2 of 2. -->
<!--
The CompositeConfigurableUserGroupProvider will provide support for retrieving users and groups from multiple sources.
Additionally, a single configurable user group provider is required. Users from the configurable user group provider
are configurable, however users loaded from one of the User Group Provider [unique key] will not be.
- Configurable User Group Provider - A configurable user group provider.
- User Group Provider [unique key] - The identifier of user group providers to load from. The name of
each property must be unique, for example: "User Group Provider A", "User Group Provider B",
"User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"
NOTE: Any identity mapping rules specified in nifi.properties are not applied in this implementation. This behavior
would need to be applied by the base implementation.
-->
<!-- To enable the composite-configurable-user-group-provider remove 2 lines. This is 1 of 2.
<userGroupProvider>
<identifier>composite-configurable-user-group-provider</identifier>
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
<property name="Configurable User Group Provider">file-user-group-provider</property>
<property name="User Group Provider 1"></property>
</userGroupProvider>
To enable the composite-configurable-user-group-provider remove 2 lines. This is 2 of 2. -->
<!--
The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file
on the local file system.
- User Group Provider - The identifier for an User Group Provider defined above that will be used to access
users and groups for use in the managed access policies.
- Authorizations File - The file where the FileAccessPolicyProvider will store policies.
- Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and
given the ability to create additional users, groups, and policies. The value of this property could be
a DN when using certificates or LDAP. This property will only be used when there
are no other policies defined.
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the initial admin identity,
so the value should be the unmapped identity. This identity must be found in the configured User Group Provider.
- Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node
should be defined, so that every node knows about every other node. If not clustered these properties can be ignored.
The name of each property must be unique, for example for a three node cluster:
"Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3"
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities,
so the values should be the unmapped identities (i.e. full DN from a certificate). This identity must be found
in the configured User Group Provider.
-->
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.registry.authorization.file.FileAccessPolicyProvider</class>
<property name="User Group Provider">file-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity"><!-- CN=abc, OU=xyz --></property>
<!--<property name="Node Identity 1"></property>-->
</accessPolicyProvider>
<!--
The StandardManagedAuthorizer. This authorizer implementation must be configured with the
Access Policy Provider which it will use to access and manage users, groups, and policies.
These users, groups, and policies will be used to make all access decisions during authorization
requests.
- Access Policy Provider - The identifier for an Access Policy Provider defined above.
-->
<authorizer>
<identifier>managed-authorizer</identifier>
<class>org.apache.nifi.registry.authorization.StandardManagedAuthorizer</class>
<property name="Access Policy Provider">file-access-policy-provider</property>
</authorizer>
</authorizers>

Просмотреть файл

@ -31,7 +31,8 @@ nifi.registry.security.truststore=${nifi.registry.security.truststore}
nifi.registry.security.truststoreType=${nifi.registry.security.truststoreType}
nifi.registry.security.truststorePasswd=${nifi.registry.security.truststorePasswd}
nifi.registry.security.needClientAuth=${nifi.registry.security.needClientAuth}
nifi.registry.security.authorized.users=${nifi.registry.security.authorized.users}
nifi.registry.security.authorizers.configuration.file=${nifi.registry.security.authorizers.configuration.file}
nifi.registry.security.authorizer=${nifi.registry.security.authorizer}
# providers properties #
nifi.registry.providers.configuration.file=${nifi.registry.providers.configuration.file}

Просмотреть файл

@ -0,0 +1,97 @@
<?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">
<parent>
<artifactId>nifi-registry</artifactId>
<groupId>org.apache.nifi.registry</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nifi-registry-security-api-impl</artifactId>
<packaging>jar</packaging>
<build>
<resources>
<resource>
<directory>src/main/xsd</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<id>authorizations</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<sources>
<source>src/main/xsd/authorizations.xsd</source>
</sources>
<packageName>org.apache.nifi.registry.authorization.file.generated</packageName>
</configuration>
</execution>
<execution>
<id>tenants</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<sources>
<source>src/main/xsd/tenants.xsd</source>
</sources>
<packageName>org.apache.nifi.registry.authorization.file.tenants.generated</packageName>
<clearOutputDir>false</clearOutputDir>
</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/file/generated/*.java,**/authorization/file/tenants/generated/*.java</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-properties</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-security-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

Просмотреть файл

@ -0,0 +1,822 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
/**
* An Authorizer that provides management of users, groups, and policies.
*/
public abstract class AbstractPolicyBasedAuthorizer implements ManagedAuthorizer {
static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
static final String USER_ELEMENT = "user";
static final String GROUP_USER_ELEMENT = "groupUser";
static final String GROUP_ELEMENT = "group";
static final String POLICY_ELEMENT = "policy";
static final String POLICY_USER_ELEMENT = "policyUser";
static final String POLICY_GROUP_ELEMENT = "policyGroup";
static final String IDENTIFIER_ATTR = "identifier";
static final String IDENTITY_ATTR = "identity";
static final String NAME_ATTR = "name";
static final String RESOURCE_ATTR = "resource";
static final String ACTIONS_ATTR = "actions";
@Override
public final void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
doOnConfigured(configurationContext);
}
/**
* Allows sub-classes to take action when onConfigured is called.
*
* @param configurationContext the configuration context
* @throws AuthorizerCreationException if an error occurs during onConfigured process
*/
protected abstract void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
@Override
public final AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final UsersAndAccessPolicies usersAndAccessPolicies = getUsersAndAccessPolicies();
final String resourceIdentifier = request.getResource().getIdentifier();
final AccessPolicy policy = usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, request.getAction());
if (policy == null) {
return AuthorizationResult.resourceNotFound();
}
final User user = usersAndAccessPolicies.getUser(request.getIdentity());
if (user == null) {
return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity()));
}
final Set<Group> userGroups = usersAndAccessPolicies.getGroups(user.getIdentity());
if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
return AuthorizationResult.approved();
}
return AuthorizationResult.denied(request.getExplanationSupplier().get());
}
/**
* Determines if the policy contains one of the user's groups.
*
* @param userGroups the set of the user's groups
* @param policy the policy
* @return true if one of the Groups in userGroups is contained in the policy
*/
private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
if (userGroups.isEmpty() || policy.getGroups().isEmpty()) {
return false;
}
for (Group userGroup : userGroups) {
if (policy.getGroups().contains(userGroup.getIdentifier())) {
return true;
}
}
return false;
}
/**
* Adds a new group.
*
* @param group the Group to add
* @return the added Group
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if a group with the same name already exists
*/
public final synchronized Group addGroup(Group group) throws AuthorizationAccessException {
return doAddGroup(group);
}
/**
* Adds a new group.
*
* @param group the Group to add
* @return the added Group
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Group doAddGroup(Group group) throws AuthorizationAccessException;
/**
* Retrieves a Group by id.
*
* @param identifier the identifier of the Group to retrieve
* @return the Group with the given identifier, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Group getGroup(String identifier) throws AuthorizationAccessException;
/**
* The group represented by the provided instance will be updated based on the provided instance.
*
* @param group an updated group instance
* @return the updated group instance, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a group with the same name
*/
public final synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
return doUpdateGroup(group);
}
/**
* The group represented by the provided instance will be updated based on the provided instance.
*
* @param group an updated group instance
* @return the updated group instance, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Group doUpdateGroup(Group group) throws AuthorizationAccessException;
/**
* Deletes the given group.
*
* @param group the group to delete
* @return the deleted group, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Group deleteGroup(Group group) throws AuthorizationAccessException;
/**
* Deletes the group with the given identifier.
*
* @param groupIdentifier the id of the group to delete
* @return the deleted group, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException;
/**
* Retrieves all groups.
*
* @return a list of groups
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Set<Group> getGroups() throws AuthorizationAccessException;
/**
* Adds the given user.
*
* @param user the user to add
* @return the user that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a user with the same identity
*/
public final synchronized User addUser(User user) throws AuthorizationAccessException {
return doAddUser(user);
}
/**
* Adds the given user.
*
* @param user the user to add
* @return the user that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract User doAddUser(User user) throws AuthorizationAccessException;
/**
* Retrieves the user with the given identifier.
*
* @param identifier the id of the user to retrieve
* @return the user with the given id, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract User getUser(String identifier) throws AuthorizationAccessException;
/**
* Retrieves the user with the given identity.
*
* @param identity the identity of the user to retrieve
* @return the user with the given identity, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract User getUserByIdentity(String identity) throws AuthorizationAccessException;
/**
* The user represented by the provided instance will be updated based on the provided instance.
*
* @param user an updated user instance
* @return the updated user instance, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a user with the same identity
*/
public final synchronized User updateUser(final User user) throws AuthorizationAccessException {
return doUpdateUser(user);
}
/**
* The user represented by the provided instance will be updated based on the provided instance.
*
* @param user an updated user instance
* @return the updated user instance, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract User doUpdateUser(User user) throws AuthorizationAccessException;
/**
* Deletes the given user.
*
* @param user the user to delete
* @return the user that was deleted, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract User deleteUser(User user) throws AuthorizationAccessException;
/**
* Deletes the user with the given id.
*
* @param userIdentifier the identifier of the user to delete
* @return the user that was deleted, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract User deleteUser(String userIdentifier) throws AuthorizationAccessException;
/**
* Retrieves all users.
*
* @return a list of users
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Set<User> getUsers() throws AuthorizationAccessException;
/**
* Adds the given policy ensuring that multiple policies can not be added for the same resource and action.
*
* @param accessPolicy the policy to add
* @return the policy that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public final synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return doAddAccessPolicy(accessPolicy);
}
/**
* Adds the given policy.
*
* @param accessPolicy the policy to add
* @return the policy that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
protected abstract AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Retrieves the policy with the given identifier.
*
* @param identifier the id of the policy to retrieve
* @return the policy with the given id, or null if no matching policy exists
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException;
/**
* The policy represented by the provided instance will be updated based on the provided instance.
*
* @param accessPolicy an updated policy
* @return the updated policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Deletes the given policy.
*
* @param policy the policy to delete
* @return the deleted policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException;
/**
* Deletes the policy with the given id.
*
* @param policyIdentifier the id of the policy to delete
* @return the deleted policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract AccessPolicy deleteAccessPolicy(String policyIdentifier) throws AuthorizationAccessException;
/**
* Retrieves all access policies.
*
* @return a list of policies
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException;
/**
* Returns the UserAccessPolicies instance.
*
* @return the UserAccessPolicies instance
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public abstract UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException;
/**
* Returns whether the proposed fingerprint is inheritable.
*
* @param proposedFingerprint the proposed fingerprint
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
@Override
public final void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try {
// ensure we understand the proposed fingerprint
parsePoliciesUsersAndGroups(proposedFingerprint);
} catch (final AuthorizationAccessException e) {
throw new UninheritableAuthorizationsException("Unable to parse proposed fingerprint: " + e);
}
final List<User> users = getSortedUsers();
final List<Group> groups = getSortedGroups();
final List<AccessPolicy> accessPolicies = getSortedAccessPolicies();
// ensure we're in a state to inherit
if (!users.isEmpty() || !groups.isEmpty() || !accessPolicies.isEmpty()) {
throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current Authorizations is not empty..");
}
}
/**
* Parses the fingerprint and adds any users, groups, and policies to the current Authorizer.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
*/
@Override
public final void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException {
if (fingerprint == null || fingerprint.trim().isEmpty()) {
return;
}
final PoliciesUsersAndGroups policiesUsersAndGroups = parsePoliciesUsersAndGroups(fingerprint);
policiesUsersAndGroups.getUsers().forEach(user -> addUser(user));
policiesUsersAndGroups.getGroups().forEach(group -> addGroup(group));
policiesUsersAndGroups.getAccessPolicies().forEach(policy -> addAccessPolicy(policy));
}
private PoliciesUsersAndGroups parsePoliciesUsersAndGroups(final String fingerprint) {
final List<AccessPolicy> accessPolicies = new ArrayList<>();
final List<User> users = new ArrayList<>();
final List<Group> groups = new ArrayList<>();
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
// parse all the users and add them to the current authorizer
NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
for (int i=0; i < userNodes.getLength(); i++) {
Node userNode = userNodes.item(i);
users.add(parseUser((Element) userNode));
}
// parse all the groups and add them to the current authorizer
NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
for (int i=0; i < groupNodes.getLength(); i++) {
Node groupNode = groupNodes.item(i);
groups.add(parseGroup((Element) groupNode));
}
// parse all the policies and add them to the current authorizer
NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT);
for (int i=0; i < policyNodes.getLength(); i++) {
Node policyNode = policyNodes.item(i);
accessPolicies.add(parsePolicy((Element) policyNode));
}
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
return new PoliciesUsersAndGroups(accessPolicies, users, groups);
}
private User parseUser(final Element element) {
final User.Builder builder = new User.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.identity(element.getAttribute(IDENTITY_ATTR));
return builder.build();
}
private Group parseGroup(final Element element) {
final Group.Builder builder = new Group.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.name(element.getAttribute(NAME_ATTR));
NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT);
for (int i=0; i < groupUsers.getLength(); i++) {
Element groupUserNode = (Element) groupUsers.item(i);
builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR));
}
return builder.build();
}
private AccessPolicy parsePolicy(final Element element) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.resource(element.getAttribute(RESOURCE_ATTR));
final String actions = element.getAttribute(ACTIONS_ATTR);
if (actions.equals(RequestAction.READ.name())) {
builder.action(RequestAction.READ);
} else if (actions.equals(RequestAction.WRITE.name())) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + actions);
}
NodeList policyUsers = element.getElementsByTagName(POLICY_USER_ELEMENT);
for (int i=0; i < policyUsers.getLength(); i++) {
Element policyUserNode = (Element) policyUsers.item(i);
builder.addUser(policyUserNode.getAttribute(IDENTIFIER_ATTR));
}
NodeList policyGroups = element.getElementsByTagName(POLICY_GROUP_ELEMENT);
for (int i=0; i < policyGroups.getLength(); i++) {
Element policyGroupNode = (Element) policyGroups.item(i);
builder.addGroup(policyGroupNode.getAttribute(IDENTIFIER_ATTR));
}
return builder.build();
}
@Override
public final AccessPolicyProvider getAccessPolicyProvider() {
return new ConfigurableAccessPolicyProvider() {
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getAccessPolicies();
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getAccessPolicy(identifier);
}
@Override
public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.addAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.updateAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteAccessPolicy(accessPolicyIdentifier);
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
return usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, action);
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public UserGroupProvider getUserGroupProvider() {
return new ConfigurableUserGroupProvider() {
@Override
public User addUser(User user) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.addUser(user);
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.updateUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteUser(user);
}
@Override
public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteUser(userIdentifier);
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.addGroup(group);
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.updateGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteGroup(group);
}
@Override
public Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteGroup(groupIdentifier);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getUsers();
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getUser(identifier);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getUserByIdentity(identity);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getGroups();
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getGroup(identifier);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
final User user = usersAndAccessPolicies.getUser(identity);
final Set<Group> groups = usersAndAccessPolicies.getGroups(identity);
return new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return groups;
}
};
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two policy-based authorizers represent a compatible set of users,
* groups, and policies.
*
* @return the fingerprint for this Authorizer
*/
@Override
public final String getFingerprint() throws AuthorizationAccessException {
final List<User> users = getSortedUsers();
final List<Group> groups = getSortedGroups();
final List<AccessPolicy> policies = getSortedAccessPolicies();
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("authorizations");
for (User user : users) {
writeUser(writer, user);
}
for (Group group : groups) {
writeGroup(writer, group);
}
for (AccessPolicy policy : policies) {
writePolicy(writer, policy);
}
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException {
writer.writeStartElement(USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier());
writer.writeAttribute(IDENTITY_ATTR, user.getIdentity());
writer.writeEndElement();
}
private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException {
List<String> users = new ArrayList<>(group.getUsers());
Collections.sort(users);
writer.writeStartElement(GROUP_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier());
writer.writeAttribute(NAME_ATTR, group.getName());
for (String user : users) {
writer.writeStartElement(GROUP_USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, user);
writer.writeEndElement();
}
writer.writeEndElement();
}
private void writePolicy(final XMLStreamWriter writer, final AccessPolicy policy) throws XMLStreamException {
// sort the users for the policy
List<String> policyUsers = new ArrayList<>(policy.getUsers());
Collections.sort(policyUsers);
// sort the groups for this policy
List<String> policyGroups = new ArrayList<>(policy.getGroups());
Collections.sort(policyGroups);
writer.writeStartElement(POLICY_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policy.getIdentifier());
writer.writeAttribute(RESOURCE_ATTR, policy.getResource());
writer.writeAttribute(ACTIONS_ATTR, policy.getAction().name());
for (String policyUser : policyUsers) {
writer.writeStartElement(POLICY_USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policyUser);
writer.writeEndElement();
}
for (String policyGroup : policyGroups) {
writer.writeStartElement(POLICY_GROUP_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policyGroup);
writer.writeEndElement();
}
writer.writeEndElement();
}
private List<AccessPolicy> getSortedAccessPolicies() {
final List<AccessPolicy> policies = new ArrayList<>(getAccessPolicies());
Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier));
return policies;
}
private List<Group> getSortedGroups() {
final List<Group> groups = new ArrayList<>(getGroups());
Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
return groups;
}
private List<User> getSortedUsers() {
final List<User> users = new ArrayList<>(getUsers());
Collections.sort(users, Comparator.comparing(User::getIdentifier));
return users;
}
private static class PoliciesUsersAndGroups {
final List<AccessPolicy> accessPolicies;
final List<User> users;
final List<Group> groups;
public PoliciesUsersAndGroups(List<AccessPolicy> accessPolicies, List<User> users, List<Group> groups) {
this.accessPolicies = accessPolicies;
this.users = users;
this.groups = groups;
}
public List<AccessPolicy> getAccessPolicies() {
return accessPolicies;
}
public List<User> getUsers() {
return users;
}
public List<Group> getGroups() {
return groups;
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
/*
* 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.authorization;
import org.apache.nifi.registry.util.PropertyValue;
import org.apache.nifi.registry.util.StandardPropertyValue;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class StandardAuthorizerConfigurationContext implements AuthorizerConfigurationContext {
private final String identifier;
private final Map<String, String> properties;
public StandardAuthorizerConfigurationContext(String identifier, Map<String, String> properties) {
this.identifier = identifier;
this.properties = Collections.unmodifiableMap(new HashMap<String, String>(properties));
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
@Override
public PropertyValue getProperty(String property) {
return new StandardPropertyValue(properties.get(property));
}
}

Просмотреть файл

@ -0,0 +1,55 @@
/*
* 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.authorization;
/**
*
*/
public class StandardAuthorizerInitializationContext implements AuthorizerInitializationContext {
private final String identifier;
private final UserGroupProviderLookup userGroupProviderLookup;
private final AccessPolicyProviderLookup accessPolicyProviderLookup;
private final AuthorizerLookup authorizerLookup;
public StandardAuthorizerInitializationContext(String identifier, UserGroupProviderLookup userGroupProviderLookup,
AccessPolicyProviderLookup accessPolicyProviderLookup, AuthorizerLookup authorizerLookup) {
this.identifier = identifier;
this.userGroupProviderLookup = userGroupProviderLookup;
this.accessPolicyProviderLookup = accessPolicyProviderLookup;
this.authorizerLookup = authorizerLookup;
}
@Override
public String getIdentifier() {
return identifier;
}
public AuthorizerLookup getAuthorizerLookup() {
return authorizerLookup;
}
@Override
public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
return accessPolicyProviderLookup;
}
@Override
public UserGroupProviderLookup getUserGroupProviderLookup() {
return userGroupProviderLookup;
}
}

Просмотреть файл

@ -0,0 +1,264 @@
/*
* 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.authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.util.PropertyValue;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Set;
public class StandardManagedAuthorizer implements ManagedAuthorizer {
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider";
private static final String ACCESS_POLICY_PROVIDER_ELEMENT = "accessPolicyProvider";
private AccessPolicyProviderLookup accessPolicyProviderLookup;
private AccessPolicyProvider accessPolicyProvider;
private UserGroupProvider userGroupProvider;
public StandardManagedAuthorizer() {}
// exposed for testing to inject mocks
public StandardManagedAuthorizer(AccessPolicyProvider accessPolicyProvider, UserGroupProvider userGroupProvider) {
this.accessPolicyProvider = accessPolicyProvider;
this.userGroupProvider = userGroupProvider;
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup();
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final PropertyValue accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider");
if (!accessPolicyProviderKey.isSet()) {
throw new AuthorizerCreationException("The Access Policy Provider must be set.");
}
accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey.getValue());
// ensure the desired access policy provider was found
if (accessPolicyProvider == null) {
throw new AuthorizerCreationException(String.format("Unable to locate configured Access Policy Provider: %s", accessPolicyProviderKey));
}
userGroupProvider = accessPolicyProvider.getUserGroupProvider();
// ensure the desired access policy provider has a user group provider
if (userGroupProvider == null) {
throw new AuthorizerCreationException(String.format("Configured Access Policy Provider %s does not contain a User Group Provider", accessPolicyProviderKey));
}
}
@Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final String resourceIdentifier = request.getResource().getIdentifier();
final AccessPolicy policy = accessPolicyProvider.getAccessPolicy(resourceIdentifier, request.getAction());
if (policy == null) {
return AuthorizationResult.resourceNotFound();
}
final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(request.getIdentity());
final User user = userAndGroups.getUser();
if (user == null) {
return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity()));
}
final Set<Group> userGroups = userAndGroups.getGroups();
if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
return AuthorizationResult.approved();
}
return AuthorizationResult.denied(request.getExplanationSupplier().get());
}
/**
* Determines if the policy contains one of the user's groups.
*
* @param userGroups the set of the user's groups
* @param policy the policy
* @return true if one of the Groups in userGroups is contained in the policy
*/
private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
if (userGroups == null || userGroups.isEmpty() || policy.getGroups().isEmpty()) {
return false;
}
for (Group userGroup : userGroups) {
if (policy.getGroups().contains(userGroup.getIdentifier())) {
return true;
}
}
return false;
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("managedAuthorizations");
writer.writeStartElement(ACCESS_POLICY_PROVIDER_ELEMENT);
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
writer.writeCharacters(((ConfigurableAccessPolicyProvider) accessPolicyProvider).getFingerprint());
}
writer.writeEndElement();
writer.writeStartElement(USER_GROUP_PROVIDER_ELEMENT);
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
writer.writeCharacters(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint());
}
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
if (StringUtils.isBlank(fingerprint)) {
return;
}
final FingerprintHolder fingerprintHolder = parseFingerprint(fingerprint);
if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint()) && accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
((ConfigurableAccessPolicyProvider) accessPolicyProvider).inheritFingerprint(fingerprintHolder.getPolicyFingerprint());
}
if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint()) && userGroupProvider instanceof ConfigurableUserGroupProvider) {
((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(fingerprintHolder.getUserGroupFingerprint());
}
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
final FingerprintHolder fingerprintHolder = parseFingerprint(proposedFingerprint);
if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint())) {
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
((ConfigurableAccessPolicyProvider) accessPolicyProvider).checkInheritability(fingerprintHolder.getPolicyFingerprint());
} else {
throw new UninheritableAuthorizationsException("Policy fingerprint is not blank and the configured AccessPolicyProvider does not support fingerprinting.");
}
}
if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint())) {
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(fingerprintHolder.getUserGroupFingerprint());
} else {
throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting.");
}
}
}
private final FingerprintHolder parseFingerprint(final String fingerprint) throws AuthorizationAccessException {
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
final NodeList accessPolicyProviderList = rootElement.getElementsByTagName(ACCESS_POLICY_PROVIDER_ELEMENT);
if (accessPolicyProviderList.getLength() != 1) {
throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", ACCESS_POLICY_PROVIDER_ELEMENT, fingerprint));
}
final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT);
if (userGroupProviderList.getLength() != 1) {
throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint));
}
final Node accessPolicyProvider = accessPolicyProviderList.item(0);
final Node userGroupProvider = userGroupProviderList.item(0);
return new FingerprintHolder(accessPolicyProvider.getTextContent(), userGroupProvider.getTextContent());
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
}
@Override
public AccessPolicyProvider getAccessPolicyProvider() {
return accessPolicyProvider;
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
private static class FingerprintHolder {
private final String policyFingerprint;
private final String userGroupFingerprint;
public FingerprintHolder(String policyFingerprint, String userGroupFingerprint) {
this.policyFingerprint = policyFingerprint;
this.userGroupFingerprint = userGroupFingerprint;
}
public String getPolicyFingerprint() {
return policyFingerprint;
}
public String getUserGroupFingerprint() {
return userGroupFingerprint;
}
}
}

Просмотреть файл

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization;
import java.util.Set;
/**
* A holder object to provide atomic access to policies for a given resource and users by
* identity. Implementations must ensure consistent access to the data backing this instance.
*/
public interface UsersAndAccessPolicies {
/**
* Retrieves the set of access policies for a given resource and action.
*
* @param resourceIdentifier the resource identifier to retrieve policies for
* @param action the action to retrieve policies for
* @return the access policy for the given resource and action
*/
AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action);
/**
* Retrieves a user by an identity string.
*
* @param identity the identity of the user to retrieve
* @return the user with the given identity
*/
User getUser(final String identity);
/**
* Retrieves the groups for a given user identity.
*
* @param userIdentity a user identity
* @return the set of groups for the given user identity
*/
Set<Group> getGroups(final String userIdentity);
}

Просмотреть файл

@ -0,0 +1,35 @@
/*
* 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.authorization.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*
*/
@Documented
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AuthorizerContext {
}

Просмотреть файл

@ -0,0 +1,185 @@
/*
* 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.authorization.file;
import org.apache.nifi.registry.authorization.file.generated.Authorizations;
import org.apache.nifi.registry.authorization.file.generated.Policies;
import org.apache.nifi.registry.authorization.AccessPolicy;
import org.apache.nifi.registry.authorization.RequestAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A holder to provide atomic access to data structures.
*/
public class AuthorizationsHolder {
private final Authorizations authorizations;
private final Set<AccessPolicy> allPolicies;
private final Map<String, Set<AccessPolicy>> policiesByResource;
private final Map<String, AccessPolicy> policiesById;
/**
* Creates a new holder and populates all convenience authorizations data structures.
*
* @param authorizations the current authorizations instance
*/
public AuthorizationsHolder(final Authorizations authorizations) {
this.authorizations = authorizations;
// load all access policies
final Policies policies = authorizations.getPolicies();
final Set<AccessPolicy> allPolicies = Collections.unmodifiableSet(createAccessPolicies(policies));
// create a convenience map from resource id to policies
final Map<String, Set<AccessPolicy>> policiesByResourceMap = Collections.unmodifiableMap(createResourcePolicyMap(allPolicies));
// create a convenience map from policy id to policy
final Map<String, AccessPolicy> policiesByIdMap = Collections.unmodifiableMap(createPoliciesByIdMap(allPolicies));
// set all the holders
this.allPolicies = allPolicies;
this.policiesByResource = policiesByResourceMap;
this.policiesById = policiesByIdMap;
}
/**
* Creates AccessPolicies from the JAXB Policies.
*
* @param policies the JAXB Policies element
* @return a set of AccessPolicies corresponding to the provided Resources
*/
private Set<AccessPolicy> createAccessPolicies(org.apache.nifi.registry.authorization.file.generated.Policies policies) {
Set<AccessPolicy> allPolicies = new HashSet<>();
if (policies == null || policies.getPolicy() == null) {
return allPolicies;
}
// load the new authorizations
for (final org.apache.nifi.registry.authorization.file.generated.Policy policy : policies.getPolicy()) {
final String policyIdentifier = policy.getIdentifier();
final String resourceIdentifier = policy.getResource();
// start a new builder and set the policy and resource identifiers
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(policyIdentifier)
.resource(resourceIdentifier);
// add each user identifier
for (org.apache.nifi.registry.authorization.file.generated.Policy.User user : policy.getUser()) {
builder.addUser(user.getIdentifier());
}
// add each group identifier
for (org.apache.nifi.registry.authorization.file.generated.Policy.Group group : policy.getGroup()) {
builder.addGroup(group.getIdentifier());
}
// add the appropriate request actions
final String authorizationCode = policy.getAction();
if (authorizationCode.equals(FileAccessPolicyProvider.READ_CODE)) {
builder.action(RequestAction.READ);
} else if (authorizationCode.equals(FileAccessPolicyProvider.WRITE_CODE)){
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + authorizationCode);
}
// build the policy and add it to the map
allPolicies.add(builder.build());
}
return allPolicies;
}
/**
* Creates a map from resource identifier to the set of policies for the given resource.
*
* @param allPolicies the set of all policies
* @return a map from resource identifier to policies
*/
private Map<String, Set<AccessPolicy>> createResourcePolicyMap(final Set<AccessPolicy> allPolicies) {
Map<String, Set<AccessPolicy>> resourcePolicies = new HashMap<>();
for (AccessPolicy policy : allPolicies) {
Set<AccessPolicy> policies = resourcePolicies.get(policy.getResource());
if (policies == null) {
policies = new HashSet<>();
resourcePolicies.put(policy.getResource(), policies);
}
policies.add(policy);
}
return resourcePolicies;
}
/**
* Creates a Map from policy identifier to AccessPolicy.
*
* @param policies the set of all access policies
* @return the Map from policy identifier to AccessPolicy
*/
private Map<String, AccessPolicy> createPoliciesByIdMap(final Set<AccessPolicy> policies) {
Map<String,AccessPolicy> policyMap = new HashMap<>();
for (AccessPolicy policy : policies) {
policyMap.put(policy.getIdentifier(), policy);
}
return policyMap;
}
public Authorizations getAuthorizations() {
return authorizations;
}
public Set<AccessPolicy> getAllPolicies() {
return allPolicies;
}
public Map<String, Set<AccessPolicy>> getPoliciesByResource() {
return policiesByResource;
}
public Map<String, AccessPolicy> getPoliciesById() {
return policiesById;
}
public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) {
if (resourceIdentifier == null) {
throw new IllegalArgumentException("Resource Identifier cannot be null");
}
final Set<AccessPolicy> resourcePolicies = policiesByResource.get(resourceIdentifier);
if (resourcePolicies == null) {
return null;
}
for (AccessPolicy accessPolicy : resourcePolicies) {
if (accessPolicy.getAction() == action) {
return accessPolicy;
}
}
return null;
}
}

Просмотреть файл

@ -0,0 +1,746 @@
/*
* 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.authorization.file;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.util.PropertyValue;
import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
import org.apache.nifi.registry.authorization.file.generated.Authorizations;
import org.apache.nifi.registry.authorization.file.generated.Policies;
import org.apache.nifi.registry.authorization.file.generated.Policy;
import org.apache.nifi.registry.properties.util.IdentityMapping;
import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
import org.apache.nifi.registry.authorization.AccessPolicy;
import org.apache.nifi.registry.authorization.AccessPolicyProviderInitializationContext;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.registry.authorization.ConfigurableAccessPolicyProvider;
import org.apache.nifi.registry.authorization.RequestAction;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.authorization.User;
import org.apache.nifi.registry.authorization.UserGroupProvider;
import org.apache.nifi.registry.authorization.UserGroupProviderLookup;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider {
private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class);
private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd";
private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.registry.authorization.file.generated";
private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH);
/**
* Load the JAXBContext.
*/
private static JAXBContext initializeJaxbContext(final String contextPath) {
try {
return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext.");
}
}
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
private static final String POLICY_ELEMENT = "policy";
private static final String POLICY_USER_ELEMENT = "policyUser";
private static final String POLICY_GROUP_ELEMENT = "policyGroup";
private static final String IDENTIFIER_ATTR = "identifier";
private static final String RESOURCE_ATTR = "resource";
private static final String ACTIONS_ATTR = "actions";
static final String READ_CODE = "R";
static final String WRITE_CODE = "W";
/* TODO, add DELETE_CODE */
/* TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider
* (and also gets us away from requiring magic strings here) */
private static final ResourceActionPair[] INITIAL_ADMIN_ACCESS_POLICIES = {
new ResourceActionPair("/resources", READ_CODE),
new ResourceActionPair("/resources", WRITE_CODE),
new ResourceActionPair("/tenants", READ_CODE),
new ResourceActionPair("/tenants", WRITE_CODE),
new ResourceActionPair("/policies", READ_CODE),
new ResourceActionPair("/policies", WRITE_CODE),
new ResourceActionPair("/buckets", READ_CODE),
new ResourceActionPair("/buckets", WRITE_CODE),
};
static final String PROP_NODE_IDENTITY_PREFIX = "Node Identity ";
static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile(PROP_NODE_IDENTITY_PREFIX + "\\S+");
private Schema authorizationsSchema;
private NiFiRegistryProperties properties;
private File authorizationsFile;
private String initialAdminIdentity;
private List<IdentityMapping> identityMappings;
private UserGroupProvider userGroupProvider;
private UserGroupProviderLookup userGroupProviderLookup;
private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
try {
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD));
// usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
} catch (Exception e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try {
final PropertyValue userGroupProviderIdentifier = configurationContext.getProperty(PROP_USER_GROUP_PROVIDER);
if (!userGroupProviderIdentifier.isSet()) {
throw new AuthorizerCreationException("The user group provider must be specified.");
}
userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue());
if (userGroupProvider == null) {
throw new AuthorizerCreationException("Unable to locate user group provider with identifier " + userGroupProviderIdentifier.getValue());
}
final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
if (StringUtils.isBlank(authorizationsPath.getValue())) {
throw new AuthorizerCreationException("The authorizations file must be specified.");
}
// get the authorizations file and ensure it exists
authorizationsFile = new File(authorizationsPath.getValue());
if (!authorizationsFile.exists()) {
logger.info("Creating new authorizations file at {}", new Object[] {authorizationsFile.getAbsolutePath()});
saveAuthorizations(new Authorizations());
}
// extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
// get the value of the initial admin identity
final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
initialAdminIdentity = initialAdminIdentityProp.isSet() ? IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings) : null;
// // extract any node identities
// nodeIdentities = new HashSet<>();
// for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
// Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey());
// if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
// nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
// }
// }
// load the authorizations
load();
logger.info(String.format("Authorizations file loaded at %s", new Date().toString()));
} catch (AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public UserGroupProvider getUserGroupProvider() {
return userGroupProvider;
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return authorizationsHolder.get().getAllPolicies();
}
@Override
public synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (accessPolicy == null) {
throw new IllegalArgumentException("AccessPolicy cannot be null");
}
// create the new JAXB Policy
final Policy policy = createJAXBPolicy(accessPolicy);
// add the new Policy to the top-level list of policies
final AuthorizationsHolder holder = authorizationsHolder.get();
final Authorizations authorizations = holder.getAuthorizations();
authorizations.getPolicies().getPolicy().add(policy);
saveAndRefreshHolder(authorizations);
return authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
if (identifier == null) {
return null;
}
final AuthorizationsHolder holder = authorizationsHolder.get();
return holder.getPoliciesById().get(identifier);
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
return authorizationsHolder.get().getAccessPolicy(resourceIdentifier, action);
}
@Override
public synchronized AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (accessPolicy == null) {
throw new IllegalArgumentException("AccessPolicy cannot be null");
}
final AuthorizationsHolder holder = this.authorizationsHolder.get();
final Authorizations authorizations = holder.getAuthorizations();
// try to find an existing Authorization that matches the policy id
Policy updatePolicy = null;
for (Policy policy : authorizations.getPolicies().getPolicy()) {
if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
updatePolicy = policy;
break;
}
}
// no matching Policy so return null
if (updatePolicy == null) {
return null;
}
// update the Policy, save, reload, and return
transferUsersAndGroups(accessPolicy, updatePolicy);
saveAndRefreshHolder(authorizations);
return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
}
@Override
public synchronized AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (accessPolicy == null) {
throw new IllegalArgumentException("AccessPolicy cannot be null");
}
return deleteAccessPolicy(accessPolicy.getIdentifier());
}
@Override
public synchronized AccessPolicy deleteAccessPolicy(String accessPolicyIdentifer) throws AuthorizationAccessException {
if (accessPolicyIdentifer == null) {
throw new IllegalArgumentException("Access policy identifier cannot be null");
}
final AuthorizationsHolder holder = this.authorizationsHolder.get();
AccessPolicy deletedPolicy = holder.getPoliciesById().get(accessPolicyIdentifer);
if (deletedPolicy == null) {
return null;
}
// find the matching Policy and remove it
final Authorizations authorizations = holder.getAuthorizations();
Iterator<Policy> policyIter = authorizations.getPolicies().getPolicy().iterator();
while (policyIter.hasNext()) {
final Policy policy = policyIter.next();
if (policy.getIdentifier().equals(accessPolicyIdentifer)) {
policyIter.remove();
break;
}
}
saveAndRefreshHolder(authorizations);
return deletedPolicy;
}
AuthorizationsHolder getAuthorizationsHolder() {
return authorizationsHolder.get();
}
@AuthorizerContext
public void setNiFiProperties(NiFiRegistryProperties properties) {
this.properties = properties;
}
@Override
public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
parsePolicies(fingerprint).forEach(policy -> addAccessPolicy(policy));
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try {
// ensure we can understand the proposed fingerprint
parsePolicies(proposedFingerprint);
} catch (final AuthorizationAccessException e) {
throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
}
// ensure we are in a proper state to inherit the fingerprint
if (!getAccessPolicies().isEmpty()) {
throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current access policies is not empty.");
}
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
final List<AccessPolicy> policies = new ArrayList<>(getAccessPolicies());
Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier));
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("accessPolicies");
for (AccessPolicy policy : policies) {
writePolicy(writer, policy);
}
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
private List<AccessPolicy> parsePolicies(final String fingerprint) {
final List<AccessPolicy> policies = new ArrayList<>();
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
// parse all the policies and add them to the current access policy provider
NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT);
for (int i = 0; i < policyNodes.getLength(); i++) {
Node policyNode = policyNodes.item(i);
policies.add(parsePolicy((Element) policyNode));
}
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
return policies;
}
private AccessPolicy parsePolicy(final Element element) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.resource(element.getAttribute(RESOURCE_ATTR));
final String actions = element.getAttribute(ACTIONS_ATTR);
if (actions.equals(RequestAction.READ.name())) {
builder.action(RequestAction.READ);
} else if (actions.equals(RequestAction.WRITE.name())) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + actions);
}
NodeList policyUsers = element.getElementsByTagName(POLICY_USER_ELEMENT);
for (int i=0; i < policyUsers.getLength(); i++) {
Element policyUserNode = (Element) policyUsers.item(i);
builder.addUser(policyUserNode.getAttribute(IDENTIFIER_ATTR));
}
NodeList policyGroups = element.getElementsByTagName(POLICY_GROUP_ELEMENT);
for (int i=0; i < policyGroups.getLength(); i++) {
Element policyGroupNode = (Element) policyGroups.item(i);
builder.addGroup(policyGroupNode.getAttribute(IDENTIFIER_ATTR));
}
return builder.build();
}
private void writePolicy(final XMLStreamWriter writer, final AccessPolicy policy) throws XMLStreamException {
// sort the users for the policy
List<String> policyUsers = new ArrayList<>(policy.getUsers());
Collections.sort(policyUsers);
// sort the groups for this policy
List<String> policyGroups = new ArrayList<>(policy.getGroups());
Collections.sort(policyGroups);
writer.writeStartElement(POLICY_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policy.getIdentifier());
writer.writeAttribute(RESOURCE_ATTR, policy.getResource());
writer.writeAttribute(ACTIONS_ATTR, policy.getAction().name());
for (String policyUser : policyUsers) {
writer.writeStartElement(POLICY_USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policyUser);
writer.writeEndElement();
}
for (String policyGroup : policyGroups) {
writer.writeStartElement(POLICY_GROUP_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policyGroup);
writer.writeEndElement();
}
writer.writeEndElement();
}
/**
* Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
*
* @throws JAXBException Unable to reload the authorized users file
*/
private synchronized void load() throws JAXBException, SAXException {
// attempt to unmarshal
final Authorizations authorizations = unmarshallAuthorizations();
if (authorizations.getPolicies() == null) {
authorizations.setPolicies(new Policies());
}
final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations);
final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty();
final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity));
// if we are starting fresh then we might need to populate an initial admin
if (emptyAuthorizations && hasInitialAdminIdentity) {
logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity);
populateInitialAdmin(authorizations);
saveAndRefreshHolder(authorizations);
} else {
this.authorizationsHolder.set(authorizationsHolder);
}
}
private void saveAuthorizations(final Authorizations authorizations) throws JAXBException {
final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller();
marshaller.setSchema(authorizationsSchema);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(authorizations, authorizationsFile);
}
private Authorizations unmarshallAuthorizations() throws JAXBException {
final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(authorizationsSchema);
final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class);
return element.getValue();
}
/**
* Creates the initial admin user and sets policies managing buckets, users, and policies.
*
* TODO - move this somewhere into nifi-registry-security-framework so it can be applied to any ConfigurableAccessPolicyProvider
*/
private void populateInitialAdmin(final Authorizations authorizations) {
final User initialAdmin = userGroupProvider.getUserByIdentity(initialAdminIdentity);
if (initialAdmin == null) {
throw new AuthorizerCreationException("Unable to locate initial admin " + initialAdminIdentity + " to seed policies");
}
for (ResourceActionPair resourceAction : INITIAL_ADMIN_ACCESS_POLICIES) {
addUserToAccessPolicy(authorizations, resourceAction.resource, initialAdmin.getIdentifier(), resourceAction.actionCode);
}
}
// /**
// * Creates a user for each node and gives the nodes write permission to /proxy.
// *
// * @param authorizations the overall authorizations
// */
// private void populateNodes(Authorizations authorizations) {
// for (String nodeIdentity : nodeIdentities) {
// final User node = userGroupProvider.getUserByIdentity(nodeIdentity);
// if (node == null) {
// throw new AuthorizerCreationException("Unable to locate node " + nodeIdentity + " to seed policies.");
// }
//
// // grant access to the proxy resource
// addUserToAccessPolicy(authorizations, ResourceType.Proxy.getValue(), node.getIdentifier(), WRITE_CODE);
//
// // grant the user read/write access data of the root group
// if (rootGroupId != null) {
// addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), READ_CODE);
// addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), WRITE_CODE);
// }
// }
// }
/**
* Creates and adds an access policy for the given resource, identity, and actions to the specified authorizations.
*
* @param authorizations the Authorizations instance to add the policy to
* @param resource the resource for the policy
* @param userIdentifier the identifier for the user to add to the policy
* @param action the action for the policy
*/
private void addUserToAccessPolicy(final Authorizations authorizations, final String resource, final String userIdentifier, final String action) {
// first try to find an existing policy for the given resource and action
Policy foundPolicy = null;
for (Policy policy : authorizations.getPolicies().getPolicy()) {
if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
foundPolicy = policy;
break;
}
}
if (foundPolicy == null) {
// if we didn't find an existing policy create a new one
final String uuidSeed = resource + action;
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifierGenerateFromSeed(uuidSeed)
.resource(resource)
.addUser(userIdentifier);
if (action.equals(READ_CODE)) {
builder.action(RequestAction.READ);
} else if (action.equals(WRITE_CODE)) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + action);
}
final AccessPolicy accessPolicy = builder.build();
final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
authorizations.getPolicies().getPolicy().add(jaxbPolicy);
} else {
// otherwise add the user to the existing policy
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(userIdentifier);
foundPolicy.getUser().add(policyUser);
}
}
private Policy createJAXBPolicy(final AccessPolicy accessPolicy) {
final Policy policy = new Policy();
policy.setIdentifier(accessPolicy.getIdentifier());
policy.setResource(accessPolicy.getResource());
switch (accessPolicy.getAction()) {
case READ:
policy.setAction(READ_CODE);
break;
case WRITE:
policy.setAction(WRITE_CODE);
break;
default:
break;
}
transferUsersAndGroups(accessPolicy, policy);
return policy;
}
/**
* Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and
* set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy.
*
* Does not set the identifier.
*
* @param accessPolicy the AccessPolicy to transfer state from
* @param policy the Policy to transfer state to
*/
private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy policy) {
// add users to the policy
policy.getUser().clear();
for (String userIdentifier : accessPolicy.getUsers()) {
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(userIdentifier);
policy.getUser().add(policyUser);
}
// add groups to the policy
policy.getGroup().clear();
for (String groupIdentifier : accessPolicy.getGroups()) {
Policy.Group policyGroup = new Policy.Group();
policyGroup.setIdentifier(groupIdentifier);
policy.getGroup().add(policyGroup);
}
}
/**
* Adds the given user identifier to the policy if it doesn't already exist.
*
* @param userIdentifier a user identifier
* @param policy a policy to add the user to
*/
private void addUserToPolicy(final String userIdentifier, final Policy policy) {
// determine if the user already exists in the policy
boolean userExists = false;
for (Policy.User policyUser : policy.getUser()) {
if (policyUser.getIdentifier().equals(userIdentifier)) {
userExists = true;
break;
}
}
// add the user to the policy if doesn't already exist
if (!userExists) {
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(userIdentifier);
policy.getUser().add(policyUser);
}
}
/**
* Adds the given group identifier to the policy if it doesn't already exist.
*
* @param groupIdentifier a group identifier
* @param policy a policy to add the user to
*/
private void addGroupToPolicy(final String groupIdentifier, final Policy policy) {
// determine if the group already exists in the policy
boolean groupExists = false;
for (Policy.Group policyGroup : policy.getGroup()) {
if (policyGroup.getIdentifier().equals(groupIdentifier)) {
groupExists = true;
break;
}
}
// add the group to the policy if doesn't already exist
if (!groupExists) {
Policy.Group policyGroup = new Policy.Group();
policyGroup.setIdentifier(groupIdentifier);
policy.getGroup().add(policyGroup);
}
}
/**
* Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies.
*
* @param policies the policies to search through
* @param seedIdentity the seedIdentity to use when creating identifiers for new policies
* @param resource the resource for the policy
* @param action the action string for the police (R or RW)
* @return the matching policy or a new policy
*/
private Policy getOrCreatePolicy(final List<Policy> policies, final String seedIdentity, final String resource, final String action) {
Policy foundPolicy = null;
// try to find a policy with the same resource and actions
for (Policy policy : policies) {
if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
foundPolicy = policy;
break;
}
}
// if a matching policy wasn't found then create one
if (foundPolicy == null) {
final String uuidSeed = resource + action + seedIdentity;
final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed);
foundPolicy = new Policy();
foundPolicy.setIdentifier(policyIdentifier);
foundPolicy.setResource(resource);
foundPolicy.setAction(action);
policies.add(foundPolicy);
}
return foundPolicy;
}
/**
* Saves the Authorizations instance by marshalling to a file, then re-populates the
* in-memory data structures and sets the new holder.
*
* Synchronized to ensure only one thread writes the file at a time.
*
* @param authorizations the authorizations to save and populate from
* @throws AuthorizationAccessException if an error occurs saving the authorizations
*/
private synchronized void saveAndRefreshHolder(final Authorizations authorizations) throws AuthorizationAccessException {
try {
saveAuthorizations(authorizations);
this.authorizationsHolder.set(new AuthorizationsHolder(authorizations));
} catch (JAXBException e) {
throw new AuthorizationAccessException("Unable to save Authorizations", e);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
private static class ResourceActionPair {
public String resource;
public String actionCode;
public ResourceActionPair(String resource, String actionCode) {
this.resource = resource;
this.actionCode = actionCode;
}
}
}

Просмотреть файл

@ -0,0 +1,288 @@
/*
* 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.authorization.file;
import org.apache.nifi.registry.authorization.StandardAuthorizerConfigurationContext;
import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
import org.apache.nifi.registry.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.registry.authorization.AccessPolicy;
import org.apache.nifi.registry.authorization.AccessPolicyProviderInitializationContext;
import org.apache.nifi.registry.authorization.AccessPolicyProviderLookup;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.AuthorizerInitializationContext;
import org.apache.nifi.registry.authorization.Group;
import org.apache.nifi.registry.authorization.RequestAction;
import org.apache.nifi.registry.authorization.User;
import org.apache.nifi.registry.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.registry.authorization.UserGroupProviderLookup;
import org.apache.nifi.registry.authorization.UsersAndAccessPolicies;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
/**
* Provides authorizes requests to resources using policies persisted in a file.
*/
public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
private static final Logger logger = LoggerFactory.getLogger(FileAuthorizer.class);
private static final String FILE_USER_GROUP_PROVIDER_ID = "file-user-group-provider";
private static final String FILE_ACCESS_POLICY_PROVIDER_ID = "file-access-policy-provider";
static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized Users File";
private FileUserGroupProvider userGroupProvider = new FileUserGroupProvider();
private FileAccessPolicyProvider accessPolicyProvider = new FileAccessPolicyProvider();
@Override
public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
// initialize the user group provider
userGroupProvider.initialize(new UserGroupProviderInitializationContext() {
@Override
public String getIdentifier() {
return FILE_USER_GROUP_PROVIDER_ID;
}
@Override
public UserGroupProviderLookup getUserGroupProviderLookup() {
return (identifier) -> null;
}
});
// initialize the access policy provider
accessPolicyProvider.initialize(new AccessPolicyProviderInitializationContext() {
@Override
public String getIdentifier() {
return FILE_ACCESS_POLICY_PROVIDER_ID;
}
@Override
public UserGroupProviderLookup getUserGroupProviderLookup() {
return (identifier) -> {
if (FILE_USER_GROUP_PROVIDER_ID.equals(identifier)) {
return userGroupProvider;
}
return null;
};
}
@Override
public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
return (identifier) -> null;
}
});
}
@Override
public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final Map<String, String> configurationProperties = configurationContext.getProperties();
// relay the relevant config
final Map<String, String> userGroupProperties = new HashMap<>();
if (configurationProperties.containsKey(FileUserGroupProvider.PROP_TENANTS_FILE)) {
userGroupProperties.put(FileUserGroupProvider.PROP_TENANTS_FILE, configurationProperties.get(FileUserGroupProvider.PROP_TENANTS_FILE));
}
if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) {
userGroupProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
}
// relay the relevant config
final Map<String, String> accessPolicyProperties = new HashMap<>();
accessPolicyProperties.put(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER, FILE_USER_GROUP_PROVIDER_ID);
if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE)) {
accessPolicyProperties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, configurationProperties.get(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE));
}
if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
accessPolicyProperties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
}
if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) {
accessPolicyProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
}
// ensure all node identities are seeded into the user provider
configurationProperties.forEach((property, value) -> {
final Matcher matcher = FileAccessPolicyProvider.NODE_IDENTITY_PATTERN.matcher(property);
if (matcher.matches()) {
accessPolicyProperties.put(property, value);
userGroupProperties.put(property.replace(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX, FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX), value);
}
});
// ensure the initial admin is seeded into the user provider if appropriate
if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
int i = 0;
while (true) {
final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
if (!userGroupProperties.containsKey(key)) {
userGroupProperties.put(key, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
break;
}
}
}
// configure the user group provider
userGroupProvider.onConfigured(new StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, userGroupProperties));
// configure the access policy provider
accessPolicyProvider.onConfigured(new StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, accessPolicyProperties));
}
@Override
public void preDestruction() {
}
// ------------------ Groups ------------------
@Override
public synchronized Group doAddGroup(Group group) throws AuthorizationAccessException {
return userGroupProvider.addGroup(group);
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return userGroupProvider.getGroup(identifier);
}
@Override
public synchronized Group doUpdateGroup(Group group) throws AuthorizationAccessException {
return userGroupProvider.updateGroup(group);
}
@Override
public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
return userGroupProvider.deleteGroup(group);
}
@Override
public synchronized Group deleteGroup(String groupId) throws AuthorizationAccessException {
return userGroupProvider.deleteGroup(groupId);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return userGroupProvider.getGroups();
}
// ------------------ Users ------------------
@Override
public synchronized User doAddUser(final User user) throws AuthorizationAccessException {
return userGroupProvider.addUser(user);
}
@Override
public User getUser(final String identifier) throws AuthorizationAccessException {
return userGroupProvider.getUser(identifier);
}
@Override
public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
return userGroupProvider.getUserByIdentity(identity);
}
@Override
public synchronized User doUpdateUser(final User user) throws AuthorizationAccessException {
return userGroupProvider.updateUser(user);
}
@Override
public synchronized User deleteUser(final User user) throws AuthorizationAccessException {
return userGroupProvider.deleteUser(user);
}
@Override
public synchronized User deleteUser(final String userId) throws AuthorizationAccessException {
return userGroupProvider.deleteUser(userId);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return userGroupProvider.getUsers();
}
// ------------------ AccessPolicies ------------------
@Override
public synchronized AccessPolicy doAddAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
return accessPolicyProvider.addAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
return accessPolicyProvider.getAccessPolicy(identifier);
}
@Override
public synchronized AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
return accessPolicyProvider.updateAccessPolicy(accessPolicy);
}
@Override
public synchronized AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
return accessPolicyProvider.deleteAccessPolicy(accessPolicy);
}
@Override
public synchronized AccessPolicy deleteAccessPolicy(final String accessPolicyIdentifier) throws AuthorizationAccessException {
return accessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return accessPolicyProvider.getAccessPolicies();
}
@AuthorizerContext
public void setNiFiProperties(NiFiRegistryProperties properties) {
userGroupProvider.setNiFiProperties(properties);
accessPolicyProvider.setNiFiProperties(properties);
}
@Override
public synchronized UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
final AuthorizationsHolder authorizationsHolder = accessPolicyProvider.getAuthorizationsHolder();
final UserGroupHolder userGroupHolder = userGroupProvider.getUserGroupHolder();
return new UsersAndAccessPolicies() {
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
return authorizationsHolder.getAccessPolicy(resourceIdentifier, action);
}
@Override
public User getUser(String identity) {
return userGroupHolder.getUser(identity);
}
@Override
public Set<Group> getGroups(String userIdentity) {
return userGroupHolder.getGroups(userIdentity);
}
};
}
}

Просмотреть файл

@ -0,0 +1,775 @@
/*
* 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.authorization.file;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
import org.apache.nifi.registry.authorization.file.tenants.generated.Groups;
import org.apache.nifi.registry.authorization.file.tenants.generated.Tenants;
import org.apache.nifi.registry.authorization.file.tenants.generated.Users;
import org.apache.nifi.registry.properties.util.IdentityMapping;
import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
import org.apache.nifi.registry.util.PropertyValue;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.registry.authorization.ConfigurableUserGroupProvider;
import org.apache.nifi.registry.authorization.Group;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.authorization.User;
import org.apache.nifi.registry.authorization.UserAndGroups;
import org.apache.nifi.registry.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class);
private static final String TENANTS_XSD = "/tenants.xsd";
private static final String JAXB_TENANTS_PATH = "org.apache.nifi.registry.authorization.file.tenants.generated";
private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH);
/**
* Load the JAXBContext.
*/
private static JAXBContext initializeJaxbContext(final String contextPath) {
try {
return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
//return JAXBContext.newInstance(contextPath);
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext: " + e);
}
}
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
private static final String USER_ELEMENT = "user";
private static final String GROUP_USER_ELEMENT = "groupUser";
private static final String GROUP_ELEMENT = "group";
private static final String IDENTIFIER_ATTR = "identifier";
private static final String IDENTITY_ATTR = "identity";
private static final String NAME_ATTR = "name";
static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity ";
static final String PROP_TENANTS_FILE = "Users File";
static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
private Schema usersSchema;
private Schema tenantsSchema;
private NiFiRegistryProperties properties;
private File tenantsFile;
private File restoreTenantsFile;
private Set<String> initialUserIdentities;
private List<IdentityMapping> identityMappings;
private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
try {
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
//usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
} catch (Exception e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try {
final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE);
if (StringUtils.isBlank(tenantsPath.getValue())) {
throw new AuthorizerCreationException("The users file must be specified.");
}
// get the tenants file and ensure it exists
tenantsFile = new File(tenantsPath.getValue());
if (!tenantsFile.exists()) {
logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()});
saveTenants(new Tenants());
}
final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile();
// extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
// extract any node identities
initialUserIdentities = new HashSet<>();
for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
}
}
load();
// if we've copied the authorizations file to a restore directory synchronize it
if (restoreTenantsFile != null) {
FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger);
}
logger.info(String.format("Users/Groups file loaded at %s", new Date().toString()));
} catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return userGroupHolder.get().getAllUsers();
}
@Override
public synchronized User addUser(User user) throws AuthorizationAccessException {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
final org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user);
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
tenants.getUsers().getUser().add(jaxbUser);
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getUsersById().get(user.getIdentifier());
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
if (identifier == null) {
return null;
}
final UserGroupHolder holder = userGroupHolder.get();
return holder.getUsersById().get(identifier);
}
@Override
public synchronized User updateUser(User user) throws AuthorizationAccessException {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
final List<org.apache.nifi.registry.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
// fine the User that needs to be updated
org.apache.nifi.registry.authorization.file.tenants.generated.User updateUser = null;
for (org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser : users) {
if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
updateUser = jaxbUser;
break;
}
}
// if user wasn't found return null, otherwise update the user and save changes
if (updateUser == null) {
return null;
} else {
updateUser.setIdentity(user.getIdentity());
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getUsersById().get(user.getIdentifier());
}
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
if (identity == null) {
return null;
}
final UserGroupHolder holder = userGroupHolder.get();
return holder.getUsersByIdentity().get(identity);
}
@Override
public synchronized User deleteUser(User user) throws AuthorizationAccessException {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
return deleteUser(user.getIdentifier());
}
@Override
public synchronized User deleteUser(String userIdentifier) throws AuthorizationAccessException {
if (userIdentifier == null) {
throw new IllegalArgumentException("User identifier cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final User deletedUser = holder.getUsersById().get(userIdentifier);
if (deletedUser == null) {
return null;
}
// for each group iterate over the user references and remove the user reference if it matches the user being deleted
final Tenants tenants = holder.getTenants();
for (org.apache.nifi.registry.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
Iterator<org.apache.nifi.registry.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator();
while (groupUserIter.hasNext()) {
org.apache.nifi.registry.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next();
if (groupUser.getIdentifier().equals(userIdentifier)) {
groupUserIter.remove();
break;
}
}
}
// remove the actual user
Iterator<org.apache.nifi.registry.authorization.file.tenants.generated.User> iter = tenants.getUsers().getUser().iterator();
while (iter.hasNext()) {
org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser = iter.next();
if (userIdentifier.equals(jaxbUser.getIdentifier())) {
iter.remove();
break;
}
}
saveAndRefreshHolder(tenants);
return deletedUser;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return userGroupHolder.get().getAllGroups();
}
@Override
public synchronized Group addGroup(Group group) throws AuthorizationAccessException {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
// determine that all users in the group exist before doing anything, throw an exception if they don't
checkGroupUsers(group, tenants.getUsers().getUser());
// create a new JAXB Group based on the incoming Group
final org.apache.nifi.registry.authorization.file.tenants.generated.Group jaxbGroup =
new org.apache.nifi.registry.authorization.file.tenants.generated.Group();
jaxbGroup.setIdentifier(group.getIdentifier());
jaxbGroup.setName(group.getName());
// add each user to the group
for (String groupUser : group.getUsers()) {
org.apache.nifi.registry.authorization.file.tenants.generated.Group.User jaxbGroupUser =
new org.apache.nifi.registry.authorization.file.tenants.generated.Group.User();
jaxbGroupUser.setIdentifier(groupUser);
jaxbGroup.getUser().add(jaxbGroupUser);
}
tenants.getGroups().getGroup().add(jaxbGroup);
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
if (identifier == null) {
return null;
}
return userGroupHolder.get().getGroupsById().get(identifier);
}
@Override
public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException {
final UserGroupHolder holder = userGroupHolder.get();
final User user = holder.getUser(identity);
final Set<Group> groups = holder.getGroups(identity);
return new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return groups;
}
};
}
@Override
public synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
// find the group that needs to be update
org.apache.nifi.registry.authorization.file.tenants.generated.Group updateGroup = null;
for (org.apache.nifi.registry.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) {
updateGroup = jaxbGroup;
break;
}
}
// if the group wasn't found return null, otherwise update the group and save changes
if (updateGroup == null) {
return null;
}
// reset the list of users and add each user to the group
updateGroup.getUser().clear();
for (String groupUser : group.getUsers()) {
org.apache.nifi.registry.authorization.file.tenants.generated.Group.User jaxbGroupUser =
new org.apache.nifi.registry.authorization.file.tenants.generated.Group.User();
jaxbGroupUser.setIdentifier(groupUser);
updateGroup.getUser().add(jaxbGroupUser);
}
updateGroup.setName(group.getName());
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
}
@Override
public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
return deleteGroup(group.getIdentifier());
}
@Override
public synchronized Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
if (groupIdentifier == null) {
throw new IllegalArgumentException("Group identifier cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Group deletedGroup = holder.getGroupsById().get(groupIdentifier);
if (deletedGroup == null) {
return null;
}
// now remove the actual group from the top-level list of groups
final Tenants tenants = holder.getTenants();
Iterator<org.apache.nifi.registry.authorization.file.tenants.generated.Group> iter = tenants.getGroups().getGroup().iterator();
while (iter.hasNext()) {
org.apache.nifi.registry.authorization.file.tenants.generated.Group jaxbGroup = iter.next();
if (groupIdentifier.equals(jaxbGroup.getIdentifier())) {
iter.remove();
break;
}
}
saveAndRefreshHolder(tenants);
return deletedGroup;
}
UserGroupHolder getUserGroupHolder() {
return userGroupHolder.get();
}
@AuthorizerContext
public void setNiFiProperties(NiFiRegistryProperties properties) {
this.properties = properties;
}
@Override
public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint);
usersAndGroups.getUsers().forEach(user -> addUser(user));
usersAndGroups.getGroups().forEach(group -> addGroup(group));
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException {
try {
// ensure we understand the proposed fingerprint
parseUsersAndGroups(proposedFingerprint);
} catch (final AuthorizationAccessException e) {
throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
}
final UserGroupHolder usersAndGroups = userGroupHolder.get();
// ensure we are in a proper state to inherit the fingerprint
if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) {
throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current users and groups is not empty.");
}
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
final UserGroupHolder usersAndGroups = userGroupHolder.get();
final List<User> users = new ArrayList<>(usersAndGroups.getAllUsers());
Collections.sort(users, Comparator.comparing(User::getIdentifier));
final List<Group> groups = new ArrayList<>(usersAndGroups.getAllGroups());
Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("tenants");
for (User user : users) {
writeUser(writer, user);
}
for (Group group : groups) {
writeGroup(writer, group);
}
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
private UsersAndGroups parseUsersAndGroups(final String fingerprint) {
final List<User> users = new ArrayList<>();
final List<Group> groups = new ArrayList<>();
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
// parse all the users and add them to the current user group provider
NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
for (int i=0; i < userNodes.getLength(); i++) {
Node userNode = userNodes.item(i);
users.add(parseUser((Element) userNode));
}
// parse all the groups and add them to the current user group provider
NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
for (int i=0; i < groupNodes.getLength(); i++) {
Node groupNode = groupNodes.item(i);
groups.add(parseGroup((Element) groupNode));
}
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
return new UsersAndGroups(users, groups);
}
private User parseUser(final Element element) {
final User.Builder builder = new User.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.identity(element.getAttribute(IDENTITY_ATTR));
return builder.build();
}
private Group parseGroup(final Element element) {
final Group.Builder builder = new Group.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.name(element.getAttribute(NAME_ATTR));
NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT);
for (int i=0; i < groupUsers.getLength(); i++) {
Element groupUserNode = (Element) groupUsers.item(i);
builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR));
}
return builder.build();
}
private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException {
writer.writeStartElement(USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier());
writer.writeAttribute(IDENTITY_ATTR, user.getIdentity());
writer.writeEndElement();
}
private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException {
List<String> users = new ArrayList<>(group.getUsers());
Collections.sort(users);
writer.writeStartElement(GROUP_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier());
writer.writeAttribute(NAME_ATTR, group.getName());
for (String user : users) {
writer.writeStartElement(GROUP_USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, user);
writer.writeEndElement();
}
writer.writeEndElement();
}
private org.apache.nifi.registry.authorization.file.tenants.generated.User createJAXBUser(User user) {
final org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser =
new org.apache.nifi.registry.authorization.file.tenants.generated.User();
jaxbUser.setIdentifier(user.getIdentifier());
jaxbUser.setIdentity(user.getIdentity());
return jaxbUser;
}
private Set<org.apache.nifi.registry.authorization.file.tenants.generated.User> checkGroupUsers(
final Group group,
final List<org.apache.nifi.registry.authorization.file.tenants.generated.User> users) {
final Set<org.apache.nifi.registry.authorization.file.tenants.generated.User> jaxbUsers = new HashSet<>();
for (String groupUser : group.getUsers()) {
boolean found = false;
for (org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser : users) {
if (jaxbUser.getIdentifier().equals(groupUser)) {
jaxbUsers.add(jaxbUser);
found = true;
break;
}
}
if (!found) {
throw new IllegalStateException("Unable to add group because user " + groupUser + " does not exist");
}
}
return jaxbUsers;
}
/**
* Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
*
* @throws JAXBException Unable to reload the authorized users file
* @throws IllegalStateException Unable to sync file with restore
* @throws SAXException Unable to unmarshall tenants
*/
private synchronized void load() throws JAXBException, IllegalStateException, SAXException {
final Tenants tenants = unmarshallTenants();
if (tenants.getUsers() == null) {
tenants.setUsers(new Users());
}
if (tenants.getGroups() == null) {
tenants.setGroups(new Groups());
}
final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants);
final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty();
// final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
if (emptyTenants) {
// if (hasLegacyAuthorizedUsers) {
// logger.info("Loading users from legacy model " + legacyAuthorizedUsersFile + " into new users file.");
// convertLegacyAuthorizedUsers(tenants);
// }
populateInitialUsers(tenants);
// save any changes that were made and repopulate the holder
saveAndRefreshHolder(tenants);
} else {
this.userGroupHolder.set(userGroupHolder);
}
}
private void saveTenants(final Tenants tenants) throws JAXBException {
final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller();
marshaller.setSchema(tenantsSchema);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(tenants, tenantsFile);
}
private Tenants unmarshallTenants() throws JAXBException {
final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(tenantsSchema);
final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class);
return element.getValue();
}
private void populateInitialUsers(final Tenants tenants) {
for (String initialUserIdentity : initialUserIdentities) {
getOrCreateUser(tenants, initialUserIdentity);
}
}
/**
* Finds the User with the given identity, or creates a new one and adds it to the Tenants.
*
* @param tenants the Tenants reference
* @param userIdentity the user identity to find or create
* @return the User from Tenants with the given identity, or a new instance that was added to Tenants
*/
private org.apache.nifi.registry.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) {
if (StringUtils.isBlank(userIdentity)) {
return null;
}
org.apache.nifi.registry.authorization.file.tenants.generated.User foundUser = null;
for (org.apache.nifi.registry.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) {
if (user.getIdentity().equals(userIdentity)) {
foundUser = user;
break;
}
}
if (foundUser == null) {
final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity);
foundUser = new org.apache.nifi.registry.authorization.file.tenants.generated.User();
foundUser.setIdentifier(userIdentifier);
foundUser.setIdentity(userIdentity);
tenants.getUsers().getUser().add(foundUser);
}
return foundUser;
}
/**
* Finds the Group with the given name, or creates a new one and adds it to Tenants.
*
* @param tenants the Tenants reference
* @param groupName the name of the group to look for
* @return the Group from Tenants with the given name, or a new instance that was added to Tenants
*/
private org.apache.nifi.registry.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) {
if (StringUtils.isBlank(groupName)) {
return null;
}
org.apache.nifi.registry.authorization.file.tenants.generated.Group foundGroup = null;
for (org.apache.nifi.registry.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
if (group.getName().equals(groupName)) {
foundGroup = group;
break;
}
}
if (foundGroup == null) {
final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName);
foundGroup = new org.apache.nifi.registry.authorization.file.tenants.generated.Group();
foundGroup.setIdentifier(newGroupIdentifier);
foundGroup.setName(groupName);
tenants.getGroups().getGroup().add(foundGroup);
}
return foundGroup;
}
/**
* Saves the Authorizations instance by marshalling to a file, then re-populates the
* in-memory data structures and sets the new holder.
*
* Synchronized to ensure only one thread writes the file at a time.
*
* @param tenants the tenants to save and populate from
* @throws AuthorizationAccessException if an error occurs saving the authorizations
*/
private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException {
try {
saveTenants(tenants);
this.userGroupHolder.set(new UserGroupHolder(tenants));
} catch (JAXBException e) {
throw new AuthorizationAccessException("Unable to save Authorizations", e);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
private static class UsersAndGroups {
final List<User> users;
final List<Group> groups;
public UsersAndGroups(List<User> users, List<Group> groups) {
this.users = users;
this.groups = groups;
}
public List<User> getUsers() {
return users;
}
public List<Group> getGroups() {
return groups;
}
}
}

Просмотреть файл

@ -0,0 +1,35 @@
/*
* 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.authorization.file;
import org.apache.commons.lang3.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public final class IdentifierUtil {
static String getIdentifier(final String seed) {
if (StringUtils.isBlank(seed)) {
return null;
}
return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
}
private IdentifierUtil() {}
}

Просмотреть файл

@ -0,0 +1,241 @@
/*
* 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.authorization.file;
import org.apache.nifi.registry.authorization.file.tenants.generated.Groups;
import org.apache.nifi.registry.authorization.file.tenants.generated.Tenants;
import org.apache.nifi.registry.authorization.file.tenants.generated.Users;
import org.apache.nifi.registry.authorization.Group;
import org.apache.nifi.registry.authorization.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A holder to provide atomic access to user group data structures.
*/
public class UserGroupHolder {
private final Tenants tenants;
private final Set<User> allUsers;
private final Map<String,User> usersById;
private final Map<String,User> usersByIdentity;
private final Set<Group> allGroups;
private final Map<String,Group> groupsById;
private final Map<String, Set<Group>> groupsByUserIdentity;
/**
* Creates a new holder and populates all convenience data structures.
*
* @param tenants the current tenants instance
*/
public UserGroupHolder(final Tenants tenants) {
this.tenants = tenants;
// load all users
final Users users = tenants.getUsers();
final Set<User> allUsers = Collections.unmodifiableSet(createUsers(users));
// load all groups
final Groups groups = tenants.getGroups();
final Set<Group> allGroups = Collections.unmodifiableSet(createGroups(groups, users));
// create a convenience map to retrieve a user by id
final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
// create a convenience map to retrieve a user by identity
final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
// create a convenience map to retrieve a group by id
final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
// create a convenience map to retrieve the groups for a user identity
final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
// set all the holders
this.allUsers = allUsers;
this.allGroups = allGroups;
this.usersById = userByIdMap;
this.usersByIdentity = userByIdentityMap;
this.groupsById = groupByIdMap;
this.groupsByUserIdentity = groupsByUserIdentityMap;
}
/**
* Creates a set of Users from the JAXB Users.
*
* @param users the JAXB Users
* @return a set of API Users matching the provided JAXB Users
*/
private Set<User> createUsers(Users users) {
Set<User> allUsers = new HashSet<>();
if (users == null || users.getUser() == null) {
return allUsers;
}
for (org.apache.nifi.registry.authorization.file.tenants.generated.User user : users.getUser()) {
final User.Builder builder = new User.Builder()
.identity(user.getIdentity())
.identifier(user.getIdentifier());
allUsers.add(builder.build());
}
return allUsers;
}
/**
* Creates a set of Groups from the JAXB Groups.
*
* @param groups the JAXB Groups
* @return a set of API Groups matching the provided JAXB Groups
*/
private Set<Group> createGroups(Groups groups,
Users users) {
Set<Group> allGroups = new HashSet<>();
if (groups == null || groups.getGroup() == null) {
return allGroups;
}
for (org.apache.nifi.registry.authorization.file.tenants.generated.Group group : groups.getGroup()) {
final Group.Builder builder = new Group.Builder()
.identifier(group.getIdentifier())
.name(group.getName());
for (org.apache.nifi.registry.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) {
builder.addUser(groupUser.getIdentifier());
}
allGroups.add(builder.build());
}
return allGroups;
}
/**
* Creates a Map from user identifier to User.
*
* @param users the set of all users
* @return the Map from user identifier to User
*/
private Map<String,User> createUserByIdMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentifier(), user);
}
return usersMap;
}
/**
* Creates a Map from user identity to User.
*
* @param users the set of all users
* @return the Map from user identity to User
*/
private Map<String,User> createUserByIdentityMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentity(), user);
}
return usersMap;
}
/**
* Creates a Map from group identifier to Group.
*
* @param groups the set of all groups
* @return the Map from group identifier to Group
*/
private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
Map<String,Group> groupsMap = new HashMap<>();
for (Group group : groups) {
groupsMap.put(group.getIdentifier(), group);
}
return groupsMap;
}
/**
* Creates a Map from user identity to the set of Groups for that identity.
*
* @param groups all groups
* @param users all users
* @return a Map from User identity to the set of Groups for that identity
*/
private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
for (User user : users) {
Set<Group> userGroups = new HashSet<>();
for (Group group : groups) {
for (String groupUser : group.getUsers()) {
if (groupUser.equals(user.getIdentifier())) {
userGroups.add(group);
}
}
}
groupsByUserIdentity.put(user.getIdentity(), userGroups);
}
return groupsByUserIdentity;
}
public Tenants getTenants() {
return tenants;
}
public Set<User> getAllUsers() {
return allUsers;
}
public Map<String, User> getUsersById() {
return usersById;
}
public Map<String, User> getUsersByIdentity() {
return usersByIdentity;
}
public Set<Group> getAllGroups() {
return allGroups;
}
public Map<String, Group> getGroupsById() {
return groupsById;
}
public User getUser(String identity) {
if (identity == null) {
throw new IllegalArgumentException("Identity cannot be null");
}
return usersByIdentity.get(identity);
}
public Set<Group> getGroups(String userIdentity) {
if (userIdentity == null) {
throw new IllegalArgumentException("User Identity cannot be null");
}
return groupsByUserIdentity.get(userIdentity);
}
}

Просмотреть файл

@ -0,0 +1,86 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Policy">
<xs:sequence>
<xs:element name="group" minOccurs="0" maxOccurs="unbounded" >
<xs:complexType>
<xs:attribute name="identifier">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="user" minOccurs="0" maxOccurs="unbounded" >
<xs:complexType>
<xs:attribute name="identifier">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="identifier">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="resource">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="action">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="R"/>
<xs:enumeration value="W"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType name="Policies">
<xs:sequence>
<xs:element name="policy" type="Policy" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- top-level authorizations element -->
<xs:element name="authorizations">
<xs:complexType>
<xs:sequence>
<xs:element name="policies" type="Policies" minOccurs="0" maxOccurs="1" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Просмотреть файл

@ -0,0 +1,96 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- group -->
<xs:complexType name="Group">
<xs:sequence>
<xs:element name="user" minOccurs="0" maxOccurs="unbounded" >
<xs:complexType>
<xs:attribute name="identifier">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="identifier">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="name">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- groups -->
<xs:complexType name="Groups">
<xs:sequence>
<xs:element name="group" type="Group" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- user -->
<xs:complexType name="User">
<xs:attribute name="identifier">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="identity">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- users -->
<xs:complexType name="Users">
<xs:sequence>
<xs:element name="user" type="User" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- top-level authorizations element -->
<xs:element name="tenants">
<xs:complexType>
<xs:sequence>
<xs:element name="groups" type="Groups" minOccurs="0" maxOccurs="1" />
<xs:element name="users" type="Users" minOccurs="0" maxOccurs="1" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Просмотреть файл

@ -0,0 +1,38 @@
<?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">
<parent>
<artifactId>nifi-registry</artifactId>
<groupId>org.apache.nifi.registry</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nifi-registry-security-api</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

Просмотреть файл

@ -0,0 +1,367 @@
/*
* 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.authorization;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* Defines a policy for a set of userIdentifiers to perform a set of actions on a given resource.
*/
public class AccessPolicy {
private final String identifier;
private final String resource;
private final Set<String> users;
private final Set<String> groups;
private final RequestAction action;
private AccessPolicy(final Builder builder) {
this.identifier = builder.identifier;
this.resource = builder.resource;
this.action = builder.action;
this.users = Collections.unmodifiableSet(new HashSet<>(builder.users));
this.groups = Collections.unmodifiableSet(new HashSet<>(builder.groups));
if (this.identifier == null || this.identifier.trim().isEmpty()) {
throw new IllegalArgumentException("Identifier can not be null or empty");
}
if (this.resource == null) {
throw new IllegalArgumentException("Resource can not be null");
}
if (this.action == null) {
throw new IllegalArgumentException("Action can not be null");
}
}
/**
* @return the identifier for this policy
*/
public String getIdentifier() {
return identifier;
}
/**
* @return the resource for this policy
*/
public String getResource() {
return resource;
}
/**
* @return an unmodifiable set of user ids for this policy
*/
public Set<String> getUsers() {
return users;
}
/**
* @return an unmodifiable set of group ids for this policy
*/
public Set<String> getGroups() {
return groups;
}
/**
* @return the action for this policy
*/
public RequestAction getAction() {
return action;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final AccessPolicy other = (AccessPolicy) obj;
return Objects.equals(this.identifier, other.identifier);
}
@Override
public int hashCode() {
return Objects.hashCode(this.identifier);
}
@Override
public String toString() {
return String.format("identifier[%s], resource[%s], users[%s], groups[%s], action[%s]",
getIdentifier(), getResource(), getUsers(), getGroups(), getAction());
}
/**
* Builder for Access Policies.
*/
public static class Builder {
private String identifier;
private String resource;
private RequestAction action;
private Set<String> users = new HashSet<>();
private Set<String> groups = new HashSet<>();
private final boolean fromPolicy;
/**
* Default constructor for building a new AccessPolicy.
*/
public Builder() {
this.fromPolicy = false;
}
/**
* Initializes the builder with the state of the provided policy. When using this constructor
* the identifier field of the builder can not be changed and will result in an IllegalStateException
* if attempting to do so.
*
* @param other the existing access policy to initialize from
*/
public Builder(final AccessPolicy other) {
if (other == null) {
throw new IllegalArgumentException("Can not initialize builder with a null access policy");
}
this.identifier = other.getIdentifier();
this.resource = other.getResource();
this.action = other.getAction();
this.users.clear();
this.users.addAll(other.getUsers());
this.groups.clear();
this.groups.addAll(other.getGroups());
this.fromPolicy = true;
}
/**
* Sets the identifier of the builder.
*
* @param identifier the identifier to set
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
*/
public Builder identifier(final String identifier) {
if (fromPolicy) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing policy");
}
this.identifier = identifier;
return this;
}
/**
* Sets the identifier of the builder to a random UUID.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
*/
public Builder identifierGenerateRandom() {
if (fromPolicy) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing policy");
}
this.identifier = UUID.randomUUID().toString();
return this;
}
/**
* Sets the identifier of the builder with a UUID generated from the specified seed string.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
*/
public Builder identifierGenerateFromSeed(final String seed) {
if (fromPolicy) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing policy");
}
if (seed == null) {
throw new IllegalArgumentException("Cannot seed the policy identifier with a null value.");
}
this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
return this;
}
/**
* Sets the resource of the builder.
*
* @param resource the resource to set
* @return the builder
*/
public Builder resource(final String resource) {
this.resource = resource;
return this;
}
/**
* Adds all the users from the provided set to the builder's set of users.
*
* @param users the users to add
* @return the builder
*/
public Builder addUsers(final Set<String> users) {
if (users != null) {
this.users.addAll(users);
}
return this;
}
/**
* Adds the given user to the builder's set of users.
*
* @param user the user to add
* @return the builder
*/
public Builder addUser(final String user) {
if (user != null) {
this.users.add(user);
}
return this;
}
/**
* Removes all users in the provided set from the builder's set of users.
*
* @param users the users to remove
* @return the builder
*/
public Builder removeUsers(final Set<String> users) {
if (users != null) {
this.users.removeAll(users);
}
return this;
}
/**
* Removes the provided user from the builder's set of users.
*
* @param user the user to remove
* @return the builder
*/
public Builder removeUser(final String user) {
if (user != null) {
this.users.remove(user);
}
return this;
}
/**
* Clears the builder's set of users so that it is non-null and size == 0.
*
* @return the builder
*/
public Builder clearUsers() {
this.users.clear();
return this;
}
/**
* Adds all the groups from the provided set to the builder's set of groups.
*
* @param groups the groups to add
* @return the builder
*/
public Builder addGroups(final Set<String> groups) {
if (groups != null) {
this.groups.addAll(groups);
}
return this;
}
/**
* Adds the given group to the builder's set of groups.
*
* @param group the group to add
* @return the builder
*/
public Builder addGroup(final String group) {
if (group != null) {
this.groups.add(group);
}
return this;
}
/**
* Removes all groups in the provided set from the builder's set of groups.
*
* @param groups the groups to remove
* @return the builder
*/
public Builder removeGroups(final Set<String> groups) {
if (groups != null) {
this.groups.removeAll(groups);
}
return this;
}
/**
* Removes the provided groups from the builder's set of groups.
*
* @param group the group to remove
* @return the builder
*/
public Builder removeGroup(final String group) {
if (group != null) {
this.groups.remove(group);
}
return this;
}
/**
* Clears the builder's set of groups so that it is non-null and size == 0.
*
* @return the builder
*/
public Builder clearGroups() {
this.groups.clear();
return this;
}
/**
* Sets the action for this builder.
*
* @param action the action to set
* @return the builder
*/
public Builder action(final RequestAction action) {
this.action = action;
return this;
}
/**
* @return a new AccessPolicy constructed from the state of the builder
*/
public AccessPolicy build() {
return new AccessPolicy(this);
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import java.util.Set;
/**
* Provides access to AccessPolicies and the configured UserGroupProvider.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface AccessPolicyProvider {
/**
* Retrieves all access policies. Must be non null
*
* @return a list of policies
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException;
/**
* Retrieves the policy with the given identifier.
*
* @param identifier the id of the policy to retrieve
* @return the policy with the given id, or null if no matching policy exists
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException;
/**
* Gets the access policies for the specified resource identifier and request action.
*
* @param resourceIdentifier the resource identifier
* @param action the request action
* @return the policy matching the resouce and action, or null if no matching policy exists
* @throws AuthorizationAccessException if there was any unexpected error performing the operation
*/
AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException;
/**
* Returns the UserGroupProvider for this managed Authorizer. Must be non null
*
* @return the UserGroupProvider
*/
UserGroupProvider getUserGroupProvider();
/**
* Called immediately after instance creation for implementers to perform additional setup
*
* @param initializationContext in which to initialize
*/
void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException;
/**
* Called to configure the Authorizer.
*
* @param configurationContext at the time of configuration
* @throws AuthorizerCreationException for any issues configuring the provider
*/
void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
/**
* Called immediately before instance destruction for implementers to release resources.
*
* @throws AuthorizerDestructionException If pre-destruction fails.
*/
void preDestruction() throws AuthorizerDestructionException;
}

Просмотреть файл

@ -0,0 +1,30 @@
/*
* 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.authorization;
/**
* Initialization content for AccessPolicyProviders.
*/
public interface AccessPolicyProviderInitializationContext extends UserGroupProviderInitializationContext {
/**
* The lookup for accessing other configured AccessPolicyProviders.
*
* @return The AccessPolicyProvider lookup
*/
AccessPolicyProviderLookup getAccessPolicyProviderLookup();
}

Просмотреть файл

@ -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.
*/
package org.apache.nifi.registry.authorization;
/**
*
*/
public interface AccessPolicyProviderLookup {
/**
* Looks up the AccessPolicyProvider with the specified identifier
*
* @param identifier The identifier of the AccessPolicyProvider
* @return The AccessPolicyProvider
*/
AccessPolicyProvider getAccessPolicyProvider(String identifier);
}

Просмотреть файл

@ -0,0 +1,30 @@
/*
* 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.authorization;
public interface AuthorizationAuditor {
/**
* Audits an authorization request. Will be invoked for any Approved or Denied results. ResourceNotFound
* will either re-attempt authorization using a parent resource or will generate a failure result and
* audit that.
*
* @param request the request for authorization
* @param result the authorization result
*/
void auditAccessAttempt(final AuthorizationRequest request, final AuthorizationResult result);
}

Просмотреть файл

@ -0,0 +1,245 @@
/*
* 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.authorization;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
/**
* Represents an authorization request for a given user/entity performing an action against a resource within some userContext.
*/
public class AuthorizationRequest {
public static final String DEFAULT_EXPLANATION = "Unable to perform the desired action.";
private final Resource resource;
private final Resource requestedResource;
private final String identity;
private final Set<String> groups;
private final RequestAction action;
private final boolean isAccessAttempt;
private final boolean isAnonymous;
private final Map<String, String> userContext;
private final Map<String, String> resourceContext;
private final Supplier<String> explanationSupplier;
private AuthorizationRequest(final Builder builder) {
Objects.requireNonNull(builder.resource, "The resource is required when creating an authorization request");
Objects.requireNonNull(builder.action, "The action is required when creating an authorization request");
Objects.requireNonNull(builder.isAccessAttempt, "Whether this request is an access attempt is request");
Objects.requireNonNull(builder.isAnonymous, "Whether this request is being performed by an anonymous user is required");
this.resource = builder.resource;
this.identity = builder.identity;
this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
this.action = builder.action;
this.isAccessAttempt = builder.isAccessAttempt;
this.isAnonymous = builder.isAnonymous;
this.userContext = builder.userContext == null ? null : Collections.unmodifiableMap(builder.userContext);
this.resourceContext = builder.resourceContext == null ? null : Collections.unmodifiableMap(builder.resourceContext);
this.explanationSupplier = () -> {
final String explanation = builder.explanationSupplier.get();
// ensure the specified supplier returns non null
if (explanation == null) {
return DEFAULT_EXPLANATION;
} else {
return explanation;
}
};
if (builder.requestedResource == null) {
this.requestedResource = builder.resource;
} else {
this.requestedResource = builder.requestedResource;
}
}
/**
* The Resource being authorized. Not null.
*
* @return The resource
*/
public Resource getResource() {
return resource;
}
/**
* The original Resource being requested. In cases with inherited policies, this will be a ancestor resource of
* of the current resource. The initial request, and cases without inheritance, the requested resource will be
* the same as the current resource.
*
* @return The requested resource
*/
public Resource getRequestedResource() {
return requestedResource;
}
/**
* The identity accessing the Resource. May be null if the user could not authenticate.
*
* @return The identity
*/
public String getIdentity() {
return identity;
}
/**
* The groups the user making this request belongs to. May be null if this NiFi is not configured to load user
* groups or empty if the user has no groups
*
* @return The groups
*/
public Set<String> getGroups() {
return groups;
}
/**
* Whether this is a direct access attempt of the Resource if if it's being checked as part of another response.
*
* @return if this is a direct access attempt
*/
public boolean isAccessAttempt() {
return isAccessAttempt;
}
/**
* Whether the entity accessing is anonymous.
*
* @return whether the entity is anonymous
*/
public boolean isAnonymous() {
return isAnonymous;
}
/**
* The action being taken against the Resource. Not null.
*
* @return The action
*/
public RequestAction getAction() {
return action;
}
/**
* The userContext of the user request to make additional access decisions. May be null.
*
* @return The userContext of the user request
*/
public Map<String, String> getUserContext() {
return userContext;
}
/**
* The event attributes to make additional access decisions for provenance events. May be null.
*
* @return The event attributes
*/
public Map<String, String> getResourceContext() {
return resourceContext;
}
/**
* A supplier for the explanation if access is denied. Non null.
*
* @return The explanation supplier if access is denied
*/
public Supplier<String> getExplanationSupplier() {
return explanationSupplier;
}
/**
* AuthorizationRequest builder.
*/
public static final class Builder {
private Resource resource;
private Resource requestedResource;
private String identity;
private Set<String> groups;
private Boolean isAnonymous;
private Boolean isAccessAttempt;
private RequestAction action;
private Map<String, String> userContext;
private Map<String, String> resourceContext;
private Supplier<String> explanationSupplier = () -> DEFAULT_EXPLANATION;
public Builder resource(final Resource resource) {
this.resource = resource;
return this;
}
public Builder requestedResource(final Resource requestedResource) {
this.requestedResource = requestedResource;
return this;
}
public Builder identity(final String identity) {
this.identity = identity;
return this;
}
public Builder groups(final Set<String> groups) {
this.groups = groups;
return this;
}
public Builder anonymous(final Boolean isAnonymous) {
this.isAnonymous = isAnonymous;
return this;
}
public Builder accessAttempt(final Boolean isAccessAttempt) {
this.isAccessAttempt = isAccessAttempt;
return this;
}
public Builder action(final RequestAction action) {
this.action = action;
return this;
}
public Builder userContext(final Map<String, String> userContext) {
if (userContext != null) {
this.userContext = new HashMap<>(userContext);
}
return this;
}
public Builder resourceContext(final Map<String, String> resourceContext) {
if (resourceContext != null) {
this.resourceContext = new HashMap<>(resourceContext);
}
return this;
}
public Builder explanationSupplier(final Supplier<String> explanationSupplier) {
if (explanationSupplier != null) {
this.explanationSupplier = explanationSupplier;
}
return this;
}
public AuthorizationRequest build() {
return new AuthorizationRequest(this);
}
}
}

Просмотреть файл

@ -0,0 +1,103 @@
/*
* 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.authorization;
/**
* Represents a decision whether authorization is granted.
*/
public class AuthorizationResult {
public enum Result {
Approved,
Denied,
ResourceNotFound
}
private static final AuthorizationResult APPROVED = new AuthorizationResult(Result.Approved, null);
private static final AuthorizationResult RESOURCE_NOT_FOUND = new AuthorizationResult(Result.ResourceNotFound, "Not authorized for the requested resource.");
private final Result result;
private final String explanation;
/**
* Creates a new AuthorizationResult with the specified result and explanation.
*
* @param result of the authorization
* @param explanation for the authorization attempt
*/
private AuthorizationResult(Result result, String explanation) {
if (Result.Denied.equals(result) && explanation == null) {
throw new IllegalArgumentException("An explanation is required when the authorization request is denied.");
}
if (Result.ResourceNotFound.equals(result) && explanation == null) {
throw new IllegalArgumentException("An explanation is required when the authorization request is resource not found.");
}
this.result = result;
this.explanation = explanation;
}
/**
* @return Whether or not the request is approved
*/
public Result getResult() {
return result;
}
/**
* @return If the request is denied, the reason why. Null otherwise
*/
public String getExplanation() {
return explanation;
}
/**
* @return a new approved AuthorizationResult
*/
public static AuthorizationResult approved() {
return APPROVED;
}
/**
* Resource not found will indicate that there are no specific authorization rules for this resource.
* @return a new resource not found AuthorizationResult
*/
public static AuthorizationResult resourceNotFound() {
return RESOURCE_NOT_FOUND;
}
/**
* Creates a new denied AuthorizationResult with a message indicating 'Access is denied'.
*
* @return a new denied AuthorizationResult
*/
public static AuthorizationResult denied() {
return denied(AuthorizationRequest.DEFAULT_EXPLANATION);
}
/**
* Creates a new denied AuthorizationResult with the specified explanation.
*
* @param explanation for why it was denied
* @return a new denied AuthorizationResult with the specified explanation
* @throws IllegalArgumentException if explanation is null
*/
public static AuthorizationResult denied(String explanation) {
return new AuthorizationResult(Result.Denied, explanation);
}
}

Просмотреть файл

@ -0,0 +1,63 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
/**
* Authorizes user requests.
*/
public interface Authorizer {
/**
* Determines if the specified user/entity is authorized to access the specified resource within the given context.
* These details are all contained in the AuthorizationRequest.
*
* NOTE: This method will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* @param request The authorization request
* @return the authorization result
* @throws AuthorizationAccessException if unable to access the policies
*/
AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException;
/**
* Called immediately after instance creation for implementers to perform additional setup
*
* @param initializationContext in which to initialize
*/
void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException;
/**
* Called to configure the Authorizer.
*
* @param configurationContext at the time of configuration
* @throws AuthorizerCreationException for any issues configuring the provider
*/
void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
/**
* Called immediately before instance destruction for implementers to release resources.
*
* @throws AuthorizerDestructionException If pre-destruction fails.
*/
void preDestruction() throws AuthorizerDestructionException;
}

Просмотреть файл

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.authorization;
import org.apache.nifi.registry.util.PropertyValue;
import java.util.Map;
/**
*
*/
public interface AuthorizerConfigurationContext {
/**
* @return identifier for the authorizer
*/
String getIdentifier();
/**
* Retrieves all properties the component currently understands regardless
* of whether a value has been set for them or not. If no value is present
* then its value is null and thus any registered default for the property
* descriptor applies.
*
* @return Map of all properties
*/
Map<String, String> getProperties();
/**
* @param property to lookup the descriptor and value of
* @return the value the component currently understands for the given PropertyDescriptor
*/
PropertyValue getProperty(String property);
}

Просмотреть файл

@ -0,0 +1,30 @@
/*
* 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.authorization;
/**
* Initialization content for Authorizers.
*/
public interface AuthorizerInitializationContext extends AccessPolicyProviderInitializationContext {
/**
* The lookup for accessing other configured Authorizers.
*
* @return The Authorizer lookup
*/
AuthorizerLookup getAuthorizerLookup();
}

Просмотреть файл

@ -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.
*/
package org.apache.nifi.registry.authorization;
/**
*
*/
public interface AuthorizerLookup {
/**
* Looks up the Authorizer with the specified identifier
*
* @param identifier The identifier of the Authorizer
* @return The Authorizer
*/
Authorizer getAuthorizer(String identifier);
}

Просмотреть файл

@ -0,0 +1,108 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
/**
* Provides support for configuring AccessPolicies.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface ConfigurableAccessPolicyProvider extends AccessPolicyProvider {
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two policy-based authorizers represent a compatible set of policies.
*
* @return the fingerprint for this Authorizer
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
String getFingerprint() throws AuthorizationAccessException;
/**
* Parses the fingerprint and adds any policies to the current AccessPolicyProvider.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
/**
* When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
* If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
*
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
/**
* Adds the given policy ensuring that multiple policies can not be added for the same resource and action.
*
* @param accessPolicy the policy to add
* @return the policy that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Determines whether the specified access policy is configurable. Provides the opportunity for a ConfigurableAccessPolicyProvider to prevent
* editing of a specific access policy. By default, all known access policies are configurable.
*
* @param accessPolicy the access policy
* @return is configurable
*/
default boolean isConfigurable(AccessPolicy accessPolicy) {
if (accessPolicy == null) {
throw new IllegalArgumentException("Access policy cannot be null");
}
return getAccessPolicy(accessPolicy.getIdentifier()) != null;
}
/**
* The policy represented by the provided instance will be updated based on the provided instance.
*
* @param accessPolicy an updated policy
* @return the updated policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Deletes the given policy.
*
* @param accessPolicy the policy to delete
* @return the deleted policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Deletes the policy with the specified identifier.
*
* @param accessPolicyIdentifier the policy to delete
* @return the deleted policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException;
}

Просмотреть файл

@ -0,0 +1,163 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
/**
* Provides support for configuring Users and Groups.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface ConfigurableUserGroupProvider extends UserGroupProvider {
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two policy-based authorizers represent a compatible set of users and/or groups.
*
* @return the fingerprint for this Authorizer
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
String getFingerprint() throws AuthorizationAccessException;
/**
* Parses the fingerprint and adds any users and groups to the current Authorizer.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
/**
* When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
* If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
*
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
/**
* Adds the given user.
*
* @param user the user to add
* @return the user that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a user with the same identity
*/
User addUser(User user) throws AuthorizationAccessException;
/**
* Determines whether the specified user is configurable. Provides the opportunity for a ConfigurableUserGroupProvider to prevent
* editing of a specific user. By default, all known users are configurable.
*
* @param user the user
* @return is configurable
*/
default boolean isConfigurable(User user) {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
return getUser(user.getIdentifier()) != null;
}
/**
* The user represented by the provided instance will be updated based on the provided instance.
*
* @param user an updated user instance
* @return the updated user instance, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a user with the same identity
*/
User updateUser(final User user) throws AuthorizationAccessException;
/**
* Deletes the given user.
*
* @param user the user to delete
* @return the user that was deleted, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User deleteUser(User user) throws AuthorizationAccessException;
/**
* Deletes the user for the given ID.
*
* @param userIdentifier the user to delete
* @return the user that was deleted, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User deleteUser(String userIdentifier) throws AuthorizationAccessException;
/**
* Adds a new group.
*
* @param group the Group to add
* @return the added Group
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if a group with the same name already exists
*/
Group addGroup(Group group) throws AuthorizationAccessException;
/**
* Determines whether the specified group is configurable. Provides the opportunity for a ConfigurableUserGroupProvider to prevent
* editing of a specific group. By default, all known groups are configurable.
*
* @param group the group
* @return is configurable
*/
default boolean isConfigurable(Group group) {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
return getGroup(group.getIdentifier()) != null;
}
/**
* The group represented by the provided instance will be updated based on the provided instance.
*
* @param group an updated group instance
* @return the updated group instance, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a group with the same name
*/
Group updateGroup(Group group) throws AuthorizationAccessException;
/**
* Deletes the given group.
*
* @param group the group to delete
* @return the deleted group, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Group deleteGroup(Group group) throws AuthorizationAccessException;
/**
* Deletes the given group.
*
* @param groupIdentifier the group to delete
* @return the deleted group, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException;
}

Просмотреть файл

@ -0,0 +1,263 @@
/*
* 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.authorization;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* A group that users can belong to.
*/
public class Group {
private final String identifier;
private final String name;
private final Set<String> users;
private Group(final Builder builder) {
this.identifier = builder.identifier;
this.name = builder.name;
this.users = Collections.unmodifiableSet(new HashSet<>(builder.users));
if (this.identifier == null || this.identifier.trim().isEmpty()) {
throw new IllegalArgumentException("Identifier can not be null or empty");
}
if (this.name == null || this.name.trim().isEmpty()) {
throw new IllegalArgumentException("Name can not be null or empty");
}
}
/**
* @return the identifier of the group
*/
public String getIdentifier() {
return identifier;
}
/**
* @return the name of the group
*/
public String getName() {
return name;
}
/**
* @return an unmodifiable set of user identifiers that belong to this group
*/
public Set<String> getUsers() {
return users;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Group other = (Group) obj;
return Objects.equals(this.identifier, other.identifier);
}
@Override
public int hashCode() {
return Objects.hashCode(this.identifier);
}
@Override
public String toString() {
return String.format("identifier[%s], name[%s]", getIdentifier(), getName());
}
/**
* Builder for creating Groups.
*/
public static class Builder {
private String identifier;
private String name;
private Set<String> users = new HashSet<>();
private final boolean fromGroup;
public Builder() {
this.fromGroup = false;
}
/**
* Initializes the builder with the state of the provided group. When using this constructor
* the identifier field of the builder can not be changed and will result in an IllegalStateException
* if attempting to do so.
*
* @param other the existing access policy to initialize from
*/
public Builder(final Group other) {
if (other == null) {
throw new IllegalArgumentException("Provided group can not be null");
}
this.identifier = other.getIdentifier();
this.name = other.getName();
this.users.clear();
this.users.addAll(other.getUsers());
this.fromGroup = true;
}
/**
* Sets the identifier of the builder.
*
* @param identifier the identifier
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
*/
public Builder identifier(final String identifier) {
if (fromGroup) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing group");
}
this.identifier = identifier;
return this;
}
/**
* Sets the identifier of the builder to a random UUID.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
*/
public Builder identifierGenerateRandom() {
if (fromGroup) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing group");
}
this.identifier = UUID.randomUUID().toString();
return this;
}
/**
* Sets the identifier of the builder with a UUID generated from the specified seed string.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
*/
public Builder identifierGenerateFromSeed(final String seed) {
if (fromGroup) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing group");
}
if (seed == null) {
throw new IllegalArgumentException("Cannot seed the group identifier with a null value.");
}
this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
return this;
}
/**
* Sets the name of the builder.
*
* @param name the name
* @return the builder
*/
public Builder name(final String name) {
this.name = name;
return this;
}
/**
* Adds all users from the provided set to the builder's set of users.
*
* @param users a set of users to add
* @return the builder
*/
public Builder addUsers(final Set<String> users) {
if (users != null) {
this.users.addAll(users);
}
return this;
}
/**
* Adds the given user to the builder's set of users.
*
* @param user the user to add
* @return the builder
*/
public Builder addUser(final String user) {
if (user != null) {
this.users.add(user);
}
return this;
}
/**
* Removes the given user from the builder's set of users.
*
* @param user the user to remove
* @return the builder
*/
public Builder removeUser(final String user) {
if (user != null) {
this.users.remove(user);
}
return this;
}
/**
* Removes all users from the provided set from the builder's set of users.
*
* @param users the users to remove
* @return the builder
*/
public Builder removeUsers(final Set<String> users) {
if (users != null) {
this.users.removeAll(users);
}
return this;
}
/**
* Clears the builder's set of users so that users is non-null with size 0.
*
* @return the builder
*/
public Builder clearUsers() {
this.users.clear();
return this;
}
/**
* @return a new Group constructed from the state of the builder
*/
public Group build() {
return new Group(this);
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
public interface ManagedAuthorizer extends Authorizer {
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two managed authorizers represent a compatible set of users,
* groups, and/or policies. Must be non null
*
* @return the fingerprint for this Authorizer
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
String getFingerprint() throws AuthorizationAccessException;
/**
* Parses the fingerprint and adds any users, groups, and policies to the current Authorizer.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
/**
* When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
* If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
*
* @param proposedFingerprint the proposed fingerprint
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
/**
* Returns the AccessPolicy provider for this managed Authorizer. Must be non null
*
* @return the AccessPolicy provider
*/
AccessPolicyProvider getAccessPolicyProvider();
}

Просмотреть файл

@ -0,0 +1,54 @@
/*
* 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.authorization;
import java.util.StringJoiner;
/**
* Actions a user/entity can take on a resource.
*/
public enum RequestAction {
READ("read"),
WRITE("write");
// TODO, add DELETE RequestAction feature
private String value;
RequestAction(String value) {
this.value = value;
}
@Override
public String toString() {
return value.toLowerCase();
}
public static RequestAction valueOfValue(final String action) {
if (RequestAction.READ.toString().equals(action)) {
return RequestAction.READ;
} else if (RequestAction.WRITE.toString().equals(action)) {
return RequestAction.WRITE;
} else {
StringJoiner stringJoiner = new StringJoiner(", ");
for(RequestAction ra : RequestAction.values()) {
stringJoiner.add(ra.toString());
}
String allowableValues = stringJoiner.toString();
throw new IllegalArgumentException("Action must be one of [" + allowableValues + "]");
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
/*
* 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.authorization;
/**
* Resource in an authorization request.
*/
public interface Resource {
/**
* The identifier for this resource.
*
* @return identifier for this resource
*/
String getIdentifier();
/**
* The name of this resource. May be null.
*
* @return name of this resource
*/
String getName();
/**
* The description of this resource that may be safely used in messages to the client.
*
* @return safe description
*/
String getSafeDescription();
}

Просмотреть файл

@ -0,0 +1,188 @@
/*
* 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.authorization;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.UUID;
/**
* A user to create authorization policies for.
*/
public class User {
private final String identifier;
private final String identity;
private User(final Builder builder) {
this.identifier = builder.identifier;
this.identity = builder.identity;
if (identifier == null || identifier.trim().isEmpty()) {
throw new IllegalArgumentException("Identifier can not be null or empty");
}
if (identity == null || identity.trim().isEmpty()) {
throw new IllegalArgumentException("Identity can not be null or empty");
}
}
/**
* @return the identifier of the user
*/
public String getIdentifier() {
return identifier;
}
/**
* @return the identity string of the user
*/
public String getIdentity() {
return identity;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final User other = (User) obj;
return Objects.equals(this.identifier, other.identifier);
}
@Override
public int hashCode() {
return Objects.hashCode(this.identifier);
}
@Override
public String toString() {
return String.format("identifier[%s], identity[%s]", getIdentifier(), getIdentity());
}
/**
* Builder for Users.
*/
public static class Builder {
private String identifier;
private String identity;
private final boolean fromUser;
/**
* Default constructor for building a new User.
*/
public Builder() {
this.fromUser = false;
}
/**
* Initializes the builder with the state of the provided user. When using this constructor
* the identifier field of the builder can not be changed and will result in an IllegalStateException
* if attempting to do so.
*
* @param other the existing user to initialize from
*/
public Builder(final User other) {
if (other == null) {
throw new IllegalArgumentException("Provided user can not be null");
}
this.identifier = other.getIdentifier();
this.identity = other.getIdentity();
this.fromUser = true;
}
/**
* Sets the identifier of the builder.
*
* @param identifier the identifier to set
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing User
*/
public Builder identifier(final String identifier) {
if (fromUser) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing user");
}
this.identifier = identifier;
return this;
}
/**
* Sets the identifier of the builder to a random UUID.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing User
*/
public Builder identifierGenerateRandom() {
if (fromUser) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing user");
}
this.identifier = UUID.randomUUID().toString();
return this;
}
/**
* Sets the identifier of the builder with a UUID generated from the specified seed string.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing User
*/
public Builder identifierGenerateFromSeed(final String seed) {
if (fromUser) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing user");
}
if (seed == null) {
throw new IllegalArgumentException("Cannot seed the user identifier with a null value.");
}
this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
return this;
}
/**
* Sets the identity of the builder.
*
* @param identity the identity to set
* @return the builder
*/
public Builder identity(final String identity) {
this.identity = identity;
return this;
}
/**
* @return a new User constructed from the state of the builder
*/
public User build() {
return new User(this);
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
/*
* 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.authorization;
import java.util.Set;
/**
* A holder object to provide atomic access to a user and their groups.
*/
public interface UserAndGroups {
/**
* Retrieves the user, or null if the user is unknown
*
* @return the user with the given identity
*/
User getUser();
/**
* Retrieves the groups for the user, or null if the user is unknown or has no groups.
*
* @return the set of groups for the given user identity
*/
Set<Group> getGroups();
}

Просмотреть файл

@ -0,0 +1,26 @@
/*
* 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.authorization;
/**
* Constants for keys that can be passed in the AuthorizationRequest user context Map.
*/
public enum UserContextKeys {
CLIENT_ADDRESS;
}

Просмотреть файл

@ -0,0 +1,108 @@
/*
* 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.authorization;
import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
import java.util.Set;
/**
* Provides access to Users and Groups.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface UserGroupProvider {
/**
* Retrieves all users. Must be non null
*
* @return a list of users
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Set<User> getUsers() throws AuthorizationAccessException;
/**
* Retrieves the user with the given identifier.
*
* @param identifier the id of the user to retrieve
* @return the user with the given id, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User getUser(String identifier) throws AuthorizationAccessException;
/**
* Retrieves the user with the given identity.
*
* @param identity the identity of the user to retrieve
* @return the user with the given identity, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User getUserByIdentity(String identity) throws AuthorizationAccessException;
/**
* Retrieves all groups. Must be non null
*
* @return a list of groups
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Set<Group> getGroups() throws AuthorizationAccessException;
/**
* Retrieves a Group by id.
*
* @param identifier the identifier of the Group to retrieve
* @return the Group with the given identifier, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Group getGroup(String identifier) throws AuthorizationAccessException;
/**
* Gets a user and their groups. Must be non null. If the user is not known the UserAndGroups.getUser() and
* UserAndGroups.getGroups() should return null
*
* @return the UserAndGroups for the specified identity
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException;
/**
* Called immediately after instance creation for implementers to perform additional setup
*
* @param initializationContext in which to initialize
*/
void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException;
/**
* Called to configure the Authorizer.
*
* @param configurationContext at the time of configuration
* @throws AuthorizerCreationException for any issues configuring the provider
*/
void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
/**
* Called immediately before instance destruction for implementers to release resources.
*
* @throws AuthorizerDestructionException If pre-destruction fails.
*/
void preDestruction() throws AuthorizerDestructionException;
}

Просмотреть файл

@ -0,0 +1,37 @@
/*
* 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.authorization;
/**
* Initialization content for UserGroupProviders.
*/
public interface UserGroupProviderInitializationContext {
/**
* The identifier of the UserGroupProvider.
*
* @return The identifier
*/
String getIdentifier();
/**
* The lookup for accessing other configured UserGroupProviders.
*
* @return The UserGroupProvider lookup
*/
UserGroupProviderLookup getUserGroupProviderLookup();
}

Просмотреть файл

@ -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.
*/
package org.apache.nifi.registry.authorization;
/**
*
*/
public interface UserGroupProviderLookup {
/**
* Looks up the UserGroupProvider with the specified identifier
*
* @param identifier The identifier of the UserGroupProvider
* @return The UserGroupProvider
*/
UserGroupProvider getUserGroupProvider(String identifier);
}

Просмотреть файл

@ -0,0 +1,39 @@
/*
* 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.authorization.exception;
/**
* Represents any error that might occur while authorizing user requests.
*/
public class AccessDeniedException extends RuntimeException {
private static final long serialVersionUID = -5683444815269084134L;
public AccessDeniedException(Throwable cause) {
super(cause);
}
public AccessDeniedException(String message, Throwable cause) {
super(message, cause);
}
public AccessDeniedException(String message) {
super(message);
}
public AccessDeniedException() {
}
}

Просмотреть файл

@ -0,0 +1,32 @@
/*
* 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.authorization.exception;
/**
* Represents the case when an authorization decision could not be made because the Authorizer was unable to access the underlying data store.
*/
public class AuthorizationAccessException extends RuntimeException {
public AuthorizationAccessException(String message, Throwable cause) {
super(message, cause);
}
public AuthorizationAccessException(String message) {
super(message);
}
}

Просмотреть файл

@ -0,0 +1,39 @@
/*
* 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.authorization.exception;
/**
* Represents the exceptional case when an Authorizer fails instantiation.
*
*/
public class AuthorizerCreationException extends RuntimeException {
public AuthorizerCreationException() {
}
public AuthorizerCreationException(String msg) {
super(msg);
}
public AuthorizerCreationException(Throwable cause) {
super(cause);
}
public AuthorizerCreationException(String msg, Throwable cause) {
super(msg, cause);
}
}

Просмотреть файл

@ -0,0 +1,39 @@
/*
* 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.authorization.exception;
/**
* Represents the exceptional case when an Authorizer fails destruction.
*
*/
public class AuthorizerDestructionException extends RuntimeException {
public AuthorizerDestructionException() {
}
public AuthorizerDestructionException(String msg) {
super(msg);
}
public AuthorizerDestructionException(Throwable cause) {
super(cause);
}
public AuthorizerDestructionException(String msg, Throwable cause) {
super(msg, cause);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
/*
* 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.authorization.exception;
/**
* Represents the case when the proposed authorizations are not inheritable.
*/
public class UninheritableAuthorizationsException extends RuntimeException {
public UninheritableAuthorizationsException(String message) {
super(message);
}
}

Просмотреть файл

@ -1,70 +0,0 @@
<?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.registry</groupId>
<artifactId>nifi-registry</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>nifi-registry-security</artifactId>
<packaging>jar</packaging>
<build>
<resources>
<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.registry.user.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>**/user/generated/*.java</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-properties</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
</project>

Просмотреть файл

@ -1,88 +0,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.
*/
package org.apache.nifi.registry.security;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.user.generated.User;
import org.apache.nifi.registry.user.generated.Users;
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.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AuthorizationProvider {
private static final String USERS_XSD = "/users.xsd";
private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.user.generated";
private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
/**
* Load the JAXBContext.
*/
private static JAXBContext initializeJaxbContext() {
try {
return JAXBContext.newInstance(JAXB_GENERATED_PATH, AuthorizationProvider.class.getClassLoader());
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext.");
}
}
private final List<String> authorizedUsers;
public AuthorizationProvider(final NiFiRegistryProperties properties) {
final File usersFile = properties.getAuthorizedUsersFile();
final List<String> userList = new ArrayList<>();
// load the users from the specified file
if (usersFile != null && usersFile.exists()) {
try {
// find the schema
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = schemaFactory.newSchema(AuthorizationProvider.class.getResource(USERS_XSD));
// attempt to unmarshal
final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(schema);
final JAXBElement<Users> element = unmarshaller.unmarshal(new StreamSource(usersFile), Users.class);
final Users users = element.getValue();
// add each users dn
for (final User user : users.getUser()) {
userList.add(user.getDn());
}
} catch (SAXException | JAXBException e) {
throw new RuntimeException("Unable to read the authorized useres file: " + e, e);
}
}
authorizedUsers = Collections.unmodifiableList(userList);
}
public boolean canAccess(final String dn) {
return authorizedUsers.contains(dn);
}
}

Просмотреть файл

@ -1,87 +0,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.
*/
package org.apache.nifi.registry.security;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.cert.X509Certificate;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AuthorizedUserFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AuthorizedUserFilter.class);
private final AuthorizationProvider provider;
public AuthorizedUserFilter(final AuthorizationProvider provider) {
this.provider = provider;
}
@Override
public void init(FilterConfig fc) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (request.isSecure()) {
final String dn = getDn(httpServletRequest);
// if the user has a certificate, extract the dn and see if they can access
if (dn != null && provider.canAccess(dn)) {
chain.doFilter(request, response);
} else {
// set the response status
httpServletResponse.setContentType("text/plain");
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
// write the response message
PrintWriter out = httpServletResponse.getWriter();
out.println("Access is denied.");
// log the failure
logger.info(String.format(String.format("User <%s> is not authorized.", dn)));
}
} else {
chain.doFilter(request, response);
}
}
private String getDn(final HttpServletRequest request) {
X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
return certs[0].getSubjectDN().getName().trim();
} else {
return null;
}
}
@Override
public void destroy() {
}
}

Просмотреть файл

@ -1,37 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- user -->
<xs:complexType name="User">
<xs:attribute name="dn">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*[^\s].*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- users -->
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" type="User" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Просмотреть файл

@ -0,0 +1,245 @@
/*
* 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.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public enum DataUnit {
/**
* Bytes
*/
B {
@Override
public double toB(double value) {
return value;
}
@Override
public double toKB(double value) {
return value / POWERS[1];
}
@Override
public double toMB(double value) {
return value / POWERS[2];
}
@Override
public double toGB(double value) {
return value / POWERS[3];
}
@Override
public double toTB(double value) {
return value / POWERS[4];
}
@Override
public double convert(double sourceSize, DataUnit sourceUnit) {
return sourceUnit.toB(sourceSize);
}
},
/**
* Kilobytes
*/
KB {
@Override
public double toB(double value) {
return value * POWERS[1];
}
@Override
public double toKB(double value) {
return value;
}
@Override
public double toMB(double value) {
return value / POWERS[1];
}
@Override
public double toGB(double value) {
return value / POWERS[2];
}
@Override
public double toTB(double value) {
return value / POWERS[3];
}
@Override
public double convert(double sourceSize, DataUnit sourceUnit) {
return sourceUnit.toKB(sourceSize);
}
},
/**
* Megabytes
*/
MB {
@Override
public double toB(double value) {
return value * POWERS[2];
}
@Override
public double toKB(double value) {
return value * POWERS[1];
}
@Override
public double toMB(double value) {
return value;
}
@Override
public double toGB(double value) {
return value / POWERS[1];
}
@Override
public double toTB(double value) {
return value / POWERS[2];
}
@Override
public double convert(double sourceSize, DataUnit sourceUnit) {
return sourceUnit.toMB(sourceSize);
}
},
/**
* Gigabytes
*/
GB {
@Override
public double toB(double value) {
return value * POWERS[3];
}
@Override
public double toKB(double value) {
return value * POWERS[2];
}
@Override
public double toMB(double value) {
return value * POWERS[1];
}
@Override
public double toGB(double value) {
return value;
}
@Override
public double toTB(double value) {
return value / POWERS[1];
}
@Override
public double convert(double sourceSize, DataUnit sourceUnit) {
return sourceUnit.toGB(sourceSize);
}
},
/**
* Terabytes
*/
TB {
@Override
public double toB(double value) {
return value * POWERS[4];
}
@Override
public double toKB(double value) {
return value * POWERS[3];
}
@Override
public double toMB(double value) {
return value * POWERS[2];
}
@Override
public double toGB(double value) {
return value * POWERS[1];
}
@Override
public double toTB(double value) {
return value;
}
@Override
public double convert(double sourceSize, DataUnit sourceUnit) {
return sourceUnit.toTB(sourceSize);
}
};
public double convert(final double sourceSize, final DataUnit sourceUnit) {
throw new AbstractMethodError();
}
public double toB(double size) {
throw new AbstractMethodError();
}
public double toKB(double size) {
throw new AbstractMethodError();
}
public double toMB(double size) {
throw new AbstractMethodError();
}
public double toGB(double size) {
throw new AbstractMethodError();
}
public double toTB(double size) {
throw new AbstractMethodError();
}
public static final double[] POWERS = {1,
1024D,
1024 * 1024D,
1024 * 1024 * 1024D,
1024 * 1024 * 1024 * 1024D};
public static final String DATA_SIZE_REGEX = "(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB|TB)";
public static final Pattern DATA_SIZE_PATTERN = Pattern.compile(DATA_SIZE_REGEX);
public static Double parseDataSize(final String value, final DataUnit units) {
if (value == null) {
return null;
}
final Matcher matcher = DATA_SIZE_PATTERN.matcher(value.toUpperCase());
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid data size: " + value);
}
final String sizeValue = matcher.group(1);
final String unitValue = matcher.group(2);
final DataUnit sourceUnit = DataUnit.valueOf(unitValue);
final double size = Double.parseDouble(sizeValue);
return units.convert(size, sourceUnit);
}
}

Просмотреть файл

@ -16,9 +16,19 @@
*/
package org.apache.nifi.registry.util;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
@ -31,8 +41,39 @@ import org.slf4j.Logger;
*/
public class FileUtils {
public static final long TRANSFER_CHUNK_SIZE_BYTES = 1024 * 1024 * 8; //8 MB chunks
public static final long MILLIS_BETWEEN_ATTEMPTS = 50L;
/**
* Closes the given closeable quietly - no logging, no exceptions...
*
* @param closeable the thing to close
*/
public static void closeQuietly(final Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (final IOException io) {/*IGNORE*/
}
}
}
/**
* Releases the given lock quietly no logging, no exception
*
* @param lock the lock to release
*/
public static void releaseQuietly(final FileLock lock) {
if (null != lock) {
try {
lock.release();
} catch (final IOException io) {
/*IGNORE*/
}
}
}
/* Superseded by renamed class bellow */
@Deprecated
public static void ensureDirectoryExistAndCanAccess(final File dir) throws IOException {
@ -67,6 +108,140 @@ public class FileUtils {
}
}
/**
* Copies the given source file to the given destination file. The given destination will be overwritten if it already exists.
*
* @param source the file to copy
* @param destination the file to copy to
* @param lockInputFile if true will lock input file during copy; if false will not
* @param lockOutputFile if true will lock output file during copy; if false will not
* @param move if true will perform what is effectively a move operation rather than a pure copy. This allows for potentially highly efficient movement of the file but if not possible this will
* revert to a copy then delete behavior. If false, then the file is copied and the source file is retained. If a true rename/move occurs then no lock is held during that time.
* @param logger if failures occur, they will be logged to this logger if possible. If this logger is null, an IOException will instead be thrown, indicating the problem.
* @return long number of bytes copied
* @throws FileNotFoundException if the source file could not be found
* @throws IOException if unable to read or write the underlying streams
* @throws SecurityException if a security manager denies the needed file operations
*/
public static long copyFile(final File source, final File destination, final boolean lockInputFile, final boolean lockOutputFile, final boolean move, final Logger logger)
throws FileNotFoundException, IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
FileLock inLock = null;
FileLock outLock = null;
long fileSize = 0L;
if (!source.canRead()) {
throw new IOException("Must at least have read permission");
}
if (move && source.renameTo(destination)) {
fileSize = destination.length();
} else {
try {
fis = new FileInputStream(source);
fos = new FileOutputStream(destination);
final FileChannel in = fis.getChannel();
final FileChannel out = fos.getChannel();
if (lockInputFile) {
inLock = in.tryLock(0, Long.MAX_VALUE, true);
if (null == inLock) {
throw new IOException("Unable to obtain shared file lock for: " + source.getAbsolutePath());
}
}
if (lockOutputFile) {
outLock = out.tryLock(0, Long.MAX_VALUE, false);
if (null == outLock) {
throw new IOException("Unable to obtain exclusive file lock for: " + destination.getAbsolutePath());
}
}
long bytesWritten = 0;
do {
bytesWritten += out.transferFrom(in, bytesWritten, TRANSFER_CHUNK_SIZE_BYTES);
fileSize = in.size();
} while (bytesWritten < fileSize);
out.force(false);
FileUtils.closeQuietly(fos);
FileUtils.closeQuietly(fis);
fos = null;
fis = null;
if (move && !FileUtils.deleteFile(source, null, 5)) {
if (logger == null) {
FileUtils.deleteFile(destination, null, 5);
throw new IOException("Could not remove file " + source.getAbsolutePath());
} else {
logger.warn("Configured to delete source file when renaming/move not successful. However, unable to delete file at: " + source.getAbsolutePath());
}
}
} finally {
FileUtils.releaseQuietly(inLock);
FileUtils.releaseQuietly(outLock);
FileUtils.closeQuietly(fos);
FileUtils.closeQuietly(fis);
}
}
return fileSize;
}
/**
* Copies the given source file to the given destination file. The given destination will be overwritten if it already exists.
*
* @param source the file to copy from
* @param destination the file to copy to
* @param lockInputFile if true will lock input file during copy; if false will not
* @param lockOutputFile if true will lock output file during copy; if false will not
* @param logger the logger to use
* @return long number of bytes copied
* @throws FileNotFoundException if the source file could not be found
* @throws IOException if unable to read or write to file
* @throws SecurityException if a security manager denies the needed file operations
*/
public static long copyFile(final File source, final File destination, final boolean lockInputFile, final boolean lockOutputFile, final Logger logger) throws FileNotFoundException, IOException {
return FileUtils.copyFile(source, destination, lockInputFile, lockOutputFile, false, logger);
}
public static long copyFile(final File source, final OutputStream stream, final boolean closeOutputStream, final boolean lockInputFile) throws FileNotFoundException, IOException {
FileInputStream fis = null;
FileLock inLock = null;
long fileSize = 0L;
try {
fis = new FileInputStream(source);
final FileChannel in = fis.getChannel();
if (lockInputFile) {
inLock = in.tryLock(0, Long.MAX_VALUE, true);
if (inLock == null) {
throw new IOException("Unable to obtain exclusive file lock for: " + source.getAbsolutePath());
}
}
byte[] buffer = new byte[1 << 18]; //256 KB
int bytesRead = -1;
while ((bytesRead = fis.read(buffer)) != -1) {
stream.write(buffer, 0, bytesRead);
}
in.force(false);
stream.flush();
fileSize = in.size();
} finally {
FileUtils.releaseQuietly(inLock);
FileUtils.closeQuietly(fis);
if (closeOutputStream) {
FileUtils.closeQuietly(stream);
}
}
return fileSize;
}
public static long copyFile(final InputStream stream, final File destination, final boolean closeInputStream, final boolean lockOutputFile) throws FileNotFoundException, IOException {
final Path destPath = destination.toPath();
final long size = Files.copy(stream, destPath);
if (closeInputStream) {
stream.close();
}
return size;
}
/**
* Deletes the given file. If the given file exists but could not be deleted
* this will be printed as a warning to the given logger

Просмотреть файл

@ -0,0 +1,261 @@
/*
* 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.util;
import java.text.NumberFormat;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FormatUtils {
private static final String UNION = "|";
// for Data Sizes
private static final double BYTES_IN_KILOBYTE = 1024;
private static final double BYTES_IN_MEGABYTE = BYTES_IN_KILOBYTE * 1024;
private static final double BYTES_IN_GIGABYTE = BYTES_IN_MEGABYTE * 1024;
private static final double BYTES_IN_TERABYTE = BYTES_IN_GIGABYTE * 1024;
// for Time Durations
private static final String NANOS = join(UNION, "ns", "nano", "nanos", "nanosecond", "nanoseconds");
private static final String MILLIS = join(UNION, "ms", "milli", "millis", "millisecond", "milliseconds");
private static final String SECS = join(UNION, "s", "sec", "secs", "second", "seconds");
private static final String MINS = join(UNION, "m", "min", "mins", "minute", "minutes");
private static final String HOURS = join(UNION, "h", "hr", "hrs", "hour", "hours");
private static final String DAYS = join(UNION, "d", "day", "days");
private static final String WEEKS = join(UNION, "w", "wk", "wks", "week", "weeks");
private static final String VALID_TIME_UNITS = join(UNION, NANOS, MILLIS, SECS, MINS, HOURS, DAYS, WEEKS);
public static final String TIME_DURATION_REGEX = "(\\d+)\\s*(" + VALID_TIME_UNITS + ")";
public static final Pattern TIME_DURATION_PATTERN = Pattern.compile(TIME_DURATION_REGEX);
/**
* Formats the specified count by adding commas.
*
* @param count the value to add commas to
* @return the string representation of the given value with commas included
*/
public static String formatCount(final long count) {
return NumberFormat.getIntegerInstance().format(count);
}
/**
* Formats the specified duration in 'mm:ss.SSS' format.
*
* @param sourceDuration the duration to format
* @param sourceUnit the unit to interpret the duration
* @return representation of the given time data in minutes/seconds
*/
public static String formatMinutesSeconds(final long sourceDuration, final TimeUnit sourceUnit) {
final long millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit);
final long millisInMinute = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
final int minutes = (int) (millis / millisInMinute);
final long secondsMillisLeft = millis - minutes * millisInMinute;
final long millisInSecond = TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
final int seconds = (int) (secondsMillisLeft / millisInSecond);
final long millisLeft = secondsMillisLeft - seconds * millisInSecond;
return pad2Places(minutes) + ":" + pad2Places(seconds) + "." + pad3Places(millisLeft);
}
/**
* Formats the specified duration in 'HH:mm:ss.SSS' format.
*
* @param sourceDuration the duration to format
* @param sourceUnit the unit to interpret the duration
* @return representation of the given time data in hours/minutes/seconds
*/
public static String formatHoursMinutesSeconds(final long sourceDuration, final TimeUnit sourceUnit) {
final long millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit);
final long millisInHour = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
final int hours = (int) (millis / millisInHour);
final long minutesSecondsMillisLeft = millis - hours * millisInHour;
return pad2Places(hours) + ":" + formatMinutesSeconds(minutesSecondsMillisLeft, TimeUnit.MILLISECONDS);
}
private static String pad2Places(final long val) {
return (val < 10) ? "0" + val : String.valueOf(val);
}
private static String pad3Places(final long val) {
return (val < 100) ? "0" + pad2Places(val) : String.valueOf(val);
}
/**
* Formats the specified data size in human readable format.
*
* @param dataSize Data size in bytes
* @return Human readable format
*/
public static String formatDataSize(final double dataSize) {
// initialize the formatter
final NumberFormat format = NumberFormat.getNumberInstance();
format.setMaximumFractionDigits(2);
// check terabytes
double dataSizeToFormat = dataSize / BYTES_IN_TERABYTE;
if (dataSizeToFormat > 1) {
return format.format(dataSizeToFormat) + " TB";
}
// check gigabytes
dataSizeToFormat = dataSize / BYTES_IN_GIGABYTE;
if (dataSizeToFormat > 1) {
return format.format(dataSizeToFormat) + " GB";
}
// check megabytes
dataSizeToFormat = dataSize / BYTES_IN_MEGABYTE;
if (dataSizeToFormat > 1) {
return format.format(dataSizeToFormat) + " MB";
}
// check kilobytes
dataSizeToFormat = dataSize / BYTES_IN_KILOBYTE;
if (dataSizeToFormat > 1) {
return format.format(dataSizeToFormat) + " KB";
}
// default to bytes
return format.format(dataSize) + " bytes";
}
public static long getTimeDuration(final String value, final TimeUnit desiredUnit) {
final Matcher matcher = TIME_DURATION_PATTERN.matcher(value.toLowerCase());
if (!matcher.matches()) {
throw new IllegalArgumentException("Value '" + value + "' is not a valid Time Duration");
}
final String duration = matcher.group(1);
final String units = matcher.group(2);
TimeUnit specifiedTimeUnit = null;
switch (units.toLowerCase()) {
case "ns":
case "nano":
case "nanos":
case "nanoseconds":
specifiedTimeUnit = TimeUnit.NANOSECONDS;
break;
case "ms":
case "milli":
case "millis":
case "milliseconds":
specifiedTimeUnit = TimeUnit.MILLISECONDS;
break;
case "s":
case "sec":
case "secs":
case "second":
case "seconds":
specifiedTimeUnit = TimeUnit.SECONDS;
break;
case "m":
case "min":
case "mins":
case "minute":
case "minutes":
specifiedTimeUnit = TimeUnit.MINUTES;
break;
case "h":
case "hr":
case "hrs":
case "hour":
case "hours":
specifiedTimeUnit = TimeUnit.HOURS;
break;
case "d":
case "day":
case "days":
specifiedTimeUnit = TimeUnit.DAYS;
break;
case "w":
case "wk":
case "wks":
case "week":
case "weeks":
final long durationVal = Long.parseLong(duration);
return desiredUnit.convert(durationVal, TimeUnit.DAYS)*7;
}
final long durationVal = Long.parseLong(duration);
return desiredUnit.convert(durationVal, specifiedTimeUnit);
}
public static String formatUtilization(final double utilization) {
return utilization + "%";
}
private static String join(final String delimiter, final String... values) {
if (values.length == 0) {
return "";
} else if (values.length == 1) {
return values[0];
}
final StringBuilder sb = new StringBuilder();
sb.append(values[0]);
for (int i = 1; i < values.length; i++) {
sb.append(delimiter).append(values[i]);
}
return sb.toString();
}
/**
* Formats nanoseconds in the format:
* 3 seconds, 8 millis, 3 nanos - if includeTotalNanos = false,
* 3 seconds, 8 millis, 3 nanos (3008000003 nanos) - if includeTotalNanos = true
*
* @param nanos the number of nanoseconds to format
* @param includeTotalNanos whether or not to include the total number of nanoseconds in parentheses in the returned value
* @return a human-readable String that is a formatted representation of the given number of nanoseconds.
*/
public static String formatNanos(final long nanos, final boolean includeTotalNanos) {
final StringBuilder sb = new StringBuilder();
final long seconds = nanos > 1000000000L ? nanos / 1000000000L : 0L;
long millis = nanos > 1000000L ? nanos / 1000000L : 0L;
final long nanosLeft = nanos % 1000000L;
if (seconds > 0) {
sb.append(seconds).append(" seconds");
}
if (millis > 0) {
if (seconds > 0) {
sb.append(", ");
millis -= seconds * 1000L;
}
sb.append(millis).append(" millis");
}
if (seconds > 0 || millis > 0) {
sb.append(", ");
}
sb.append(nanosLeft).append(" nanos");
if (includeTotalNanos) {
sb.append(" (").append(nanos).append(" nanos)");
}
return sb.toString();
}
}

Просмотреть файл

@ -0,0 +1,91 @@
/*
* 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.util;
import java.util.concurrent.TimeUnit;
/**
* <p>
* A PropertyValue provides a mechanism whereby the currently configured value
* can be obtained in different forms.
* </p>
*/
public interface PropertyValue {
/**
* @return the raw property value as a string
*/
String getValue();
/**
* @return an integer representation of the property value, or
* <code>null</code> if not set
* @throws NumberFormatException if not able to parse
*/
Integer asInteger();
/**
* @return a Long representation of the property value, or <code>null</code>
* if not set
* @throws NumberFormatException if not able to parse
*/
Long asLong();
/**
* @return a Boolean representation of the property value, or
* <code>null</code> if not set
*/
Boolean asBoolean();
/**
* @return a Float representation of the property value, or
* <code>null</code> if not set
* @throws NumberFormatException if not able to parse
*/
Float asFloat();
/**
* @return a Double representation of the property value, of
* <code>null</code> if not set
* @throws NumberFormatException if not able to parse
*/
Double asDouble();
/**
* @param timeUnit specifies the TimeUnit to convert the time duration into
* @return a Long value representing the value of the configured time period
* in terms of the specified TimeUnit; if the property is not set, returns
* <code>null</code>
*/
Long asTimePeriod(TimeUnit timeUnit);
/**
*
* @param dataUnit specifies the DataUnit to convert the data size into
* @return a Long value representing the value of the configured data size
* in terms of the specified DataUnit; if hte property is not set, returns
* <code>null</code>
*/
Double asDataSize(DataUnit dataUnit);
/**
* @return <code>true</code> if the user has configured a value, or if the
* PropertyDescriptor for the associated property has a default
* value, <code>false</code> otherwise
*/
boolean isSet();
}

Просмотреть файл

@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.util;
import java.util.concurrent.TimeUnit;
public class StandardPropertyValue implements PropertyValue {
private final String rawValue;
public StandardPropertyValue(final String rawValue) {
this.rawValue = rawValue;
}
@Override
public String getValue() {
return rawValue;
}
@Override
public Integer asInteger() {
return (rawValue == null) ? null : Integer.parseInt(rawValue.trim());
}
@Override
public Long asLong() {
return (rawValue == null) ? null : Long.parseLong(rawValue.trim());
}
@Override
public Boolean asBoolean() {
return (rawValue == null) ? null : Boolean.parseBoolean(rawValue.trim());
}
@Override
public Float asFloat() {
return (rawValue == null) ? null : Float.parseFloat(rawValue.trim());
}
@Override
public Double asDouble() {
return (rawValue == null) ? null : Double.parseDouble(rawValue.trim());
}
@Override
public Long asTimePeriod(final TimeUnit timeUnit) {
return (rawValue == null) ? null : FormatUtils.getTimeDuration(rawValue.trim(), timeUnit);
}
@Override
public Double asDataSize(final DataUnit dataUnit) {
return rawValue == null ? null : DataUnit.parseDataSize(rawValue.trim(), dataUnit);
}
@Override
public boolean isSet() {
return rawValue != null;
}
@Override
public String toString() {
return rawValue;
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше