Skip to content

NIFI-14245 Enable caching in DatabaseUserGroupProvider and DatabaseAccessPolicyProvider to improve performance #9707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.authorization.database;

import org.apache.nifi.registry.security.authorization.AccessPolicy;
import org.apache.nifi.registry.security.authorization.util.AccessPolicyProviderUtils;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
* A holder to provide atomic access to data structures.
*/
public class DatabaseAccessPolicyHolder {

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 access policies data structures.
*
* @param allPolicies all access policies
*/
public DatabaseAccessPolicyHolder(final Set<AccessPolicy> allPolicies) {
this.allPolicies = allPolicies;
this.policiesByResource = Collections.unmodifiableMap(AccessPolicyProviderUtils.createResourcePolicyMap(allPolicies));
this.policiesById = Collections.unmodifiableMap(AccessPolicyProviderUtils.createPoliciesByIdMap(allPolicies));
}

public Set<AccessPolicy> getAllPolicies() {
return allPolicies;
}

public Map<String, Set<AccessPolicy>> getPoliciesByResource() {
return policiesByResource;
}

public Map<String, AccessPolicy> getPoliciesById() {
return policiesById;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
* Implementation of {@link org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider} backed by a relational database.
Expand All @@ -63,6 +64,8 @@ public class DatabaseAccessPolicyProvider extends AbstractConfigurableAccessPoli

private JdbcTemplate jdbcTemplate;

private final AtomicReference<DatabaseAccessPolicyHolder> accessPoliciesHolder = new AtomicReference<>();

@AuthorizerContext
public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource;
Expand All @@ -81,6 +84,8 @@ protected void doInitialize(AccessPolicyProviderInitializationContext initializa

@Override
public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
refreshAccessPolicyHolder();

final String initialAdminIdentity = AccessPolicyProviderUtils.getInitialAdminIdentity(configurationContext, identityMapper);
final Set<String> nifiIdentities = AccessPolicyProviderUtils.getNiFiIdentities(configurationContext, identityMapper);
final String nifiGroupName = AccessPolicyProviderUtils.getNiFiGroupName(configurationContext, identityMapper);
Expand Down Expand Up @@ -158,7 +163,7 @@ public void checkInheritability(String proposedFingerprint) throws Authorization
// ---- access policy methods

@Override
public AccessPolicy addAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
public synchronized AccessPolicy addAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
Objects.requireNonNull(accessPolicy);

// insert to the policy table
Expand All @@ -168,11 +173,13 @@ public AccessPolicy addAccessPolicy(final AccessPolicy accessPolicy) throws Auth
// insert to the policy-user and policy groups table
createPolicyUserAndGroups(accessPolicy);

refreshAccessPolicyHolder();

return accessPolicy;
}

@Override
public AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
public synchronized AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
Objects.requireNonNull(accessPolicy);

// determine if policy exists
Expand All @@ -193,11 +200,17 @@ public AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws A
// re-create the associations
createPolicyUserAndGroups(accessPolicy);

refreshAccessPolicyHolder();

return accessPolicy;
}

@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return accessPoliciesHolder.get().getAllPolicies();
}

private Set<AccessPolicy> getDatabaseAccessPolicies() throws AuthorizationAccessException {
// retrieve all the policies
final String sql = "SELECT * FROM APP_POLICY";
final List<DatabaseAccessPolicy> databasePolicies = jdbcTemplate.query(sql, new DatabaseAccessPolicyRowMapper());
Expand Down Expand Up @@ -237,36 +250,18 @@ public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException
@Override
public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
Validate.notBlank(identifier);

final DatabaseAccessPolicy databaseAccessPolicy = getDatabaseAcessPolicy(identifier);
if (databaseAccessPolicy == null) {
return null;
}

final Set<String> userIdentifiers = getPolicyUsers(identifier);
final Set<String> groupIdentifiers = getPolicyGroups(identifier);
return mapTopAccessPolicy(databaseAccessPolicy, userIdentifiers, groupIdentifiers);
return accessPoliciesHolder.get().getPoliciesById().get(identifier);
}

@Override
public AccessPolicy getAccessPolicy(final String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
Validate.notBlank(resourceIdentifier);
Objects.requireNonNull(action);

final String policySql = "SELECT * FROM APP_POLICY WHERE RESOURCE = ? AND ACTION = ?";
final Object[] args = new Object[]{resourceIdentifier, action.toString()};
final DatabaseAccessPolicy databaseAccessPolicy = queryForObject(policySql, new DatabaseAccessPolicyRowMapper(), args);
if (databaseAccessPolicy == null) {
return null;
}

final Set<String> userIdentifiers = getPolicyUsers(databaseAccessPolicy.getIdentifier());
final Set<String> groupIdentifiers = getPolicyGroups(databaseAccessPolicy.getIdentifier());
return mapTopAccessPolicy(databaseAccessPolicy, userIdentifiers, groupIdentifiers);
return AccessPolicyProviderUtils.getAccessPolicy(resourceIdentifier, action, accessPoliciesHolder.get().getPoliciesByResource());
}

