/* * 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.karaf.jaas.modules.jdbc; import org.apache.karaf.jaas.boot.principal.GroupPrincipal; import org.apache.karaf.jaas.boot.principal.RolePrincipal; import org.apache.karaf.jaas.boot.principal.UserPrincipal; import org.apache.karaf.jaas.modules.BackingEngine; import org.apache.karaf.jaas.modules.encryption.EncryptionSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.security.Principal; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; public class JDBCBackingEngine implements BackingEngine { private final Logger logger = LoggerFactory.getLogger(JDBCBackingEngine.class); private DataSource dataSource; private EncryptionSupport encryptionSupport; private String addUserStatement = "INSERT INTO USERS VALUES(?,?)"; private String addRoleStatement = "INSERT INTO ROLES VALUES(?,?)"; private String deleteRoleStatement = "DELETE FROM ROLES WHERE USERNAME=? AND ROLE=?"; private String deleteAllUserRolesStatement = "DELETE FROM ROLES WHERE USERNAME=?"; private String deleteUserStatement = "DELETE FROM USERS WHERE USERNAME=?"; private String selectUsersQuery = "SELECT USERNAME FROM USERS"; private String selectRolesQuery = "SELECT ROLE FROM ROLES WHERE USERNAME=?"; public JDBCBackingEngine(DataSource dataSource) { this.dataSource = dataSource; } public JDBCBackingEngine(DataSource dataSource, EncryptionSupport encryptionSupport) { this.dataSource = dataSource; this.encryptionSupport = encryptionSupport; } /** * Add a new user. * * @param username the user name. * @param password the user password. */ public void addUser(String username, String password) { if (username.startsWith(GROUP_PREFIX)) { throw new IllegalArgumentException("Prefix not permitted: " + GROUP_PREFIX); } //If encryption support is enabled, encrypt password if (encryptionSupport != null && encryptionSupport.getEncryption() != null) { password = encryptionSupport.getEncryption().encryptPassword(password); if (encryptionSupport.getEncryptionPrefix() != null) { password = encryptionSupport.getEncryptionPrefix() + password; } if (encryptionSupport.getEncryptionSuffix() != null) { password = password + encryptionSupport.getEncryptionSuffix(); } } try { try (Connection connection = dataSource.getConnection()) { rawUpdate(connection, addUserStatement, username, password); } } catch (SQLException e) { throw new RuntimeException("Error adding user", e); } } /** * Delete user by username. * * @param username the user name. */ public void deleteUser(String username) { try { try (Connection connection = dataSource.getConnection()) { rawUpdate(connection, deleteAllUserRolesStatement, username); rawUpdate(connection, deleteUserStatement, username); if (!connection.getAutoCommit()) { connection.commit(); } } } catch (SQLException e) { throw new RuntimeException("Error deleting user", e); } } /** * List all users. * * @return the list of {@link UserPrincipal}. */ public List<UserPrincipal> listUsers() { try { try (Connection connection = dataSource.getConnection()) { List<UserPrincipal> users = new ArrayList<>(); for (String name : rawSelect(connection, selectUsersQuery)) { if (!name.startsWith(GROUP_PREFIX)) { users.add(new UserPrincipal(name)); } } return users; } } catch (SQLException e) { throw new RuntimeException("Error listing users", e); } } /** * List the roles of the <code>principal</code>. * * @param principal the principal (user or group). * @return the list of {@link RolePrincipal}. */ public List<RolePrincipal> listRoles(Principal principal) { try { try (Connection connection = dataSource.getConnection()) { if (principal instanceof GroupPrincipal) { return listRoles(connection, GROUP_PREFIX + principal.getName()); } else { return listRoles(connection, principal.getName()); } } } catch (SQLException e) { throw new RuntimeException("Error listing roles", e); } } private List<RolePrincipal> listRoles(Connection connection, String name) throws SQLException { List<RolePrincipal> roles = new ArrayList<>(); for (String role : rawSelect(connection, selectRolesQuery, name)) { if (role.startsWith(GROUP_PREFIX)) { roles.addAll(listRoles(connection, role)); } else { roles.add(new RolePrincipal(role)); } } return roles; } /** * Add a role to a user. * * @param username the user name. * @param role the role. */ public void addRole(String username, String role) { try { try (Connection connection = dataSource.getConnection()) { rawUpdate(connection, addRoleStatement, username, role); if (!connection.getAutoCommit()) { connection.commit(); } } } catch (SQLException e) { throw new RuntimeException("Error adding role", e); } } /** * Remove role from user. * * @param username the user name. * @param role the role to remove. */ public void deleteRole(String username, String role) { try { try (Connection connection = dataSource.getConnection()) { rawUpdate(connection, deleteRoleStatement, username, role); if (!connection.getAutoCommit()) { connection.commit(); } } } catch (SQLException e) { throw new RuntimeException("Error deleting role", e); } } @Override public List<GroupPrincipal> listGroups(UserPrincipal principal) { try { try (Connection connection = dataSource.getConnection()) { List<GroupPrincipal> roles = new ArrayList<>(); for (String role : rawSelect(connection, selectRolesQuery, principal.getName())) { if (role.startsWith(GROUP_PREFIX)) { roles.add(new GroupPrincipal(role.substring(GROUP_PREFIX.length()))); } } return roles; } } catch (SQLException e) { throw new RuntimeException("Error deleting role", e); } } @Override public void addGroup(String username, String group) { try { try (Connection connection = dataSource.getConnection()) { String groupName = GROUP_PREFIX + group; rawUpdate(connection, addRoleStatement, username, groupName); if (!connection.getAutoCommit()) { connection.commit(); } } } catch (SQLException e) { logger.error("Error executing statement", e); } } @Override public void deleteGroup(String username, String group) { try { try (Connection connection = dataSource.getConnection()) { rawUpdate(connection, deleteRoleStatement, username, GROUP_PREFIX + group); // garbage collection, clean up the groups if needed boolean inUse = false; for (String user : rawSelect(connection, selectUsersQuery)) { for (String g : rawSelect(connection, selectRolesQuery, user)) { if (group.equals(g)) { // there is another user of this group, nothing to clean up inUse = true; break; } } } // nobody is using this group any more, remove it if (!inUse) { rawUpdate(connection, deleteAllUserRolesStatement, GROUP_PREFIX + group); } if (!connection.getAutoCommit()) { connection.commit(); } } } catch (SQLException e) { logger.error("Error executing statement", e); } } @Override public void addGroupRole(String group, String role) { addRole(GROUP_PREFIX + group, role); } @Override public void deleteGroupRole(String group, String role) { deleteRole(GROUP_PREFIX + group, role); } protected void rawUpdate(Connection connection, String query, String... params) throws SQLException { int rows = JDBCUtils.rawUpdate(connection, query, params); if (logger.isDebugEnabled()) { logger.debug(String.format("Executing [%s], params=%s. %d rows affected.", query, Arrays.toString(params), rows)); } } protected List<String> rawSelect(Connection connection, String query, String... params) throws SQLException { return JDBCUtils.rawSelect(connection, query, params); } public String getAddUserStatement() { return addUserStatement; } public void setAddUserStatement(String addUserStatement) { this.addUserStatement = addUserStatement; } public String getAddRoleStatement() { return addRoleStatement; } public void setAddRoleStatement(String addRoleStatement) { this.addRoleStatement = addRoleStatement; } public String getDeleteRoleStatement() { return deleteRoleStatement; } public void setDeleteRoleStatement(String deleteRoleStatement) { this.deleteRoleStatement = deleteRoleStatement; } public String getDeleteAllUserRolesStatement() { return deleteAllUserRolesStatement; } public void setDeleteAllUserRolesStatement(String deleteAllUserRolesStatement) { this.deleteAllUserRolesStatement = deleteAllUserRolesStatement; } public String getDeleteUserStatement() { return deleteUserStatement; } public void setDeleteUserStatement(String deleteUserStatement) { this.deleteUserStatement = deleteUserStatement; } public String getSelectUsersQuery() { return selectUsersQuery; } public void setSelectUsersQuery(String selectUsersQuery) { this.selectUsersQuery = selectUsersQuery; } public String getSelectRolesQuery() { return selectRolesQuery; } public void setSelectRolesQuery(String selectRolesQuery) { this.selectRolesQuery = selectRolesQuery; } @Override public Map<GroupPrincipal, String> listGroups() { throw new UnsupportedOperationException(); } @Override public void createGroup(String group) { throw new UnsupportedOperationException(); } }