forked from huawei/openGauss-server
435 lines
15 KiB
C++
435 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
*
|
|
* openGauss is licensed under Mulan PSL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
* You may obtain a copy of Mulan PSL v2 at:
|
|
*
|
|
* http://license.coscl.org.cn/MulanPSL2
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PSL v2 for more details.
|
|
* -------------------------------------------------------------------------
|
|
*
|
|
* gs_policy_logical_tree.cpp
|
|
* dealing polish-notation format string into policy logical node of policy
|
|
* plugin for gaussdb kernel
|
|
*
|
|
* IDENTIFICATION
|
|
* contrib/security_plugin/gs_policy_logical_tree.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "gs_policy_logical_tree.h"
|
|
#include <list>
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/acl.h"
|
|
|
|
PolicyLogicalNode &PolicyLogicalNode::operator =(const PolicyLogicalNode &arg)
|
|
{
|
|
if (&arg == this) {
|
|
return *this;
|
|
}
|
|
m_type = arg.m_type;
|
|
m_apps = arg.m_apps;
|
|
m_roles = arg.m_roles;
|
|
m_has_operator_NOT = arg.m_has_operator_NOT;
|
|
m_left = arg.m_left;
|
|
m_right = arg.m_right;
|
|
m_eval_res = arg.m_eval_res;
|
|
m_ip_range = arg.m_ip_range;
|
|
return *this;
|
|
}
|
|
|
|
bool PolicyLogicalNode::operator <(const PolicyLogicalNode &arg) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void PolicyLogicalNode::make_eval(const FilterData *filter_item)
|
|
{
|
|
switch (m_type) {
|
|
case E_FILTER_ROLE_NODE: /* filter type is role */
|
|
m_eval_res = (m_roles.find(GetCurrentUserId()) != m_roles.end());
|
|
break;
|
|
case E_FILTER_APP_NODE: /* filter type is app */
|
|
m_eval_res = (m_apps.find(filter_item->m_app.c_str()) != m_apps.end());
|
|
break;
|
|
case E_FILTER_IP_NODE: /* filter type is ip */
|
|
m_eval_res = m_ip_range.is_in_range(&filter_item->m_ip);
|
|
break;
|
|
default:
|
|
/* should not get here */
|
|
m_eval_res = false;
|
|
break;
|
|
}
|
|
if (m_has_operator_NOT) {
|
|
m_eval_res = !m_eval_res;
|
|
}
|
|
}
|
|
|
|
/* Parses polish-notation format string into policy logical node */
|
|
static bool parse_values(const gs_stl::gs_string logical_expr_str, int *offset, PolicyLogicalNode *root)
|
|
{
|
|
std::size_t found = gs_stl::gs_string::npos;
|
|
char buff[512] = {0};
|
|
int nRet;
|
|
size_t limit_pos = logical_expr_str.find(']', *offset);
|
|
if (limit_pos == gs_stl::gs_string::npos) {
|
|
return false;
|
|
}
|
|
while ((found = logical_expr_str.find(',', *offset)) != gs_stl::gs_string::npos && found < limit_pos) {
|
|
nRet = snprintf_s(buff, sizeof(buff), sizeof(buff) - 1, "%.*s", (int)(found - *offset),
|
|
logical_expr_str.c_str() + *offset);
|
|
securec_check_ss(nRet, "\0", "\0");
|
|
switch (root->m_type) {
|
|
case E_FILTER_IP_NODE:
|
|
root->m_ip_range.add_range(buff, strlen(buff));
|
|
break;
|
|
case E_FILTER_ROLE_NODE:
|
|
root->m_roles.push_back(isdigit(buff[0]) ? atol(buff) : get_role_oid(buff, true));
|
|
break;
|
|
default:
|
|
root->m_apps.push_back(buff);
|
|
break;
|
|
}
|
|
*offset = found + 1;
|
|
}
|
|
|
|
if (*offset < (int)limit_pos) {
|
|
nRet = snprintf_s(buff, sizeof(buff), sizeof(buff) - 1, "%.*s", (int)(limit_pos - *offset),
|
|
logical_expr_str.c_str() + *offset);
|
|
securec_check_ss(nRet, "\0", "\0");
|
|
switch (root->m_type) {
|
|
case E_FILTER_IP_NODE:
|
|
root->m_ip_range.add_range(buff, strlen(buff));
|
|
break;
|
|
case E_FILTER_ROLE_NODE:
|
|
{
|
|
root->m_roles.push_back(isdigit(buff[0]) ? atol(buff) : get_role_oid(buff, true));
|
|
}
|
|
break;
|
|
default:
|
|
root->m_apps.push_back(buff);
|
|
break;
|
|
}
|
|
*offset = limit_pos + 1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* C-tor */
|
|
PolicyLogicalTree::PolicyLogicalTree() : m_has_ip(false), m_has_role(false), m_has_app(false) { }
|
|
/* D-tor */
|
|
PolicyLogicalTree::~PolicyLogicalTree()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
PolicyLogicalTree &PolicyLogicalTree::operator =(const PolicyLogicalTree &arg)
|
|
{
|
|
if (&arg == this) {
|
|
return *this;
|
|
}
|
|
reset();
|
|
for (size_t i = 0; i < arg.m_nodes.size(); ++i) {
|
|
m_nodes.push_back(arg.m_nodes[i]);
|
|
}
|
|
flatten_tree();
|
|
m_has_ip = arg.m_has_ip;
|
|
m_has_role = arg.m_has_role;
|
|
m_has_app = arg.m_has_app;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/* Resets logical tree vector */
|
|
void PolicyLogicalTree::reset()
|
|
{
|
|
m_nodes.clear();
|
|
m_flat_tree.clear();
|
|
}
|
|
|
|
/* Matches logical tree with provided filter item */
|
|
bool PolicyLogicalTree::match(const FilterData *filter_item)
|
|
{
|
|
/* optimizations */
|
|
if (m_flat_tree.size() == 0) {
|
|
return false;
|
|
}
|
|
size_t nodes_num = m_flat_tree.size();
|
|
while (nodes_num > 0) {
|
|
int idx = m_flat_tree[nodes_num - 1];
|
|
PolicyLogicalNode *item = &m_nodes[idx];
|
|
switch (item->m_type) {
|
|
case E_AND_NODE:
|
|
item->m_eval_res = m_nodes[item->m_left].m_eval_res && m_nodes[item->m_right].m_eval_res;
|
|
break;
|
|
case E_OR_NODE:
|
|
item->m_eval_res = m_nodes[item->m_left].m_eval_res || m_nodes[item->m_right].m_eval_res;
|
|
break;
|
|
default:
|
|
m_nodes[idx].make_eval(filter_item);
|
|
break;
|
|
}
|
|
nodes_num--;
|
|
}
|
|
return m_nodes[0].m_eval_res;
|
|
}
|
|
|
|
bool PolicyLogicalTree::get_roles(global_roles_in_use *roles)
|
|
{
|
|
for (size_t i = 0; i < m_flat_tree.size(); ++i) {
|
|
PolicyLogicalNode *item = &m_nodes[m_flat_tree[i]];
|
|
if (item->m_type == E_FILTER_ROLE_NODE) {
|
|
for (size_t idx = 0; idx < item->m_roles.size(); ++idx) {
|
|
roles->insert(item->m_roles[idx]);
|
|
}
|
|
}
|
|
}
|
|
return !roles->empty();
|
|
}
|
|
|
|
/* Creates node structure for logical tree */
|
|
inline void PolicyLogicalTree::create_node(int *idx, EnodeType type, bool has_operator_NOT)
|
|
{
|
|
m_nodes.push_back(PolicyLogicalNode(type, has_operator_NOT));
|
|
*idx = (m_nodes.size() - 1);
|
|
}
|
|
|
|
/*
|
|
* Parses (recursively) polish-notation foramt string into logical tree, we support *(and) +(or) !(not) operation here
|
|
* take an example: we change the polish-notation into operator expression to make it clean
|
|
* *ip[127.0.0.1]roles[10]: ip && role
|
|
* **ip[127.0.0.1]app[javaw]roles[10]: ip && role && app
|
|
* *!ip[127.0.0.1]+app[javaw]roles[10]: (!ip) && (app || role)
|
|
*/
|
|
bool PolicyLogicalTree::parse_logical_expression_impl(const gs_stl::gs_string logical_expr_str, int *offset,
|
|
int *idx, Edirection direction)
|
|
{
|
|
int logical_expr_len = logical_expr_str.size();
|
|
|
|
bool have_operator_NOT = false;
|
|
while (*offset < logical_expr_len) {
|
|
/* AND/OR node */
|
|
if ((logical_expr_str[*offset] == '*') || (logical_expr_str[*offset] == '+')) {
|
|
create_node(idx, ((logical_expr_str[*offset] == '*') ? E_AND_NODE : E_OR_NODE), have_operator_NOT);
|
|
PolicyLogicalNode *item = &m_nodes.back();
|
|
(*offset)++;
|
|
return (parse_logical_expression_impl(logical_expr_str, offset, &item->m_left, E_LEFT) &&
|
|
parse_logical_expression_impl(logical_expr_str, offset, &item->m_right, E_RIGHT));
|
|
} else if (logical_expr_str[*offset] == '!') { /* NOT operator */
|
|
have_operator_NOT = true;
|
|
(*offset)++;
|
|
} else if (logical_expr_str[*offset] == 'i') { /* IP filter node */
|
|
create_node(idx, E_FILTER_IP_NODE, have_operator_NOT);
|
|
*offset += 3; /* 3: skip 'ip[' */
|
|
have_operator_NOT = false; /* forget that we have met NOT operator */
|
|
return parse_values(logical_expr_str, offset, &m_nodes.back());
|
|
} else if (logical_expr_str[*offset] == 'r') { /* ROLE filter node */
|
|
create_node(idx, E_FILTER_ROLE_NODE, have_operator_NOT);
|
|
*offset += 6; /* 6: skip 'roles[' */
|
|
have_operator_NOT = false; /* forget that we have met NOT operator */
|
|
return parse_values(logical_expr_str, offset, &m_nodes.back());
|
|
} else if (logical_expr_str[*offset] == 'a') { /* APPLICATION filter node */
|
|
create_node(idx, E_FILTER_APP_NODE, have_operator_NOT);
|
|
*offset += 4; /* 4: skip 'app[' */
|
|
have_operator_NOT = false; /* forget that we have met NOT operator */
|
|
return parse_values(logical_expr_str, offset, &m_nodes.back());
|
|
} else {
|
|
/* unsupported node or out of stream */
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Parses polish-notation format string into logical tree (wrapper around real implementation function) */
|
|
bool PolicyLogicalTree::parse_logical_expression(const gs_stl::gs_string logical_expr_str)
|
|
{
|
|
int offset = 0;
|
|
m_flat_tree.clear();
|
|
m_nodes.clear();
|
|
int idx = 0;
|
|
if (logical_expr_str.size() > 0 && parse_logical_expression_impl(logical_expr_str, &offset, &idx, E_LEFT)) {
|
|
flatten_tree();
|
|
return true;
|
|
}
|
|
/* in case of error reset tree */
|
|
return false;
|
|
}
|
|
|
|
/* Flattens logical tree into vector for later logical evaluation */
|
|
void PolicyLogicalTree::flatten_tree()
|
|
{
|
|
if (m_nodes.size() == 0) {
|
|
return;
|
|
}
|
|
gs_stl::gs_vector<int> nodes_stack;
|
|
nodes_stack.push_back(0);
|
|
while (nodes_stack.size() > 0) {
|
|
int idx = nodes_stack.front();
|
|
if (idx < (int)m_nodes.size()) {
|
|
PolicyLogicalNode cur_node = m_nodes[idx];
|
|
nodes_stack.pop_front();
|
|
switch (cur_node.m_type) {
|
|
case E_AND_NODE:
|
|
case E_OR_NODE:
|
|
m_flat_tree.push_back(idx);
|
|
nodes_stack.push_back(cur_node.m_left);
|
|
nodes_stack.push_back(cur_node.m_right);
|
|
break;
|
|
case E_FILTER_IP_NODE:
|
|
case E_FILTER_ROLE_NODE:
|
|
case E_FILTER_APP_NODE:
|
|
m_flat_tree.push_back(idx);
|
|
break;
|
|
default:
|
|
Assert(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* check_apps_intersect
|
|
*
|
|
* check two apps set have interscetion
|
|
*/
|
|
bool PolicyLogicalTree::check_apps_intersect(string_sort_vector *apps_first, string_sort_vector *apps_second)
|
|
{
|
|
if (apps_first == NULL || apps_second == NULL) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < apps_first->size(); ++i) {
|
|
if (apps_second->find((*apps_first)[i]) != apps_second->end()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* check_roles_intersect
|
|
*
|
|
* check two role set have interscetion
|
|
*/
|
|
bool PolicyLogicalTree::check_roles_intersect(oid_sort_vector *roles_first, oid_sort_vector *roles_second)
|
|
{
|
|
if (roles_first == NULL || roles_second == NULL) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < roles_first->size(); ++i) {
|
|
if (roles_second->find((*roles_first)[i]) != roles_second->end()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* has_intersect
|
|
*
|
|
* check this logical tree has intersect whith logicaltree(arg)
|
|
*/
|
|
bool PolicyLogicalTree::has_intersect(PolicyLogicalTree *arg)
|
|
{
|
|
if (m_flat_tree.empty()) {
|
|
return true;
|
|
}
|
|
/* flag intersection for each filter item */
|
|
bool is_app_intersect = false;
|
|
bool is_ip_intersect = false;
|
|
bool is_role_intersect = false;
|
|
|
|
/* is this logical contains app/ip/role */
|
|
bool has_app = false;
|
|
bool has_ip = false;
|
|
bool has_role = false;
|
|
|
|
/* arg logical tree vs this logical tree */
|
|
bool has_arg_app = false;
|
|
bool has_arg_ip = false;
|
|
bool has_arg_role = false;
|
|
|
|
for (size_t idx = 0; idx < m_flat_tree.size(); ++idx) {
|
|
PolicyLogicalNode *item = &m_nodes[m_flat_tree[idx]];
|
|
/*
|
|
* because ',' in logical tree mean 'AND', so there is at least one intersect when all flags are true
|
|
* for example: filterA: **ip[xxx.xxx.1.1]app[jdbc]role[dev]
|
|
* and filterB: **ip[xxx.xxx.1.2]app[jdbc]role[dev] are no intersection,
|
|
* because filterA:only dev using jdbc with ip 123.123.1.1
|
|
* filterB:only dev using jdbc with ip 123.123.1.2
|
|
* filterA and filterB are different user scenarioes
|
|
*/
|
|
if (is_app_intersect && is_ip_intersect && is_role_intersect) {
|
|
break;
|
|
}
|
|
/* ignore operator node */
|
|
if (item->m_type == E_AND_NODE || item->m_type == E_OR_NODE) {
|
|
continue;
|
|
}
|
|
has_app = has_app || (item->m_type == E_FILTER_APP_NODE);
|
|
has_ip = has_ip || (item->m_type == E_FILTER_IP_NODE);
|
|
has_role = has_role || (item->m_type == E_FILTER_ROLE_NODE);
|
|
|
|
for (size_t arg_idx = 0; arg_idx < arg->m_flat_tree.size(); ++arg_idx) {
|
|
PolicyLogicalNode *arg_item = &arg->m_nodes[arg->m_flat_tree[arg_idx]];
|
|
switch (arg_item->m_type) {
|
|
case E_FILTER_APP_NODE:
|
|
{
|
|
has_arg_app = true;
|
|
if (item->m_type == arg_item->m_type && !is_app_intersect) {
|
|
/* for now 'NOT' operator is not supported */
|
|
is_app_intersect = check_apps_intersect(&item->m_apps, &arg_item->m_apps);
|
|
}
|
|
}
|
|
break;
|
|
case E_FILTER_ROLE_NODE:
|
|
{
|
|
has_arg_role = true;
|
|
if (item->m_type == arg_item->m_type && !is_role_intersect) {
|
|
/* for now 'NOT' operator is not supported */
|
|
is_role_intersect = check_roles_intersect(&item->m_roles, &arg_item->m_roles);
|
|
}
|
|
}
|
|
break;
|
|
case E_FILTER_IP_NODE:
|
|
{
|
|
has_arg_ip = true;
|
|
if (item->m_type == arg_item->m_type && !is_ip_intersect) {
|
|
/* for now 'NOT' operator is not supported */
|
|
is_ip_intersect = item->m_ip_range.is_intersect(&arg_item->m_ip_range);
|
|
}
|
|
}
|
|
break;
|
|
/* ignore operator node */
|
|
case E_AND_NODE:
|
|
case E_OR_NODE:
|
|
break;
|
|
default:
|
|
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("Unknown logical filter node")));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* if filter not contains means for all scenario
|
|
* ip/app/role:
|
|
* if one logical contains but the other not contains, they must have intersection
|
|
* if both logicals are not contains, they must have intersection
|
|
*/
|
|
is_app_intersect = !has_app || !has_arg_app || is_app_intersect;
|
|
is_ip_intersect = !has_ip || !has_arg_ip || is_ip_intersect;
|
|
is_role_intersect = !has_role || !has_arg_role || is_role_intersect;
|
|
|
|
return is_app_intersect && is_ip_intersect && is_role_intersect;
|
|
}
|