@Override
public AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
public synchronized AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
Objects.requireNonNull(accessPolicy);

final String sql = "DELETE FROM APP_POLICY WHERE IDENTIFIER = ?";
Expand All @@ -275,6 +270,8 @@ public AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws A
return null;
}

refreshAccessPolicyHolder();

return accessPolicy;
}

Expand All @@ -295,38 +292,20 @@ protected void createPolicyUserAndGroups(final AccessPolicy accessPolicy) {
protected void insertPolicyGroup(final String policyIdentifier, final String groupIdentifier) {
final String policyGroupSql = "INSERT INTO APP_POLICY_GROUP(POLICY_IDENTIFIER, GROUP_IDENTIFIER) VALUES (?, ?)";
jdbcTemplate.update(policyGroupSql, policyIdentifier, groupIdentifier);
refreshAccessPolicyHolder();
}

protected void insertPolicyUser(final String policyIdentifier, final String userIdentifier) {
final String policyUserSql = "INSERT INTO APP_POLICY_USER(POLICY_IDENTIFIER, USER_IDENTIFIER) VALUES (?, ?)";
jdbcTemplate.update(policyUserSql, policyIdentifier, userIdentifier);
refreshAccessPolicyHolder();
}

protected DatabaseAccessPolicy getDatabaseAcessPolicy(final String policyIdentifier) {
final String sql = "SELECT * FROM APP_POLICY WHERE IDENTIFIER = ?";
return queryForObject(sql, new DatabaseAccessPolicyRowMapper(), policyIdentifier);
}

protected Set<String> getPolicyUsers(final String policyIdentifier) {
final String sql = "SELECT * FROM APP_POLICY_USER WHERE POLICY_IDENTIFIER = ?";

final Set<String> userIdentifiers = new HashSet<>();
jdbcTemplate.query(sql, (rs) -> {
userIdentifiers.add(rs.getString("USER_IDENTIFIER"));
}, policyIdentifier);
return userIdentifiers;
}

protected Set<String> getPolicyGroups(final String policyIdentifier) {
final String sql = "SELECT * FROM APP_POLICY_GROUP WHERE POLICY_IDENTIFIER = ?";

final Set<String> groupIdentifiers = new HashSet<>();
jdbcTemplate.query(sql, (rs) -> {
groupIdentifiers.add(rs.getString("GROUP_IDENTIFIER"));
}, policyIdentifier);
return groupIdentifiers;
}

protected AccessPolicy mapTopAccessPolicy(final DatabaseAccessPolicy databaseAccessPolicy, final Set<String> userIdentifiers, final Set<String> groupIdentifiers) {
return new AccessPolicy.Builder()
.identifier(databaseAccessPolicy.getIdentifier())
Expand Down Expand Up @@ -387,6 +366,11 @@ protected void populateInitialPolicy(final Group initialGroup, final ResourceAnd
}
}

private synchronized void refreshAccessPolicyHolder() {
final Set<AccessPolicy> allPolicies = getDatabaseAccessPolicies();
this.accessPoliciesHolder.set(new DatabaseAccessPolicyHolder(allPolicies));
}

//-- util methods

protected <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.authorization.database;

import org.apache.nifi.registry.security.authorization.Group;
import org.apache.nifi.registry.security.authorization.User;
import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
* A holder to provide atomic access to user group data structures.
*/
public class DatabaseUserGroupHolder {

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 allUsers all users
*/
public DatabaseUserGroupHolder(final Set<User> allUsers, final Set<Group> allGroups) {
this.allUsers = allUsers;
this.allGroups = allGroups;
this.usersById = Collections.unmodifiableMap(UserGroupProviderUtils.createUserByIdMap(allUsers));
this.usersByIdentity = Collections.unmodifiableMap(UserGroupProviderUtils.createUserByIdentityMap(allUsers));
this.groupsById = Collections.unmodifiableMap(UserGroupProviderUtils.createGroupByIdMap(allGroups));
this.groupsByUserIdentity = Collections.unmodifiableMap(
UserGroupProviderUtils.createGroupsByUserIdentityMap(allGroups, allUsers));
}

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);
}

}
Loading