prole/pg-knoe-auth/test/unit/test_aud_check.c
chrisfu 57886f9268 feat(pg-knoe-auth): import upstream PostgreSQL JWT auth extension; compile in knoe-db image
- Copy pg-knoe-auth/ wholesale from upstream/knoe-db/20260523 (Task 1 of
  docs/plans/junie/upstream-knoe-db-20260523-integration.md).
- Extension: PG18 OAUTHBEARER JWT validator using libcurl + OpenSSL RS256.
- knoe-db/Dockerfile: add libcurl4-openssl-dev to dev deps; COPY src/ and
  build with make USE_PGXS=1 install after tds_fdw.
- NOT enabled in the default database build (absent from 20_create_extensions.sh).
  To enable: CREATE EXTENSION pg_knoe_auth; (requires pg_hba.conf oauth_issuer).

Closes Task 1 of upstream-knoe-db-20260523-integration.md.
2026-05-23 21:52:06 -07:00

94 lines
3.0 KiB
C

/*
* test_aud_check.c — unit tests for M1: aud claim exact match + array form.
*
* Tests string exact match, substring rejection, array form pass/fail.
* See docs/plans/junie/pg_knoe_auth-rename-harden-modularize.md §2.2.4
*/
#include "test_helpers.h"
/*
* aud_contains — copied verbatim from pg_knoe_auth.c so we can test it
* without linking against PostgreSQL.
*/
static bool
aud_contains(const char *aud_value, const char *expected)
{
if (!aud_value || !expected)
return false;
/* Array form: ["val1","val2",...] */
if (aud_value[0] == '[') {
const char *p = aud_value + 1;
while (*p) {
const char *elem_start, *elem_end;
size_t elem_len;
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',')
p++;
if (*p == ']' || *p == '\0')
break;
if (*p != '"')
break;
p++;
elem_start = p;
while (*p && *p != '"') {
if (*p == '\\') p++;
if (*p) p++;
}
elem_end = p;
if (*p == '"') p++;
elem_len = (size_t)(elem_end - elem_start);
if (elem_len == strlen(expected) &&
strncmp(elem_start, expected, elem_len) == 0)
return true;
}
return false;
}
/* String form: exact equality */
return strcmp(aud_value, expected) == 0;
}
int main(void)
{
/* String form — exact match */
ASSERT( aud_contains("pg.0.knoe.dev", "pg.0.knoe.dev"),
"string exact match passes");
/* String form — substring must NOT pass (old strstr bug) */
ASSERT(!aud_contains("evil.pg.0.knoe.dev", "pg.0.knoe.dev"),
"substring of expected in aud string is rejected");
ASSERT(!aud_contains("pg.0.knoe.dev.evil", "pg.0.knoe.dev"),
"expected as prefix of aud string is rejected");
/* String form — wrong value */
ASSERT(!aud_contains("other.service", "pg.0.knoe.dev"),
"wrong string aud is rejected");
/* Array form — single element match */
ASSERT( aud_contains("[\"pg.0.knoe.dev\"]", "pg.0.knoe.dev"),
"array with matching element passes");
/* Array form — multiple elements, one matches */
ASSERT( aud_contains("[\"other\",\"pg.0.knoe.dev\",\"more\"]", "pg.0.knoe.dev"),
"array with matching element among others passes");
/* Array form — no element matches */
ASSERT(!aud_contains("[\"other\",\"service\"]", "pg.0.knoe.dev"),
"array without matching element is rejected");
/* Array form — substring element must NOT match */
ASSERT(!aud_contains("[\"evil.pg.0.knoe.dev\"]", "pg.0.knoe.dev"),
"array element that is superset of expected is rejected");
/* NULL inputs */
ASSERT(!aud_contains(NULL, "pg.0.knoe.dev"),
"NULL aud_value is rejected");
ASSERT(!aud_contains("pg.0.knoe.dev", NULL),
"NULL expected is rejected");
TEST_SUMMARY();
}