blob: c44a553266a73f2485c36b7a6fa36395dc57ad8a [file] [log] [blame]
/*
* 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.commons.lang3.Validate;
import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
import org.apache.nifi.registry.security.authorization.Group;
import org.apache.nifi.registry.security.authorization.User;
import org.apache.nifi.registry.security.authorization.UserAndGroups;
import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
import org.apache.nifi.registry.security.authorization.database.entity.DatabaseGroup;
import org.apache.nifi.registry.security.authorization.database.entity.DatabaseUser;
import org.apache.nifi.registry.security.authorization.database.mapper.DatabaseGroupRowMapper;
import org.apache.nifi.registry.security.authorization.database.mapper.DatabaseUserRowMapper;
import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;
import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
import org.apache.nifi.registry.security.identity.IdentityMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Implementation of {@link org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider} backed by a relational database.
*/
public class DatabaseUserGroupProvider implements ConfigurableUserGroupProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseUserGroupProvider.class);
private DataSource dataSource;
private IdentityMapper identityMapper;
private JdbcTemplate jdbcTemplate;
@AuthorizerContext
public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource;
}
@AuthorizerContext
public void setIdentityMapper(final IdentityMapper identityMapper) {
this.identityMapper = identityMapper;
}
@Override
public void initialize(final UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
final Set<String> initialUserIdentities = UserGroupProviderUtils.getInitialUserIdentities(configurationContext, identityMapper);
for (final String initialUserIdentity : initialUserIdentities) {
final User existingUser = getUserByIdentity(initialUserIdentity);
if (existingUser == null) {
final User initialUser = new User.Builder()
.identifierGenerateFromSeed(initialUserIdentity)
.identity(initialUserIdentity)
.build();
addUser(initialUser);
LOGGER.info("Created initial user with identity {}", new Object[]{initialUserIdentity});
} else {
LOGGER.debug("User already exists with identity {}", new Object[]{initialUserIdentity});
}
}
}
@Override
public void preDestruction() throws SecurityProviderDestructionException {
}
//-- fingerprint methods
@Override
public String getFingerprint() throws AuthorizationAccessException {
throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
}
@Override
public void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException {
throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
}
@Override
public void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
throw new UnsupportedOperationException("Fingerprinting is not supported by this provider");
}
//-- User CRUD
@Override
public User addUser(final User user) throws AuthorizationAccessException {
Validate.notNull(user);
final String sql = "INSERT INTO UGP_USER(IDENTIFIER, IDENTITY) VALUES (?, ?)";
jdbcTemplate.update(sql, new Object[] {user.getIdentifier(), user.getIdentity()});
return user;
}
@Override
public User updateUser(final User user) throws AuthorizationAccessException {
Validate.notNull(user);
// update the user identity
final String sql = "UPDATE UGP_USER SET IDENTITY = ? WHERE IDENTIFIER = ?";
final int updated = jdbcTemplate.update(sql, user.getIdentity(), user.getIdentifier());
// if no rows were updated then there is no user with the given identifier, so return null
if (updated <= 0) {
return null;
}
return user;
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
final String sql = "SELECT * FROM UGP_USER";
final List<DatabaseUser> databaseUsers = jdbcTemplate.query(sql, new DatabaseUserRowMapper());
final Set<User> users = new HashSet<>();
databaseUsers.forEach(u -> {
users.add(mapToUser(u));
});
return users;
}
@Override
public User getUser(final String identifier) throws AuthorizationAccessException {
Validate.notBlank(identifier);
final DatabaseUser databaseUser = getDatabaseUser(identifier);
if (databaseUser == null) {
return null;
}
return mapToUser(databaseUser);
}
@Override
public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
Validate.notBlank(identity);
final String sql = "SELECT * FROM UGP_USER WHERE IDENTITY = ?";
final DatabaseUser databaseUser = queryForObject(sql, new Object[] {identity}, new DatabaseUserRowMapper());
if (databaseUser == null) {
return null;
}
return mapToUser(databaseUser);
}
@Override
public UserAndGroups getUserAndGroups(final String userIdentity) throws AuthorizationAccessException {
Validate.notBlank(userIdentity);
// retrieve the user
final User user = getUserByIdentity(userIdentity);
// if the user exists, then retrieve the groups for the user
final Set<Group> groups;
if (user == null) {
groups = null;
} else {
final String userGroupSql =
"SELECT " +
"G.IDENTIFIER AS IDENTIFIER, " +
"G.IDENTITY AS IDENTITY " +
"FROM " +
"UGP_GROUP AS G, " +
"UGP_USER_GROUP AS UG " +
"WHERE " +
"G.IDENTIFIER = UG.GROUP_IDENTIFIER AND " +
"UG.USER_IDENTIFIER = ?";
final Object[] args = {user.getIdentifier()};
final List<DatabaseGroup> databaseGroups = jdbcTemplate.query(userGroupSql, args, new DatabaseGroupRowMapper());
groups = new HashSet<>();
databaseGroups.forEach(g -> {
final Set<String> userIdentifiers = getUserIdentifiers(g.getIdentifier());
groups.add(mapToGroup(g, userIdentifiers));
});
}
return new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return groups;
}
};
}
@Override
public User deleteUser(final User user) throws AuthorizationAccessException {
Validate.notNull(user);
final String deleteFromUserGroupSql = "DELETE FROM UGP_USER_GROUP WHERE USER_IDENTIFIER = ?";
jdbcTemplate.update(deleteFromUserGroupSql, user.getIdentifier());
final String deleteFromUserSql = "DELETE FROM UGP_USER WHERE IDENTIFIER = ?";
final int rowsDeletedFromUser = jdbcTemplate.update(deleteFromUserSql, user.getIdentifier());
if (rowsDeletedFromUser <= 0) {
return null;
}
return user;
}
private DatabaseUser getDatabaseUser(final String userIdentifier) {
final String sql = "SELECT * FROM UGP_USER WHERE IDENTIFIER = ?";
return queryForObject(sql, new Object[] {userIdentifier}, new DatabaseUserRowMapper());
}
private User mapToUser(final DatabaseUser databaseUser) {
return new User.Builder()
.identifier(databaseUser.getIdentifier())
.identity(databaseUser.getIdentity())
.build();
}
//-- Group CRUD
@Override
public Group addGroup(final Group group) throws AuthorizationAccessException {
Validate.notNull(group);
// insert to the group table...
final String groupSql = "INSERT INTO UGP_GROUP(IDENTIFIER, IDENTITY) VALUES (?, ?)";
jdbcTemplate.update(groupSql, group.getIdentifier(), group.getName());
// insert to the user-group table...
createUserGroups(group);
return group;
}
@Override
public Group updateGroup(final Group group) throws AuthorizationAccessException {
Validate.notNull(group);
// update the group identity
final String updateGroupSql = "UPDATE UGP_GROUP SET IDENTITY = ? WHERE IDENTIFIER = ?";
final int updated = jdbcTemplate.update(updateGroupSql, group.getName(), group.getIdentifier());
// if no rows were updated then a group does not exist for the given identifier, so return null
if (updated <= 0) {
return null;
}
// delete any user-group associations
final String deleteUserGroups = "DELETE FROM UGP_USER_GROUP WHERE GROUP_IDENTIFIER = ?";
jdbcTemplate.update(deleteUserGroups, group.getIdentifier());
// re-create any user-group associations
createUserGroups(group);
return group;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
// retrieve all the groups
final String sql = "SELECT * FROM UGP_GROUP";
final List<DatabaseGroup> databaseGroups = jdbcTemplate.query(sql, new DatabaseGroupRowMapper());
// retrieve all the users in the groups, mapped by group id
final Map<String,Set<String>> groupToUsers = new HashMap<>();
jdbcTemplate.query("SELECT * FROM UGP_USER_GROUP", (rs) -> {
final String groupIdentifier = rs.getString("GROUP_IDENTIFIER");
final String userIdentifier = rs.getString("USER_IDENTIFIER");
final Set<String> userIdentifiers = groupToUsers.computeIfAbsent(groupIdentifier, (k) -> new HashSet<>());
userIdentifiers.add(userIdentifier);
});
// convert from database model to api model
final Set<Group> groups = new HashSet<>();
databaseGroups.forEach(g -> {
groups.add(mapToGroup(g, groupToUsers.get(g.getIdentifier())));
});
return groups;
}
@Override
public Group getGroup(final String groupIdentifier) throws AuthorizationAccessException {
Validate.notBlank(groupIdentifier);
final DatabaseGroup databaseGroup = getDatabaseGroup(groupIdentifier);
if (databaseGroup == null) {
return null;
}
final Set<String> userIdentifiers = getUserIdentifiers(groupIdentifier);
return mapToGroup(databaseGroup, userIdentifiers);
}
@Override
public Group deleteGroup(final Group group) throws AuthorizationAccessException {
Validate.notNull(group);
final String sql = "DELETE FROM UGP_GROUP WHERE IDENTIFIER = ?";
final int rowsUpdated = jdbcTemplate.update(sql, group.getIdentifier());
if (rowsUpdated <= 0) {
return null;
}
return group;
}
private void createUserGroups(final Group group) {
if (group.getUsers() != null) {
for (final String userIdentifier : group.getUsers()) {
final String userGroupSql = "INSERT INTO UGP_USER_GROUP (USER_IDENTIFIER, GROUP_IDENTIFIER) VALUES (?, ?)";
jdbcTemplate.update(userGroupSql, userIdentifier, group.getIdentifier());
}
}
}
private DatabaseGroup getDatabaseGroup(final String groupIdentifier) {
final String sql = "SELECT * FROM UGP_GROUP WHERE IDENTIFIER = ?";
return queryForObject(sql, new Object[] {groupIdentifier}, new DatabaseGroupRowMapper());
}
private Set<String> getUserIdentifiers(final String groupIdentifier) {
final String sql = "SELECT * FROM UGP_USER_GROUP WHERE GROUP_IDENTIFIER = ?";
final Set<String> userIdentifiers = new HashSet<>();
jdbcTemplate.query(sql, new Object[]{groupIdentifier}, (rs) -> {
userIdentifiers.add(rs.getString("USER_IDENTIFIER"));
});
return userIdentifiers;
}
private Group mapToGroup(final DatabaseGroup databaseGroup, final Set<String> userIdentifiers) {
return new Group.Builder()
.identifier(databaseGroup.getIdentifier())
.name(databaseGroup.getIdentity())
.addUsers(userIdentifiers == null ? Collections.emptySet() : userIdentifiers)
.build();
}
//-- util methods
private <T> T queryForObject(final String sql, final Object[] args, final RowMapper<T> rowMapper) {
try {
return jdbcTemplate.queryForObject(sql, args, rowMapper);
} catch(final EmptyResultDataAccessException e) {
return null;
}
}
}