mirror of
https://github.com/postgres/postgres.git
synced 2026-04-15 22:10:45 -04:00
libpq-oauth: Use the PGoauthBearerRequestV2 API
Switch the private libpq-oauth ABI to a public one, based on the new PGoauthBearerRequestV2 API. A huge amount of glue code can be removed as part of this, and several code paths can be deduplicated. Additionally, the shared library no longer needs to change its name for every major release; it's now just "libpq-oauth.so". Reviewed-by: Chao Li <li.evan.chao@gmail.com> Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com> Discussion: https://postgr.es/m/CAOYmi%2BmrGg%2Bn_X2MOLgeWcj3v_M00gR8uz_D7mM8z%3DdX1JYVbg%40mail.gmail.com
This commit is contained in:
parent
be43c48c22
commit
6225403f27
11 changed files with 348 additions and 432 deletions
|
|
@ -16,13 +16,10 @@ include $(top_builddir)/src/Makefile.global
|
||||||
PGFILEDESC = "libpq-oauth - device authorization OAuth support"
|
PGFILEDESC = "libpq-oauth - device authorization OAuth support"
|
||||||
|
|
||||||
# This is an internal module; we don't want an SONAME and therefore do not set
|
# This is an internal module; we don't want an SONAME and therefore do not set
|
||||||
# SO_MAJOR_VERSION.
|
# SO_MAJOR_VERSION. This requires an explicit override for the shared library
|
||||||
NAME = pq-oauth-$(MAJORVERSION)
|
# name.
|
||||||
|
NAME = pq-oauth
|
||||||
# Force the name "libpq-oauth" for both the static and shared libraries. The
|
|
||||||
# staticlib doesn't need version information in its name.
|
|
||||||
override shlib := lib$(NAME)$(DLSUFFIX)
|
override shlib := lib$(NAME)$(DLSUFFIX)
|
||||||
override stlib := libpq-oauth.a
|
|
||||||
|
|
||||||
override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(CPPFLAGS) $(LIBCURL_CPPFLAGS)
|
override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(CPPFLAGS) $(LIBCURL_CPPFLAGS)
|
||||||
override CFLAGS += $(PTHREAD_CFLAGS)
|
override CFLAGS += $(PTHREAD_CFLAGS)
|
||||||
|
|
|
||||||
|
|
@ -10,48 +10,33 @@ results in a failed connection.
|
||||||
|
|
||||||
= Load-Time ABI =
|
= Load-Time ABI =
|
||||||
|
|
||||||
This module ABI is an internal implementation detail, so it's subject to change
|
As of v19, this module ABI is public and cannot change incompatibly without also
|
||||||
across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
|
changing the entry points. Both libpq and libpq-oauth must gracefully handle
|
||||||
The module exports the following symbols:
|
situations where the other library is of a different release version, past or
|
||||||
|
future, since upgrades to the libraries may happen in either order.
|
||||||
|
|
||||||
- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
|
(Don't assume that package version dependencies from libpq-oauth to libpq will
|
||||||
- void pg_fe_cleanup_oauth_flow(PGconn *conn);
|
simplify the situation! Since libpq delay-loads libpq-oauth, we still have to
|
||||||
|
handle cases where a long-running client application has a libpq that's older
|
||||||
|
than a newly upgraded plugin.)
|
||||||
|
|
||||||
pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
|
The module exports the following symbol:
|
||||||
conn->async_auth and conn->cleanup_async_auth, respectively.
|
|
||||||
|
|
||||||
At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
|
- int pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request);
|
||||||
libpq_gettext(), which must be injected by libpq using this initialization
|
|
||||||
function before the flow is run:
|
|
||||||
|
|
||||||
- void libpq_oauth_init(pgthreadlock_t threadlock,
|
The module then behaves as if it had received a PQAUTHDATA_OAUTH_BEARER_TOKEN_V2
|
||||||
libpq_gettext_func gettext_impl,
|
request via the PQauthDataHook API, and it either fills in an existing token or
|
||||||
conn_errorMessage_func errmsg_impl,
|
populates the necessary callbacks for a token to be obtained asynchronously.
|
||||||
conn_oauth_client_id_func clientid_impl,
|
(See the documentation for PGoauthBearerRequest.) The function returns zero on
|
||||||
conn_oauth_client_secret_func clientsecret_impl,
|
success and nonzero on failure.
|
||||||
conn_oauth_discovery_uri_func discoveryuri_impl,
|
|
||||||
conn_oauth_issuer_id_func issuerid_impl,
|
|
||||||
conn_oauth_scope_func scope_impl,
|
|
||||||
conn_sasl_state_func saslstate_impl,
|
|
||||||
set_conn_altsock_func setaltsock_impl,
|
|
||||||
set_conn_oauth_token_func settoken_impl);
|
|
||||||
|
|
||||||
It also relies on access to several members of the PGconn struct. Not only can
|
Additionally, libpq-oauth relies on libpq's libpq_gettext(), which must be
|
||||||
these change positions across minor versions, but the offsets aren't necessarily
|
injected by libpq using this initialization function before the flow is run:
|
||||||
stable within a single minor release (conn->errorMessage, for instance, can
|
|
||||||
change offsets depending on configure-time options). Therefore the necessary
|
- void libpq_oauth_init(libpq_gettext_func gettext_impl);
|
||||||
accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
|
|
||||||
approach, we can safely search the standard dlopen() paths (e.g. RPATH,
|
|
||||||
LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
|
|
||||||
module wasn't compiled at the same time as libpq -- which becomes especially
|
|
||||||
important during "live upgrade" situations where a running libpq application has
|
|
||||||
the libpq-oauth module updated out from under it before it's first loaded from
|
|
||||||
disk.
|
|
||||||
|
|
||||||
= Static Build =
|
= Static Build =
|
||||||
|
|
||||||
The static library libpq.a does not perform any dynamic loading. If the builtin
|
The static library libpq.a does not perform any dynamic loading. If the builtin
|
||||||
flow is enabled, the application is expected to link against libpq-oauth.a
|
flow is enabled, the application is expected to link against libpq-oauth.a to
|
||||||
directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
|
provide the necessary symbol, or else implement pg_start_oauthbearer() itself.
|
||||||
part of the same build. Unlike the dynamic module, there are no translation
|
|
||||||
shims provided.)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# src/interfaces/libpq-oauth/exports.txt
|
# src/interfaces/libpq-oauth/exports.txt
|
||||||
libpq_oauth_init 1
|
libpq_oauth_init 1
|
||||||
pg_fe_run_oauth_flow 2
|
pg_start_oauthbearer 2
|
||||||
pg_fe_cleanup_oauth_flow 3
|
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,8 @@ endif
|
||||||
|
|
||||||
# This is an internal module; we don't want an SONAME and therefore do not set
|
# This is an internal module; we don't want an SONAME and therefore do not set
|
||||||
# SO_MAJOR_VERSION.
|
# SO_MAJOR_VERSION.
|
||||||
libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
|
|
||||||
|
|
||||||
if build_shared_lib
|
if build_shared_lib
|
||||||
libpq_oauth_so = shared_module(libpq_oauth_name,
|
libpq_oauth_so = shared_module('libpq-oauth',
|
||||||
libpq_oauth_sources + libpq_oauth_so_sources,
|
libpq_oauth_sources + libpq_oauth_so_sources,
|
||||||
include_directories: [libpq_oauth_inc, postgres_inc],
|
include_directories: [libpq_oauth_inc, postgres_inc],
|
||||||
c_args: libpq_oauth_so_c_args,
|
c_args: libpq_oauth_so_c_args,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "common/jsonapi.h"
|
#include "common/jsonapi.h"
|
||||||
#include "fe-auth-oauth.h"
|
|
||||||
#include "mb/pg_wchar.h"
|
#include "mb/pg_wchar.h"
|
||||||
#include "oauth-curl.h"
|
#include "oauth-curl.h"
|
||||||
|
|
||||||
|
|
@ -44,23 +43,10 @@
|
||||||
|
|
||||||
#else /* !USE_DYNAMIC_OAUTH */
|
#else /* !USE_DYNAMIC_OAUTH */
|
||||||
|
|
||||||
/*
|
/* Static builds may make use of libpq internals directly. */
|
||||||
* Static builds may rely on PGconn offsets directly. Keep these aligned with
|
#include "fe-auth-oauth.h"
|
||||||
* the bank of callbacks in oauth-utils.h.
|
|
||||||
*/
|
|
||||||
#include "libpq-int.h"
|
#include "libpq-int.h"
|
||||||
|
|
||||||
#define conn_errorMessage(CONN) (&CONN->errorMessage)
|
|
||||||
#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
|
|
||||||
#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
|
|
||||||
#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
|
|
||||||
#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
|
|
||||||
#define conn_oauth_scope(CONN) (CONN->oauth_scope)
|
|
||||||
#define conn_sasl_state(CONN) (CONN->sasl_state)
|
|
||||||
|
|
||||||
#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
|
|
||||||
#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
|
|
||||||
|
|
||||||
#endif /* USE_DYNAMIC_OAUTH */
|
#endif /* USE_DYNAMIC_OAUTH */
|
||||||
|
|
||||||
/* One final guardrail against accidental inclusion... */
|
/* One final guardrail against accidental inclusion... */
|
||||||
|
|
@ -227,6 +213,15 @@ enum OAuthStep
|
||||||
*/
|
*/
|
||||||
struct async_ctx
|
struct async_ctx
|
||||||
{
|
{
|
||||||
|
/* relevant connection options cached from the PGconn */
|
||||||
|
char *client_id; /* oauth_client_id */
|
||||||
|
char *client_secret; /* oauth_client_secret (may be NULL) */
|
||||||
|
|
||||||
|
/* options cached from the PGoauthBearerRequest (we don't own these) */
|
||||||
|
const char *discovery_uri;
|
||||||
|
const char *issuer_id;
|
||||||
|
const char *scope;
|
||||||
|
|
||||||
enum OAuthStep step; /* where are we in the flow? */
|
enum OAuthStep step; /* where are we in the flow? */
|
||||||
|
|
||||||
int timerfd; /* descriptor for signaling async timeouts */
|
int timerfd; /* descriptor for signaling async timeouts */
|
||||||
|
|
@ -339,29 +334,72 @@ free_async_ctx(struct async_ctx *actx)
|
||||||
if (actx->timerfd >= 0)
|
if (actx->timerfd >= 0)
|
||||||
close(actx->timerfd);
|
close(actx->timerfd);
|
||||||
|
|
||||||
|
free(actx->client_id);
|
||||||
|
free(actx->client_secret);
|
||||||
|
|
||||||
free(actx);
|
free(actx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release resources used for the asynchronous exchange and disconnect the
|
* Release resources used for the asynchronous exchange.
|
||||||
* altsock.
|
|
||||||
*
|
|
||||||
* This is called either at the end of a successful authentication, or during
|
|
||||||
* pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
|
|
||||||
* calls us back.
|
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
pg_fe_cleanup_oauth_flow(PGconn *conn)
|
pg_fe_cleanup_oauth_flow(PGconn *conn, PGoauthBearerRequest *request)
|
||||||
{
|
{
|
||||||
fe_oauth_state *state = conn_sasl_state(conn);
|
struct async_ctx *actx = request->user;
|
||||||
|
|
||||||
if (state->async_ctx)
|
/* request->cleanup is only set after actx has been allocated. */
|
||||||
|
Assert(actx);
|
||||||
|
|
||||||
|
free_async_ctx(actx);
|
||||||
|
request->user = NULL;
|
||||||
|
|
||||||
|
/* libpq has made its own copy of the token; clear ours now. */
|
||||||
|
if (request->token)
|
||||||
{
|
{
|
||||||
free_async_ctx(state->async_ctx);
|
explicit_bzero(request->token, strlen(request->token));
|
||||||
state->async_ctx = NULL;
|
free(request->token);
|
||||||
|
request->token = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Builds an error message from actx and stores it in req->error. The allocation
|
||||||
|
* is backed by actx->work_data (which will be reset first).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
append_actx_error(PGoauthBearerRequestV2 *req, struct async_ctx *actx)
|
||||||
|
{
|
||||||
|
PQExpBuffer errbuf = &actx->work_data;
|
||||||
|
|
||||||
|
resetPQExpBuffer(errbuf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assemble the three parts of our error: context, body, and detail. See
|
||||||
|
* also the documentation for struct async_ctx.
|
||||||
|
*/
|
||||||
|
if (actx->errctx)
|
||||||
|
appendPQExpBuffer(errbuf, "%s: ", actx->errctx);
|
||||||
|
|
||||||
|
if (PQExpBufferDataBroken(actx->errbuf))
|
||||||
|
appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
|
||||||
|
else
|
||||||
|
appendPQExpBufferStr(errbuf, actx->errbuf.data);
|
||||||
|
|
||||||
|
if (actx->curl_err[0])
|
||||||
|
{
|
||||||
|
appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
|
||||||
|
|
||||||
|
/* Sometimes libcurl adds a newline to the error buffer. :( */
|
||||||
|
if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
|
||||||
|
{
|
||||||
|
errbuf->data[errbuf->len - 2] = ')';
|
||||||
|
errbuf->data[errbuf->len - 1] = '\0';
|
||||||
|
errbuf->len--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_conn_altsock(conn, PGINVALID_SOCKET);
|
req->error = errbuf->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -2197,7 +2235,7 @@ static bool
|
||||||
check_issuer(struct async_ctx *actx, PGconn *conn)
|
check_issuer(struct async_ctx *actx, PGconn *conn)
|
||||||
{
|
{
|
||||||
const struct provider *provider = &actx->provider;
|
const struct provider *provider = &actx->provider;
|
||||||
const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
|
const char *oauth_issuer_id = actx->issuer_id;
|
||||||
|
|
||||||
Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
|
Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
|
||||||
Assert(provider->issuer); /* ensured by parse_provider() */
|
Assert(provider->issuer); /* ensured by parse_provider() */
|
||||||
|
|
@ -2300,8 +2338,8 @@ check_for_device_flow(struct async_ctx *actx)
|
||||||
static bool
|
static bool
|
||||||
add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
|
add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
|
||||||
{
|
{
|
||||||
const char *oauth_client_id = conn_oauth_client_id(conn);
|
const char *oauth_client_id = actx->client_id;
|
||||||
const char *oauth_client_secret = conn_oauth_client_secret(conn);
|
const char *oauth_client_secret = actx->client_secret;
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
char *username = NULL;
|
char *username = NULL;
|
||||||
|
|
@ -2384,11 +2422,10 @@ cleanup:
|
||||||
static bool
|
static bool
|
||||||
start_device_authz(struct async_ctx *actx, PGconn *conn)
|
start_device_authz(struct async_ctx *actx, PGconn *conn)
|
||||||
{
|
{
|
||||||
const char *oauth_scope = conn_oauth_scope(conn);
|
const char *oauth_scope = actx->scope;
|
||||||
const char *device_authz_uri = actx->provider.device_authorization_endpoint;
|
const char *device_authz_uri = actx->provider.device_authorization_endpoint;
|
||||||
PQExpBuffer work_buffer = &actx->work_data;
|
PQExpBuffer work_buffer = &actx->work_data;
|
||||||
|
|
||||||
Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
|
|
||||||
Assert(device_authz_uri); /* ensured by check_for_device_flow() */
|
Assert(device_authz_uri); /* ensured by check_for_device_flow() */
|
||||||
|
|
||||||
/* Construct our request body. */
|
/* Construct our request body. */
|
||||||
|
|
@ -2476,7 +2513,6 @@ start_token_request(struct async_ctx *actx, PGconn *conn)
|
||||||
const char *device_code = actx->authz.device_code;
|
const char *device_code = actx->authz.device_code;
|
||||||
PQExpBuffer work_buffer = &actx->work_data;
|
PQExpBuffer work_buffer = &actx->work_data;
|
||||||
|
|
||||||
Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
|
|
||||||
Assert(token_uri); /* ensured by parse_provider() */
|
Assert(token_uri); /* ensured by parse_provider() */
|
||||||
Assert(device_code); /* ensured by parse_device_authz() */
|
Assert(device_code); /* ensured by parse_device_authz() */
|
||||||
|
|
||||||
|
|
@ -2655,7 +2691,7 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
|
||||||
* function will not try to reinitialize Curl on successive calls.
|
* function will not try to reinitialize Curl on successive calls.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
initialize_curl(PGconn *conn)
|
initialize_curl(PGoauthBearerRequestV2 *req)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Don't let the compiler play tricks with this variable. In the
|
* Don't let the compiler play tricks with this variable. In the
|
||||||
|
|
@ -2689,8 +2725,7 @@ initialize_curl(PGconn *conn)
|
||||||
goto done;
|
goto done;
|
||||||
else if (init_successful == PG_BOOL_NO)
|
else if (init_successful == PG_BOOL_NO)
|
||||||
{
|
{
|
||||||
libpq_append_conn_error(conn,
|
req->error = libpq_gettext("curl_global_init previously failed during OAuth setup");
|
||||||
"curl_global_init previously failed during OAuth setup");
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2708,8 +2743,7 @@ initialize_curl(PGconn *conn)
|
||||||
*/
|
*/
|
||||||
if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
|
if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
|
||||||
{
|
{
|
||||||
libpq_append_conn_error(conn,
|
req->error = libpq_gettext("curl_global_init failed during OAuth setup");
|
||||||
"curl_global_init failed during OAuth setup");
|
|
||||||
init_successful = PG_BOOL_NO;
|
init_successful = PG_BOOL_NO;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -2730,11 +2764,11 @@ initialize_curl(PGconn *conn)
|
||||||
* In a downgrade situation, the damage is already done. Curl global
|
* In a downgrade situation, the damage is already done. Curl global
|
||||||
* state may be corrupted. Be noisy.
|
* state may be corrupted. Be noisy.
|
||||||
*/
|
*/
|
||||||
libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
|
req->error = libpq_gettext("libcurl is no longer thread-safe\n"
|
||||||
"\tCurl initialization was reported thread-safe when libpq\n"
|
"\tCurl initialization was reported thread-safe when libpq\n"
|
||||||
"\twas compiled, but the currently installed version of\n"
|
"\twas compiled, but the currently installed version of\n"
|
||||||
"\tlibcurl reports that it is not. Recompile libpq against\n"
|
"\tlibcurl reports that it is not. Recompile libpq against\n"
|
||||||
"\tthe installed version of libcurl.");
|
"\tthe installed version of libcurl.");
|
||||||
init_successful = PG_BOOL_NO;
|
init_successful = PG_BOOL_NO;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -2764,54 +2798,16 @@ done:
|
||||||
* provider.
|
* provider.
|
||||||
*/
|
*/
|
||||||
static PostgresPollingStatusType
|
static PostgresPollingStatusType
|
||||||
pg_fe_run_oauth_flow_impl(PGconn *conn)
|
pg_fe_run_oauth_flow_impl(PGconn *conn, PGoauthBearerRequestV2 *request,
|
||||||
|
int *altsock)
|
||||||
{
|
{
|
||||||
fe_oauth_state *state = conn_sasl_state(conn);
|
struct async_ctx *actx = request->v1.user;
|
||||||
struct async_ctx *actx;
|
|
||||||
char *oauth_token = NULL;
|
char *oauth_token = NULL;
|
||||||
PQExpBuffer errbuf;
|
|
||||||
|
|
||||||
if (!initialize_curl(conn))
|
|
||||||
return PGRES_POLLING_FAILED;
|
|
||||||
|
|
||||||
if (!state->async_ctx)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Create our asynchronous state, and hook it into the upper-level
|
|
||||||
* OAuth state immediately, so any failures below won't leak the
|
|
||||||
* context allocation.
|
|
||||||
*/
|
|
||||||
actx = calloc(1, sizeof(*actx));
|
|
||||||
if (!actx)
|
|
||||||
{
|
|
||||||
libpq_append_conn_error(conn, "out of memory");
|
|
||||||
return PGRES_POLLING_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
actx->mux = PGINVALID_SOCKET;
|
|
||||||
actx->timerfd = -1;
|
|
||||||
|
|
||||||
/* Should we enable unsafe features? */
|
|
||||||
actx->debugging = oauth_unsafe_debugging_enabled();
|
|
||||||
|
|
||||||
state->async_ctx = actx;
|
|
||||||
|
|
||||||
initPQExpBuffer(&actx->work_data);
|
|
||||||
initPQExpBuffer(&actx->errbuf);
|
|
||||||
|
|
||||||
if (!setup_multiplexer(actx))
|
|
||||||
goto error_return;
|
|
||||||
|
|
||||||
if (!setup_curl_handles(actx))
|
|
||||||
goto error_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
actx = state->async_ctx;
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
/* By default, the multiplexer is the altsock. Reassign as desired. */
|
/* By default, the multiplexer is the altsock. Reassign as desired. */
|
||||||
set_conn_altsock(conn, actx->mux);
|
*altsock = actx->mux;
|
||||||
|
|
||||||
switch (actx->step)
|
switch (actx->step)
|
||||||
{
|
{
|
||||||
|
|
@ -2876,7 +2872,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
|
||||||
|
|
||||||
if (!expired)
|
if (!expired)
|
||||||
{
|
{
|
||||||
set_conn_altsock(conn, actx->timerfd);
|
*altsock = actx->timerfd;
|
||||||
return PGRES_POLLING_READING;
|
return PGRES_POLLING_READING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2893,7 +2889,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
|
||||||
{
|
{
|
||||||
case OAUTH_STEP_INIT:
|
case OAUTH_STEP_INIT:
|
||||||
actx->errctx = libpq_gettext("failed to fetch OpenID discovery document");
|
actx->errctx = libpq_gettext("failed to fetch OpenID discovery document");
|
||||||
if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
|
if (!start_discovery(actx, actx->discovery_uri))
|
||||||
goto error_return;
|
goto error_return;
|
||||||
|
|
||||||
actx->step = OAUTH_STEP_DISCOVERY;
|
actx->step = OAUTH_STEP_DISCOVERY;
|
||||||
|
|
@ -2933,10 +2929,10 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
|
||||||
goto error_return;
|
goto error_return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hook any oauth_token into the PGconn immediately so that
|
* Hook any oauth_token into the request struct immediately so
|
||||||
* the allocation isn't lost in case of an error.
|
* that the allocation isn't lost in case of an error.
|
||||||
*/
|
*/
|
||||||
set_conn_oauth_token(conn, oauth_token);
|
request->v1.token = oauth_token;
|
||||||
|
|
||||||
if (!actx->user_prompted)
|
if (!actx->user_prompted)
|
||||||
{
|
{
|
||||||
|
|
@ -2965,7 +2961,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
|
||||||
* the client wait directly on the timerfd rather than the
|
* the client wait directly on the timerfd rather than the
|
||||||
* multiplexer.
|
* multiplexer.
|
||||||
*/
|
*/
|
||||||
set_conn_altsock(conn, actx->timerfd);
|
*altsock = actx->timerfd;
|
||||||
|
|
||||||
actx->step = OAUTH_STEP_WAIT_INTERVAL;
|
actx->step = OAUTH_STEP_WAIT_INTERVAL;
|
||||||
actx->running = 1;
|
actx->running = 1;
|
||||||
|
|
@ -2991,48 +2987,21 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
|
||||||
return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
|
return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
|
||||||
|
|
||||||
error_return:
|
error_return:
|
||||||
errbuf = conn_errorMessage(conn);
|
append_actx_error(request, actx);
|
||||||
|
|
||||||
/*
|
|
||||||
* Assemble the three parts of our error: context, body, and detail. See
|
|
||||||
* also the documentation for struct async_ctx.
|
|
||||||
*/
|
|
||||||
if (actx->errctx)
|
|
||||||
appendPQExpBuffer(errbuf, "%s: ", actx->errctx);
|
|
||||||
|
|
||||||
if (PQExpBufferDataBroken(actx->errbuf))
|
|
||||||
appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
|
|
||||||
else
|
|
||||||
appendPQExpBufferStr(errbuf, actx->errbuf.data);
|
|
||||||
|
|
||||||
if (actx->curl_err[0])
|
|
||||||
{
|
|
||||||
appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
|
|
||||||
|
|
||||||
/* Sometimes libcurl adds a newline to the error buffer. :( */
|
|
||||||
if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
|
|
||||||
{
|
|
||||||
errbuf->data[errbuf->len - 2] = ')';
|
|
||||||
errbuf->data[errbuf->len - 1] = '\0';
|
|
||||||
errbuf->len--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appendPQExpBufferChar(errbuf, '\n');
|
|
||||||
|
|
||||||
return PGRES_POLLING_FAILED;
|
return PGRES_POLLING_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The top-level entry point. This is a convenient place to put necessary
|
* The top-level entry point for the flow. This is a convenient place to put
|
||||||
* wrapper logic before handing off to the true implementation, above.
|
* necessary wrapper logic before handing off to the true implementation, above.
|
||||||
*/
|
*/
|
||||||
PostgresPollingStatusType
|
static PostgresPollingStatusType
|
||||||
pg_fe_run_oauth_flow(PGconn *conn)
|
pg_fe_run_oauth_flow(PGconn *conn, struct PGoauthBearerRequest *request,
|
||||||
|
int *altsock)
|
||||||
{
|
{
|
||||||
PostgresPollingStatusType result;
|
PostgresPollingStatusType result;
|
||||||
fe_oauth_state *state = conn_sasl_state(conn);
|
struct async_ctx *actx = request->user;
|
||||||
struct async_ctx *actx;
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
sigset_t osigset;
|
sigset_t osigset;
|
||||||
bool sigpipe_pending;
|
bool sigpipe_pending;
|
||||||
|
|
@ -3059,20 +3028,16 @@ pg_fe_run_oauth_flow(PGconn *conn)
|
||||||
masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
|
masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
result = pg_fe_run_oauth_flow_impl(conn);
|
result = pg_fe_run_oauth_flow_impl(conn,
|
||||||
|
(PGoauthBearerRequestV2 *) request,
|
||||||
|
altsock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* To assist with finding bugs in comb_multiplexer() and
|
* To assist with finding bugs in comb_multiplexer() and
|
||||||
* drain_timer_events(), when we're in debug mode, track the total number
|
* drain_timer_events(), when we're in debug mode, track the total number
|
||||||
* of calls to this function and print that at the end of the flow.
|
* of calls to this function and print that at the end of the flow.
|
||||||
*
|
|
||||||
* Be careful that state->async_ctx could be NULL if early initialization
|
|
||||||
* fails during the first call.
|
|
||||||
*/
|
*/
|
||||||
actx = state->async_ctx;
|
if (actx->debugging)
|
||||||
Assert(actx || result == PGRES_POLLING_FAILED);
|
|
||||||
|
|
||||||
if (actx && actx->debugging)
|
|
||||||
{
|
{
|
||||||
actx->dbg_num_calls++;
|
actx->dbg_num_calls++;
|
||||||
if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED)
|
if (result == PGRES_POLLING_OK || result == PGRES_POLLING_FAILED)
|
||||||
|
|
@ -3093,3 +3058,104 @@ pg_fe_run_oauth_flow(PGconn *conn)
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback registration for OAUTHBEARER. libpq calls this once per OAuth
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request)
|
||||||
|
{
|
||||||
|
struct async_ctx *actx;
|
||||||
|
PQconninfoOption *conninfo = NULL;
|
||||||
|
|
||||||
|
if (!initialize_curl(request))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create our asynchronous state, and hook it into the upper-level OAuth
|
||||||
|
* state immediately, so any failures below won't leak the context
|
||||||
|
* allocation.
|
||||||
|
*/
|
||||||
|
actx = calloc(1, sizeof(*actx));
|
||||||
|
if (!actx)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
actx->mux = PGINVALID_SOCKET;
|
||||||
|
actx->timerfd = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we have a valid (but still useless) actx, so we can fill in the
|
||||||
|
* request object. From this point onward, failures will result in a call
|
||||||
|
* to pg_fe_cleanup_oauth_flow(). Further cleanup logic belongs there.
|
||||||
|
*/
|
||||||
|
request->v1.async = pg_fe_run_oauth_flow;
|
||||||
|
request->v1.cleanup = pg_fe_cleanup_oauth_flow;
|
||||||
|
request->v1.user = actx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now finish filling in the actx.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Should we enable unsafe features? */
|
||||||
|
actx->debugging = oauth_unsafe_debugging_enabled();
|
||||||
|
|
||||||
|
initPQExpBuffer(&actx->work_data);
|
||||||
|
initPQExpBuffer(&actx->errbuf);
|
||||||
|
|
||||||
|
/* Pull relevant connection options. */
|
||||||
|
conninfo = PQconninfo(conn);
|
||||||
|
if (!conninfo)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
|
for (PQconninfoOption *opt = conninfo; opt->keyword; opt++)
|
||||||
|
{
|
||||||
|
if (!opt->val)
|
||||||
|
continue; /* simplifies the strdup logic below */
|
||||||
|
|
||||||
|
if (strcmp(opt->keyword, "oauth_client_id") == 0)
|
||||||
|
{
|
||||||
|
actx->client_id = strdup(opt->val);
|
||||||
|
if (!actx->client_id)
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
else if (strcmp(opt->keyword, "oauth_client_secret") == 0)
|
||||||
|
{
|
||||||
|
actx->client_secret = strdup(opt->val);
|
||||||
|
if (!actx->client_secret)
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PQconninfoFree(conninfo);
|
||||||
|
conninfo = NULL; /* keeps `goto oom` safe */
|
||||||
|
|
||||||
|
actx->discovery_uri = request->v1.openid_configuration;
|
||||||
|
actx->issuer_id = request->issuer;
|
||||||
|
actx->scope = request->v1.scope;
|
||||||
|
|
||||||
|
Assert(actx->client_id); /* ensured by setup_oauth_parameters() */
|
||||||
|
Assert(actx->issuer_id); /* ensured by setup_oauth_parameters() */
|
||||||
|
Assert(actx->discovery_uri); /* ensured by oauth_exchange() */
|
||||||
|
|
||||||
|
if (!setup_multiplexer(actx))
|
||||||
|
{
|
||||||
|
append_actx_error(request, actx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setup_curl_handles(actx))
|
||||||
|
{
|
||||||
|
append_actx_error(request, actx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
oom:
|
||||||
|
if (conninfo)
|
||||||
|
PQconninfoFree(conninfo);
|
||||||
|
|
||||||
|
request->error = libpq_gettext("out of memory");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
#include "libpq-fe.h"
|
#include "libpq-fe.h"
|
||||||
|
|
||||||
/* Exported async-auth callbacks. */
|
/* Exported flow callback. */
|
||||||
extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
|
extern PGDLLEXPORT int pg_start_oauthbearer(PGconn *conn,
|
||||||
extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
|
PGoauthBearerRequestV2 *request);
|
||||||
|
|
||||||
#endif /* OAUTH_CURL_H */
|
#endif /* OAUTH_CURL_H */
|
||||||
|
|
|
||||||
|
|
@ -35,85 +35,18 @@
|
||||||
pgthreadlock_t pg_g_threadlock;
|
pgthreadlock_t pg_g_threadlock;
|
||||||
static libpq_gettext_func libpq_gettext_impl;
|
static libpq_gettext_func libpq_gettext_impl;
|
||||||
|
|
||||||
conn_errorMessage_func conn_errorMessage;
|
|
||||||
conn_oauth_client_id_func conn_oauth_client_id;
|
|
||||||
conn_oauth_client_secret_func conn_oauth_client_secret;
|
|
||||||
conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
|
|
||||||
conn_oauth_issuer_id_func conn_oauth_issuer_id;
|
|
||||||
conn_oauth_scope_func conn_oauth_scope;
|
|
||||||
conn_sasl_state_func conn_sasl_state;
|
|
||||||
|
|
||||||
set_conn_altsock_func set_conn_altsock;
|
|
||||||
set_conn_oauth_token_func set_conn_oauth_token;
|
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* Initializes libpq-oauth by setting necessary callbacks.
|
* Initializes libpq-oauth by setting necessary callbacks.
|
||||||
*
|
*
|
||||||
* The current implementation relies on the following private implementation
|
* The current implementation relies on libpq_gettext to translate error
|
||||||
* details of libpq:
|
* messages using libpq's message domain, so libpq injects it here. We also use
|
||||||
*
|
* this chance to initialize our threadlock.
|
||||||
* - pg_g_threadlock: protects libcurl initialization if the underlying Curl
|
|
||||||
* installation is not threadsafe
|
|
||||||
*
|
|
||||||
* - libpq_gettext: translates error messages using libpq's message domain
|
|
||||||
*
|
|
||||||
* The implementation also needs access to several members of the PGconn struct,
|
|
||||||
* which are not guaranteed to stay in place across minor versions. Accessors
|
|
||||||
* (named conn_*) and mutators (named set_conn_*) are injected here.
|
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
libpq_oauth_init(pgthreadlock_t threadlock_impl,
|
libpq_oauth_init(libpq_gettext_func gettext_impl)
|
||||||
libpq_gettext_func gettext_impl,
|
|
||||||
conn_errorMessage_func errmsg_impl,
|
|
||||||
conn_oauth_client_id_func clientid_impl,
|
|
||||||
conn_oauth_client_secret_func clientsecret_impl,
|
|
||||||
conn_oauth_discovery_uri_func discoveryuri_impl,
|
|
||||||
conn_oauth_issuer_id_func issuerid_impl,
|
|
||||||
conn_oauth_scope_func scope_impl,
|
|
||||||
conn_sasl_state_func saslstate_impl,
|
|
||||||
set_conn_altsock_func setaltsock_impl,
|
|
||||||
set_conn_oauth_token_func settoken_impl)
|
|
||||||
{
|
{
|
||||||
pg_g_threadlock = threadlock_impl;
|
pg_g_threadlock = PQgetThreadLock();
|
||||||
libpq_gettext_impl = gettext_impl;
|
libpq_gettext_impl = gettext_impl;
|
||||||
conn_errorMessage = errmsg_impl;
|
|
||||||
conn_oauth_client_id = clientid_impl;
|
|
||||||
conn_oauth_client_secret = clientsecret_impl;
|
|
||||||
conn_oauth_discovery_uri = discoveryuri_impl;
|
|
||||||
conn_oauth_issuer_id = issuerid_impl;
|
|
||||||
conn_oauth_scope = scope_impl;
|
|
||||||
conn_sasl_state = saslstate_impl;
|
|
||||||
set_conn_altsock = setaltsock_impl;
|
|
||||||
set_conn_oauth_token = settoken_impl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Append a formatted string to the error message buffer of the given
|
|
||||||
* connection, after translating it. This is a copy of libpq's internal API.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
libpq_append_conn_error(PGconn *conn, const char *fmt,...)
|
|
||||||
{
|
|
||||||
int save_errno = errno;
|
|
||||||
bool done;
|
|
||||||
va_list args;
|
|
||||||
PQExpBuffer errorMessage = conn_errorMessage(conn);
|
|
||||||
|
|
||||||
Assert(fmt[strlen(fmt) - 1] != '\n');
|
|
||||||
|
|
||||||
if (PQExpBufferBroken(errorMessage))
|
|
||||||
return; /* already failed */
|
|
||||||
|
|
||||||
/* Loop in case we have to retry after enlarging the buffer. */
|
|
||||||
do
|
|
||||||
{
|
|
||||||
errno = save_errno;
|
|
||||||
va_start(args, fmt);
|
|
||||||
done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
|
|
||||||
va_end(args);
|
|
||||||
} while (!done);
|
|
||||||
|
|
||||||
appendPQExpBufferChar(errorMessage, '\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_NLS
|
#ifdef ENABLE_NLS
|
||||||
|
|
|
||||||
|
|
@ -15,53 +15,13 @@
|
||||||
#ifndef OAUTH_UTILS_H
|
#ifndef OAUTH_UTILS_H
|
||||||
#define OAUTH_UTILS_H
|
#define OAUTH_UTILS_H
|
||||||
|
|
||||||
#include "fe-auth-oauth.h"
|
|
||||||
#include "libpq-fe.h"
|
#include "libpq-fe.h"
|
||||||
#include "pqexpbuffer.h"
|
#include "pqexpbuffer.h"
|
||||||
|
|
||||||
/*
|
|
||||||
* A bank of callbacks to safely access members of PGconn, which are all passed
|
|
||||||
* to libpq_oauth_init() by libpq.
|
|
||||||
*
|
|
||||||
* Keep these aligned with the definitions in fe-auth-oauth.c as well as the
|
|
||||||
* static declarations in oauth-curl.c.
|
|
||||||
*/
|
|
||||||
#define DECLARE_GETTER(TYPE, MEMBER) \
|
|
||||||
typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
|
|
||||||
extern conn_ ## MEMBER ## _func conn_ ## MEMBER;
|
|
||||||
|
|
||||||
#define DECLARE_SETTER(TYPE, MEMBER) \
|
|
||||||
typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
|
|
||||||
extern set_conn_ ## MEMBER ## _func set_conn_ ## MEMBER;
|
|
||||||
|
|
||||||
DECLARE_GETTER(PQExpBuffer, errorMessage);
|
|
||||||
DECLARE_GETTER(char *, oauth_client_id);
|
|
||||||
DECLARE_GETTER(char *, oauth_client_secret);
|
|
||||||
DECLARE_GETTER(char *, oauth_discovery_uri);
|
|
||||||
DECLARE_GETTER(char *, oauth_issuer_id);
|
|
||||||
DECLARE_GETTER(char *, oauth_scope);
|
|
||||||
DECLARE_GETTER(fe_oauth_state *, sasl_state);
|
|
||||||
|
|
||||||
DECLARE_SETTER(pgsocket, altsock);
|
|
||||||
DECLARE_SETTER(char *, oauth_token);
|
|
||||||
|
|
||||||
#undef DECLARE_GETTER
|
|
||||||
#undef DECLARE_SETTER
|
|
||||||
|
|
||||||
typedef char *(*libpq_gettext_func) (const char *msgid);
|
typedef char *(*libpq_gettext_func) (const char *msgid);
|
||||||
|
|
||||||
/* Initializes libpq-oauth. */
|
/* Initializes libpq-oauth. */
|
||||||
extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
|
extern PGDLLEXPORT void libpq_oauth_init(libpq_gettext_func gettext_impl);
|
||||||
libpq_gettext_func gettext_impl,
|
|
||||||
conn_errorMessage_func errmsg_impl,
|
|
||||||
conn_oauth_client_id_func clientid_impl,
|
|
||||||
conn_oauth_client_secret_func clientsecret_impl,
|
|
||||||
conn_oauth_discovery_uri_func discoveryuri_impl,
|
|
||||||
conn_oauth_issuer_id_func issuerid_impl,
|
|
||||||
conn_oauth_scope_func scope_impl,
|
|
||||||
conn_sasl_state_func saslstate_impl,
|
|
||||||
set_conn_altsock_func setaltsock_impl,
|
|
||||||
set_conn_oauth_token_func settoken_impl);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
|
* Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
|
||||||
|
|
@ -75,7 +35,6 @@ typedef enum
|
||||||
PG_BOOL_NO /* No (false) */
|
PG_BOOL_NO /* No (false) */
|
||||||
} PGTernaryBool;
|
} PGTernaryBool;
|
||||||
|
|
||||||
extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
|
|
||||||
extern bool oauth_unsafe_debugging_enabled(void);
|
extern bool oauth_unsafe_debugging_enabled(void);
|
||||||
extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
|
extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
|
||||||
extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
|
extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ oauth_init(PGconn *conn, const char *password,
|
||||||
* This handles only mechanism state tied to the connection lifetime; state
|
* This handles only mechanism state tied to the connection lifetime; state
|
||||||
* stored in state->async_ctx is freed up either immediately after the
|
* stored in state->async_ctx is freed up either immediately after the
|
||||||
* authentication handshake succeeds, or before the mechanism is cleaned up on
|
* authentication handshake succeeds, or before the mechanism is cleaned up on
|
||||||
* failure. See pg_fe_cleanup_oauth_flow() and cleanup_user_oauth_flow().
|
* failure. See pg_fe_cleanup_oauth_flow() and cleanup_oauth_flow().
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
oauth_free(void *opaq)
|
oauth_free(void *opaq)
|
||||||
|
|
@ -680,30 +680,54 @@ cleanup:
|
||||||
* it's added to conn->errorMessage here.
|
* it's added to conn->errorMessage here.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
report_user_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
|
report_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
|
||||||
{
|
{
|
||||||
appendPQExpBufferStr(&conn->errorMessage,
|
fe_oauth_state *state = conn->sasl_state;
|
||||||
libpq_gettext("user-defined OAuth flow failed"));
|
const char *errmsg = request->error;
|
||||||
|
|
||||||
if (request->error)
|
/*
|
||||||
|
* User-defined flows are called out explicitly so that the user knows who
|
||||||
|
* to blame. Builtin flows don't need that extra message length; we expect
|
||||||
|
* them to always fill in request->error on failure anyway.
|
||||||
|
*/
|
||||||
|
if (state->builtin)
|
||||||
{
|
{
|
||||||
appendPQExpBufferStr(&conn->errorMessage, ": ");
|
if (!errmsg)
|
||||||
appendPQExpBufferStr(&conn->errorMessage, request->error);
|
{
|
||||||
|
/*
|
||||||
|
* Don't turn a bug here into a crash in production, but don't
|
||||||
|
* bother translating either.
|
||||||
|
*/
|
||||||
|
Assert(false);
|
||||||
|
errmsg = "builtin flow failed but did not provide an error message";
|
||||||
|
}
|
||||||
|
|
||||||
|
appendPQExpBufferStr(&conn->errorMessage, errmsg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendPQExpBufferStr(&conn->errorMessage,
|
||||||
|
libpq_gettext("user-defined OAuth flow failed"));
|
||||||
|
if (errmsg)
|
||||||
|
{
|
||||||
|
appendPQExpBufferStr(&conn->errorMessage, ": ");
|
||||||
|
appendPQExpBufferStr(&conn->errorMessage, errmsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendPQExpBufferChar(&conn->errorMessage, '\n');
|
appendPQExpBufferChar(&conn->errorMessage, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Callback implementation of conn->async_auth() for a user-defined OAuth flow.
|
* Callback implementation of conn->async_auth() for OAuth flows. Delegates the
|
||||||
* Delegates the retrieval of the token to the application's async callback.
|
* retrieval of the token to the PGoauthBearerRequestV2.async() callback.
|
||||||
*
|
*
|
||||||
* This will be called multiple times as needed; the application is responsible
|
* This will be called multiple times as needed; the callback is responsible for
|
||||||
* for setting an altsock to signal and returning the correct PGRES_POLLING_*
|
* setting an altsock to signal and returning the correct PGRES_POLLING_*
|
||||||
* statuses for use by PQconnectPoll().
|
* statuses for use by PQconnectPoll().
|
||||||
*/
|
*/
|
||||||
static PostgresPollingStatusType
|
static PostgresPollingStatusType
|
||||||
run_user_oauth_flow(PGconn *conn)
|
run_oauth_flow(PGconn *conn)
|
||||||
{
|
{
|
||||||
fe_oauth_state *state = conn->sasl_state;
|
fe_oauth_state *state = conn->sasl_state;
|
||||||
PGoauthBearerRequestV2 *request = state->async_ctx;
|
PGoauthBearerRequestV2 *request = state->async_ctx;
|
||||||
|
|
@ -711,6 +735,7 @@ run_user_oauth_flow(PGconn *conn)
|
||||||
|
|
||||||
if (!request->v1.async)
|
if (!request->v1.async)
|
||||||
{
|
{
|
||||||
|
Assert(!state->builtin); /* be very noisy if our code does this */
|
||||||
libpq_append_conn_error(conn,
|
libpq_append_conn_error(conn,
|
||||||
"user-defined OAuth flow provided neither a token nor an async callback");
|
"user-defined OAuth flow provided neither a token nor an async callback");
|
||||||
return PGRES_POLLING_FAILED;
|
return PGRES_POLLING_FAILED;
|
||||||
|
|
@ -722,7 +747,7 @@ run_user_oauth_flow(PGconn *conn)
|
||||||
|
|
||||||
if (status == PGRES_POLLING_FAILED)
|
if (status == PGRES_POLLING_FAILED)
|
||||||
{
|
{
|
||||||
report_user_flow_error(conn, request);
|
report_flow_error(conn, request);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
else if (status == PGRES_POLLING_OK)
|
else if (status == PGRES_POLLING_OK)
|
||||||
|
|
@ -734,6 +759,7 @@ run_user_oauth_flow(PGconn *conn)
|
||||||
*/
|
*/
|
||||||
if (!request->v1.token)
|
if (!request->v1.token)
|
||||||
{
|
{
|
||||||
|
Assert(!state->builtin);
|
||||||
libpq_append_conn_error(conn,
|
libpq_append_conn_error(conn,
|
||||||
"user-defined OAuth flow did not provide a token");
|
"user-defined OAuth flow did not provide a token");
|
||||||
return PGRES_POLLING_FAILED;
|
return PGRES_POLLING_FAILED;
|
||||||
|
|
@ -752,6 +778,7 @@ run_user_oauth_flow(PGconn *conn)
|
||||||
/* The hook wants the client to poll the altsock. Make sure it set one. */
|
/* The hook wants the client to poll the altsock. Make sure it set one. */
|
||||||
if (conn->altsock == PGINVALID_SOCKET)
|
if (conn->altsock == PGINVALID_SOCKET)
|
||||||
{
|
{
|
||||||
|
Assert(!state->builtin);
|
||||||
libpq_append_conn_error(conn,
|
libpq_append_conn_error(conn,
|
||||||
"user-defined OAuth flow did not provide a socket for polling");
|
"user-defined OAuth flow did not provide a socket for polling");
|
||||||
return PGRES_POLLING_FAILED;
|
return PGRES_POLLING_FAILED;
|
||||||
|
|
@ -761,12 +788,16 @@ run_user_oauth_flow(PGconn *conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cleanup callback for the async user flow. Delegates most of its job to
|
* Cleanup callback for the async flow. Delegates most of its job to
|
||||||
* PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
|
* PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
|
||||||
* request itself.
|
* request itself.
|
||||||
|
*
|
||||||
|
* This is called either at the end of a successful authentication, or during
|
||||||
|
* pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
|
||||||
|
* calls us back.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
cleanup_user_oauth_flow(PGconn *conn)
|
cleanup_oauth_flow(PGconn *conn)
|
||||||
{
|
{
|
||||||
fe_oauth_state *state = conn->sasl_state;
|
fe_oauth_state *state = conn->sasl_state;
|
||||||
PGoauthBearerRequestV2 *request = state->async_ctx;
|
PGoauthBearerRequestV2 *request = state->async_ctx;
|
||||||
|
|
@ -786,12 +817,16 @@ cleanup_user_oauth_flow(PGconn *conn)
|
||||||
*
|
*
|
||||||
* There are three potential implementations of use_builtin_flow:
|
* There are three potential implementations of use_builtin_flow:
|
||||||
*
|
*
|
||||||
* 1) If the OAuth client is disabled at configuration time, return false.
|
* 1) If the OAuth client is disabled at configuration time, return zero.
|
||||||
* Dependent clients must provide their own flow.
|
* Dependent clients must provide their own flow.
|
||||||
* 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
|
* 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
|
||||||
* the libpq-oauth plugin and use its implementation.
|
* the libpq-oauth plugin and use its implementation.
|
||||||
* 3) Otherwise, use flow callbacks that are statically linked into the
|
* 3) Otherwise, use flow callbacks that are statically linked into the
|
||||||
* executable.
|
* executable.
|
||||||
|
*
|
||||||
|
* For caller convenience, the return value follows the convention of
|
||||||
|
* PQauthDataHook: zero means no implementation is provided, negative indicates
|
||||||
|
* failure, and positive indicates success.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if !defined(USE_LIBCURL)
|
#if !defined(USE_LIBCURL)
|
||||||
|
|
@ -800,10 +835,10 @@ cleanup_user_oauth_flow(PGconn *conn)
|
||||||
* This configuration doesn't support the builtin flow.
|
* This configuration doesn't support the builtin flow.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool
|
static int
|
||||||
use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
|
||||||
{
|
{
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(USE_DYNAMIC_OAUTH)
|
#elif defined(USE_DYNAMIC_OAUTH)
|
||||||
|
|
@ -814,36 +849,6 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
||||||
|
|
||||||
typedef char *(*libpq_gettext_func) (const char *msgid);
|
typedef char *(*libpq_gettext_func) (const char *msgid);
|
||||||
|
|
||||||
/*
|
|
||||||
* Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
|
|
||||||
* depend on the offsets within PGconn. (These have changed during minor version
|
|
||||||
* updates in the past.)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DEFINE_GETTER(TYPE, MEMBER) \
|
|
||||||
typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
|
|
||||||
static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
|
|
||||||
|
|
||||||
/* Like DEFINE_GETTER, but returns a pointer to the member. */
|
|
||||||
#define DEFINE_GETTER_P(TYPE, MEMBER) \
|
|
||||||
typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
|
|
||||||
static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
|
|
||||||
|
|
||||||
#define DEFINE_SETTER(TYPE, MEMBER) \
|
|
||||||
typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
|
|
||||||
static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
|
|
||||||
|
|
||||||
DEFINE_GETTER_P(PQExpBuffer, errorMessage);
|
|
||||||
DEFINE_GETTER(char *, oauth_client_id);
|
|
||||||
DEFINE_GETTER(char *, oauth_client_secret);
|
|
||||||
DEFINE_GETTER(char *, oauth_discovery_uri);
|
|
||||||
DEFINE_GETTER(char *, oauth_issuer_id);
|
|
||||||
DEFINE_GETTER(char *, oauth_scope);
|
|
||||||
DEFINE_GETTER(fe_oauth_state *, sasl_state);
|
|
||||||
|
|
||||||
DEFINE_SETTER(pgsocket, altsock);
|
|
||||||
DEFINE_SETTER(char *, oauth_token);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
|
* Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
|
||||||
* callbacks into the connection's async auth handlers.
|
* callbacks into the connection's async auth handlers.
|
||||||
|
|
@ -852,27 +857,19 @@ DEFINE_SETTER(char *, oauth_token);
|
||||||
* handle the use case where the build supports loading a flow but a user does
|
* handle the use case where the build supports loading a flow but a user does
|
||||||
* not want to install it. Troubleshooting of linker/loader failures can be done
|
* not want to install it. Troubleshooting of linker/loader failures can be done
|
||||||
* via PGOAUTHDEBUG.
|
* via PGOAUTHDEBUG.
|
||||||
|
*
|
||||||
|
* The lifetime of *request ends shortly after this call, so it must be copied
|
||||||
|
* to longer-lived storage.
|
||||||
*/
|
*/
|
||||||
bool
|
static int
|
||||||
use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
|
||||||
{
|
{
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
int lockerr;
|
int lockerr;
|
||||||
|
|
||||||
void (*init) (pgthreadlock_t threadlock,
|
void (*init) (libpq_gettext_func gettext_impl);
|
||||||
libpq_gettext_func gettext_impl,
|
int (*start_flow) (PGconn *conn, PGoauthBearerRequestV2 *request);
|
||||||
conn_errorMessage_func errmsg_impl,
|
|
||||||
conn_oauth_client_id_func clientid_impl,
|
|
||||||
conn_oauth_client_secret_func clientsecret_impl,
|
|
||||||
conn_oauth_discovery_uri_func discoveryuri_impl,
|
|
||||||
conn_oauth_issuer_id_func issuerid_impl,
|
|
||||||
conn_oauth_scope_func scope_impl,
|
|
||||||
conn_sasl_state_func saslstate_impl,
|
|
||||||
set_conn_altsock_func setaltsock_impl,
|
|
||||||
set_conn_oauth_token_func settoken_impl);
|
|
||||||
PostgresPollingStatusType (*flow) (PGconn *conn);
|
|
||||||
void (*cleanup) (PGconn *conn);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* On macOS only, load the module using its absolute install path; the
|
* On macOS only, load the module using its absolute install path; the
|
||||||
|
|
@ -885,9 +882,9 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
||||||
*/
|
*/
|
||||||
const char *const module_name =
|
const char *const module_name =
|
||||||
#if defined(__darwin__)
|
#if defined(__darwin__)
|
||||||
LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
|
LIBDIR "/libpq-oauth" DLSUFFIX;
|
||||||
#else
|
#else
|
||||||
"libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
|
"libpq-oauth" DLSUFFIX;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
|
state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
|
||||||
|
|
@ -903,22 +900,25 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
||||||
if (oauth_unsafe_debugging_enabled())
|
if (oauth_unsafe_debugging_enabled())
|
||||||
fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
|
fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
|
||||||
|
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
|
if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
|
||||||
|| (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
|
|| (start_flow = dlsym(state->builtin_flow, "pg_start_oauthbearer")) == NULL)
|
||||||
|| (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This is more of an error condition than the one above, but due to
|
* This is more of an error condition than the one above, but the
|
||||||
* the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
|
* cause is still locked behind PGOAUTHDEBUG due to the dlerror()
|
||||||
|
* threadsafety issue.
|
||||||
*/
|
*/
|
||||||
if (oauth_unsafe_debugging_enabled())
|
if (oauth_unsafe_debugging_enabled())
|
||||||
fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
|
fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
|
||||||
|
|
||||||
dlclose(state->builtin_flow);
|
dlclose(state->builtin_flow);
|
||||||
return false;
|
state->builtin_flow = NULL;
|
||||||
|
|
||||||
|
request->error = libpq_gettext("could not find entry point for libpq-oauth");
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -937,57 +937,45 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
||||||
/* Should not happen... but don't continue if it does. */
|
/* Should not happen... but don't continue if it does. */
|
||||||
Assert(false);
|
Assert(false);
|
||||||
|
|
||||||
libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
|
appendPQExpBuffer(&conn->errorMessage,
|
||||||
return false;
|
"use_builtin_flow: failed to lock mutex (%d)\n",
|
||||||
|
lockerr);
|
||||||
|
|
||||||
|
request->error = ""; /* satisfy report_flow_error() */
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
{
|
{
|
||||||
init(pg_g_threadlock,
|
init(
|
||||||
#ifdef ENABLE_NLS
|
#ifdef ENABLE_NLS
|
||||||
libpq_gettext,
|
libpq_gettext
|
||||||
#else
|
#else
|
||||||
NULL,
|
NULL
|
||||||
#endif
|
#endif
|
||||||
conn_errorMessage,
|
);
|
||||||
conn_oauth_client_id,
|
|
||||||
conn_oauth_client_secret,
|
|
||||||
conn_oauth_discovery_uri,
|
|
||||||
conn_oauth_issuer_id,
|
|
||||||
conn_oauth_scope,
|
|
||||||
conn_sasl_state,
|
|
||||||
set_conn_altsock,
|
|
||||||
set_conn_oauth_token);
|
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&init_mutex);
|
pthread_mutex_unlock(&init_mutex);
|
||||||
|
|
||||||
/* Set our asynchronous callbacks. */
|
return (start_flow(conn, request) == 0) ? 1 : -1;
|
||||||
conn->async_auth = flow;
|
|
||||||
conn->cleanup_async_auth = cleanup;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
|
* For static builds, we can just call pg_start_oauthbearer() directly. It's
|
||||||
|
* provided by libpq-oauth.a.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
|
extern int pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request);
|
||||||
extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
|
|
||||||
|
|
||||||
bool
|
static int
|
||||||
use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
|
||||||
{
|
{
|
||||||
/* Set our asynchronous callbacks. */
|
return (pg_start_oauthbearer(conn, request) == 0) ? 1 : -1;
|
||||||
conn->async_auth = pg_fe_run_oauth_flow;
|
|
||||||
conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* USE_LIBCURL */
|
#endif /* USE_LIBCURL */
|
||||||
|
|
@ -1000,11 +988,11 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
|
||||||
* If the application has registered a custom flow handler using
|
* If the application has registered a custom flow handler using
|
||||||
* PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
|
* PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
|
||||||
* (e.g. if it has one cached for immediate use), or set up for a series of
|
* (e.g. if it has one cached for immediate use), or set up for a series of
|
||||||
* asynchronous callbacks which will be managed by run_user_oauth_flow().
|
* asynchronous callbacks which will be managed by run_oauth_flow().
|
||||||
*
|
*
|
||||||
* If the default handler is used instead, a Device Authorization flow is used
|
* If the default handler is used instead, a Device Authorization flow is used
|
||||||
* for the connection if support has been compiled in. (See
|
* for the connection if support has been compiled in. (See oauth-curl.c for
|
||||||
* fe-auth-oauth-curl.c for implementation details.)
|
* implementation details.)
|
||||||
*
|
*
|
||||||
* If neither a custom handler nor the builtin flow is available, the connection
|
* If neither a custom handler nor the builtin flow is available, the connection
|
||||||
* fails here.
|
* fails here.
|
||||||
|
|
@ -1026,11 +1014,17 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The client may have overridden the OAuth flow. Try the v2 hook first,
|
* The client may have overridden the OAuth flow. Try the v2 hook first,
|
||||||
* then fall back to the v1 implementation.
|
* then fall back to the v1 implementation. If neither is available, try
|
||||||
|
* the builtin flow.
|
||||||
*/
|
*/
|
||||||
res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, conn, &request);
|
res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, conn, &request);
|
||||||
if (res == 0)
|
if (res == 0)
|
||||||
res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
|
res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
state->builtin = true;
|
||||||
|
res = use_builtin_flow(conn, state, &request);
|
||||||
|
}
|
||||||
|
|
||||||
if (res > 0)
|
if (res > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -1065,22 +1059,21 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
|
||||||
|
|
||||||
*request_copy = request;
|
*request_copy = request;
|
||||||
|
|
||||||
conn->async_auth = run_user_oauth_flow;
|
conn->async_auth = run_oauth_flow;
|
||||||
conn->cleanup_async_auth = cleanup_user_oauth_flow;
|
conn->cleanup_async_auth = cleanup_oauth_flow;
|
||||||
state->async_ctx = request_copy;
|
state->async_ctx = request_copy;
|
||||||
}
|
|
||||||
else if (res < 0)
|
return true;
|
||||||
{
|
|
||||||
report_user_flow_error(conn, &request);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
else if (!use_builtin_flow(conn, state))
|
|
||||||
{
|
|
||||||
libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
/*
|
||||||
|
* Failure cases: either we tried to set up a flow and failed, or there
|
||||||
|
* was no flow to try.
|
||||||
|
*/
|
||||||
|
if (res < 0)
|
||||||
|
report_flow_error(conn, &request);
|
||||||
|
else
|
||||||
|
libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
if (request.v1.cleanup)
|
if (request.v1.cleanup)
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,6 @@ enum fe_oauth_step
|
||||||
FE_OAUTH_SERVER_ERROR,
|
FE_OAUTH_SERVER_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* This struct is exported to the libpq-oauth module. If changes are needed
|
|
||||||
* during backports to stable branches, please keep ABI compatibility (no
|
|
||||||
* changes to existing members, add new members at the end, etc.).
|
|
||||||
*/
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
enum fe_oauth_step step;
|
enum fe_oauth_step step;
|
||||||
|
|
@ -39,12 +34,12 @@ typedef struct
|
||||||
PGconn *conn;
|
PGconn *conn;
|
||||||
void *async_ctx;
|
void *async_ctx;
|
||||||
|
|
||||||
|
bool builtin;
|
||||||
void *builtin_flow;
|
void *builtin_flow;
|
||||||
} fe_oauth_state;
|
} fe_oauth_state;
|
||||||
|
|
||||||
extern void pqClearOAuthToken(PGconn *conn);
|
extern void pqClearOAuthToken(PGconn *conn);
|
||||||
extern bool oauth_unsafe_debugging_enabled(void);
|
extern bool oauth_unsafe_debugging_enabled(void);
|
||||||
extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
|
|
||||||
|
|
||||||
/* Mechanisms in fe-auth-oauth.c */
|
/* Mechanisms in fe-auth-oauth.c */
|
||||||
extern const pg_fe_sasl_mech pg_oauth_mech;
|
extern const pg_fe_sasl_mech pg_oauth_mech;
|
||||||
|
|
|
||||||
|
|
@ -3589,13 +3589,6 @@ colormaprange
|
||||||
compare_context
|
compare_context
|
||||||
config_handle
|
config_handle
|
||||||
config_var_value
|
config_var_value
|
||||||
conn_errorMessage_func
|
|
||||||
conn_oauth_client_id_func
|
|
||||||
conn_oauth_client_secret_func
|
|
||||||
conn_oauth_discovery_uri_func
|
|
||||||
conn_oauth_issuer_id_func
|
|
||||||
conn_oauth_scope_func
|
|
||||||
conn_sasl_state_func
|
|
||||||
contain_aggs_of_level_context
|
contain_aggs_of_level_context
|
||||||
contain_placeholder_references_context
|
contain_placeholder_references_context
|
||||||
convert_testexpr_context
|
convert_testexpr_context
|
||||||
|
|
@ -4169,8 +4162,6 @@ security_class_t
|
||||||
sem_t
|
sem_t
|
||||||
sepgsql_context_info_t
|
sepgsql_context_info_t
|
||||||
sequence_magic
|
sequence_magic
|
||||||
set_conn_altsock_func
|
|
||||||
set_conn_oauth_token_func
|
|
||||||
set_join_pathlist_hook_type
|
set_join_pathlist_hook_type
|
||||||
set_rel_pathlist_hook_type
|
set_rel_pathlist_hook_type
|
||||||
shared_ts_iter
|
shared_ts_iter
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue