PostgreSQL Source Code git master
Loading...
Searching...
No Matches
fe-auth-oauth.c File Reference
#include "postgres_fe.h"
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
#include "common/oauth-common.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
#include "oauth-debug.h"
#include "pg_config_paths.h"
#include "utils/memdebug.h"
Include dependency graph for fe-auth-oauth.c:

Go to the source code of this file.

Data Structures

struct  json_ctx
 

Macros

#define kvsep   "\x01"
 
#define ERROR_STATUS_FIELD   "status"
 
#define ERROR_SCOPE_FIELD   "scope"
 
#define ERROR_OPENID_CONFIGURATION_FIELD   "openid-configuration"
 
#define MAX_SASL_NESTING_LEVEL   8
 
#define oauth_json_has_error(ctx)    (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
 
#define oauth_json_set_error(ctx, fmt, ...)
 
#define oauth_json_set_error_internal(ctx, ...)
 
#define HTTPS_SCHEME   "https://"
 
#define HTTP_SCHEME   "http://"
 
#define WK_PREFIX   "/.well-known/"
 
#define OPENID_WK_SUFFIX   "openid-configuration"
 
#define OAUTH_WK_SUFFIX   "oauth-authorization-server"
 
#define MASK_BITS   ((uintptr_t) 0x55aa55aa55aa55aa)
 
#define POISON_MASK(ptr)   ((void *) (((uintptr_t) ptr) ^ MASK_BITS))
 

Functions

static PostgresPollingStatusType do_async (fe_oauth_state *state, PGoauthBearerRequestV2 *request)
 
static void do_cleanup (fe_oauth_state *state, PGoauthBearerRequestV2 *request)
 
static void poison_req_v2 (PGoauthBearerRequestV2 *request, bool poison)
 
static voidoauth_init (PGconn *conn, const char *password, const char *sasl_mechanism)
 
static SASLStatus oauth_exchange (void *opaq, bool final, char *input, int inputlen, char **output, int *outputlen)
 
static bool oauth_channel_bound (void *opaq)
 
static void oauth_free (void *opaq)
 
static charclient_initial_response (PGconn *conn, bool discover)
 
static JsonParseErrorType oauth_json_object_start (void *state)
 
static JsonParseErrorType oauth_json_object_end (void *state)
 
static JsonParseErrorType oauth_json_object_field_start (void *state, char *name, bool isnull)
 
static JsonParseErrorType oauth_json_array_start (void *state)
 
static JsonParseErrorType oauth_json_array_end (void *state)
 
static JsonParseErrorType oauth_json_scalar (void *state, char *token, JsonTokenType type)
 
static charissuer_from_well_known_uri (PGconn *conn, const char *wkuri)
 
static bool handle_oauth_sasl_error (PGconn *conn, const char *msg, int msglen)
 
static void report_flow_error (PGconn *conn, const PGoauthBearerRequestV2 *request)
 
static PostgresPollingStatusType run_oauth_flow (PGconn *conn)
 
static void cleanup_oauth_flow (PGconn *conn)
 
