package org.batfish.coordinator.authorizer; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.batfish.common.BatfishException; import org.batfish.common.BatfishLogger; import org.batfish.coordinator.Main; //An authorizer that is backed by a file //Useful for testing public class DbAuthorizer implements Authorizer { private static final String COLUMN_APIKEY = "APIKey"; private static final String COLUMN_CONTAINER_NAME = "ContainerName"; private static final String COLUMN_DATE_CREATED = "DateCreated"; private static final String COLUMN_DATE_LAST_ACCESSED = "DateLastAccessed"; private static final int DB_VALID_CHECK_TIMEOUT_SECS = 3; private static final int MAX_DB_TRIES = 3; private static final String TABLE_CONTAINERS = "containers"; private static final String TABLE_PERMISSIONS = "containerpermissions"; private static final String TABLE_USERS = "members"; private Map<String, Date> _cacheApiKeys = new HashMap<>(); private Map<String, Date> _cachePermissions = new HashMap<>(); private Connection _dbConn; private BatfishLogger _logger; private java.text.SimpleDateFormat DateFormatter = new java.text.SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); public DbAuthorizer() throws SQLException { _logger = Main.getLogger(); openDbConnection(); } @Override public void authorizeContainer(String apiKey, String containerName) throws Exception { _logger.infof("Authorizing %s for %s\n", apiKey, containerName); // add the container if it does not exist; update datelastaccessed if it // does Date now = new Date(); String insertQuery = String.format( "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s') " + " ON DUPLICATE KEY UPDATE %s = '%s'", TABLE_CONTAINERS, COLUMN_CONTAINER_NAME, COLUMN_DATE_CREATED, COLUMN_DATE_LAST_ACCESSED, containerName, DateFormatter.format(now), DateFormatter.format(now), COLUMN_DATE_LAST_ACCESSED, DateFormatter.format(now)); int numInsertRows = executeUpdate(insertQuery); // MySQL says 2 rows impacted when an old row is updated; otherwise it // says 1 if (numInsertRows == 1) { _logger.infof("New container added\n"); } // return if already accessible if (isAccessibleContainer(apiKey, containerName, false)) { return; } String query = String.format( "INSERT INTO %s (%s, %s) VALUES ('%s', '%s')", TABLE_PERMISSIONS, COLUMN_APIKEY, COLUMN_CONTAINER_NAME, apiKey, containerName); int numRows = executeUpdate(query); if (numRows > 0) { String cacheKey = getPermsCacheKey(apiKey, containerName); insertInCache(_cachePermissions, cacheKey); _logger.infof("Authorization successful\n"); } } private synchronized ResultSet executeQuery(String query) { int triesLeft = MAX_DB_TRIES; while (triesLeft > 0) { triesLeft--; try { Statement stmt = _dbConn.createStatement(); return stmt.executeQuery(query); } catch (SQLException e) { _logger.errorf("SQLException while executing query '%s': %s", query, e.getMessage()); _logger.errorf("Tries left = %d\n", triesLeft); if (triesLeft > 0) { try { if (!_dbConn.isValid(DB_VALID_CHECK_TIMEOUT_SECS)) { openDbConnection(); } } catch (SQLException e1) { return null; } } } } return null; } private synchronized int executeUpdate(String query) { int triesLeft = MAX_DB_TRIES; while (triesLeft > 0) { triesLeft--; try { Statement stmt = _dbConn.createStatement(); return stmt.executeUpdate(query); } catch (SQLException e) { _logger.errorf("SQLException while executing query '%s': %s", query, e.getMessage()); _logger.errorf("Tries left = %d\n", triesLeft); if (triesLeft > 0) { try { if (!_dbConn.isValid(DB_VALID_CHECK_TIMEOUT_SECS)) { openDbConnection(); } } catch (SQLException e1) { return 0; } } } } return 0; } private String getPermsCacheKey(String apiKey, String containerName) { return apiKey + "::" + containerName; } private synchronized void insertInCache(Map<String, Date> cache, String key) { cache.put(key, new Date()); } @Override public boolean isAccessibleContainer(String apiKey, String containerName, boolean logError) throws Exception { String cacheKey = getPermsCacheKey(apiKey, containerName); boolean validInCache = isValidInCache(_cachePermissions, cacheKey); if (validInCache) { return true; } String query = String.format( "SELECT * FROM %s WHERE %s = '%s' AND %s = '%s'", TABLE_PERMISSIONS, COLUMN_APIKEY, apiKey, COLUMN_CONTAINER_NAME, containerName); ResultSet rs = executeQuery(query); if (rs != null && rs.first()) { insertInCache(_cachePermissions, cacheKey); // a valid access was made; update datelastaccessed Date now = new Date(); String updateQuery = String.format( "UPDATE %s SET %s='%s' WHERE %s='%s'", TABLE_CONTAINERS, COLUMN_DATE_LAST_ACCESSED, DateFormatter.format(now), COLUMN_CONTAINER_NAME, containerName); executeUpdate(updateQuery); return true; } if (logError) { _logger.infof("Authorizer: %s is NOT allowed to access %s\n", apiKey, containerName); } return false; } private synchronized boolean isValidInCache(Map<String, Date> cache, String key) { if (cache.containsKey(key)) { // check if the entry is expired if (new Date().getTime() - cache.get(key).getTime() > Main .getSettings().getDbAuthorizerCacheExpiryMs()) { cache.remove(key); return false; } return true; } return false; } @Override public boolean isValidWorkApiKey(String apiKey) throws Exception { boolean validInCache = isValidInCache(_cacheApiKeys, apiKey); if (validInCache) { return true; } String query = String.format("SELECT * FROM %s WHERE %s = '%s'", TABLE_USERS, COLUMN_APIKEY, apiKey); ResultSet rs = executeQuery(query); if (rs != null && rs.first()) { insertInCache(_cacheApiKeys, apiKey); return true; } _logger.infof("Authorizer: %s is NOT a valid key\n", apiKey); return false; } private synchronized void openDbConnection() throws SQLException { int triesLeft = MAX_DB_TRIES; while (triesLeft > 0) { triesLeft--; try { if (_dbConn != null) { _dbConn.close(); } String driverClass = Main.getSettings().getDriverClass(); if (driverClass != null) { Class.forName(Main.getSettings().getDriverClass()); } _dbConn = DriverManager.getConnection( Main.getSettings().getDbAuthorizerConnString()); return; } catch (SQLException e) { if (e.getMessage().contains("Access denied for user") || e.getMessage().contains("No suitable driver found") || triesLeft == 0) { throw e; } _logger.errorf("SQLException while opening Db connection: %s\n", e.getMessage()); _logger.errorf("Tries left = %d\n", triesLeft); } catch (ClassNotFoundException e) { throw new BatfishException("Could not load class", e); } } } }