Skip to content

Commit

Permalink
feat: dropping table triggers w/o being owners
Browse files Browse the repository at this point in the history
  • Loading branch information
soedirgo committed Sep 25, 2024
1 parent bf49d9d commit d0fc6c5
Show file tree
Hide file tree
Showing 8 changed files with 435 additions and 33 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ else
endif

MODULE_big = supautils
OBJS = src/supautils.o src/privileged_extensions.o src/constrained_extensions.o src/extensions_parameter_overrides.o src/policy_grants.o src/utils.o
OBJS = src/supautils.o src/privileged_extensions.o src/drop_trigger_grants.o src/constrained_extensions.o src/extensions_parameter_overrides.o src/policy_grants.o src/utils.o

PG_VERSION = $(strip $(shell $(PG_CONFIG) --version | $(GREP) -oP '(?<=PostgreSQL )[0-9]+'))
PG_GE16 = $(shell test $(PG_VERSION) -ge 16; echo $$?)
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ supautils.policy_grants = '{ "my_role": ["public.not_my_table", "public.also_not
This allows `my_role` to manage policies for `public.not_my_table` and
`public.also_not_my_table` without being an owner of these tables.

## Dropping Triggers Without Table Ownership

In addition to managing policies, you can also allow certain roles to drop
triggers on a table without being the table owner:

```
supautils.drop_trigger_grants = '{ "my_role": ["public.not_my_table", "public.also_not_my_table"] }'
```

## Development