static int use_builtin_flow (PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
 
static bool setup_token_request (PGconn *conn, fe_oauth_state *state)
 
static bool setup_oauth_parameters (PGconn *conn)
 
void pqClearOAuthToken (PGconn *conn)
 

Variables

const pg_fe_sasl_mech pg_oauth_mech
 

Macro Definition Documentation

◆ ERROR_OPENID_CONFIGURATION_FIELD

#define ERROR_OPENID_CONFIGURATION_FIELD   "openid-configuration"

Definition at line 165 of file fe-auth-oauth.c.

◆ ERROR_SCOPE_FIELD

#define ERROR_SCOPE_FIELD   "scope"

Definition at line 164 of file fe-auth-oauth.c.

◆ ERROR_STATUS_FIELD

#define ERROR_STATUS_FIELD   "status"

Definition at line 163 of file fe-auth-oauth.c.

◆ HTTP_SCHEME

#define HTTP_SCHEME   "http://"

Definition at line 361 of file fe-auth-oauth.c.

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

Definition at line 360 of file fe-auth-oauth.c.

◆ kvsep

#define kvsep   "\x01"

Definition at line 101 of file fe-auth-oauth.c.

◆ MASK_BITS

#define MASK_BITS   ((uintptr_t) 0x55aa55aa55aa55aa)

Definition at line 1455 of file fe-auth-oauth.c.

◆ MAX_SASL_NESTING_LEVEL

#define MAX_SASL_NESTING_LEVEL   8

Definition at line 173 of file fe-auth-oauth.c.

◆ oauth_json_has_error

#define oauth_json_has_error (   ctx)     (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)

Definition at line 190 of file fe-auth-oauth.c.

193 { \
194 appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
195 (ctx)->errmsg = (ctx)->errbuf.data; \
196 } while (0)
197
198/* An untranslated version of oauth_json_set_error(). */
199#define oauth_json_set_error_internal(ctx, ...) \
200 do { \
201 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
202 (ctx)->errmsg = (ctx)->errbuf.data; \
203 } while (0)
204
207{
208 struct json_ctx *ctx = state;
209
210 if (ctx->target_field)
211 {
212 Assert(ctx->nested == 1);
213
215 "field \"%s\" must be a string",
216 ctx->target_field_name);
217 }
218
219 ++ctx->nested;
221 oauth_json_set_error(ctx, "JSON is too deeply nested");
222
224}
225
228{
229 struct json_ctx *ctx = state;
230
231 --ctx->nested;
232 return JSON_SUCCESS;
233}
234
236oauth_json_object_field_start(void *state, char *name, bool isnull)
237{
238 struct json_ctx *ctx = state;
239
240 /* Only top-level keys are considered. */
241 if (ctx->nested == 1)
242 {
243 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
244 {
246 ctx->target_field = &ctx->status;
247 }
248 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
249 {
251 ctx->target_field = &ctx->scope;
252 }
254 {
256 ctx->target_field = &ctx->discovery_uri;
257 }
258 }
259
260 return JSON_SUCCESS;
261}
262
265{
266 struct json_ctx *ctx = state;
267
268 if (!ctx->nested)
269 {
270 oauth_json_set_error(ctx, "top-level element must be an object");
271 }
272 else if (ctx->target_field)
273 {
274 Assert(ctx->nested == 1);
275
277 "field \"%s\" must be a string",
278 ctx->target_field_name);
279 }
280
281 ++ctx->nested;
283 oauth_json_set_error(ctx, "JSON is too deeply nested");
284
286}
287
290{
291 struct json_ctx *ctx = state;
292
293 --ctx->nested;
294 return JSON_SUCCESS;
295}
296
299{
300 struct json_ctx *ctx = state;
301
302 if (!ctx->nested)
303 {
304 oauth_json_set_error(ctx, "top-level element must be an object");
306 }
307
308 if (ctx->target_field)
309 {
310 if (ctx->nested != 1)
311 {
312 /*
313 * ctx->target_field should not have been set for nested keys.
314 * Assert and don't continue any further for production builds.
315 */
316 Assert(false);
318 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
319 ctx->nested);
321 }
322
323 /*
324 * We don't allow duplicate field names; error out if the target has
325 * already been set.
326 */
327 if (*ctx->target_field)
328 {
330 "field \"%s\" is duplicated",
331 ctx->target_field_name);
333 }
334
335 /* The only fields we support are strings. */
336 if (type != JSON_TOKEN_STRING)
337 {
339 "field \"%s\" must be a string",
340 ctx->target_field_name);
342 }
343
344 *ctx->target_field = strdup(token);
345 if (!*ctx->target_field)
346 return JSON_OUT_OF_MEMORY;
347
348 ctx->target_field = NULL;
349 ctx->target_field_name = NULL;
350 }
351 else
352 {
353 /* otherwise we just ignore it */
354 }
355
356 return JSON_SUCCESS;
357}
358
359#define HTTPS_SCHEME "https://"
360#define HTTP_SCHEME "http://"
361
362/* We support both well-known suffixes defined by RFC 8414. */
363#define WK_PREFIX "/.well-known/"
364#define OPENID_WK_SUFFIX "openid-configuration"
365#define OAUTH_WK_SUFFIX "oauth-authorization-server"
366
367/*
368 * Derives an issuer identifier from one of our recognized .well-known URIs,
369 * using the rules in RFC 8414.
370 */
371static char *
373{
374 const char *authority_start = NULL;
375 const char *wk_start;
376 const char *wk_end;
377 char *issuer;
380 size_t end_len;
381
382 /*
383 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
384 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
385 * level (but issuer identifier comparison at the level above this is
386 * case-sensitive, so in practice it's probably moot).
387 */
390
391 if (!authority_start
394 {
395 /* Allow http:// for testing only. */
397 }
398
399 if (!authority_start)
400 {
402 "OAuth discovery URI \"%s\" must use HTTPS",
403 wkuri);
404 return NULL;
405 }
406
407 /*
408 * Well-known URIs in general may support queries and fragments, but the
409 * two types we support here do not. (They must be constructed from the
410 * components of issuer identifiers, which themselves may not contain any
411 * queries or fragments.)
412 *
413 * It's important to check this first, to avoid getting tricked later by a
414 * prefix buried inside a query or fragment.
415 */
416 if (strpbrk(authority_start, "?#") != NULL)
417 {
419 "OAuth discovery URI \"%s\" must not contain query or fragment components",
420 wkuri);
421 return NULL;
422 }
423
424 /*
425 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
426 * this must be at the beginning of the path component, but OIDC defined
427 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
428 * search for it anywhere.
429 */
431 if (!wk_start)
432 {
434 "OAuth discovery URI \"%s\" is not a .well-known URI",
435 wkuri);
436 return NULL;
437 }
438
439 /*
440 * Now find the suffix type. We only support the two defined in OIDC
441 * Discovery 1.0 and RFC 8414.
442 */
444
449 else
450 wk_end = NULL;
451
452 /*
453 * Even if there's a match, we still need to check to make sure the suffix
454 * takes up the entire path segment, to weed out constructions like
455 * "/.well-known/openid-configuration-bad".
456 */
457 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
458 {
460 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
461 wkuri);
462 return NULL;
463 }
464
465 /*
466 * Finally, make sure the .well-known components are provided either as a
467 * prefix (IETF style) or as a postfix (OIDC style). In other words,
468 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
469 * to claim association with "https://localhost/a/b".
470 */
471 if (*wk_end != '\0')
472 {
473 /*
474 * It's not at the end, so it's required to be at the beginning at the
475 * path. Find the starting slash.
476 */
477 const char *path_start;
478
480 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
481
482 if (wk_start != path_start)
483 {
485 "OAuth discovery URI \"%s\" uses an invalid format",
486 wkuri);
487 return NULL;
488 }
489 }
490
491 /* Checks passed! Now build the issuer. */
492 issuer = strdup(wkuri);
493 if (!issuer)
494 {
495 libpq_append_conn_error(conn, "out of memory");
496 return NULL;
497 }
498
499 /*
500 * The .well-known components are from [wk_start, wk_end). Remove those to
501 * form the issuer ID, by shifting the path suffix (which may be empty)
502 * leftwards.
503 */
506 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
507
508 memmove(issuer + start_offset, issuer + end_offset, end_len);
509
510 return issuer;
511}
512
513/*
514 * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
515 * stores any discovered openid_configuration and scope settings for the
516 * connection.
517 */
518static bool
519handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
520{
521 JsonLexContext *lex;
522 JsonSemAction sem = {0};
524 struct json_ctx ctx = {0};
525 char *errmsg = NULL;
526 bool success = false;
527
528 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
529
530 /* Sanity check. */
531 if (strlen(msg) != msglen)
532 {
534 "server's error message contained an embedded NULL, and was discarded");
535 return false;
536 }
537
538 /*
539 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
540 * that up front.
541 */
543 {
545 "server's error response is not valid UTF-8");
546 return false;
547 }
548
550 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
551
553 sem.semstate = &ctx;
554
561
562 err = pg_parse_json(lex, &sem);
563
565 {
567 errmsg = libpq_gettext("out of memory");
568 else if (ctx.errmsg)
569 errmsg = ctx.errmsg;
570 else
571 {
572 /*
573 * Developer error: one of the action callbacks didn't call
574 * oauth_json_set_error() before erroring out.
575 */
577 errmsg = "<unexpected empty error>";
578 }
579 }
580 else if (err != JSON_SUCCESS)
581 errmsg = json_errdetail(err, lex);
582
583 if (errmsg)
585 "failed to parse server's error response: %s",
586 errmsg);
587
588 /* Don't need the error buffer or the JSON lexer anymore. */
591
592 if (errmsg)
593 goto cleanup;
594
595 if (ctx.discovery_uri)
596 {
597 char *discovery_issuer;
598
599 /*
600 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
601 *
602 * Issuer comparison is done byte-wise, rather than performing any URL
603 * normalization; this follows the suggestions for issuer comparison
604 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
605 * vastly simplifies things. Since this is the key protection against
606 * a rogue server sending the client to an untrustworthy location,
607 * simpler is better.
608 */
610 if (!discovery_issuer)
611 goto cleanup; /* error message already set */
612
614 {
616 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
619
621 goto cleanup;
622 }
623
625
627 {
629 ctx.discovery_uri = NULL;
630 }
631 else
632 {
633 /* This must match the URI we'd previously determined. */
635 {
637 "server's discovery document has moved to %s (previous location was %s)",
638 ctx.discovery_uri,
640 goto cleanup;
641 }
642 }
643 }
644
645 if (ctx.scope)
646 {
647 /* Servers may not override a previously set oauth_scope. */
648 if (!conn->oauth_scope)
649 {
650 conn->oauth_scope = ctx.scope;
651 ctx.scope = NULL;
652 }
653 }
654
655 if (!ctx.status)
656 {
658 "server sent error response without a status");
659 goto cleanup;
660 }
661
662 if (strcmp(ctx.status, "invalid_token") != 0)
663 {
664 /*
665 * invalid_token is the only error code we'll automatically retry for;
666 * otherwise, just bail out now.
667 */
669 "server rejected OAuth bearer token: %s",
670 ctx.status);
671 goto cleanup;
672 }
673
674 success = true;
675
676cleanup:
677 free(ctx.status);
678 free(ctx.scope);
679 free(ctx.discovery_uri);
680
681 return success;
682}
683
684/*
685 * Helper for handling flow failures. If anything was put into request->error,
686 * it's added to conn->errorMessage here.
687 */
688static void
690{
692 const char *errmsg = request->error;
693
694 /*
695 * User-defined flows are called out explicitly so that the user knows who
696 * to blame. Builtin flows don't need that extra message length; we expect
697 * them to always fill in request->error on failure anyway.
698 */
699 if (state->builtin)
700 {
701 if (!errmsg)
702 {
703 /*
704 * Don't turn a bug here into a crash in production, but don't
705 * bother translating either.
706 */
707 Assert(false);
708 errmsg = "builtin flow failed but did not provide an error message";
709 }
710
712 }
713 else
714 {
716 libpq_gettext("user-defined OAuth flow failed"));
717 if (errmsg)
718 {
721 }
722 }
723
725}
726
727/*
728 * Callback implementation of conn->async_auth() for OAuth flows. Delegates the
729 * retrieval of the token to the PGoauthBearerRequestV2.async() callback.
730 *
731 * This will be called multiple times as needed; the callback is responsible for
732 * setting an altsock to signal and returning the correct PGRES_POLLING_*
733 * statuses for use by PQconnectPoll().
734 */
737{
741
742 if (!request->v1.async)
743 {
744 Assert(!state->builtin); /* be very noisy if our code does this */
746 "user-defined OAuth flow provided neither a token nor an async callback");
748 }
749
751
753 {
755 return status;
756 }
757 else if (status == PGRES_POLLING_OK)
758 {
759 /*
760 * We already have a token, so copy it into the conn. (We can't hold
761 * onto the original string, since it may not be safe for us to free()
762 * it.)
763 */
764 if (!request->v1.token)
765 {
766 Assert(!state->builtin);
768 "user-defined OAuth flow did not provide a token");
770 }
771
772 conn->oauth_token = strdup(request->v1.token);
773 if (!conn->oauth_token)
774 {
775 libpq_append_conn_error(conn, "out of memory");
777 }
778
779 return PGRES_POLLING_OK;
780 }
781
782 /* The hook wants the client to poll the altsock. Make sure it set one. */
784 {
785 Assert(!state->builtin);
787 "user-defined OAuth flow did not provide a socket for polling");
789 }
790
791 return status;
792}
793
794/*
795 * Cleanup callback for the async flow. Delegates most of its job to
796 * PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
797 * request itself.
798 *
799 * This is called either at the end of a successful authentication, or during
800 * pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
801 * calls us back.
802 */
803static void
805{
808
810
813
814 free(request);
815 state->async_ctx = NULL;
816}
817
818/*-------------
819 * Builtin Flow
820 *
821 * There are three potential implementations of use_builtin_flow:
822 *
823 * 1) If the OAuth client is disabled at configuration time, return zero.
824 * Dependent clients must provide their own flow.
825 * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
826 * the libpq-oauth plugin and use its implementation.
827 * 3) Otherwise, use flow callbacks that are statically linked into the
828 * executable.
829 *
830 * For caller convenience, the return value follows the convention of
831 * PQauthDataHook: zero means no implementation is provided, negative indicates
832 * failure, and positive indicates success.
833 */
834
835#if !defined(USE_LIBCURL)
836
837/*
838 * This configuration doesn't support the builtin flow.
839 */
840
841static int
843{
844 return 0;
845}
846
847#elif defined(USE_DYNAMIC_OAUTH)
848
849/*
850 * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
851 */
852
853typedef char *(*libpq_gettext_func) (const char *msgid);
854
855/*
856 * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
857 * callbacks into the connection's async auth handlers.
858 *
859 * Failure to load here results in a relatively quiet connection error, to
860 * handle the use case where the build supports loading a flow but a user does
861 * not want to install it. Troubleshooting of linker/loader failures can be done
862 * via PGOAUTHDEBUG.
863 *
864 * The lifetime of *request ends shortly after this call, so it must be copied
865 * to longer-lived storage.
866 */
867static int
869{
870 static bool initialized = false;
872 int lockerr;
873
876
877 /*
878 * On macOS only, load the module using its absolute install path; the
879 * standard search behavior is not very helpful for this use case. Unlike
880 * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
881 * absolute paths (modulo SIP effects), so tests can continue to work.
882 *
883 * On the other platforms, load the module using only the basename, to
884 * rely on the runtime linker's standard search behavior.
885 */
886 const char *const module_name =
887#if defined(__darwin__)
888 LIBDIR "/libpq-oauth" DLSUFFIX;
889#else
890 "libpq-oauth" DLSUFFIX;
891#endif
892
893 state->flow_module = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
894 if (!state->flow_module)
895 {
896 /*
897 * For end users, this probably isn't an error condition, it just
898 * means the flow isn't installed. Developers and package maintainers
899 * may want to debug this via the PGOAUTHDEBUG envvar, though.
900 *
901 * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
902 */
904 fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
905
906 return 0;
907 }
908
909 /*
910 * Our libpq-oauth.so provides a special initialization function for libpq
911 * integration. If we don't find this, assume that a custom module is in
912 * use instead.
913 */
914 init = dlsym(state->flow_module, "libpq_oauth_init");
915 if (!init)
916 state->builtin = false; /* adjust our error messages */
917
918 if ((start_flow = dlsym(state->flow_module, "pg_start_oauthbearer")) == NULL)
919 {
920 /*
921 * This is more of an error condition than the one above, but the
922 * cause is still locked behind PGOAUTHDEBUG due to the dlerror()
923 * threadsafety issue.
924 */
926 fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
927
928 dlclose(state->flow_module);
929 state->flow_module = NULL;
930
931 request->error = libpq_gettext("could not find entry point for libpq-oauth");
932 return -1;
933 }
934
935 /*
936 * Past this point, we do not unload the module. It stays in the process
937 * permanently.
938 */
939
940 if (init)
941 {
942 /*
943 * We need to inject necessary function pointers into the module. This
944 * only needs to be done once -- even if the pointers are constant,
945 * assigning them while another thread is executing the flows feels
946 * like tempting fate.
947 */
949 {
950 /* Should not happen... but don't continue if it does. */
951 Assert(false);
952
954 "use_builtin_flow: failed to lock mutex (%d)\n",
955 lockerr);
956
957 request->error = ""; /* satisfy report_flow_error() */
958 return -1;
959 }
960
961 if (!initialized)
962 {
963 init(
966#else
967 NULL
968#endif
969 );
970
971 initialized = true;
972 }
973
975 }
976
977 return (start_flow(conn, request) == 0) ? 1 : -1;
978}
979
980#else
981
982/*
983 * For static builds, we can just call pg_start_oauthbearer() directly. It's
984 * provided by libpq-oauth.a.
985 */
986
988
989static int
991{
992 return (pg_start_oauthbearer(conn, request) == 0) ? 1 : -1;
993}
994
995#endif /* USE_LIBCURL */
996
997
998/*
999 * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
1000 * token for presentation to the server.
1001 *
1002 * If the application has registered a custom flow handler using
1003 * PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
1004 * (e.g. if it has one cached for immediate use), or set up for a series of
1005 * asynchronous callbacks which will be managed by run_oauth_flow().
1006 *
1007 * If the default handler is used instead, a Device Authorization flow is used
1008 * for the connection if support has been compiled in. (See oauth-curl.c for
1009 * implementation details.)
1010 *
1011 * If neither a custom handler nor the builtin flow is available, the connection
1012 * fails here.
1013 */
1014static bool
1016{
1017 int res;
1019 .v1 = {
1021 .scope = conn->oauth_scope,
1022 },
1023 .issuer = conn->oauth_issuer_id,
1024 };
1025
1026 Assert(request.v1.openid_configuration);
1027 Assert(request.issuer);
1028
1029 /*
1030 * The client may have overridden the OAuth flow. Try the v2 hook first,
1031 * then fall back to the v1 implementation. If neither is available, try
1032 * the builtin flow.
1033 */
1035 if (res == 0)
1036 {
1037 poison_req_v2(&request, true);
1038
1040 state->v1 = (res != 0);
1041
1042 poison_req_v2(&request, false);
1043 }
1044 if (res == 0)
1045 {
1046 state->builtin = true;
1048 }
1049
1050 if (res > 0)
1051 {
1053
1054 if (request.v1.token)
1055 {
1056 /*
1057 * We already have a token, so copy it into the conn. (We can't
1058 * hold onto the original string, since it may not be safe for us
1059 * to free() it.)
1060 */
1061 conn->oauth_token = strdup(request.v1.token);
1062 if (!conn->oauth_token)
1063 {
1064 libpq_append_conn_error(conn, "out of memory");
1065 goto fail;
1066 }
1067
1068 /* short-circuit */
1070 return true;
1071 }
1072
1073 request_copy = malloc(sizeof(*request_copy));
1074 if (!request_copy)
1075 {
1076 libpq_append_conn_error(conn, "out of memory");
1077 goto fail;
1078 }
1079
1081
1084 state->async_ctx = request_copy;
1085
1086 return true;
1087 }
1088
1089 /*
1090 * Failure cases: either we tried to set up a flow and failed, or there
1091 * was no flow to try.
1092 */
1093 if (res < 0)
1095 else
1096 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1097
1098fail:
1100 return false;
1101}
1102
1103/*
1104 * Fill in our issuer identifier (and discovery URI, if possible) using the
1105 * connection parameters. If conn->oauth_discovery_uri can't be populated in
1106 * this function, it will be requested from the server.
1107 */
1108static bool
1110{
1111 /*
1112 * This is the only function that sets conn->oauth_issuer_id. If a
1113 * previous connection attempt has already computed it, don't overwrite it
1114 * or the discovery URI. (There's no reason for them to change once
1115 * they're set, and handle_oauth_sasl_error() will fail the connection if
1116 * the server attempts to switch them on us later.)
1117 */
1118 if (conn->oauth_issuer_id)
1119 return true;
1120
1121 /*---
1122 * To talk to a server, we require the user to provide issuer and client
1123 * identifiers.
1124 *
1125 * While it's possible for an OAuth client to support multiple issuers, it
1126 * requires additional effort to make sure the flows in use are safe -- to
1127 * quote RFC 9207,
1128 *
1129 * OAuth clients that interact with only one authorization server are
1130 * not vulnerable to mix-up attacks. However, when such clients decide
1131 * to add support for a second authorization server in the future, they
1132 * become vulnerable and need to apply countermeasures to mix-up
1133 * attacks.
1134 *
1135 * For now, we allow only one.
1136 */
1138 {
1140 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1141 return false;
1142 }
1143
1144 /*
1145 * oauth_issuer is interpreted differently if it's a well-known discovery
1146 * URI rather than just an issuer identifier.
1147 */
1149 {
1150 /*
1151 * Convert the URI back to an issuer identifier. (This also performs
1152 * validation of the URI format.)
1153 */
1156 if (!conn->oauth_issuer_id)
1157 return false; /* error message already set */
1158
1161 {
1162 libpq_append_conn_error(conn, "out of memory");
1163 return false;
1164 }
1165 }
1166 else
1167 {
1168 /*
1169 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1170 * for the discovery URI.
1171 */
1173 if (!conn->oauth_issuer_id)
1174 {
1175 libpq_append_conn_error(conn, "out of memory");
1176 return false;
1177 }
1178 }
1179
1180 return true;
1181}
1182
1183/*
1184 * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
1185 *
1186 * If the necessary OAuth parameters are set up on the connection, this will run
1187 * the client flow asynchronously and present the resulting token to the server.
1188 * Otherwise, an empty discovery response will be sent and any parameters sent
1189 * back by the server will be stored for a second attempt.
1190 *
1191 * For a full description of the API, see libpq/sasl.h.
1192 */
1193static SASLStatus
1194oauth_exchange(void *opaq, bool final,
1195 char *input, int inputlen,
1196 char **output, int *outputlen)
1197{
1199 PGconn *conn = state->conn;
1200 bool discover = false;
1201
1202 *output = NULL;
1203 *outputlen = 0;
1204
1205 switch (state->step)
1206 {
1207 case FE_OAUTH_INIT:
1208 /* We begin in the initial response phase. */
1209 Assert(inputlen == -1);
1210
1212 return SASL_FAILED;
1213
1214 if (conn->oauth_token)
1215 {
1216 /*
1217 * A previous connection already fetched the token; we'll use
1218 * it below.
1219 */
1220 }
1221 else if (conn->oauth_discovery_uri)
1222 {
1223 /*
1224 * We don't have a token, but we have a discovery URI already
1225 * stored. Decide whether we're using a user-provided OAuth
1226 * flow or the one we have built in.
1227 */
1229 return SASL_FAILED;
1230
1231 if (conn->oauth_token)
1232 {
1233 /*
1234 * A really smart user implementation may have already
1235 * given us the token (e.g. if there was an unexpired copy
1236 * already cached), and we can use it immediately.
1237 */
1238 }
1239 else
1240 {
1241 /*
1242 * Otherwise, we'll have to hand the connection over to
1243 * our OAuth implementation.
1244 *
1245 * This could take a while, since it generally involves a
1246 * user in the loop. To avoid consuming the server's
1247 * authentication timeout, we'll continue this handshake
1248 * to the end, so that the server can close its side of
1249 * the connection. We'll open a second connection later
1250 * once we've retrieved a token.
1251 */
1252 discover = true;
1253 }
1254 }
1255 else
1256 {
1257 /*
1258 * If we don't have a token, and we don't have a discovery URI
1259 * to be able to request a token, we ask the server for one
1260 * explicitly.
1261 */
1262 discover = true;
1263 }
1264
1265 /*
1266 * Generate an initial response. This either contains a token, if
1267 * we have one, or an empty discovery response which is doomed to
1268 * fail.
1269 */
1271 if (!*output)
1272 return SASL_FAILED;
1273
1274 *outputlen = strlen(*output);
1276
1277 if (conn->oauth_token)
1278 {
1279 /*
1280 * For the purposes of require_auth, our side of
1281 * authentication is done at this point; the server will
1282 * either accept the connection or send an error. Unlike
1283 * SCRAM, there is no additional server data to check upon
1284 * success.
1285 */
1286 conn->client_finished_auth = true;
1287 }
1288
1289 return SASL_CONTINUE;
1290
1292 if (final)
1293 {
1294 /*
1295 * OAUTHBEARER does not make use of additional data with a
1296 * successful SASL exchange, so we shouldn't get an
1297 * AuthenticationSASLFinal message.
1298 */
1300 "server sent unexpected additional OAuth data");
1301 return SASL_FAILED;
1302 }
1303
1304 /*
1305 * An error message was sent by the server. Respond with the
1306 * required dummy message (RFC 7628, sec. 3.2.3).
1307 */
1308 *output = strdup(kvsep);
1309 if (unlikely(!*output))
1310 {
1311 libpq_append_conn_error(conn, "out of memory");
1312 return SASL_FAILED;
1313 }
1314 *outputlen = strlen(*output); /* == 1 */
1315
1316 /* Grab the settings from discovery. */
1318 return SASL_FAILED;
1319
1320 if (conn->oauth_token)
1321 {
1322 /*
1323 * The server rejected our token. Continue onwards towards the
1324 * expected FATAL message, but mark our state to catch any
1325 * unexpected "success" from the server.
1326 */
1328 return SASL_CONTINUE;
1329 }
1330
1331 if (!conn->async_auth)
1332 {
1333 /*
1334 * No OAuth flow is set up yet. Did we get enough information
1335 * from the server to create one?
1336 */
1338 {
1340 "server requires OAuth authentication, but no discovery metadata was provided");
1341 return SASL_FAILED;
1342 }
1343
1344 /* Yes. Set up the flow now. */
1346 return SASL_FAILED;
1347
1348 if (conn->oauth_token)
1349 {
1350 /*
1351 * A token was available in a custom flow's cache. Skip
1352 * the asynchronous processing.
1353 */
1354 goto reconnect;
1355 }
1356 }
1357
1358 /*
1359 * Time to retrieve a token. This involves a number of HTTP
1360 * connections and timed waits, so we escape the synchronous auth
1361 * processing and tell PQconnectPoll to transfer control to our
1362 * async implementation.
1363 */
1364 Assert(conn->async_auth); /* should have been set already */
1366 return SASL_ASYNC;
1367
1369
1370 /*
1371 * We've returned successfully from token retrieval. Double-check
1372 * that we have what we need for the next connection.
1373 */
1374 if (!conn->oauth_token)
1375 {
1376 Assert(false); /* should have failed before this point! */
1378 "internal error: OAuth flow did not set a token");
1379 return SASL_FAILED;
1380 }
1381
1382 goto reconnect;
1383
1385
1386 /*
1387 * After an error, the server should send an error response to
1388 * fail the SASL handshake, which is handled in higher layers.
1389 *
1390 * If we get here, the server either sent *another* challenge
1391 * which isn't defined in the RFC, or completed the handshake
1392 * successfully after telling us it was going to fail. Neither is
1393 * acceptable.
1394 */
1396 "server sent additional OAuth data after error");
1397 return SASL_FAILED;
1398
1399 default:
1400 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1401 break;
1402 }
1403
1404 Assert(false); /* should never get here */
1405 return SASL_FAILED;
1406
1407reconnect:
1408
1409 /*
1410 * Despite being a failure from the point of view of SASL, we have enough
1411 * information to restart with a new connection.
1412 */
1413 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1414 conn->oauth_want_retry = true;
1415 return SASL_FAILED;
1416}
1417
1418static bool
1420{
1421 /* This mechanism does not support channel binding. */
1422 return false;
1423}
1424
1425/*
1426 * Fully clears out any stored OAuth token. This is done proactively upon
1427 * successful connection as well as during pqClosePGconn().
1428 */
1429void
1431{
1432 if (!conn->oauth_token)
1433 return;
1434
1438}
1439
1440/*
1441 * Hook v1 Poisoning
1442 *
1443 * Try to catch misuses of the v1 PQAUTHDATA_OAUTH_BEARER_TOKEN hook and its
1444 * callbacks, which are not allowed to downcast their request argument to
1445 * PGoauthBearerRequestV2. (Such clients may crash or worse when speaking to
1446 * libpq 18.)
1447 *
1448 * This attempts to use Valgrind hooks, if present, to mark the extra members as
1449 * inaccessible. For uninstrumented builds, it also munges request->issuer to
1450 * try to crash clients that perform string operations, and it aborts if
1451 * request->error is set.
1452 */
1453
1454#define MASK_BITS ((uintptr_t) 0x55aa55aa55aa55aa)
1455#define POISON_MASK(ptr) ((void *) (((uintptr_t) ptr) ^ MASK_BITS))
1456
1457/*
1458 * Workhorse for v2 request poisoning. This must be called exactly twice: once
1459 * to poison, once to unpoison.
1460 *
1461 * NB: Unpoisoning must restore the request to its original state, because we
1462 * might still switch back to a v2 implementation internally. Don't do anything
1463 * destructive during the poison operation.
1464 */
1465static void
1467{
1468#ifdef USE_VALGRIND
1469 void *const base = (char *) request + sizeof(request->v1);
1470 const size_t len = sizeof(*request) - sizeof(request->v1);
1471#endif
1472
1473 if (poison)
1474 {
1475 /* Poison request->issuer with a mask to help uninstrumented builds. */
1476 request->issuer = POISON_MASK(request->issuer);
1477
1478 /*
1479 * We'll check to make sure request->error wasn't assigned when
1480 * unpoisoning, so it had better not be assigned now.
1481 */
1482 Assert(!request->error);
1483
1485 }
1486 else
1487 {
1488 /*
1489 * XXX Using DEFINED here is technically too lax; we might catch
1490 * struct padding in the blast radius. But since this API has to
1491 * poison stack addresses, and Valgrind can't track/manage undefined
1492 * stack regions, we can't be any stricter without tracking the
1493 * original state of the memory.
1494 */
1496
1497 /* Undo our mask. */
1498 request->issuer = POISON_MASK(request->issuer);
1499
1500 /*
1501 * For uninstrumented builds, make sure request->error wasn't touched.
1502 */
1503 if (request->error)
1504 {
1506 "abort! out-of-bounds write to PGoauthBearerRequest by PQAUTHDATA_OAUTH_BEARER_TOKEN hook\n");
1507 abort();
1508 }
1509 }
1510}
1511
1512/*
1513 * Wrapper around PGoauthBearerRequest.async() which applies poison during the
1514 * callback when necessary.
1515 */
1518{
1520 PGconn *conn = state->conn;
1521
1522 Assert(request->v1.async);
1523
1524 if (state->v1)
1525 poison_req_v2(request, true);
1526
1527 ret = request->v1.async(conn,
1529 &conn->altsock);
1530
1531 if (state->v1)
1532 poison_req_v2(request, false);
1533
1534 return ret;
1535}
1536
1537/*
1538 * Similar wrapper for the optional PGoauthBearerRequest.cleanup() callback.
1539 * Does nothing if one is not defined.
1540 */
1541static void
1543{
1544 if (!request->v1.cleanup)
1545 return;
1546
1547 if (state->v1)
1548 poison_req_v2(request, true);
1549
1550 request->v1.cleanup(state->conn, (PGoauthBearerRequest *) request);
1551
1552 if (state->v1)
1553 poison_req_v2(request, false);
1554}
static void cleanup(void)
Definition bootstrap.c:886
#define Assert(condition)
Definition c.h:1002
#define unlikely(x)
Definition c.h:497
#define fprintf(file, fmt, msg)
Definition cubescan.l:21
void err(int eval, const char *fmt,...)
Definition err.c:43
#define HTTP_SCHEME
static PostgresPollingStatusType do_async(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
#define ERROR_SCOPE_FIELD
static bool setup_token_request(PGconn *conn, fe_oauth_state *state)
static JsonParseErrorType oauth_json_array_end(void *state)
static char * issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
#define HTTPS_SCHEME
static bool handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
#define oauth_json_set_error(ctx, fmt,...)
static bool setup_oauth_parameters(PGconn *conn)
static void poison_req_v2(PGoauthBearerRequestV2 *request, bool poison)
#define WK_PREFIX
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
#define OPENID_WK_SUFFIX
static SASLStatus oauth_exchange(void *opaq, bool final, char *input, int inputlen, char **output, int *outputlen)
#define OAUTH_WK_SUFFIX
static bool oauth_channel_bound(void *opaq)
static PostgresPollingStatusType run_oauth_flow(PGconn *conn)
static int use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
static void do_cleanup(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
static void report_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
#define oauth_json_has_error(ctx)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
#define ERROR_OPENID_CONFIGURATION_FIELD
static void cleanup_oauth_flow(PGconn *conn)
#define oauth_json_set_error_internal(ctx,...)
void pqClearOAuthToken(PGconn *conn)
#define kvsep
static char * client_initial_response(PGconn *conn, bool discover)
#define ERROR_STATUS_FIELD
#define MAX_SASL_NESTING_LEVEL
#define POISON_MASK(ptr)
static JsonParseErrorType oauth_json_object_start(void *state)
@ FE_OAUTH_REQUESTING_TOKEN
@ FE_OAUTH_SERVER_ERROR
@ FE_OAUTH_INIT
@ FE_OAUTH_BEARER_SENT
SASLStatus
@ SASL_ASYNC
@ SASL_CONTINUE
@ SASL_FAILED
PQauthDataHook_type PQauthDataHook
Definition fe-auth.c:1586
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition fe-misc.c:1548
FILE * input
FILE * output
static bool success
Definition initdb.c:188
JsonParseErrorType pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
Definition jsonapi.c:744
JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, bool need_escapes)
Definition jsonapi.c:392
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
Definition jsonapi.c:542
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
Definition jsonapi.c:2453
void freeJsonLexContext(JsonLexContext *lex)
Definition jsonapi.c:687
JsonParseErrorType
Definition jsonapi.h:35
@ JSON_OUT_OF_MEMORY
Definition jsonapi.h:52
@ JSON_SEM_ACTION_FAILED
Definition jsonapi.h:59
@ JSON_SUCCESS
Definition jsonapi.h:36
JsonTokenType
Definition jsonapi.h:18
@ JSON_TOKEN_STRING
Definition jsonapi.h:20
PostgresPollingStatusType
Definition libpq-fe.h:120
@ PGRES_POLLING_OK
Definition libpq-fe.h:124
@ PGRES_POLLING_FAILED
Definition libpq-fe.h:121
@ PQAUTHDATA_OAUTH_BEARER_TOKEN
Definition libpq-fe.h:202
@ PQAUTHDATA_OAUTH_BEARER_TOKEN_V2
Definition libpq-fe.h:204
#define PG_UTF8
Definition mbprint.c:43
#define VALGRIND_MAKE_MEM_DEFINED(addr, size)
Definition memdebug.h:26
#define VALGRIND_MAKE_MEM_NOACCESS(addr, size)
Definition memdebug.h:27
int pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request)
#define OAUTHDEBUG_UNSAFE_HTTP
Definition oauth-debug.h:38
static uint32 oauth_parse_debug_flags(void)
Definition oauth-debug.h:79
#define OAUTHDEBUG_PLUGIN_ERRORS
Definition oauth-debug.h:49
#define libpq_gettext(x)
Definition oauth-utils.h:44
char *(* libpq_gettext_func)(const char *msgid)
Definition oauth-utils.h:21
static char * errmsg
const void size_t len
void explicit_bzero(void *buf, size_t len)
#define PGINVALID_SOCKET
Definition port.h:31
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
void initPQExpBuffer(PQExpBuffer str)
Definition pqexpbuffer.c:90
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
void appendPQExpBufferChar(PQExpBuffer str, char ch)
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
void termPQExpBuffer(PQExpBuffer str)
#define PQExpBufferDataBroken(buf)
Definition pqexpbuffer.h:67
static int fb(int x)
int pthread_mutex_unlock(pthread_mutex_t *mp)
int pthread_mutex_lock(pthread_mutex_t *mp)
#define PTHREAD_MUTEX_INITIALIZER
#define free(a)
#define malloc(a)
#define init()
PGconn * conn
Definition streamutil.c:52
json_struct_action array_end
Definition jsonapi.h:157
json_struct_action object_start
Definition jsonapi.h:154
json_ofield_action object_field_start
Definition jsonapi.h:158
json_scalar_action scalar
Definition jsonapi.h:162
void * semstate
Definition jsonapi.h:153
json_struct_action array_start
Definition jsonapi.h:156
json_struct_action object_end
Definition jsonapi.h:155
PGoauthBearerRequest v1
Definition libpq-fe.h:833
const char * openid_configuration
Definition libpq-fe.h:774
char * discovery_uri
const char * target_field_name
char * status
char * scope
PQExpBufferData errbuf
char ** target_field
char * errmsg
char * oauth_discovery_uri
Definition libpq-int.h:443
char * oauth_scope
Definition libpq-int.h:447
void(* cleanup_async_auth)(PGconn *conn)
Definition libpq-int.h:532
bool client_finished_auth
Definition libpq-int.h:524
char * oauth_client_id
Definition libpq-int.h:445
char * oauth_issuer
Definition libpq-int.h:441
bool oauth_want_retry
Definition libpq-int.h:450
char * oauth_token
Definition libpq-int.h:448
char * oauth_issuer_id
Definition libpq-int.h:442
PQExpBufferData errorMessage
Definition libpq-int.h:686
pgsocket altsock
Definition libpq-int.h:533
PostgresPollingStatusType(* async_auth)(PGconn *conn)
Definition libpq-int.h:531
void * sasl_state
Definition libpq-int.h:615
static JsonSemAction sem
const char * type
const char * name
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition wchar.c:2002
void * dlopen(const char *file, int mode)
Definition win32dlopen.c:76
char * dlerror(void)
Definition win32dlopen.c:40
void * dlsym(void *handle, const char *symbol)
Definition win32dlopen.c:61
#define RTLD_NOW
Definition win32_port.h:530
int dlclose(void *handle)
Definition win32dlopen.c:49
static bool initialized
Definition win32ntdll.c:36

