/*
* 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.smscserver.usermanager.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.smscserver.SmscServerConfigurationException;
import org.apache.smscserver.smsclet.Authentication;
import org.apache.smscserver.smsclet.AuthenticationFailedException;
import org.apache.smscserver.smsclet.Authority;
import org.apache.smscserver.smsclet.SmscException;
import org.apache.smscserver.smsclet.User;
import org.apache.smscserver.usermanager.DbUserManagerFactory;
import org.apache.smscserver.usermanager.PasswordEncryptor;
import org.apache.smscserver.usermanager.UsernamePasswordAuthentication;
import org.apache.smscserver.util.DBUtils;
import org.apache.smscserver.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <strong>Internal class, do not use directly.</strong>
*
* This is another database based user manager class. It has been tested in MySQL and Oracle 8i database. The schema
* file is </code>res/smsc-db.sql</code>
*
* All the user attributes are replaced during run-time. So we can use your database schema. Then you need to modify the
* SQLs in the configuration file.
*
* @author hceylan
*/
public class DbUserManager extends AbstractUserManager {
private static final Logger LOG = LoggerFactory.getLogger(DbUserManager.class);
private String insertUserStmt;
private String updateUserStmt;
private String deleteUserStmt;
private String selectUserStmt;
private String selectAllStmt;
private String isAdminStmt;
private String authenticateStmt;
private DataSource dataSource;
/**
* Internal constructor, do not use directly. Use {@link DbUserManagerFactory} instead.
*/
public DbUserManager(DataSource dataSource, String selectAllStmt, String selectUserStmt, String insertUserStmt,
String updateUserStmt, String deleteUserStmt, String authenticateStmt, String isAdminStmt,
PasswordEncryptor passwordEncryptor, String adminName) {
super(adminName, passwordEncryptor);
this.dataSource = dataSource;
this.selectAllStmt = selectAllStmt;
this.selectUserStmt = selectUserStmt;
this.insertUserStmt = insertUserStmt;
this.updateUserStmt = updateUserStmt;
this.deleteUserStmt = deleteUserStmt;
this.authenticateStmt = authenticateStmt;
this.isAdminStmt = isAdminStmt;
Connection con = null;
try {
// test the connection
con = this.createConnection();
DbUserManager.LOG.info("Database connection for user manager successfully opened.");
} catch (SQLException e) {
String msg = "Failed to open connection to user database";
DbUserManager.LOG.error(msg, e);
throw new SmscServerConfigurationException(msg, e);
} finally {
DBUtils.closeQuitely(con);
}
}
private Connection createConnection() throws SQLException {
return DBUtils.createConnection(this.dataSource);
}
/**
* Delete user. Delete the row from the table.
*/
public void delete(String name) throws SmscException {
Statement stmt = null;
String sql = null;
try {
// create sql query
Map<String, Object> map = new HashMap<String, Object>();
map.put(AbstractUserManager.ATTR_SYSTEM_ID, DBUtils.escapeString(name));
sql = StringUtils.replaceString(this.deleteUserStmt, map);
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
stmt.executeUpdate(sql);
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(stmt);
}
}
/**
* User existance check.
*/
public boolean doesExist(String name) throws SmscException {
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// create the sql
Map<String, Object> map = new HashMap<String, Object>();
map.put(AbstractUserManager.ATTR_SYSTEM_ID, DBUtils.escapeString(name));
sql = StringUtils.replaceString(this.selectUserStmt, map);
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
return rs.next();
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
/**
* Get all user names from the database.
*/
public String[] getAllUserNames() throws SmscException {
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// create sql query
sql = this.selectAllStmt;
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
// populate list
ArrayList<String> names = new ArrayList<String>();
while (rs.next()) {
names.add(rs.getString(AbstractUserManager.ATTR_SYSTEM_ID));
}
return names.toArray(new String[0]);
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
public DataSource getDataSource() {
return this.dataSource;
}
public String getSqlUserAdmin() {
return this.isAdminStmt;
}
public String getSqlUserAuthenticate() {
return this.authenticateStmt;
}
public String getSqlUserDelete() {
return this.deleteUserStmt;
}
public String getSqlUserInsert() {
return this.insertUserStmt;
}
public String getSqlUserSelect() {
return this.selectUserStmt;
}
public String getSqlUserSelectAll() {
return this.selectAllStmt;
}
public String getSqlUserUpdate() {
return this.updateUserStmt;
}
/**
* Get the user object. Fetch the row from the table.
*/
public User getUserByName(String name) throws SmscException {
BaseUser user = this.selectUserByName(name);
if (user != null) {
// reset the password, not to be sent to API users
user.setPassword(null);
}
return user;
}
@Override
protected User internalAuthenticate(Authentication authentication) throws SmscException {
if (authentication instanceof UsernamePasswordAuthentication) {
UsernamePasswordAuthentication upauth = (UsernamePasswordAuthentication) authentication;
String username = upauth.getUsername();
String password = upauth.getPassword();
if (username == null) {
throw new AuthenticationFailedException("Authentication failed");
}
if (password == null) {
password = "";
}
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// create the sql query
Map<String, Object> map = new HashMap<String, Object>();
map.put(AbstractUserManager.ATTR_SYSTEM_ID, DBUtils.escapeString(username));
sql = StringUtils.replaceString(this.authenticateStmt, map);
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
if (rs.next()) {
try {
String storedPassword = rs.getString(AbstractUserManager.ATTR_PASSWORD);
if (this.getPasswordEncryptor().matches(password, storedPassword)) {
User user = this.getUserByName(username);
this.authorizeConcurency(authentication, user);
return user;
} else {
throw new AuthenticationFailedException("Authentication failed");
}
} catch (SmscException e) {
throw new AuthenticationFailedException("Authentication failed", e);
}
} else {
throw new AuthenticationFailedException("Authentication failed");
}
} catch (Exception e) {
if (e instanceof AuthenticationFailedException) {
throw (AuthenticationFailedException) e;
}
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
} else {
throw new IllegalArgumentException("Authentication not supported by this user manager");
}
}
/**
* @return true if user with this bind is administrator
*/
@Override
public boolean isAdmin(String systemid) throws SmscException {
// check input
if (systemid == null) {
return false;
}
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// create the sql query
Map<String, Object> map = new HashMap<String, Object>();
map.put(AbstractUserManager.ATTR_SYSTEM_ID, DBUtils.escapeString(systemid));
sql = StringUtils.replaceString(this.isAdminStmt, map);
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
return rs.next();
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
/**
* Save user. If new insert a new row, else update the existing row.
*/
public void save(User user) throws SmscException {
// null value check
if (user.getName() == null) {
throw new NullPointerException("User name is null.");
}
Statement stmt = null;
String sql = null;
try {
// create sql query
Map<String, Object> map = new HashMap<String, Object>();
map.put(AbstractUserManager.ATTR_SYSTEM_ID, DBUtils.escapeString(user.getName()));
String password = null;
if (user.getPassword() != null) {
// password provided, encrypt it and store the encrypted value
password = this.getPasswordEncryptor().encrypt(user.getPassword());
} else {
// password was not provided, either load from the existing user and store that again
// or store as null
ResultSet rs = null;
try {
User userWithPassword = this.selectUserByName(user.getName());
if (userWithPassword != null) {
// user exists, reuse password
password = userWithPassword.getPassword();
}
} finally {
DBUtils.closeQuitely(rs);
}
}
map.put(AbstractUserManager.ATTR_PASSWORD, DBUtils.escapeString(password));
map.put(AbstractUserManager.ATTR_ENABLE, String.valueOf(user.getEnabled()));
map.put(AbstractUserManager.ATTR_MAX_IDLE_TIME, user.getMaxIdleTime());
// request that always will succeed
ConcurrentBindRequest concurrentBindRequest = new ConcurrentBindRequest(0, 0);
concurrentBindRequest = (ConcurrentBindRequest) user.authorize(concurrentBindRequest);
if (concurrentBindRequest != null) {
map.put(AbstractUserManager.ATTR_MAX_BIND_NUMBER, concurrentBindRequest.getMaxConcurrentBinds());
map.put(AbstractUserManager.ATTR_MAX_BIND_PER_IP, concurrentBindRequest.getMaxConcurrentBindsPerIP());
} else {
map.put(AbstractUserManager.ATTR_MAX_BIND_NUMBER, 0);
map.put(AbstractUserManager.ATTR_MAX_BIND_PER_IP, 0);
}
if (!this.doesExist(user.getName())) {
sql = StringUtils.replaceString(this.insertUserStmt, map);
} else {
sql = StringUtils.replaceString(this.updateUserStmt, map);
}
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
stmt.executeUpdate(sql);
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(stmt);
}
}
private BaseUser selectUserByName(String systemid) throws SmscException {
Statement stmt = null;
ResultSet rs = null;
String sql = null;
try {
// create sql query
Map<String, Object> map = new HashMap<String, Object>();
map.put(AbstractUserManager.ATTR_SYSTEM_ID, DBUtils.escapeString(systemid));
sql = StringUtils.replaceString(this.selectUserStmt, map);
DbUserManager.LOG.debug(sql);
// execute query
stmt = this.createConnection().createStatement();
rs = stmt.executeQuery(sql);
// populate user object
BaseUser thisUser = null;
if (rs.next()) {
thisUser = new BaseUser();
thisUser.setName(rs.getString(AbstractUserManager.ATTR_SYSTEM_ID));
thisUser.setPassword(rs.getString(AbstractUserManager.ATTR_PASSWORD));
thisUser.setEnabled(rs.getBoolean(AbstractUserManager.ATTR_ENABLE));
thisUser.setMaxIdleTime(rs.getInt(AbstractUserManager.ATTR_MAX_IDLE_TIME));
List<Authority> authorities = new ArrayList<Authority>();
authorities.add(new ConcurrentBindPermission(rs.getInt(AbstractUserManager.ATTR_MAX_BIND_NUMBER), rs
.getInt(AbstractUserManager.ATTR_MAX_BIND_PER_IP)));
thisUser.setAuthorities(authorities);
}
return thisUser;
} catch (Exception e) {
throw DBUtils.handleException(sql, e);
} finally {
DBUtils.closeQuitelyWithConnection(rs, stmt);
}
}
/**
* Set the data source to be used by the user manager
*
* @param dataSource
* The data source to use
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Set the SQL SELECT statement used to find whether an user is admin or not. All the dynamic values will be
* replaced during runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserAdmin(String sql) {
this.isAdminStmt = sql;
}
/**
* Set the SQL SELECT statement used to authenticate user. All the dynamic values will be replaced during runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserAuthenticate(String sql) {
this.authenticateStmt = sql;
}
/**
* Set the SQL DELETE statement used to delete an existing user. All the dynamic values will be replaced during
* runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserDelete(String sql) {
this.deleteUserStmt = sql;
}
/**
* Set the SQL INSERT statement used to add a new user. All the dynamic values will be replaced during runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserInsert(String sql) {
this.insertUserStmt = sql;
}
/**
* Set the SQL SELECT statement used to select an existing user. All the dynamic values will be replaced during
* runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserSelect(String sql) {
this.selectUserStmt = sql;
}
/**
* Set the SQL SELECT statement used to select all user ids. All the dynamic values will be replaced during runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserSelectAll(String sql) {
this.selectAllStmt = sql;
}
/**
* Set the SQL UPDATE statement used to update an existing user. All the dynamic values will be replaced during
* runtime.
*
* @param sql
* The SQL statement
*/
public void setSqlUserUpdate(String sql) {
this.updateUserStmt = sql;
}
}