diff --git a/config.sample.php b/config.sample.php index 099170c..9755b41 100644 --- a/config.sample.php +++ b/config.sample.php @@ -10,10 +10,11 @@ 'server' => 'localhost', 'port' => 3306, 'type' => 'mysql', - 'table_blacklist' => array(), - 'column_blacklist' => array(), + 'table_blocklist' => array(), + 'column_blocklist' => array(), + 'table_allowlist' => array(), ); register_db_api( 'dataset-name', $args ); -*/ \ No newline at end of file +*/ diff --git a/includes/.DS_Store b/includes/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/includes/.DS_Store and /dev/null differ diff --git a/includes/class.db-api.php b/includes/class.db-api.php index f88d4de..3bbac19 100644 --- a/includes/class.db-api.php +++ b/includes/class.db-api.php @@ -10,6 +10,7 @@ class DB_API { public $ttl = 3600; public $cache = array(); public $connections = array(); + public $format = 'json'; function __construct() { @@ -40,8 +41,9 @@ function register_db( $name = null, $args = array() ) { 'server' => 'localhost', 'port' => 3306, 'type' => 'mysql', - 'table_blacklist' => array(), - 'column_blacklist' => array(), + 'table_blocklist' => array(), + 'table_allowlist' => array(), + 'column_blocklist' => array(), 'ttl' => $this->ttl, ); @@ -166,9 +168,13 @@ function parse_query( $query = null ) { $db = $this->get_db( $parts['db'] ); - if ( in_array( $parts['table'], $db->table_blacklist ) ) { + if ( in_array( $parts['table'], $db->table_blocklist ) ) { $this->error( 'Invalid table', 404 ); } + + if ( sizeof($db->table_allowlist)>0 && !in_array( $parts['table'], $db->table_allowlist ) ) { + $this->error( 'Invalid method: '.$parts['table'], 404 ); + } if ( !in_array( $parts['direction'], array( 'ASC', 'DESC' ) ) ) { $parts['direction'] = null; @@ -177,7 +183,7 @@ function parse_query( $query = null ) { if ( !in_array( $parts['format'], array( 'html', 'xml', 'json' ) ) ) { $parts['format'] = null; } - + return $parts; } @@ -335,6 +341,8 @@ function get_first_column( $table, $db = null ) { * @return array an array of results */ function query( $query, $db = null ) { + + $this->format = $query['format']; $key = md5( serialize( $query ) . $this->get_db( $db )->name ); @@ -400,18 +408,18 @@ function query( $query, $db = null ) { } /** - * Remove any blacklisted columns from the data set. + * Remove any blocklisted columns from the data set. */ function sanitize_results( $results, $db = null ) { $db = $this->get_db( $db ); - if ( empty( $db->column_blacklist ) ) { + if ( empty( $db->column_blocklist ) ) { return $results; } foreach ( $results as $ID => $result ) { - foreach ( $db->column_blacklist as $column ) { + foreach ( $db->column_blocklist as $column ) { unset( $results[ $ID ] -> $column ); } @@ -427,11 +435,16 @@ function sanitize_results( $results, $db = null ) { * @param int $code (optional) the error code with which to respond */ function error( $error, $code = '500' ) { - - if ( is_object( $error ) && method_exists( $error, 'get_message' ) ) { - $error = $error->get_message(); - } - + + if ( is_object( $error ) && method_exists( $error, 'get_message' ) ) { + $error = $error->get_message(); + } + + if('json'==$this->format) { + $this->render_json(['error'=>$error], []); + exit; + } + http_response_code( $code ); die( $error ); return false; @@ -443,6 +456,9 @@ function error( $error, $code = '500' ) { * @todo Support JSONP, with callback filtering. */ function render_json( $data, $query ) { + if(false === $data) { + $data = ['error'=>"No data found"]; + } header('Content-type: application/json'); $output = json_encode( $data ); @@ -480,9 +496,9 @@ function jsonp_callback_filter( $callback ) { */ function render_html( $data ) { - require_once( dirname( __FILE__ ) . '/bootstrap/header.html' ); + require_once( dirname( __FILE__ ) . '/bootstrap/header.html' ); - //err out if no results + //err out if no results if ( empty( $data ) ) { $this->error( 'No results found', 404 ); return; @@ -495,29 +511,29 @@ function render_html( $data ) { echo "\n\n\n"; foreach ( array_keys( get_object_vars( reset( $data ) ) ) as $heading ) { - echo "\t\n"; + echo "\t\n"; } echo "\n\n"; //loop data and render foreach ( $data as $row ) { - - echo "\n"; - - foreach ( $row as $cell ) { - - echo "\t\n"; - - } - - echo ""; - + + echo "\n"; + + foreach ( $row as $cell ) { + + echo "\t\n"; + + } + + echo ""; + } echo "
$heading$heading
$cell
$cell
"; - require_once( dirname( __FILE__ ) . '/bootstrap/footer.html' ); + require_once( dirname( __FILE__ ) . '/bootstrap/footer.html' ); } @@ -576,13 +592,13 @@ function object_to_xml( $array, $xml ) { * Clean up XML domdocument formatting and return as string */ function tidy_xml( $xml ) { - + $dom = new DOMDocument(); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->loadXML( $xml->asXML() ); return $dom->saveXML(); - + } /** @@ -627,4 +643,4 @@ function cache_set( $key, $value, $ttl = null ) { } -$db_api = new DB_API(); \ No newline at end of file +$db_api = new DB_API(); diff --git a/readme.md b/readme.md index 27b44af..4849189 100644 --- a/readme.md +++ b/readme.md @@ -76,8 +76,9 @@ $args = array( 'server' => 'localhost', 'port' => 3306, 'type' => 'mysql', - 'table_blacklist' => array(), - 'column_blacklist' => array(), + 'table_blocklist' => array(), + 'column_blocklist' => array(), + 'table_allowlist' => array(), ); register_db_api( 'dataset_name', $args ); @@ -97,8 +98,9 @@ $args = array( 'server' => 'localhost', 'port' => 3306, 'type' => 'mysql', - 'table_blacklist' => array('cache', 'passwords'), - 'column_blacklist' => array('password_hint'), + 'table_blocklist' => array('cache', 'passwords'), + 'column_blocklist' => array('password_hint'), + 'table_allowlist' => array(), ); register_db_api( 'facility-inspections', $args ); @@ -107,6 +109,33 @@ register_db_api( 'facility-inspections', $args ); Retrieving the contents of the table `history` within this dataset as JSON would be accomplished with a request for `/facility-inspections/history.json`. Note that it is the name of the dataset (`facility-inspections`) and not the name of the database (`inspections`) that is specified in the URL. +Using the Allowlist +-------------------- +If you specify tables via table_allowlist, it will prevent exposing *all* other tables. If you do not want to use the allowlist, you can leave it blank, i.e. ``'table_allowlist' => array()`` and it will be ignored. + +```php + +$args = array( + 'name' => 'inspections', + 'username' => 'website', + 'password' => 's3cr3tpa55w0rd', + 'server' => 'localhost', + 'port' => 3306, + 'type' => 'mysql', + 'table_blocklist' => array(), + 'column_blocklist' => array('password_hint'), + 'table_allowlist' => array('data_public', 'moredata_public'), +); + +register_db_api( 'api_v1', $args ); +``` + +Using the allowlist, retreiving the contents of the table `data_public` within this dataset as JSON would be accomplished with a request for `/api_v1/data_public.json`. Note that it is the name of the dataset (`api_v1`) and not the name of the database (`inspections`) that is specified in the URL. + +In the above example, using `/api_v1/users.json` would fail, even if there were a users table, because it is not specified in the allowlist. + +Other Datasets +-------------- For a SQLite database, simply provide the path to the database in `name`. For an Oracle database, you can either specify a service defined in tsnames.ora (e.g. `dept_spending`) or you can define an Oracle Instant Client connection string (e.g., `//localhost:1521/dept_spending`).