◆ oauth_json_set_error

#define oauth_json_set_error (   ctx,
  fmt,
  ... 
)
Value:
do { \
(ctx)->errmsg = (ctx)->errbuf.data; \
} while (0)

Definition at line 193 of file fe-auth-oauth.c.

194 { \
195 appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
196 (ctx)->errmsg = (ctx)->errbuf.data; \
197 } while (0)

◆ oauth_json_set_error_internal

#define oauth_json_set_error_internal (   ctx,
  ... 
)
Value:
do { \
appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
(ctx)->errmsg = (ctx)->errbuf.data; \
} while (0)

Definition at line 200 of file fe-auth-oauth.c.

201 { \
202 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
203 (ctx)->errmsg = (ctx)->errbuf.data; \
204 } while (0)

◆ OAUTH_WK_SUFFIX

#define OAUTH_WK_SUFFIX   "oauth-authorization-server"

Definition at line 366 of file fe-auth-oauth.c.

◆ OPENID_WK_SUFFIX

#define OPENID_WK_SUFFIX   "openid-configuration"

Definition at line 365 of file fe-auth-oauth.c.

◆ POISON_MASK

#define POISON_MASK (   ptr)    ((void *) (((uintptr_t) ptr) ^ MASK_BITS))

Definition at line 1456 of file fe-auth-oauth.c.

