Example: if all CGI params (including unrelated to db table 'Table') are in %Q, then:

@rows = $dbh->Select('Table', \%Q);

will execute any simple SELECT query from the table Table (defined by user-supplied parameters in %Q); and this:

@user_rows = $dbh->Select('Table', {%Q, id_user=>$id});

will make any similar query limited to records with id_user column value $id (thus allowing user to fetch any or his own records).

The module is intended for use only with a fairly simple tables and simple SQL queries. More advanced queries usually can be generated manually with help of "GetSQL" or you can just use plain DBI methods.

Also it support non-blocking SQL queries using AnyEvent::DBI::MySQL and thus can be effectively used with event-based CGI frameworks like Mojolicious or with event-based FastCGI servers like FCGI::EV.

Finally, it can be used in non-CGI environment, as simplified interface to DBI.

SECURITY OVERVIEW

At a glance, generating SQL queries based on untrusted parameters sent by user to your CGI looks very unsafe. But interface of this module designed to make it safe - while you conform to some "CONVENTIONS" and follow some simple guidelines.

User have no control over query type (SELECT/INSERT/…)

It's defined by method name you call.

User have no control over tables involved in SQL query

It's defined by separate (first) parameter in all methods, unrelated to hash with CGI parameters.

User have no direct control over SQL query

All values from hash are either quoted before inserting into SQL, or checked using very strict regular expressions if it's impossible to quote them (like for date/time INTERVAL values).

You can block/control access to "secure" fields in all tables

Name all such fields in some special way (like beginning with "_") and when receiving CGI parameters immediately delete all keys in hash which match these fields (i.e. all keys beginning with "_"). Later you can analyse user's request and manually add to hash keys for these fields before call method to execute SQL query.

You can limit user's access to some subset of records

Just instead of using plain \%Q as parameter for methods use something like { %Q, id_user => $id } - this way user will be limited to records with $id value in id_user column.

Within these security limitations user can do anything - select records with custom WHERE, GROUP BY, ORDER BY, LIMIT; set any values (allowed by table scheme, of course) for any fields on INSERT or UPDATE; etc. without any single line of your code - exclusively by using different CGI parameters.

HOW IT WORKS

Each CGI parameter belongs to one of three categories:

related to some table's field in db:fieldname, fieldname__funcname

control command:__commandname

your app's parameter

It's recommended to name fields in db beginning with lowercase letter or underscore, and name your app's parameters beginning with Uppercase letter to avoid occasional clash with field name.

To protect some fields (like "balance" or "privileges") from uncontrolled access you can use simple convention: name these fields in db beginning with "_"; when receiving CGI params just delete all with names beginning with "_" - thus it won't be possible to access these fields from CGI params. This module doesn't know about these protected fields and handle them just as usual fields. So, you should later add needed keys for these fields into hash before calling methods to execute SQL query. This way all operations on these fields will be controlled by your app.

You can use any other similar naming scheme which won't conflict with "CONVENTIONS" below - DBIx::SecureCGI will analyse db scheme (and cache it for speed) to detect which keys match field names.

CGI params may have several values. In hash, keys for such params must have ARRAYREF value. DBIx::SecureCGI support this only for keys which contain "__" (double underscore). Depending on used CGI framework you may need to convert existing CGI parameters into this format.

