Skip to content
This repository was archived by the owner on Jun 11, 2024. It is now read-only.

Row Level Security: GUI for constructing CREATE POLICY commands #209

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
88 changes: 83 additions & 5 deletions src/apps/dataq/client_src/js/dataq.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@
* Defines the DataQ.DQ object, which is what the user of the library will interact with.
*
* Simply call DataQ.DQ(repo_name, callback) and DataQ will launch. After the user builds a query,
* the callback is executed as callback(query), where query is a String representing the SQL query
* the callback is executed as callback(query), where query is a String representing the SQL query
* or null if the query was not built successfully.
*/
(function() {
// Create the global DataQ object if it doesn't exist.
window.DataQ = window.DataQ || {};

// The DataQ.Query that is being built.
// The DataQ.Query that may be built.
query = null;

// The callback to execute after the query is built. It is executed as cb(query) where query
// is a String representing the SQL query or null if the query was not built.
// The DataQ.Policy object that may be built.
policy = null;

// The callback to execute after the { query | policy } is built. It is executed as
// { cb(query) | cb(policy) } where { query | policy } is a String representing the
// SQL { query | policy } or null if the { query | policy } was not built.
var callback;

/**
Expand All @@ -40,6 +44,34 @@
})
};

/**
* @param repo_name - The name of the repo that DataQ should work on.
* @param cb - The callback to trigger when the query is built.
*/
DataQ.DQ_rls_policy = function(repo_name, table_name, cb) {
console.log(repo_name);
console.log(table_name);

// Set the callback.
callback = cb;

// Add the container to the page.
var container = $(DataQ.templates["dataq-container-rls-policy"]());
$('body').append(container);

// Create the policy object and set the repo name.
policy = DataQ.Policy();
policy.repo(repo_name);
policy.table(table_name);

// Handle DataQ close when clicking backdrop.
$(".dq-black-background").click(function() {
$(".dq-black-background").remove();
$(".dataq").remove();
callback(null);
});
};

/**
* Update the UI to reflect the latest query.
*/
Expand Down Expand Up @@ -107,7 +139,7 @@
query.sorts().forEach(function(sort) {
sort_strings.push(sort.string);
});

// Display the sorts.
if (sort_strings.length > 0) {
$(".dq-sorting-text").html(sort_strings.join(", "));
Expand Down Expand Up @@ -173,4 +205,50 @@
$(".dataq").remove();
callback(DataQ.build_query(query));
});

// Handle DataQ create policy.
$(document).on("click", ".dq-btn-create-policy", function() {
// Build policy object
policy.name($("#dq-policy-name").val());

policy.command($("#dq-policy-command-selected").text());

roles = $("#dq-policy-role-list").val().split(",").map(function(r) {
return r.trim();
});
policy.roles(roles);

using_expr_obj = {
"filter1": $("#dq-policy-using-expr-filter-1").val(),
"op" : $("#dq-policy-using-expr-op").val(),
"filter2": $("#dq-policy-using-expr-filter-2").val()
};
policy.using_expression(using_expr_obj);

check_expr_obj = {
"filter1": $("#dq-policy-check-expr-filter-1").val(),
"op" : $("#dq-policy-check-expr-op").val(),
"filter2": $("#dq-policy-check-expr-filter-2").val()
};
policy.using_expression(check_expr_obj);

// Close DataQ
$(".dq-black-background").remove();
$(".dataq").remove();

// Build policy string
callback(DataQ.build_policy(policy));
});

// Handle policy command dropdown selection
$(document).on("click", "#dq-policy-dropdown-menu a", function() {
$('#dq-policy-command-selected').text($(this).text());
});

// Handle DataQ close
$(document).on("click", ".dq-btn-cancel-create-policy", function() {
$(".dq-black-background").remove();
$(".dataq").remove();
callback(null);
});
})();
89 changes: 89 additions & 0 deletions src/apps/dataq/client_src/js/dq-rls-policy-builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Logic for constructing a PostgreSQL CREATE POLICY command from a
* DataQ.policy object.
*/
(function() {
// If the global DataQ object does not exist, create it.
window.DataQ = window.DataQ || {};

/**
* Take a DataQ.Policy object and generate a CREATE POLICY string from it.
* A CREATE POLICY command looks like:
*
* CREATE POLICY name ON table_name
* [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
* [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ]
* [ USING ( using_expression ) ]
* [ WITH CHECK ( check_expression ) ]
*
* see https://www.postgresql.org/docs/9.5/static/sql-createpolicy.html
*
* @param policy - The DataQ.policy object.
* @return A String representing the CREATE POLICY command.
*/
window.DataQ.build_policy = function(policy) {

// Name of policy to be created.
var policy_name = policy.name();

// Name of table to which the policy applies.
var table_name = policy.repo() + "." + policy.table();

// Command to which the policy applies.
var command = policy.command();

// List of roles to which the policy applies.
var role_list = policy.roles();

// SQL conditional expression to control row visibility.
// Rows for which the expression returns true will be visible.
var using_expr = policy.using_expression();

// SQL conditional expression to control INSERT and UPDATE privileges.
// Only rows for which the expression evaluates to true will be allowed.
var check_expr = policy.check_expression();

/* Build policy string */
var policy_string = "CREATE POLICY " + policy_name;

// ON clause
policy_string += " ON " + table_name;

// FOR clause
policy_string += " FOR " + command;

// TO clause
policy_string += " TO " + role_list.join(", ");

// USING clause
if (command !== "INSERT") {
policy_string += " USING " + using_expr;
} else if (using_expr !== null) {
var err_msg = "An INSERT policy cannot have a USING expression, as USING is"
+ " used for reading existing records, not adding new ones.";
alert(err_msg);
return null;
}

// WITH CHECK clause
if (command !== "SELECT" && command !== "DELETE") {
// Neither a SELECT or DELETE policy can have a WITH CHECK expression,
// as this expression is used for validating new records, not reading or
// deleting existing ones.
policy_string += " WITH CHECK " + check_expr;
} else if (check_expr !== null) {
var err_msg = "Neither a SELECT or DELETE policy can have a WITH CHECK expression"
+ ", as this expression is used for validating new records, not"
+ " reading or deleting existing ones.";
alert(err_msg);
return null;
}

// Remove leading and trailing spaces and then append semicolon.
policy_string.trim();
policy_string += ";";

return policy_string;

};
})();
134 changes: 134 additions & 0 deletions src/apps/dataq/client_src/js/dq-rls-policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* The object for building a row-level security policy.
*/
(function() {
// If the DataQ object doesn't exist, create it.
window.DataQ = window.DataQ || {};

DataQ.Policy = function() {

// Create the object and initialize its contents.
var that = {};
that._repo_name = null;
that._name = null;
that._table = null;
that._command = null;
that._roles = [];
that._using_expr = null;
that._check_expr = null;

/**
* Get or set the repo name.
*
* @param repo_name - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the repo name.
*
* @return The name of the repo.
*/
that.repo = function(repo_name) {
if (repo_name !== undefined) {
that._repo_name = repo_name;
}
return that._repo_name;
};

/**
* Get or set the name of the policy.
*
* @param policy_name - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the repo name.
*
* @return The name of the policy.
*/
that.name = function(policy_name) {
if (policy_name !== undefined) {
that._name = policy_name;
}
return that._name;
};

/**
* Get or set the name of the table to which this policy will apply.
*
* @param table_name - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the repo name.
*
* @return The name of the table.
*/
that.table = function(table_name) {
if (table_name !== undefined) {
that._table = table_name;
}
return that._table;
};

/**
* Get or set the command { ALL | SELECT | INSERT | UPDATE | DELETE } to which
* this policy will apply.
*
* @param cmd - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the allowed command.
*
* @return The name of the command.
*/
that.command = function(cmd) {
if (cmd !== undefined) {
that._command = cmd;
}
return that._command;
};

/**
* Get or set the Roles to which this policy will apply.
*
* @param role_list - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the list of roles.
*
* @return The list of Roles.
*/
that.roles = function(role_list) {
if (role_list !== undefined) {
if (!(role_list instanceof Array)) {
role_list = [role_list]
}
that._roles = role_list;
}
return that._roles;
};

/**
* Get or set the policy's using_expression.
*
* @param expr - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the USING expression.
*
* @return The full USING expression.
*/
that.using_expression = function(expr_obj) {
if (expr_obj !== undefined) {
using_expression = expr_obj.filter1 + " " + expr_obj.op + " " + expr_obj.filter2;
that._using_expr = using_expression;
}
return that._using_expr;
};

/**
* Get or set the policy's check_expression.
*
* @param expr - If this argument is omitted (or undefined), this function acts as a
* getter. Otherwise, it acts as a setter, setting the WITH CHECK expression.
*
* @return The full WITH CHECK expression.
*/
that.check_expression = function(expr_obj) {
if (expr_obj !== undefined) {
check_expression = expr_obj.filter1 + " " + expr_obj.op + " " + expr_obj.filter2;
that._check_expr = expr;
}
return that._check_expr;
};

return that;

};
})();
Loading