◆ WK_PREFIX

#define WK_PREFIX   "/.well-known/"

Definition at line 364 of file fe-auth-oauth.c.

Function Documentation

◆ cleanup_oauth_flow()

static void cleanup_oauth_flow ( PGconn conn)
static

Definition at line 805 of file fe-auth-oauth.c.

806{
809
811
814
815 free(request);
816 state->async_ctx = NULL;
817}

References pg_conn::altsock, Assert, conn, do_cleanup(), fb(), free, PGINVALID_SOCKET, and pg_conn::sasl_state.

Referenced by setup_token_request().

◆ client_initial_response()

static char * client_initial_response ( PGconn conn,
bool  discover 
)
static

Definition at line 113 of file fe-auth-oauth.c.

114{
115 static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
116
118 const char *authn_scheme;
119 char *response = NULL;
120 const char *token = conn->oauth_token;
121
122 if (discover)
123 {
124 /* Parameter discovery uses a completely empty auth value. */
125 authn_scheme = token = "";
126 }
127 else
128 {
129 /*
130 * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
131 * space is used as a separator.
132 */
133 authn_scheme = "Bearer ";
134
135 /* conn->token must have been set in this case. */
136 if (!token)
137 {
138 Assert(false);
140 "internal error: no OAuth token was set for the connection");
141 return NULL;
142 }
143 }
144
147
149 response = strdup(buf.data);
151
152 if (!response)
153 libpq_append_conn_error(conn, "out of memory");
154
155 return response;
156}
static char buf[DEFAULT_XLOG_SEG_SIZE]

References appendPQExpBuffer(), Assert, buf, conn, fb(), initPQExpBuffer(), kvsep, libpq_append_conn_error(), pg_conn::oauth_token, PQExpBufferDataBroken, and termPQExpBuffer().

Referenced by oauth_exchange().

◆ do_async()

static PostgresPollingStatusType do_async ( fe_oauth_state state,
PGoauthBearerRequestV2 request 
)
static

Definition at line 1518 of file fe-auth-oauth.c.

1519{
1521 PGconn *conn = state->conn;
1522
1523 Assert(request->v1.async);
1524
1525 if (state->v1)
1526 poison_req_v2(request, true);
1527
1528 ret = request->v1.async(conn,
1530 &conn->altsock);
1531
1532 if (state->v1)
1533 poison_req_v2(request, false);
1534
1535 return ret;
1536}

References pg_conn::altsock, Assert, conn, fb(), and poison_req_v2().

Referenced by run_oauth_flow().

◆ do_cleanup()

static void do_cleanup ( fe_oauth_state state,
PGoauthBearerRequestV2 request 
)
static

Definition at line 1543 of file fe-auth-oauth.c.

1544{
1545 if (!request->v1.cleanup)
1546 return;
1547
1548 if (state->v1)
1549 poison_req_v2(request, true);
1550
1551 request->v1.cleanup(state->conn, (PGoauthBearerRequest *) request);
1552
1553 if (state->v1)
1554 poison_req_v2(request, false);
1555}

References fb(), and poison_req_v2().

Referenced by cleanup_oauth_flow(), and setup_token_request().

◆ handle_oauth_sasl_error()

static bool handle_oauth_sasl_error ( PGconn conn,
const char msg,
int  msglen 
)
static

Definition at line 520 of file fe-auth-oauth.c.

521{
522 JsonLexContext *lex;
523 JsonSemAction sem = {0};
525 struct json_ctx ctx = {0};
526 char *errmsg = NULL;
527 bool success = false;
528
529 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
530
531 /* Sanity check. */
532 if (strlen(msg) != msglen)
533 {
535 "server's error message contained an embedded NULL, and was discarded");
536 return false;
537 }
538
539 /*
540 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
541 * that up front.
542 */
544 {
546 "server's error response is not valid UTF-8");
547 return false;
548 }
549
551 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
552
554 sem.semstate = &ctx;
555
562
563 err = pg_parse_json(lex, &sem);
564
566 {
568 errmsg = libpq_gettext("out of memory");
569 else if (ctx.errmsg)
570 errmsg = ctx.errmsg;
571 else
572 {
573 /*
574 * Developer error: one of the action callbacks didn't call
575 * oauth_json_set_error() before erroring out.
576 */
578 errmsg = "<unexpected empty error>";
579 }
580 }
581 else if (err != JSON_SUCCESS)
582 errmsg = json_errdetail(err, lex);
583
584 if (errmsg)
586 "failed to parse server's error response: %s",
587 errmsg);
588
589 /* Don't need the error buffer or the JSON lexer anymore. */
592
593 if (errmsg)
594 goto cleanup;
595
596 if (ctx.discovery_uri)
597 {
598 char *discovery_issuer;
599
600 /*
601 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
602 *
603 * Issuer comparison is done byte-wise, rather than performing any URL
604 * normalization; this follows the suggestions for issuer comparison
605 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
606 * vastly simplifies things. Since this is the key protection against
607 * a rogue server sending the client to an untrustworthy location,
608 * simpler is better.
609 */
611 if (!discovery_issuer)
612 goto cleanup; /* error message already set */
613
615 {
617 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
620
622 goto cleanup;
623 }
624
626
628 {
630 ctx.discovery_uri = NULL;
631 }
632 else
633 {
634 /* This must match the URI we'd previously determined. */
636 {
638 "server's discovery document has moved to %s (previous location was %s)",
639 ctx.discovery_uri,
641 goto cleanup;
642 }
643 }
644 }
645
646 if (ctx.scope)
647 {
648 /* Servers may not override a previously set oauth_scope. */
649 if (!conn->oauth_scope)
650 {
651 conn->oauth_scope = ctx.scope;
652 ctx.scope = NULL;
653 }
654 }
655
656 if (!ctx.status)
657 {
659 "server sent error response without a status");
660 goto cleanup;
661 }
662
663 if (strcmp(ctx.status, "invalid_token") != 0)
664 {
665 /*
666 * invalid_token is the only error code we'll automatically retry for;
667 * otherwise, just bail out now.
668 */
670 "server rejected OAuth bearer token: %s",
671 ctx.status);
672 goto cleanup;
673 }
674
675 success = true;
676
677cleanup:
678 free(ctx.status);
679 free(ctx.scope);
680 free(ctx.discovery_uri);
681
682 return success;
683}