[Nix](https://nixos.org/download.html) is required to set up the environment.
Expand Down
3 changes: 2 additions & 1 deletion nix/withTmpDb.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ placeholder_stuff_options='-c supautils.placeholders="response.headers, another.

cexts_option='-c supautils.constrained_extensions="{\"adminpack\": { \"cpu\": 64}, \"cube\": { \"mem\": \"17 GB\"}, \"lo\": { \"disk\": \"20 GB\"}, \"amcheck\": { \"cpu\": 2, \"mem\": \"100 MB\", \"disk\": \"100 MB\"}}"'
epos_option='-c supautils.extensions_parameter_overrides="{\"sslinfo\":{\"schema\":\"pg_catalog\"}}"'
drop_trigger_grants_option='-c supautils.drop_trigger_grants="{\"privileged_role\":[\"allow_drop_triggers.my_table\"]}"'
policy_grants_option='-c supautils.policy_grants="{\"privileged_role\":[\"allow_policies.my_table\",\"allow_policies.nonexistent_table\"]}"'

pg_ctl start -o "$options" -o "$reserved_stuff_options" -o "$placeholder_stuff_options" -o "$cexts_option" -o "$epos_option" -o "$policy_grants_option"
pg_ctl start -o "$options" -o "$reserved_stuff_options" -o "$placeholder_stuff_options" -o "$cexts_option" -o "$epos_option" -o "$drop_trigger_grants_option" -o "$policy_grants_option"

# print notice when creating a TLE
mkdir -p "$tmpdir/privileged_extensions_custom_scripts"
Expand Down
204 changes: 204 additions & 0 deletions src/drop_trigger_grants.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
#include <postgres.h>

#include <common/jsonapi.h>
#include <miscadmin.h>
#include <tsearch/ts_locale.h>
#include <utils/builtins.h>
#include <utils/json.h>
#include <utils/jsonb.h>
#include <utils/jsonfuncs.h>
#include <utils/memutils.h>
#include <utils/regproc.h>
#include <utils/varlena.h>

#include "drop_trigger_grants.h"
#include "utils.h"

static JSON_ACTION_RETURN_TYPE json_array_start(void *state) {
json_drop_trigger_grants_parse_state *parse = state;

switch (parse->state) {
case JDTG_EXPECT_TABLES_START:
parse->state = JDTG_EXPECT_TABLE;
break;

case JDTG_EXPECT_TOPLEVEL_START:
parse->state = JDTG_UNEXPECTED_ARRAY;
parse->error_msg = "unexpected array";
break;

case JDTG_EXPECT_TABLE:
parse->state = JDTG_UNEXPECTED_ARRAY;
parse->error_msg = "unexpected array";
break;

default:
break;
}
JSON_ACTION_RETURN;
}

static JSON_ACTION_RETURN_TYPE json_array_end(void *state) {
json_drop_trigger_grants_parse_state *parse = state;

switch (parse->state) {
case JDTG_EXPECT_TABLE:
parse->state = JDTG_EXPECT_TOPLEVEL_FIELD;
parse->total_dtgs++;
break;

default:
break;
}
JSON_ACTION_RETURN;
}

static JSON_ACTION_RETURN_TYPE json_object_start(void *state) {
json_drop_trigger_grants_parse_state *parse = state;

switch (parse->state) {
case JDTG_EXPECT_TOPLEVEL_START:
parse->state = JDTG_EXPECT_TOPLEVEL_FIELD;
break;

case JDTG_EXPECT_TABLES_START:
parse->error_msg = "unexpected object for tables, expected an array";
parse->state = JDTG_UNEXPECTED_OBJECT;
break;

case JDTG_EXPECT_TABLE:
parse->error_msg = "unexpected object for table, expected a string";
parse->state = JDTG_UNEXPECTED_OBJECT;
break;

default:
break;
}
JSON_ACTION_RETURN;
}

static JSON_ACTION_RETURN_TYPE json_object_field_start(void *state, char *fname,
bool isnull) {
json_drop_trigger_grants_parse_state *parse = state;
drop_trigger_grants *x = &parse->dtgs[parse->total_dtgs];

switch (parse->state) {
case JDTG_EXPECT_TOPLEVEL_FIELD:
x->role_name = MemoryContextStrdup(TopMemoryContext, fname);
parse->state = JDTG_EXPECT_TABLES_START;
break;

default:
break;
}
JSON_ACTION_RETURN;
}

static JSON_ACTION_RETURN_TYPE json_scalar(void *state, char *token,
JsonTokenType tokentype) {
json_drop_trigger_grants_parse_state *parse = state;
drop_trigger_grants *x = &parse->dtgs[parse->total_dtgs];

switch (parse->state) {
case JDTG_EXPECT_TABLE:
if (tokentype == JSON_TOKEN_STRING) {
x->table_names[x->total_tables] =
MemoryContextStrdup(TopMemoryContext, token);
x->total_tables++;
} else {
parse->state = JDTG_UNEXPECTED_TABLE_VALUE;
parse->error_msg = "unexpected table value, expected a string";
}
break;

case JDTG_EXPECT_TOPLEVEL_START:
parse->state = JDTG_UNEXPECTED_SCALAR;
parse->error_msg = "unexpected scalar, expected an object";
break;

case JDTG_EXPECT_TABLES_START:
parse->state = JDTG_UNEXPECTED_SCALAR;
parse->error_msg = "unexpected scalar, expected an array";
break;

default:
break;
}
JSON_ACTION_RETURN;
}

json_drop_trigger_grants_parse_state
parse_drop_trigger_grants(const char *str, drop_trigger_grants *dtgs) {
JsonLexContext *lex;
JsonParseErrorType json_error;
JsonSemAction sem;

json_drop_trigger_grants_parse_state state = {JDTG_EXPECT_TOPLEVEL_START,
NULL, 0, dtgs};

lex =
makeJsonLexContextCstringLen(pstrdup(str), strlen(str), PG_UTF8, true);

sem.semstate = &state;
sem.object_start = json_object_start;
sem.object_end = NULL;
sem.array_start = json_array_start;
sem.array_end = json_array_end;
sem.object_field_start = json_object_field_start;
sem.object_field_end = NULL;
sem.array_element_start = NULL;
sem.array_element_end = NULL;
sem.scalar = json_scalar;

json_error = pg_parse_json(lex, &sem);

if (json_error != JSON_SUCCESS)
state.error_msg = "invalid json";

return state;
}

bool is_current_role_granted_table_drop_trigger(const RangeVar *table_range_var,
const drop_trigger_grants *dtgs,
const size_t total_dtgs) {

Oid target_table_id =
RangeVarGetRelid(table_range_var, AccessExclusiveLock, false);
char *current_role_name = GetUserNameFromId(GetUserId(), false);

for (size_t i = 0; i < total_dtgs; i++) {
const drop_trigger_grants *dtg = &dtgs[i];

if (strcmp(dtg->role_name, current_role_name) != 0) {
continue;
}

for (size_t j = 0; j < dtg->total_tables; j++) {
const char *table_name = dtg->table_names[j];
List *qual_name_list;
RangeVar *range_var;
Oid table_id;
#if PG16_GTE
qual_name_list = stringToQualifiedNameList(table_name, NULL);
#else
qual_name_list = stringToQualifiedNameList(table_name);
#endif
if (qual_name_list == NULL) {
list_free(qual_name_list);
continue;
}

range_var = makeRangeVarFromNameList(qual_name_list);
table_id = RangeVarGetRelid(range_var, AccessExclusiveLock, true);
if (!OidIsValid(table_id)) {
continue;
}

if (table_id == target_table_id) {
return true;
}
}
}

return false;
}
42 changes: 42 additions & 0 deletions src/drop_trigger_grants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef DROP_TRIGGER_GRANTS_H
#define DROP_TRIGGER_GRANTS_H

#include <postgres.h>

#include <catalog/namespace.h>

#define MAX_DROP_TRIGGER_GRANT_TABLES 100

typedef struct {
char *role_name;
char *table_names[MAX_DROP_TRIGGER_GRANT_TABLES];
int total_tables;
} drop_trigger_grants;

typedef enum {
JDTG_EXPECT_TOPLEVEL_START,
JDTG_EXPECT_TOPLEVEL_FIELD,
JDTG_EXPECT_TABLES_START,
JDTG_EXPECT_TABLE,
JDTG_UNEXPECTED_ARRAY,
JDTG_UNEXPECTED_SCALAR,
JDTG_UNEXPECTED_OBJECT,
JDTG_UNEXPECTED_TABLE_VALUE
} json_drop_trigger_grants_semantic_state;

typedef struct {
json_drop_trigger_grants_semantic_state state;
char *error_msg;
int total_dtgs;
drop_trigger_grants *dtgs;
} json_drop_trigger_grants_parse_state;

extern json_drop_trigger_grants_parse_state
parse_drop_trigger_grants(const char *str, drop_trigger_grants *dtgs);

extern bool
is_current_role_granted_table_drop_trigger(const RangeVar *table_range_var,
const drop_trigger_grants *dtgs,
const size_t total_dtgs);

#endif
Loading

0 comments on commit d0fc6c5

Please sign in to comment.