Error handling: all unknown keys will be silently ignored, all other errors (unable to detect key for joining table, field without "__funcname" have ARRAYREF value, unknown "__funcname" function, etc.) will return usual DBI errors (or throw exceptions when {RaiseError=>1}.

CONVENTIONS

Each table's first field must be a PRIMARY KEY.

MOTIVATION: This module use simplified analyse of db scheme and suppose first field in every table is a PRIMARY KEY. To add support for complex primary keys or tables without primary keys we should first define how "ID" should handle them and how to automatically join such tables.

Two tables are always JOINed using field which must be PRIMARY KEY at least in one of them and have same name in both tables.

So, don't name your primary key "id" if you plan to join this table with another - name it like "id_thistable" or "thistableId".

If both tables have field corresponding to PRIMARY KEY in other table, then key field of right table (in order defined when you make array of tables in first param of method) will be used.

If more than two tables JOINed, then each table starting from second one will try to join to each of the previous tables (starting at first table) until it find table with suitable field. If it wasn't found DBI error will be returned.

MOTIVATION: Let this module automatically join tables.

Field names must not contain "__" (two adjoined underscore).

MOTIVATION: Distinguish special commands for this module from field names. Also, some methods sometimes create aliases for fields and their names begins with "__".

Hash with CGI params may contain several values (as ARRAYREF) only for key names containing "__" (keys unrelated to fields may have any values).

MOTIVATION: Allowing { field => \@values } introduce many ambiguities and in fact same as { field__eq => \@values }, so it's safer to deny it.

Hash to SQL convertion rules

__commandname

Keys beginning with "__" are control commands. Supported commands are:

__order

Define value for ORDER BY. Valid values are:

'field_name'
'field_name ASC'
'field_name DESC'

Multiple values can be given as ARRAYREF.

__group

Define value for GROUP BY. Valid values are same as for __order.

__limit

Can have up to two numeric values (when it's ARRAYREF), set LIMIT.

__force

If the value of __force key is true, then it's allowed to run "Update" and "Delete" with an empty WHERE. (This isn't a security feature, it's just for convenience to protect against occasional damage on database while playing with CGI parameters.)

fieldname__funcname

If the key contains a "__" then it is treated as applying function "funcname" to field "fieldname". If the there is no field with such name in database, this key is ignored. A valid key value - string/number or a reference to an array of strings/numbers. A list of available functions in this version is shown below.

Unless special behavior mentioned functions handle ARRAYREF value by applying itself to each value in array and joining with AND.

Example:

{ html__like => ['%<P>%', '%<BR>%'] }

will be transformed in SQL to

html LIKE '%<P>%' AND html LIKE '%<BR>%'

Typically, such keys are used in WHERE, except when "funcname" begins with "set_" - such keys will be used in SET.

fieldname

Other keys are treated as names of fields in database. If there is no field with such name, then key is ignored. A valid value for these keys - scalar.

Example:

{ name => 'Alex' }

will be transformed in SQL to

name = 'Alex'

Typically, such keys are used in part SET, except for PRIMARY KEY field in "Update" - it will be used in WHERE.

Methods injected into DBI

GetSQL

This is helper function which will analyse (cached) database scheme for given tables and generate elements of SQL query for given keys in %Q. You may use it to write own methods like "Select" or "Insert".

In %Q keys which doesn't match field names in $table / @tables are ignored.

Names of tables and fields in all keys (except {Table} and {ID}) are already quoted, field names qualified with table name (so they're ready for inserting into SQL query). Values of {Table} and {ID} should be escaped with $dbh->quote_identifier() before using in SQL query.

Returns HASHREF with keys:

{Table} first of the used tables
{ID} name of PRIMARY KEY field in {Table}
{Select} list of all field names which should be returned by
'SELECT *' excluding duplicated fields (when field with
same name exist in many tables only field from first table
will be returned); field names in {Select} are joined with ","
{From} all tables joined using chosen JOIN type (INNER by default)
{Set} string like "field=value, field2=value2" for all simple
"fieldname" keys in %Q
{Where} a-la {Set}, except fields joined using "AND" and added
"field__function" fields; if there are no fields it will
be set to string "1"
{UpdateWhere} a-la {Where}, except it uses only "field__function" keys
plus one PRIMARY KEY "fieldname" key (if it exists in %Q)
{Order} string like "field1 ASC, field2 DESC" or empty string
{Group} a-la {Order}
{Limit} set to value of __limit if it contain one number
{SelectLimit} set to value of __limit if it contain one number,
or to values of __limit joined with "," if it contain
two numbers

because if you didn't force primary_key field to be NULL in SQL (and thus use AUTO_INCREMENT value) then user may send CGI parameter to set it to -1 or 4294967295 and this will result in DoS because no more records can be added using AUTO_INCREMENT into this table.

Delete records from $table or (one-by-one) from each table in @tables. If undef given, then delete records from ALL tables (except TEMPORARY) which have ALL fields mentioned in %Q.

To use with empty WHERE part require {__force=>1} in %Q.

Return $rv (amount of deleted records or undef on error). If used to delete records from more than one table - return $rv for last table. If error happens it will be immediately returned, so some tables may not be processed in this case.

Row

If you wonder why it exists, the answer is simple: it was added circa 2002, when there was no $dbh->selectrow_hashref() and now it continue to exists for compatibility and to complement "All" and "Col".

__funcname functions for fields

eq, ne, lt, gt, le, ge

field = value field IS NULL
field != value field IS NOT NULL
field < value
field > value
field <= value
field >= value

For functions eq or ne:

eq [] - NOT 1
ne [] - NOT 0
eq only undef - name IS NULL
ne only undef - name IS NOT NULL
eq without undef - name IN (...)
ne without undef - (name IS NULL OR name NOT IN (...))
eq with undef - (name IS NULL OR name IN (...))
ne with undef - name NOT IN (...)

LICENSE AND COPYRIGHT

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Module Install Instructions

To install DBIx::SecureCGI, simply copy and paste either of the commands in to your terminal