References JsonSemAction::array_end, JsonSemAction::array_start, Assert, cleanup(), conn, json_ctx::discovery_uri, err(), json_ctx::errbuf, json_ctx::errmsg, errmsg, fb(), free, freeJsonLexContext(), initPQExpBuffer(), issuer_from_well_known_uri(), json_errdetail(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, libpq_append_conn_error(), libpq_gettext, makeJsonLexContextCstringLen(), pg_conn::oauth_discovery_uri, pg_conn::oauth_issuer_id, oauth_json_array_end(), oauth_json_array_start(), oauth_json_has_error, oauth_json_object_end(), oauth_json_object_field_start(), oauth_json_object_start(), oauth_json_scalar(), pg_conn::oauth_scope, JsonSemAction::object_end, JsonSemAction::object_field_start, JsonSemAction::object_start, pg_encoding_verifymbstr(), pg_parse_json(), PG_UTF8, PQExpBufferDataBroken, JsonSemAction::scalar, json_ctx::scope, sem, JsonSemAction::semstate, setJsonLexContextOwnsTokens(), json_ctx::status, success, and termPQExpBuffer().

Referenced by oauth_exchange().

◆ issuer_from_well_known_uri()

static char * issuer_from_well_known_uri ( PGconn conn,
const char wkuri 
)
static

Definition at line 373 of file fe-auth-oauth.c.

374{
375 const char *authority_start = NULL;
376 const char *wk_start;
377 const char *wk_end;
378 char *issuer;
381 size_t end_len;
382
383 /*
384 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
385 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
386 * level (but issuer identifier comparison at the level above this is
387 * case-sensitive, so in practice it's probably moot).
388 */
391
392 if (!authority_start
395 {
396 /* Allow http:// for testing only. */
398 }
399
400 if (!authority_start)
401 {
403 "OAuth discovery URI \"%s\" must use HTTPS",
404 wkuri);
405 return NULL;
406 }
407
408 /*
409 * Well-known URIs in general may support queries and fragments, but the
410 * two types we support here do not. (They must be constructed from the
411 * components of issuer identifiers, which themselves may not contain any
412 * queries or fragments.)
413 *
414 * It's important to check this first, to avoid getting tricked later by a
415 * prefix buried inside a query or fragment.
416 */
417 if (strpbrk(authority_start, "?#") != NULL)
418 {
420 "OAuth discovery URI \"%s\" must not contain query or fragment components",
421 wkuri);
422 return NULL;
423 }
424
425 /*
426 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
427 * this must be at the beginning of the path component, but OIDC defined
428 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
429 * search for it anywhere.
430 */
432 if (!wk_start)
433 {
435 "OAuth discovery URI \"%s\" is not a .well-known URI",
436 wkuri);
437 return NULL;
438 }
439
440 /*
441 * Now find the suffix type. We only support the two defined in OIDC
442 * Discovery 1.0 and RFC 8414.
443 */
445
450 else
451 wk_end = NULL;
452
453 /*
454 * Even if there's a match, we still need to check to make sure the suffix
455 * takes up the entire path segment, to weed out constructions like
456 * "/.well-known/openid-configuration-bad".
457 */
458 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
459 {
461 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
462 wkuri);
463 return NULL;
464 }
465
466 /*
467 * Finally, make sure the .well-known components are provided either as a
468 * prefix (IETF style) or as a postfix (OIDC style). In other words,
469 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
470 * to claim association with "https://localhost/a/b".
471 */
472 if (*wk_end != '\0')
473 {
474 /*
475 * It's not at the end, so it's required to be at the beginning at the
476 * path. Find the starting slash.
477 */
478 const char *path_start;
479
481 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
482
483 if (wk_start != path_start)
484 {
486 "OAuth discovery URI \"%s\" uses an invalid format",
487 wkuri);
488 return NULL;
489 }
490 }
491
492 /* Checks passed! Now build the issuer. */
493 issuer = strdup(wkuri);
494 if (!issuer)
495 {
496 libpq_append_conn_error(conn, "out of memory");
497 return NULL;
498 }
499
500 /*
501 * The .well-known components are from [wk_start, wk_end). Remove those to
502 * form the issuer ID, by shifting the path suffix (which may be empty)
503 * leftwards.
504 */
507 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
508
509 memmove(issuer + start_offset, issuer + end_offset, end_len);
510
511 return issuer;
512}

References Assert, conn, fb(), HTTP_SCHEME, HTTPS_SCHEME, libpq_append_conn_error(), oauth_parse_debug_flags(), OAUTH_WK_SUFFIX, OAUTHDEBUG_UNSAFE_HTTP, OPENID_WK_SUFFIX, pg_strncasecmp(), and WK_PREFIX.

Referenced by handle_oauth_sasl_error(), and setup_oauth_parameters().

◆ oauth_channel_bound()

static bool oauth_channel_bound ( void opaq)
static

Definition at line 1420 of file fe-auth-oauth.c.

1421{
1422 /* This mechanism does not support channel binding. */
1423 return false;
1424}

◆ oauth_exchange()

static SASLStatus oauth_exchange ( void opaq,
bool  final,
char input,
int  inputlen,
char **  output,
int outputlen 
)
static

Definition at line 1195 of file fe-auth-oauth.c.

1198{
1200 PGconn *conn = state->conn;
1201 bool discover = false;
1202
1203 *output = NULL;
1204 *outputlen = 0;
1205
1206 switch (state->step)
1207 {
1208 case FE_OAUTH_INIT:
1209 /* We begin in the initial response phase. */
1210 Assert(inputlen == -1);
1211
1213 return SASL_FAILED;
1214
1215 if (conn->oauth_token)
1216 {
1217 /*
1218 * A previous connection already fetched the token; we'll use
1219 * it below.
1220 */
1221 }
1222 else if (conn->oauth_discovery_uri)
1223 {
1224 /*
1225 * We don't have a token, but we have a discovery URI already
1226 * stored. Decide whether we're using a user-provided OAuth
1227 * flow or the one we have built in.
1228 */
1230 return SASL_FAILED;
1231
1232 if (conn->oauth_token)
1233 {
1234 /*
1235 * A really smart user implementation may have already
1236 * given us the token (e.g. if there was an unexpired copy
1237 * already cached), and we can use it immediately.
1238 */
1239 }
1240 else
1241 {
1242 /*
1243 * Otherwise, we'll have to hand the connection over to
1244 * our OAuth implementation.
1245 *
1246 * This could take a while, since it generally involves a
1247 * user in the loop. To avoid consuming the server's
1248 * authentication timeout, we'll continue this handshake
1249 * to the end, so that the server can close its side of
1250 * the connection. We'll open a second connection later
1251 * once we've retrieved a token.
1252 */
1253 discover = true;
1254 }
1255 }
1256 else
1257 {
1258 /*
1259 * If we don't have a token, and we don't have a discovery URI
1260 * to be able to request a token, we ask the server for one
1261 * explicitly.
1262 */
1263 discover = true;
1264 }
1265
1266 /*
1267 * Generate an initial response. This either contains a token, if
1268 * we have one, or an empty discovery response which is doomed to
1269 * fail.
1270 */
1272 if (!*output)
1273 return SASL_FAILED;
1274
1275 *outputlen = strlen(*output);
1277
1278 if (conn->oauth_token)
1279 {
1280 /*
1281 * For the purposes of require_auth, our side of
1282 * authentication is done at this point; the server will
1283 * either accept the connection or send an error. Unlike
1284 * SCRAM, there is no additional server data to check upon
1285 * success.
1286 */
1287 conn->client_finished_auth = true;
1288 }
1289
1290 return SASL_CONTINUE;
1291
1293 if (final)
1294 {
1295 /*
1296 * OAUTHBEARER does not make use of additional data with a
1297 * successful SASL exchange, so we shouldn't get an
1298 * AuthenticationSASLFinal message.
1299 */
1301 "server sent unexpected additional OAuth data");
1302 return SASL_FAILED;
1303 }
1304
1305 /*
1306 * An error message was sent by the server. Respond with the
1307 * required dummy message (RFC 7628, sec. 3.2.3).
1308 */
1309 *output = strdup(kvsep);
1310 if (unlikely(!*output))
1311 {
1312 libpq_append_conn_error(conn, "out of memory");
1313 return SASL_FAILED;
1314 }
1315 *outputlen = strlen(*output); /* == 1 */
1316
1317 /* Grab the settings from discovery. */
1319 return SASL_FAILED;
1320
1321 if (conn->oauth_token)
1322 {
1323 /*
1324 * The server rejected our token. Continue onwards towards the
1325 * expected FATAL message, but mark our state to catch any
1326 * unexpected "success" from the server.
1327 */
1329 return SASL_CONTINUE;
1330 }
1331
1332 if (!conn->async_auth)
1333 {
1334 /*
1335 * No OAuth flow is set up yet. Did we get enough information
1336 * from the server to create one?
1337 */
1339 {
1341 "server requires OAuth authentication, but no discovery metadata was provided");
1342 return SASL_FAILED;
1343 }
1344
1345 /* Yes. Set up the flow now. */
1347 return SASL_FAILED;
1348
1349 if (conn->oauth_token)
1350 {
1351 /*
1352 * A token was available in a custom flow's cache. Skip
1353 * the asynchronous processing.
1354 */
1355 goto reconnect;
1356 }
1357 }
1358
1359 /*
1360 * Time to retrieve a token. This involves a number of HTTP
1361 * connections and timed waits, so we escape the synchronous auth
1362 * processing and tell PQconnectPoll to transfer control to our
1363 * async implementation.
1364 */
1365 Assert(conn->async_auth); /* should have been set already */
1367 return SASL_ASYNC;
1368
1370
1371 /*
1372 * We've returned successfully from token retrieval. Double-check
1373 * that we have what we need for the next connection.
1374 */
1375 if (!conn->oauth_token)
1376 {
1377 Assert(false); /* should have failed before this point! */
1379 "internal error: OAuth flow did not set a token");
1380 return SASL_FAILED;
1381 }
1382
1383 goto reconnect;
1384
1386
1387 /*
1388 * After an error, the server should send an error response to
1389 * fail the SASL handshake, which is handled in higher layers.
1390 *
1391 * If we get here, the server either sent *another* challenge
1392 * which isn't defined in the RFC, or completed the handshake
1393 * successfully after telling us it was going to fail. Neither is
1394 * acceptable.
1395 */
1397 "server sent additional OAuth data after error");
1398 return SASL_FAILED;
1399
1400 default:
1401 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1402 break;
1403 }
1404
1405 Assert(false); /* should never get here */
1406 return SASL_FAILED;
1407
1408reconnect:
1409
1410 /*
1411 * Despite being a failure from the point of view of SASL, we have enough
1412 * information to restart with a new connection.
1413 */
1414 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1415 conn->oauth_want_retry = true;
1416 return SASL_FAILED;
1417}

