since-v17.diff.txt
text/plain
Filename: since-v17.diff.txt
Type: text/plain
Part: 0
1: 00976d4f75 ! 1: e2a0b48561 common/jsonapi: support FRONTEND clients
@@ Commit message
We can now partially revert b44669b2ca, now that json_errdetail() works
correctly.
+ Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
+
## src/bin/pg_verifybackup/t/005_bad_manifest.pl ##
@@ src/bin/pg_verifybackup/t/005_bad_manifest.pl: use Test::More;
my $tempdir = PostgreSQL::Test::Utils::tempdir;
@@ src/common/jsonapi.c
+#define appendStrValChar appendPQExpBufferChar
+#define createStrVal createPQExpBuffer
+#define resetStrVal resetPQExpBuffer
++#define destroyStrVal destroyPQExpBuffer
+
+#else /* !FRONTEND */
+
@@ src/common/jsonapi.c
+#define appendStrValChar appendStringInfoChar
+#define createStrVal makeStringInfo
+#define resetStrVal resetStringInfo
++#define destroyStrVal destroyStringInfo
+
+#endif
+
@@ src/common/jsonapi.c: makeJsonLexContextCstringLen(JsonLexContext *lex, char *js
+ static const JsonLexContext empty = {0};
+
if (lex->flags & JSONLEX_FREE_STRVAL)
- {
-+#ifdef FRONTEND
-+ destroyPQExpBuffer(lex->strval);
-+#else
- pfree(lex->strval->data);
- pfree(lex->strval);
-+#endif
-+ }
+- {
+- pfree(lex->strval->data);
+- pfree(lex->strval);
+- }
++ destroyStrVal(lex->strval);
++
+ if (lex->errormsg)
-+ {
-+#ifdef FRONTEND
-+ destroyPQExpBuffer(lex->errormsg);
-+#else
-+ pfree(lex->errormsg->data);
-+ pfree(lex->errormsg);
-+#endif
- }
++ destroyStrVal(lex->errormsg);
++
if (lex->flags & JSONLEX_FREE_STRUCT)
pfree(lex);
+ else
@@ src/common/parse_manifest.c: json_parse_manifest(JsonManifestParseContext *conte
json_manifest_parse_failure(context, "manifest ended unexpectedly");
+ ## src/common/stringinfo.c ##
+@@ src/common/stringinfo.c: enlargeStringInfo(StringInfo str, int needed)
+
+ str->maxlen = newlen;
+ }
++
++void
++destroyStringInfo(StringInfo str)
++{
++ pfree(str->data);
++ pfree(str);
++}
+
## src/include/common/jsonapi.h ##
@@
#ifndef JSONAPI_H
@@ src/include/common/jsonapi.h: typedef struct JsonLexContext
} JsonLexContext;
typedef JsonParseErrorType (*json_struct_action) (void *state);
+
+ ## src/include/lib/stringinfo.h ##
+@@ src/include/lib/stringinfo.h: extern void appendBinaryStringInfoNT(StringInfo str,
+ */
+ extern void enlargeStringInfo(StringInfo str, int needed);
+
++
++extern void destroyStringInfo(StringInfo str);
+ #endif /* STRINGINFO_H */
2: d8b567dd55 ! 2: db625e1d01 Refactor SASL exchange to return tri-state status
@@ src/interfaces/libpq/fe-auth-sasl.h: typedef struct pg_fe_sasl_mech
+ * SASL_CONTINUE: The output buffer is filled with a client response.
+ * Additional server challenge is expected
+ * SASL_COMPLETE: The SASL exchange has completed successfully.
-+ * SASL_FAILED: The exchance has failed and the connection should be
++ * SASL_FAILED: The exchange has failed and the connection should be
+ * dropped.
*--------
*/
@@ src/interfaces/libpq/fe-auth.c: pg_SASL_continue(PGconn *conn, int payloadlen, b
free(challenge); /* don't need the input anymore */
- if (final && !done)
-+ if (final && !(status == SASL_FAILED || status == SASL_COMPLETE))
++ if (final && status == SASL_CONTINUE)
{
if (outputlen != 0)
free(output);
3: 83d78f598c ! 3: e4ad0260d5 Explicitly require password for SCRAM exchange
@@ Commit message
Discussion: https://postgr.es/m/d1b467a78e0e36ed85a09adf979d04cf124a9d4b.camel@vmware.com
## src/interfaces/libpq/fe-auth.c ##
+@@ src/interfaces/libpq/fe-auth.c: pg_SASL_init(PGconn *conn, int payloadlen)
+ int initialresponselen;
+ const char *selected_mechanism;
+ PQExpBufferData mechanism_buf;
+- char *password;
++ char *password = NULL;
+ SASLStatus status;
+
+ initPQExpBuffer(&mechanism_buf);
@@ src/interfaces/libpq/fe-auth.c: pg_SASL_init(PGconn *conn, int payloadlen)
/*
* Parse the list of SASL authentication mechanisms in the
4: 00c8073807 ! 4: 229f602d5c libpq: add OAUTHBEARER SASL mechanism
@@ Commit message
- figure out pgsocket/int difference on Windows
- fix intermittent failure in the cleanup callback tests (race
condition?)
+ - support require_auth
- ...and more.
+ Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
+
## configure ##
@@ configure: with_uuid
with_readline
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+
+/*
+ * Macros for getting and setting state for the connection's two cURL handles,
-+ * so you don't have to write out the error handling every time. They assume
-+ * that they're embedded in a function returning bool, however.
++ * so you don't have to write out the error handling every time.
+ */
+
-+#define CHECK_MSETOPT(ACTX, OPT, VAL) \
++#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION) \
+ do { \
+ struct async_ctx *_actx = (ACTX); \
+ CURLMcode _setopterr = curl_multi_setopt(_actx->curlm, OPT, VAL); \
+ if (_setopterr) { \
+ actx_error(_actx, "failed to set %s on OAuth connection: %s",\
+ #OPT, curl_multi_strerror(_setopterr)); \
-+ return false; \
++ FAILACTION; \
+ } \
+ } while (0)
+
-+#define CHECK_SETOPT(ACTX, OPT, VAL) \
++#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION) \
+ do { \
+ struct async_ctx *_actx = (ACTX); \
+ CURLcode _setopterr = curl_easy_setopt(_actx->curl, OPT, VAL); \
+ if (_setopterr) { \
+ actx_error(_actx, "failed to set %s on OAuth connection: %s",\
+ #OPT, curl_easy_strerror(_setopterr)); \
-+ return false; \
++ FAILACTION; \
+ } \
+ } while (0)
+
-+#define CHECK_GETINFO(ACTX, INFO, OUT) \
++#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION) \
+ do { \
+ struct async_ctx *_actx = (ACTX); \
+ CURLcode _getinfoerr = curl_easy_getinfo(_actx->curl, INFO, OUT); \
+ if (_getinfoerr) { \
+ actx_error(_actx, "failed to get %s from OAuth response: %s",\
+ #INFO, curl_easy_strerror(_getinfoerr)); \
-+ return false; \
++ FAILACTION; \
+ } \
+ } while (0)
+
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+
+ while (field->name)
+ {
-+ if (!strcmp(name, field->name))
++ if (strcmp(name, field->name) == 0)
+ {
+ ctx->active = field;
+ break;
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ bool success = false;
+
+ /* Make sure the server thinks it's given us JSON. */
-+ CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type);
++ CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
+
+ if (!content_type)
+ {
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+static int
+parse_interval(const char *interval_str)
+{
-+ float parsed;
++ double parsed;
+ int cnt;
+
+ /*
+ * The JSON lexer has already validated the number, which is stricter than
+ * the %f format, so we should be good to use sscanf().
+ */
-+ cnt = sscanf(interval_str, "%f", &parsed);
++ cnt = sscanf(interval_str, "%lf", &parsed);
+
+ if (cnt != 1)
+ {
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ return 1; /* don't fall through in release builds */
+ }
+
-+ parsed = ceilf(parsed);
++ parsed = ceil(parsed);
+
+ if (parsed < 1)
+ return 1; /* TODO this slows down the tests
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+static bool
+setup_curl_handles(struct async_ctx *actx)
+{
++ curl_version_info_data *curl_info;
++
+ /*
+ * Create our multi handle. This encapsulates the entire conversation with
+ * cURL for this connection.
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ }
+
+ /*
++ * Extract information about the libcurl we are linked against.
++ */
++ curl_info = curl_version_info(CURLVERSION_NOW);
++
++ /*
+ * The multi handle tells us what to wait on using two callbacks. These
+ * will manipulate actx->mux as needed.
+ */
-+ CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket);
-+ CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx);
-+ CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer);
-+ CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx);
++ CHECK_MSETOPT(actx, CURLMOPT_SOCKETFUNCTION, register_socket, return false);
++ CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
++ CHECK_MSETOPT(actx, CURLMOPT_TIMERFUNCTION, register_timer, return false);
++ CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
+
+ /*
+ * Set up an easy handle. All of our requests are made serially, so we
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ * Multi-threaded applications must set CURLOPT_NOSIGNAL. This requires us
+ * to handle the possibility of SIGPIPE ourselves.
+ *
-+ * TODO: This disables DNS resolution timeouts unless libcurl has been
-+ * compiled against alternative resolution support. We should check that.
-+ *
+ * TODO: handle SIGPIPE via pq_block_sigpipe(), or via a
+ * CURLOPT_SOCKOPTFUNCTION maybe...
+ */
-+ CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L);
++ CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
++ if (!curl_info->ares_num)
++ {
++ /* No alternative resolver, TODO: warn about timeouts */
++ }
+
+ /* TODO investigate using conn->Pfdebug and CURLOPT_DEBUGFUNCTION here */
-+ CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L);
-+ CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err);
-+
-+ /* TODO */
-+ CHECK_SETOPT(actx, CURLOPT_WRITEDATA, stderr);
++ CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
++ CHECK_SETOPT(actx, CURLOPT_ERRORBUFFER, actx->curl_err, return false);
+
+ /*
+ * Only HTTP[S] is allowed. TODO: disallow HTTP without user opt-in
+ */
-+ CHECK_SETOPT(actx, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
++ CHECK_SETOPT(actx, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS, return false);
+
+ /*
+ * Suppress the Accept header to make our request as minimal as possible.
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ * what comes back anyway.)
+ */
+ actx->headers = curl_slist_append(actx->headers, "Accept:"); /* TODO: check result */
-+ CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers);
++ CHECK_SETOPT(actx, CURLOPT_HTTPHEADER, actx->headers, return false);
+
+ return true;
+}
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ int running;
+
+ resetPQExpBuffer(&actx->work_data);
-+ CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data);
-+ CHECK_SETOPT(actx, CURLOPT_WRITEDATA, &actx->work_data);
++ CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
++ CHECK_SETOPT(actx, CURLOPT_WRITEDATA, &actx->work_data, return false);
+
+ err = curl_multi_add_handle(actx->curlm, actx->curl);
+ if (err)
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+static bool
+start_discovery(struct async_ctx *actx, const char *discovery_uri)
+{
-+ CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L);
-+ CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri);
++ CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
++ CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
+
+ return start_request(actx);
+}
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ * validation into question), or non-authoritative responses, or any other
+ * complications.
+ */
-+ CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code);
++ CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
+
+ if (response_code != 200)
+ {
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ * Per Section 3, the default is ["authorization_code", "implicit"].
+ */
+ struct curl_slist *temp = actx->provider.grant_types_supported;
-+ bool oom = false;
+
+ temp = curl_slist_append(temp, "authorization_code");
-+ if (!temp)
-+ oom = true;
++ if (temp)
++ {
++ temp = curl_slist_append(temp, "implicit");
++ }
+
-+ temp = curl_slist_append(temp, "implicit");
+ if (!temp)
-+ oom = true;
-+
-+ if (oom)
+ {
+ actx_error(actx, "out of memory");
+ return false;
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ /* TODO check for broken buffer */
+
+ /* Make our request. */
-+ CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri);
-+ CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data);
++ CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
++ CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
+
+ if (conn->oauth_client_secret)
+ {
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ *
+ * TODO: should we omit client_id from the body in this case?
+ */
-+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
-+ CHECK_SETOPT(actx, CURLOPT_USERNAME, conn->oauth_client_id);
-+ CHECK_SETOPT(actx, CURLOPT_PASSWORD, conn->oauth_client_secret);
++ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, return false);
++ CHECK_SETOPT(actx, CURLOPT_USERNAME, conn->oauth_client_id, return false);
++ CHECK_SETOPT(actx, CURLOPT_PASSWORD, conn->oauth_client_secret, return false);
+ }
+ else
-+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE);
++ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, return false);
+
+ return start_request(actx);
+}
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+{
+ long response_code;
+
-+ CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code);
++ CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
+
+ /*
+ * The device authorization endpoint uses the same error response as the
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ /* TODO check for broken buffer */
+
+ /* Make our request. */
-+ CHECK_SETOPT(actx, CURLOPT_URL, token_uri);
-+ CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data);
++ CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
++ CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
+
+ if (conn->oauth_client_secret)
+ {
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ *
+ * TODO: should we omit client_id from the body in this case?
+ */
-+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
-+ CHECK_SETOPT(actx, CURLOPT_USERNAME, conn->oauth_client_id);
-+ CHECK_SETOPT(actx, CURLOPT_PASSWORD, conn->oauth_client_secret);
++ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_BASIC, return false);
++ CHECK_SETOPT(actx, CURLOPT_USERNAME, conn->oauth_client_id, return false);
++ CHECK_SETOPT(actx, CURLOPT_PASSWORD, conn->oauth_client_secret, return false);
+ }
+ else
-+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE);
++ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, return false);
+
+ resetPQExpBuffer(work_buffer);
-+ CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data);
-+ CHECK_SETOPT(actx, CURLOPT_WRITEDATA, work_buffer);
++ CHECK_SETOPT(actx, CURLOPT_WRITEFUNCTION, append_data, return false);
++ CHECK_SETOPT(actx, CURLOPT_WRITEDATA, work_buffer, return false);
+
+ return start_request(actx);
+}
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+{
+ long response_code;
+
-+ CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code);
++ CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
+
+ /*
+ * Per RFC 6749, Section 5, a successful response uses 200 OK. An error
@@ src/interfaces/libpq/fe-auth-oauth-curl.c (new)
+ * A slow_down error requires us to permanently increase our
+ * retry interval by five seconds. RFC 8628, Sec. 3.5.
+ */
-+ if (!strcmp(err->error, "slow_down"))
++ if (strcmp(err->error, "slow_down") == 0)
+ {
+ actx->authz.interval += 5; /* TODO check for overflow? */
+ }
@@ src/interfaces/libpq/fe-auth-oauth.c (new)
+ * error.
+ */
+ Assert(sasl_mechanism != NULL);
-+ Assert(!strcmp(sasl_mechanism, OAUTHBEARER_NAME));
++ Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
+
+ state = calloc(1, sizeof(*state));
+ if (!state)
@@ src/interfaces/libpq/fe-auth-oauth.c (new)
+
+ if (ctx->nested == 1)
+ {
-+ if (!strcmp(name, ERROR_STATUS_FIELD))
++ if (strcmp(name, ERROR_STATUS_FIELD) == 0)
+ {
+ ctx->target_field_name = ERROR_STATUS_FIELD;
+ ctx->target_field = &ctx->status;
+ }
-+ else if (!strcmp(name, ERROR_SCOPE_FIELD))
++ else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
+ {
+ ctx->target_field_name = ERROR_SCOPE_FIELD;
+ ctx->target_field = &ctx->scope;
+ }
-+ else if (!strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD))
++ else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
+ {
+ ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
+ ctx->target_field = &ctx->discovery_uri;
@@ src/interfaces/libpq/fe-auth-oauth.c (new)
+ if (strlen(msg) != msglen)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
-+ libpq_gettext("server's error message contained an embedded NULL"));
++ libpq_gettext("server's error message contained an embedded NULL, and was discarded"));
+ return false;
+ }
+
@@ src/interfaces/libpq/fe-auth-oauth.c (new)
+ return false;
+ }
+
-+ if (!strcmp(ctx.status, "invalid_token"))
++ if (strcmp(ctx.status, "invalid_token") == 0)
+ {
+ /*
+ * invalid_token is the only error code we'll automatically retry for,
@@ src/interfaces/libpq/fe-auth-sasl.h: typedef struct pg_fe_sasl_mech
+ * generated. The mechanism is responsible for setting up
+ * conn->async_auth appropriately before returning.
* SASL_COMPLETE: The SASL exchange has completed successfully.
-- * SASL_FAILED: The exchance has failed and the connection should be
-+ * SASL_FAILED: The exchange has failed and the connection should be
+ * SASL_FAILED: The exchange has failed and the connection should be
* dropped.
*--------
*/
@@ src/interfaces/libpq/fe-auth.c: pg_SSPI_startup(PGconn *conn, int use_negotiate,
{
char *initialresponse = NULL;
int initialresponselen;
- const char *selected_mechanism;
- PQExpBufferData mechanism_buf;
-- char *password;
-+ char *password = NULL;
- SASLStatus status;
-
- initPQExpBuffer(&mechanism_buf);
@@ src/interfaces/libpq/fe-auth.c: pg_SASL_init(PGconn *conn, int payloadlen)
goto error;
}
@@ src/interfaces/libpq/fe-auth.c: pg_SASL_continue(PGconn *conn, int payloadlen, b
+ return STATUS_OK;
+ }
+
- if (final && !(status == SASL_FAILED || status == SASL_COMPLETE))
+ if (final && status == SASL_CONTINUE)
{
if (outputlen != 0)
@@ src/interfaces/libpq/fe-auth.c: check_expected_areq(AuthRequest areq, PGconn *conn)
5: 29d7e3cbed ! 5: 5c5a83e44e backend: add OAUTHBEARER SASL mechanism
@@ Commit message
deal with multi-issuer setups
- ...and more.
+ Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
+
## src/backend/libpq/Makefile ##
@@ src/backend/libpq/Makefile: include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
@@ src/backend/libpq/auth-oauth.c (new)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
-+ errdetail("Comma expected, but found character %s.",
++ errdetail("Comma expected, but found character \"%s\".",
+ sanitize_char(*p))));
+ p++;
+ break;
6: 0661817808 = 6: 7a42365d62 Introduce OAuth validator libraries
7: 45755e8461 ! 7: 9c46ea6cf9 Add pytest suite for OAuth
@@ Commit message
TODOs:
- The --tap-stream option to pytest-tap is slightly broken during test
failures (it suppresses error information), which impedes debugging.
- - Unsurprisingly, Windows builds fail on the Linux-/BSD-specific backend
- changes. 32-bit builds on Ubuntu fail during testing as well.
- pyca/cryptography is pinned at an old version. Since we use it for
testing and not security, this isn't a critical problem yet, but it's
not ideal. Newer versions require a Rust compiler to build, and while
@@ Commit message
with the Rust pieces bypassed, compilation on FreeBSD takes a while.
- The with_oauth test skip logic should probably be integrated into the
Makefile side as well...
+ - See if 32-bit tests can be enabled with a 32-bit Python.
## .cirrus.tasks.yml ##
@@ .cirrus.tasks.yml: env:
@@ .cirrus.tasks.yml: task:
matrix:
- name: Linux - Debian Bullseye - Autoconf
+@@ .cirrus.tasks.yml: task:
+
+ # Also build & test in a 32bit build - it's gotten rare to test that
+ # locally.
++ # XXX 32-bit Python tests are currently disabled, as the system's 64-bit
++ # Python modules can't link against libpq.
+ configure_32_script: |
+ su postgres <<-EOF
+ export CC='ccache gcc -m32'
+@@ .cirrus.tasks.yml: task:
+ -Dllvm=disabled \
+ --pkg-config-path /usr/lib/i386-linux-gnu/pkgconfig/ \
+ -DPERL=perl5.32-i386-linux-gnu \
+- -DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
++ -DPG_TEST_EXTRA="${PG_TEST_EXTRA//"python"}" \
+ build-32
+ EOF
+
@@ .cirrus.tasks.yml: task:
folder: $CCACHE_DIR
@@ meson.build: foreach test_dir : tests
+ env.set(name, value)
+ endforeach
+
-+ reqs = files(t['requirements'])
-+ test('install_' + venv_name,
-+ python,
-+ args: [ make_venv, '--requirements', reqs, venv_path ],
-+ env: env,
-+ priority: setup_tests_priority - 1, # must run after tmp_install
-+ is_parallel: false,
-+ suite: ['setup'],
-+ timeout: 60, # 30s is too short for the cryptography package compile
-+ )
++ if get_option('PG_TEST_EXTRA').contains('python')
++ reqs = files(t['requirements'])
++ test('install_' + venv_name,
++ python,
++ args: [ make_venv, '--requirements', reqs, venv_path ],
++ env: env,
++ priority: setup_tests_priority - 1, # must run after tmp_install
++ is_parallel: false,
++ suite: ['setup'],
++ timeout: 60, # 30s is too short for the cryptography package compile
++ )
++ endif
+
+ test_group = test_dir['name']
+ test_output = test_result_dir / test_group / kind
@@ meson.build: foreach test_dir : tests
+ 'env': env,
+ }
+
-+ pytest = venv_path / 'bin' / 'py.test'
++ if fs.is_dir(venv_path / 'Scripts')
++ # Windows virtualenv layout
++ pytest = venv_path / 'Scripts' / 'py.test'
++ else
++ pytest = venv_path / 'bin' / 'py.test'
++ endif
++
+ test_command = [
+ pytest,
+ # Avoid running these tests against an existing database.
@@ meson.build: foreach test_dir : tests
+ pyt_p = fs.stem(pyt_p)
+ endif
+
++ testwrap_pytest = testwrap_base + [
++ '--testgroup', test_group,
++ '--testname', pyt_p,
++ ]
++ if not get_option('PG_TEST_EXTRA').contains('python')
++ testwrap_pytest += ['--skip', '"python" tests not enabled in PG_TEST_EXTRA']
++ endif
++
+ test(test_group / pyt_p,
+ python,
+ kwargs: test_kwargs,
-+ args: testwrap_base + [
-+ '--testgroup', test_group,
-+ '--testname', pyt_p,
++ args: testwrap_pytest + [
+ '--', test_command,
+ test_dir['sd'] / pyt,
+ ],
@@ src/test/python/client/test_oauth.py (new)
+
+if platform.system() == "Darwin":
+ libpq = ctypes.cdll.LoadLibrary("libpq.5.dylib")
++elif platform.system() == "Windows":
++ pass # TODO
+else:
+ libpq = ctypes.cdll.LoadLibrary("libpq.so.5")
+
@@ src/test/python/conftest.py (new)
+ Automatically skips the whole suite if PG_TEST_EXTRA doesn't contain
+ 'python'. pytestmark doesn't seem to work in a top-level conftest.py, so
+ I've made this an autoused fixture instead.
-+
-+ TODO: there are tests here that are probably safe, but until I do a full
-+ analysis on which are and which are not, I've made the entire thing opt-in.
+ """
+ extra_tests = os.getenv("PG_TEST_EXTRA", "").split()
+ if "python" not in extra_tests:
@@ src/test/python/pq3.py (new)
+import getpass
+import io
+import os
++import platform
+import ssl
+import sys
+import textwrap
@@ src/test/python/pq3.py (new)
+ try:
+ return os.environ["PGUSER"]
+ except KeyError:
++ if platform.system() == "Windows":
++ # libpq defaults to GetUserName() on Windows.
++ return os.getlogin()
+ return getpass.getuser()
+
+
@@ src/test/python/server/meson.build (new)
@@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
-+if not oauth.found()
-+ subdir_done()
-+endif
-+
+oauthtest_sources = files(
+ 'oauthtest.c',
+)
@@ src/test/python/server/test_oauth.py (new)
+import json
+import os
+import pathlib
++import platform
+import secrets
+import shlex
+import shutil
@@ src/test/python/server/test_oauth.py (new)
+ scope = "openid " + id
+
+ ctx = Context()
-+ hba_lines = (
++ hba_lines = [
+ f'host {ctx.dbname} {ctx.map_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" map=oauth\n',
+ f'host {ctx.dbname} {ctx.authz_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" trust_validator_authz=1\n',
+ f'host {ctx.dbname} all samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}"\n',
-+ )
-+ ident_lines = (r"oauth /^(.*)@example\.com$ \1",)
++ ]
++ ident_lines = [r"oauth /^(.*)@example\.com$ \1"]
++
++ if platform.system() == "Windows":
++ # XXX why is 'samehost' not behaving as expected on Windows?
++ for l in list(hba_lines):
++ hba_lines.append(l.replace("samehost", "::1/128"))
+
+ host, port = postgres_instance
+ conn = psycopg2.connect(host=host, port=port)
@@ src/test/python/test_pq3.py (new)
+import contextlib
+import getpass
+import io
++import platform
+import struct
+import sys
+
@@ src/test/python/test_pq3.py (new)
+ [
+ ("PGHOST", pq3.pghost, "localhost"),
+ ("PGPORT", pq3.pgport, 5432),
-+ ("PGUSER", pq3.pguser, getpass.getuser()),
++ (
++ "PGUSER",
++ pq3.pguser,
++ os.getlogin() if platform.system() == "Windows" else getpass.getuser(),
++ ),
+ ("PGDATABASE", pq3.pgdatabase, "postgres"),
+ ],
+)
@@ src/tools/make_venv (new)
+import argparse
+import subprocess
+import os
++import platform
+import sys
+
+parser = argparse.ArgumentParser()
@@ src/tools/make_venv (new)
+
+# Update pip next. This helps avoid old pip bugs; the version inside system
+# Pythons tends to be pretty out of date.
-+pip = os.path.join(args.venv_path, 'bin', 'pip')
-+run(pip, 'install', '-U', 'pip')
++bindir = 'Scripts' if platform.system() == 'Windows' else 'bin'
++python = os.path.join(args.venv_path, bindir, 'python3')
++run(python, '-m', 'pip', 'install', '-U', 'pip')
+
+# Finally, install the test's requirements. We need pytest and pytest-tap, no
+# matter what the test needs.
++pip = os.path.join(args.venv_path, bindir, 'pip')
+run(pip, 'install', 'pytest', 'pytest-tap')
+if args.requirements:
+ run(pip, 'install', '-r', args.requirements)
8: 0f9f884856 ! 8: 8ad4ce3068 XXX temporary patches to build and test
@@ src/bin/pg_verifybackup/Makefile: top_builddir = ../../..
## src/interfaces/libpq/Makefile ##
@@ src/interfaces/libpq/Makefile: libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
- ifeq (,$(filter aix solaris,$(PORTNAME)))
+ ifeq (,$(filter solaris,$(PORTNAME)))
@if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
- echo 'libpq must not be calling any function which invokes exit'; exit 1; \
+ echo 'libpq must not be calling any function which invokes exit'; \
9: de8f81bd7d ! 9: 5630465578 WIP: Python OAuth provider implementation
@@ src/test/modules/oauth_validator/t/oauth_server.py (new)
+import json
+import os
+import sys
-+from typing import TypeAlias
+
+
+class OAuthHandler(http.server.BaseHTTPRequestHandler):
-+ JsonObject: TypeAlias = dict[str, object]
++ JsonObject = dict[str, object] # TypeAlias is not available until 3.10
+
+ def do_GET(self):
+ if self.path == "/.well-known/openid-configuration":