References Assert, pg_conn::async_auth, pg_conn::client_finished_auth, client_initial_response(), conn, fb(), FE_OAUTH_BEARER_SENT, FE_OAUTH_INIT, FE_OAUTH_REQUESTING_TOKEN, FE_OAUTH_SERVER_ERROR, handle_oauth_sasl_error(), input, kvsep, libpq_append_conn_error(), pg_conn::oauth_discovery_uri, pg_conn::oauth_token, pg_conn::oauth_want_retry, output, SASL_ASYNC, SASL_CONTINUE, SASL_FAILED, setup_oauth_parameters(), setup_token_request(), and unlikely.

◆ oauth_free()

static void oauth_free ( void opaq)
static

Definition at line 91 of file fe-auth-oauth.c.

92{
94
95 /* Any async authentication state should have been cleaned up already. */
96 Assert(!state->async_ctx);
97
98 free(state);
99}

References Assert, fb(), and free.

◆ oauth_init()

static void * oauth_init ( PGconn conn,
const char password,
const char sasl_mechanism 
)
static

Definition at line 60 of file fe-auth-oauth.c.

62{
64
65 /*
66 * We only support one SASL mechanism here; anything else is programmer
67 * error.
68 */
69 Assert(sasl_mechanism != NULL);
70 Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
71
72 state = calloc(1, sizeof(*state));
73 if (!state)
74 return NULL;
75
76 state->step = FE_OAUTH_INIT;
77 state->conn = conn;
78
79 return state;
80}
#define OAUTHBEARER_NAME
#define calloc(a, b)

References Assert, calloc, conn, fb(), FE_OAUTH_INIT, and OAUTHBEARER_NAME.

◆ oauth_json_array_end()

static JsonParseErrorType oauth_json_array_end ( void state)
static

Definition at line 290 of file fe-auth-oauth.c.

291{
292 struct json_ctx *ctx = state;
293
294 --ctx->nested;
295 return JSON_SUCCESS;
296}

References JSON_SUCCESS, and json_ctx::nested.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_array_start()

static JsonParseErrorType oauth_json_array_start ( void state)
static

Definition at line 265 of file fe-auth-oauth.c.

266{
267 struct json_ctx *ctx = state;
268
269 if (!ctx->nested)
270 {
271 oauth_json_set_error(ctx, "top-level element must be an object");
272 }
273 else if (ctx->target_field)
274 {
275 Assert(ctx->nested == 1);
276
278 "field \"%s\" must be a string",
279 ctx->target_field_name);
280 }
281
282 ++ctx->nested;
284 oauth_json_set_error(ctx, "JSON is too deeply nested");
285
287}

References Assert, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, MAX_SASL_NESTING_LEVEL, json_ctx::nested, oauth_json_has_error, oauth_json_set_error, json_ctx::target_field, and json_ctx::target_field_name.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_end()

static JsonParseErrorType oauth_json_object_end ( void state)
static

Definition at line 228 of file fe-auth-oauth.c.

229{
230 struct json_ctx *ctx = state;
231
232 --ctx->nested;
233 return JSON_SUCCESS;
234}

References JSON_SUCCESS, and json_ctx::nested.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_field_start()

static JsonParseErrorType oauth_json_object_field_start ( void state,
char name,
bool  isnull 
)
static

Definition at line 237 of file fe-auth-oauth.c.

238{
239 struct json_ctx *ctx = state;
240
241 /* Only top-level keys are considered. */
242 if (ctx->nested == 1)
243 {
244 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
245 {
247 ctx->target_field = &ctx->status;
248 }
249 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
250 {
252 ctx->target_field = &ctx->scope;
253 }
255 {
257 ctx->target_field = &ctx->discovery_uri;
258 }
259 }
260
261 return JSON_SUCCESS;
262}

References json_ctx::discovery_uri, ERROR_OPENID_CONFIGURATION_FIELD, ERROR_SCOPE_FIELD, ERROR_STATUS_FIELD, fb(), JSON_SUCCESS, name, json_ctx::nested, json_ctx::scope, json_ctx::status, json_ctx::target_field, and json_ctx::target_field_name.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_start()

static JsonParseErrorType oauth_json_object_start ( void state)
static

Definition at line 207 of file fe-auth-oauth.c.

208{
209 struct json_ctx *ctx = state;
210
211 if (ctx->target_field)
212 {
213 Assert(ctx->nested == 1);
214
216 "field \"%s\" must be a string",
217 ctx->target_field_name);
218 }
219
220 ++ctx->nested;
222 oauth_json_set_error(ctx, "JSON is too deeply nested");
223
225}

References Assert, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, MAX_SASL_NESTING_LEVEL, json_ctx::nested, oauth_json_has_error, oauth_json_set_error, json_ctx::target_field, and json_ctx::target_field_name.

Referenced by handle_oauth_sasl_error().

◆ oauth_json_scalar()

static JsonParseErrorType oauth_json_scalar ( void state,
char token,
JsonTokenType  type 
)
static

Definition at line 299 of file fe-auth-oauth.c.

300{
301 struct json_ctx *ctx = state;
302
303 if (!ctx->nested)
304 {
305 oauth_json_set_error(ctx, "top-level element must be an object");
307 }
308
309 if (ctx->target_field)
310 {
311 if (ctx->nested != 1)
312 {
313 /*
314 * ctx->target_field should not have been set for nested keys.
315 * Assert and don't continue any further for production builds.
316 */
317 Assert(false);
319 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
320 ctx->nested);
322 }
323
324 /*
325 * We don't allow duplicate field names; error out if the target has
326 * already been set.
327 */
328 if (*ctx->target_field)
329 {
331 "field \"%s\" is duplicated",
332 ctx->target_field_name);
334 }
335
336 /* The only fields we support are strings. */
337 if (type != JSON_TOKEN_STRING)
338 {
340 "field \"%s\" must be a string",
341 ctx->target_field_name);
343 }
344
345 *ctx->target_field = strdup(token);
346 if (!*ctx->target_field)
347 return JSON_OUT_OF_MEMORY;
348
349 ctx->target_field = NULL;
350 ctx->target_field_name = NULL;
351 }
352 else
353 {
354 /* otherwise we just ignore it */
355 }
356
357 return JSON_SUCCESS;
358}

References Assert, fb(), JSON_OUT_OF_MEMORY, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, JSON_TOKEN_STRING, json_ctx::nested, oauth_json_set_error, oauth_json_set_error_internal, json_ctx::target_field, json_ctx::target_field_name, and type.

Referenced by handle_oauth_sasl_error().

◆ poison_req_v2()

static void poison_req_v2 ( PGoauthBearerRequestV2 request,
bool  poison 
)
static

Definition at line 1467 of file fe-auth-oauth.c.

1468{
1469#ifdef USE_VALGRIND
1470 void *const base = (char *) request + sizeof(request->v1);
1471 const size_t len = sizeof(*request) - sizeof(request->v1);
1472#endif
1473
1474 if (poison)
1475 {
1476 /* Poison request->issuer with a mask to help uninstrumented builds. */
1477 request->issuer = POISON_MASK(request->issuer);
1478
1479 /*
1480 * We'll check to make sure request->error wasn't assigned when
1481 * unpoisoning, so it had better not be assigned now.
1482 */
1483 Assert(!request->error);
1484
1486 }
1487 else
1488 {
1489 /*
1490 * XXX Using DEFINED here is technically too lax; we might catch
1491 * struct padding in the blast radius. But since this API has to
1492 * poison stack addresses, and Valgrind can't track/manage undefined
1493 * stack regions, we can't be any stricter without tracking the
1494 * original state of the memory.
1495 */
1497
1498 /* Undo our mask. */
1499 request->issuer = POISON_MASK(request->issuer);
1500
1501 /*
1502 * For uninstrumented builds, make sure request->error wasn't touched.
1503 */
1504 if (request->error)
1505 {
1507 "abort! out-of-bounds write to PGoauthBearerRequest by PQAUTHDATA_OAUTH_BEARER_TOKEN hook\n");
1508 abort();
1509 }
1510 }
1511}

References Assert, fb(), fprintf, len, POISON_MASK, VALGRIND_MAKE_MEM_DEFINED, and VALGRIND_MAKE_MEM_NOACCESS.

Referenced by do_async(), do_cleanup(), and setup_token_request().

◆ pqClearOAuthToken()

void pqClearOAuthToken ( PGconn conn)

Definition at line 1431 of file fe-auth-oauth.c.

1432{
1433 if (!conn->oauth_token)
1434 return;
1435
1439}

References conn, explicit_bzero(), fb(), free, and pg_conn::oauth_token.

Referenced by pqClosePGconn(), and PQconnectPoll().

◆ report_flow_error()

static void report_flow_error ( PGconn conn,
const PGoauthBearerRequestV2 request 
)
static

Definition at line 690 of file fe-auth-oauth.c.

691{
693 const char *errmsg = request->error;
694
695 /*
696 * User-defined flows are called out explicitly so that the user knows who
697 * to blame. Builtin flows don't need that extra message length; we expect
698 * them to always fill in request->error on failure anyway.
699 */
700 if (state->builtin)
701 {
702 if (!errmsg)
703 {
704 /*
705 * Don't turn a bug here into a crash in production, but don't
706 * bother translating either.
707 */
708 Assert(false);
709 errmsg = "builtin flow failed but did not provide an error message";
710 }
711
713 }
714 else
715 {
717 libpq_gettext("user-defined OAuth flow failed"));
718 if (errmsg)
719 {
722 }
723 }
724
726}

References appendPQExpBufferChar(), appendPQExpBufferStr(), Assert, conn, errmsg, pg_conn::errorMessage, fb(), libpq_gettext, and pg_conn::sasl_state.

Referenced by run_oauth_flow(), and setup_token_request().

◆ run_oauth_flow()

static PostgresPollingStatusType run_oauth_flow ( PGconn conn)
static

Definition at line 737 of file fe-auth-oauth.c.

738{
742
743 if (!request->v1.async)
744 {
745 Assert(!state->builtin); /* be very noisy if our code does this */
747 "user-defined OAuth flow provided neither a token nor an async callback");
749 }
750
751 status = do_async(state, request);
752
753 if (status == PGRES_POLLING_FAILED)
754 {
756 return status;
757 }
758 else if (status == PGRES_POLLING_OK)
759 {
760 /*
761 * We already have a token, so copy it into the conn. (We can't hold
762 * onto the original string, since it may not be safe for us to free()
763 * it.)
764 */
765 if (!request->v1.token)
766 {
767 Assert(!state->builtin);
769 "user-defined OAuth flow did not provide a token");
771 }
772
773 conn->oauth_token = strdup(request->v1.token);
774 if (!conn->oauth_token)
775 {
776 libpq_append_conn_error(conn, "out of memory");
778 }
779
780 return PGRES_POLLING_OK;
781 }
782
783 /* The hook wants the client to poll the altsock. Make sure it set one. */
785 {
786 Assert(!state->builtin);
788 "user-defined OAuth flow did not provide a socket for polling");
790 }
791
792 return status;
793}

References pg_conn::altsock, Assert, conn, do_async(), fb(), libpq_append_conn_error(), pg_conn::oauth_token, PGINVALID_SOCKET, PGRES_POLLING_FAILED, PGRES_POLLING_OK, report_flow_error(), pg_conn::sasl_state, and json_ctx::status.

Referenced by setup_token_request().

◆ setup_oauth_parameters()

static bool setup_oauth_parameters ( PGconn conn)
static

Definition at line 1110 of file fe-auth-oauth.c.

1111{
1112 /*
1113 * This is the only function that sets conn->oauth_issuer_id. If a
1114 * previous connection attempt has already computed it, don't overwrite it
1115 * or the discovery URI. (There's no reason for them to change once
1116 * they're set, and handle_oauth_sasl_error() will fail the connection if
1117 * the server attempts to switch them on us later.)
1118 */
1119 if (conn->oauth_issuer_id)
1120 return true;
1121
1122 /*---
1123 * To talk to a server, we require the user to provide issuer and client
1124 * identifiers.
1125 *
1126 * While it's possible for an OAuth client to support multiple issuers, it
1127 * requires additional effort to make sure the flows in use are safe -- to
1128 * quote RFC 9207,
1129 *
1130 * OAuth clients that interact with only one authorization server are
1131 * not vulnerable to mix-up attacks. However, when such clients decide
1132 * to add support for a second authorization server in the future, they
1133 * become vulnerable and need to apply countermeasures to mix-up
1134 * attacks.
1135 *
1136 * For now, we allow only one.
1137 */
1139 {
1141 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1142 return false;
1143 }
1144
1145 /*
1146 * oauth_issuer is interpreted differently if it's a well-known discovery
1147 * URI rather than just an issuer identifier.
1148 */
1150 {
1151 /*
1152 * Convert the URI back to an issuer identifier. (This also performs
1153 * validation of the URI format.)
1154 */
1157 if (!conn->oauth_issuer_id)
1158 return false; /* error message already set */
1159
1162 {
1163 libpq_append_conn_error(conn, "out of memory");
1164 return false;
1165 }
1166 }
1167 else
1168 {
1169 /*
1170 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1171 * for the discovery URI.
1172 */
1174 if (!conn->oauth_issuer_id)
1175 {
1176 libpq_append_conn_error(conn, "out of memory");
1177 return false;
1178 }
1179 }
1180
1181 return true;
1182}

References conn, fb(), issuer_from_well_known_uri(), libpq_append_conn_error(), pg_conn::oauth_client_id, pg_conn::oauth_discovery_uri, pg_conn::oauth_issuer, pg_conn::oauth_issuer_id, and WK_PREFIX.

Referenced by oauth_exchange().

◆ setup_token_request()

static bool setup_token_request ( PGconn conn,
fe_oauth_state state 
)
static

Definition at line 1016 of file fe-auth-oauth.c.

1017{
1018 int res;
1020 .v1 = {
1022 .scope = conn->oauth_scope,
1023 },
1024 .issuer = conn->oauth_issuer_id,
1025 };
1026
1027 Assert(request.v1.openid_configuration);
1028 Assert(request.issuer);
1029
1030 /*
1031 * The client may have overridden the OAuth flow. Try the v2 hook first,
1032 * then fall back to the v1 implementation. If neither is available, try
1033 * the builtin flow.
1034 */
1036 if (res == 0)
1037 {
1038 poison_req_v2(&request, true);
1039
1041 state->v1 = (res != 0);
1042
1043 poison_req_v2(&request, false);
1044 }
1045 if (res == 0)
1046 {
1047 state->builtin = true;
1049 }
1050
1051 if (res > 0)
1052 {
1054
1055 if (request.v1.token)
1056 {
1057 /*
1058 * We already have a token, so copy it into the conn. (We can't
1059 * hold onto the original string, since it may not be safe for us
1060 * to free() it.)
1061 */
1062 conn->oauth_token = strdup(request.v1.token);
1063 if (!conn->oauth_token)
1064 {
1065 libpq_append_conn_error(conn, "out of memory");
1066 goto fail;
1067 }
1068
1069 /* short-circuit */
1071 return true;
1072 }
1073
1074 request_copy = malloc(sizeof(*request_copy));
1075 if (!request_copy)
1076 {
1077 libpq_append_conn_error(conn, "out of memory");
1078 goto fail;
1079 }
1080
1082
1085 state->async_ctx = request_copy;
1086
1087 return true;
1088 }
1089
1090 /*
1091 * Failure cases: either we tried to set up a flow and failed, or there
1092 * was no flow to try.
1093 */
1094 if (res < 0)
1096 else
1097 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1098
1099fail:
1101 return false;
1102}

References Assert, pg_conn::async_auth, pg_conn::cleanup_async_auth, cleanup_oauth_flow(), conn, do_cleanup(), fb(), libpq_append_conn_error(), malloc, pg_conn::oauth_discovery_uri, pg_conn::oauth_issuer_id, pg_conn::oauth_scope, pg_conn::oauth_token, PGoauthBearerRequest::openid_configuration, poison_req_v2(), PQAUTHDATA_OAUTH_BEARER_TOKEN, PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, PQauthDataHook, report_flow_error(), run_oauth_flow(), use_builtin_flow(), and PGoauthBearerRequestV2::v1.

Referenced by oauth_exchange().

◆ use_builtin_flow()

static int use_builtin_flow ( PGconn conn,
fe_oauth_state state,
PGoauthBearerRequestV2 request 
)
static

Definition at line 843 of file fe-auth-oauth.c.

844{
845 return 0;
846}

Referenced by setup_token_request().

Variable Documentation

◆ pg_oauth_mech

const pg_fe_sasl_mech pg_oauth_mech
Initial value:
= {
}
static void oauth_free(void *opaq)
static void * oauth_init(PGconn *conn, const char *password, const char *sasl_mechanism)

Definition at line 47 of file fe-auth-oauth.c.

Referenced by pg_SASL_init(), pqConnectOptions2(), and PQconnectPoll().