PostgreSQL Source Code git master
Loading...
Searching...
No Matches
fe-auth-oauth.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * fe-auth-oauth.c
4 * The front-end (client) implementation of OAuth/OIDC authentication
5 * using the SASL OAUTHBEARER mechanism.
6 *
7 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
9 *
10 * IDENTIFICATION
11 * src/interfaces/libpq/fe-auth-oauth.c
12 *
13 *-------------------------------------------------------------------------
14 */
15
16#include "postgres_fe.h"
17
18#ifdef USE_DYNAMIC_OAUTH
19#include <dlfcn.h>
20#endif
21
22#include "common/base64.h"
23#include "common/hmac.h"
24#include "common/jsonapi.h"
25#include "common/oauth-common.h"
26#include "fe-auth.h"
27#include "fe-auth-oauth.h"
28#include "mb/pg_wchar.h"
29#include "pg_config_paths.h"
30
31/* The exported OAuth callback mechanism. */
32static void *oauth_init(PGconn *conn, const char *password,
33 const char *sasl_mechanism);
34static SASLStatus oauth_exchange(void *opaq, bool final,
35 char *input, int inputlen,
36 char **output, int *outputlen);
37static bool oauth_channel_bound(void *opaq);
38static void oauth_free(void *opaq);
39
46
47/*
48 * Initializes mechanism state for OAUTHBEARER.
49 *
50 * For a full description of the API, see libpq/fe-auth-sasl.h.
51 */
52static void *
54 const char *sasl_mechanism)
55{
57
58 /*
59 * We only support one SASL mechanism here; anything else is programmer
60 * error.
61 */
62 Assert(sasl_mechanism != NULL);
63 Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
64
65 state = calloc(1, sizeof(*state));
66 if (!state)
67 return NULL;
68
69 state->step = FE_OAUTH_INIT;
70 state->conn = conn;
71
72 return state;
73}
74
75/*
76 * Frees the state allocated by oauth_init().
77 *
78 * This handles only mechanism state tied to the connection lifetime; state
79 * stored in state->async_ctx is freed up either immediately after the
80 * authentication handshake succeeds, or before the mechanism is cleaned up on
81 * failure. See pg_fe_cleanup_oauth_flow() and cleanup_user_oauth_flow().
82 */
83static void
85{
87
88 /* Any async authentication state should have been cleaned up already. */
89 Assert(!state->async_ctx);
90
91 free(state);
92}
93
94#define kvsep "\x01"
95
96/*
97 * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
98 *
99 * If discover is true, the initial response will contain a request for the
100 * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
101 * be set; it will be sent as the connection's bearer token.
102 *
103 * Returns the response as a null-terminated string, or NULL on error.
104 */
105static char *
107{
108 static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
109
111 const char *authn_scheme;
112 char *response = NULL;
113 const char *token = conn->oauth_token;
114
115 if (discover)
116 {
117 /* Parameter discovery uses a completely empty auth value. */
118 authn_scheme = token = "";
119 }
120 else
121 {
122 /*
123 * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
124 * space is used as a separator.
125 */
126 authn_scheme = "Bearer ";
127
128 /* conn->token must have been set in this case. */
129 if (!token)
130 {
131 Assert(false);
133 "internal error: no OAuth token was set for the connection");
134 return NULL;
135 }
136 }
137
140
142 response = strdup(buf.data);
144
145 if (!response)
146 libpq_append_conn_error(conn, "out of memory");
147
148 return response;
149}
150
151/*
152 * JSON Parser (for the OAUTHBEARER error result)
153 */
154
155/* Relevant JSON fields in the error result object. */
156#define ERROR_STATUS_FIELD "status"
157#define ERROR_SCOPE_FIELD "scope"
158#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
159
160/*
161 * Limit the maximum number of nested objects/arrays. Because OAUTHBEARER
162 * doesn't have any defined extensions for its JSON yet, we can be much more
163 * conservative here than with libpq-oauth's MAX_OAUTH_NESTING_LEVEL; we expect
164 * a nesting level of 1 in practice.
165 */
166#define MAX_SASL_NESTING_LEVEL 8
167
169{
170 char *errmsg; /* any non-NULL value stops all processing */
171 PQExpBufferData errbuf; /* backing memory for errmsg */
172 int nested; /* nesting level (zero is the top) */
173
174 const char *target_field_name; /* points to a static allocation */
175 char **target_field; /* see below */
176
177 /* target_field, if set, points to one of the following: */
178 char *status;
179 char *scope;
181};
182
183#define oauth_json_has_error(ctx) \
184 (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
185
186#define oauth_json_set_error(ctx, fmt, ...) \
187 do { \
188 appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
189 (ctx)->errmsg = (ctx)->errbuf.data; \
190 } while (0)
191
192/* An untranslated version of oauth_json_set_error(). */
193#define oauth_json_set_error_internal(ctx, ...) \
194 do { \
195 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
196 (ctx)->errmsg = (ctx)->errbuf.data; \
197 } while (0)
198
201{
202 struct json_ctx *ctx = state;
203
204 if (ctx->target_field)
205 {
206 Assert(ctx->nested == 1);
207
209 "field \"%s\" must be a string",
210 ctx->target_field_name);
211 }
212
213 ++ctx->nested;
215 oauth_json_set_error(ctx, "JSON is too deeply nested");
216
218}
219
222{
223 struct json_ctx *ctx = state;
224
225 --ctx->nested;
226 return JSON_SUCCESS;
227}
228
230oauth_json_object_field_start(void *state, char *name, bool isnull)
231{
232 struct json_ctx *ctx = state;
233
234 /* Only top-level keys are considered. */
235 if (ctx->nested == 1)
236 {
237 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
238 {
240 ctx->target_field = &ctx->status;
241 }
242 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
243 {
245 ctx->target_field = &ctx->scope;
246 }
248 {
250 ctx->target_field = &ctx->discovery_uri;
251 }
252 }
253
254 return JSON_SUCCESS;
255}
256
259{
260 struct json_ctx *ctx = state;
261
262 if (!ctx->nested)
263 {
264 oauth_json_set_error(ctx, "top-level element must be an object");
265 }
266 else if (ctx->target_field)
267 {
268 Assert(ctx->nested == 1);
269
271 "field \"%s\" must be a string",
272 ctx->target_field_name);
273 }
274
275 ++ctx->nested;
277 oauth_json_set_error(ctx, "JSON is too deeply nested");
278
280}
281
284{
285 struct json_ctx *ctx = state;
286
287 --ctx->nested;
288 return JSON_SUCCESS;
289}
290
293{
294 struct json_ctx *ctx = state;
295
296 if (!ctx->nested)
297 {
298 oauth_json_set_error(ctx, "top-level element must be an object");
300 }
301
302 if (ctx->target_field)
303 {
304 if (ctx->nested != 1)
305 {
306 /*
307 * ctx->target_field should not have been set for nested keys.
308 * Assert and don't continue any further for production builds.
309 */
310 Assert(false);
312 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
313 ctx->nested);
315 }
316
317 /*
318 * We don't allow duplicate field names; error out if the target has
319 * already been set.
320 */
321 if (*ctx->target_field)
322 {
324 "field \"%s\" is duplicated",
325 ctx->target_field_name);
327 }
328
329 /* The only fields we support are strings. */
330 if (type != JSON_TOKEN_STRING)
331 {
333 "field \"%s\" must be a string",
334 ctx->target_field_name);
336 }
337
338 *ctx->target_field = strdup(token);
339 if (!*ctx->target_field)
340 return JSON_OUT_OF_MEMORY;
341
342 ctx->target_field = NULL;
343 ctx->target_field_name = NULL;
344 }
345 else
346 {
347 /* otherwise we just ignore it */
348 }
349
350 return JSON_SUCCESS;
351}
352
353#define HTTPS_SCHEME "https://"
354#define HTTP_SCHEME "http://"
355
356/* We support both well-known suffixes defined by RFC 8414. */
357#define WK_PREFIX "/.well-known/"
358#define OPENID_WK_SUFFIX "openid-configuration"
359#define OAUTH_WK_SUFFIX "oauth-authorization-server"
360
361/*
362 * Derives an issuer identifier from one of our recognized .well-known URIs,
363 * using the rules in RFC 8414.
364 */
365static char *
367{
368 const char *authority_start = NULL;
369 const char *wk_start;
370 const char *wk_end;
371 char *issuer;
374 size_t end_len;
375
376 /*
377 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
378 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
379 * level (but issuer identifier comparison at the level above this is
380 * case-sensitive, so in practice it's probably moot).
381 */
384
385 if (!authority_start
388 {
389 /* Allow http:// for testing only. */
391 }
392
393 if (!authority_start)
394 {
396 "OAuth discovery URI \"%s\" must use HTTPS",
397 wkuri);
398 return NULL;
399 }
400
401 /*
402 * Well-known URIs in general may support queries and fragments, but the
403 * two types we support here do not. (They must be constructed from the
404 * components of issuer identifiers, which themselves may not contain any
405 * queries or fragments.)
406 *
407 * It's important to check this first, to avoid getting tricked later by a
408 * prefix buried inside a query or fragment.
409 */
410 if (strpbrk(authority_start, "?#") != NULL)
411 {
413 "OAuth discovery URI \"%s\" must not contain query or fragment components",
414 wkuri);
415 return NULL;
416 }
417
418 /*
419 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
420 * this must be at the beginning of the path component, but OIDC defined
421 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
422 * search for it anywhere.
423 */
425 if (!wk_start)
426 {
428 "OAuth discovery URI \"%s\" is not a .well-known URI",
429 wkuri);
430 return NULL;
431 }
432
433 /*
434 * Now find the suffix type. We only support the two defined in OIDC
435 * Discovery 1.0 and RFC 8414.
436 */
438
443 else
444 wk_end = NULL;
445
446 /*
447 * Even if there's a match, we still need to check to make sure the suffix
448 * takes up the entire path segment, to weed out constructions like
449 * "/.well-known/openid-configuration-bad".
450 */
451 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
452 {
454 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
455 wkuri);
456 return NULL;
457 }
458
459 /*
460 * Finally, make sure the .well-known components are provided either as a
461 * prefix (IETF style) or as a postfix (OIDC style). In other words,
462 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
463 * to claim association with "https://localhost/a/b".
464 */
465 if (*wk_end != '\0')
466 {
467 /*
468 * It's not at the end, so it's required to be at the beginning at the
469 * path. Find the starting slash.
470 */
471 const char *path_start;
472
474 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
475
476 if (wk_start != path_start)
477 {
479 "OAuth discovery URI \"%s\" uses an invalid format",
480 wkuri);
481 return NULL;
482 }
483 }
484
485 /* Checks passed! Now build the issuer. */
486 issuer = strdup(wkuri);
487 if (!issuer)
488 {
489 libpq_append_conn_error(conn, "out of memory");
490 return NULL;
491 }
492
493 /*
494 * The .well-known components are from [wk_start, wk_end). Remove those to
495 * form the issuer ID, by shifting the path suffix (which may be empty)
496 * leftwards.
497 */
500 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
501
502 memmove(issuer + start_offset, issuer + end_offset, end_len);
503
504 return issuer;
505}
506
507/*
508 * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
509 * stores any discovered openid_configuration and scope settings for the
510 * connection.
511 */
512static bool
514{
515 JsonLexContext *lex;
516 JsonSemAction sem = {0};
518 struct json_ctx ctx = {0};
519 char *errmsg = NULL;
520 bool success = false;
521
522 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
523
524 /* Sanity check. */
525 if (strlen(msg) != msglen)
526 {
528 "server's error message contained an embedded NULL, and was discarded");
529 return false;
530 }
531
532 /*
533 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
534 * that up front.
535 */
537 {
539 "server's error response is not valid UTF-8");
540 return false;
541 }
542
544 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
545
547 sem.semstate = &ctx;
548
555
556 err = pg_parse_json(lex, &sem);
557
559 {
561 errmsg = libpq_gettext("out of memory");
562 else if (ctx.errmsg)
563 errmsg = ctx.errmsg;
564 else
565 {
566 /*
567 * Developer error: one of the action callbacks didn't call
568 * oauth_json_set_error() before erroring out.
569 */
571 errmsg = "<unexpected empty error>";
572 }
573 }
574 else if (err != JSON_SUCCESS)
575 errmsg = json_errdetail(err, lex);
576
577 if (errmsg)
579 "failed to parse server's error response: %s",
580 errmsg);
581
582 /* Don't need the error buffer or the JSON lexer anymore. */
585
586 if (errmsg)
587 goto cleanup;
588
589 if (ctx.discovery_uri)
590 {
591 char *discovery_issuer;
592
593 /*
594 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
595 *
596 * Issuer comparison is done byte-wise, rather than performing any URL
597 * normalization; this follows the suggestions for issuer comparison
598 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
599 * vastly simplifies things. Since this is the key protection against
600 * a rogue server sending the client to an untrustworthy location,
601 * simpler is better.
602 */
604 if (!discovery_issuer)
605 goto cleanup; /* error message already set */
606
608 {
610 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
613
615 goto cleanup;
616 }
617
619
621 {
623 ctx.discovery_uri = NULL;
624 }
625 else
626 {
627 /* This must match the URI we'd previously determined. */
629 {
631 "server's discovery document has moved to %s (previous location was %s)",
632 ctx.discovery_uri,
634 goto cleanup;
635 }
636 }
637 }
638
639 if (ctx.scope)
640 {
641 /* Servers may not override a previously set oauth_scope. */
642 if (!conn->oauth_scope)
643 {
644 conn->oauth_scope = ctx.scope;
645 ctx.scope = NULL;
646 }
647 }
648
649 if (!ctx.status)
650 {
652 "server sent error response without a status");
653 goto cleanup;
654 }
655
656 if (strcmp(ctx.status, "invalid_token") != 0)
657 {
658 /*
659 * invalid_token is the only error code we'll automatically retry for;
660 * otherwise, just bail out now.
661 */
663 "server rejected OAuth bearer token: %s",
664 ctx.status);
665 goto cleanup;
666 }
667
668 success = true;
669
670cleanup:
671 free(ctx.status);
672 free(ctx.scope);
673 free(ctx.discovery_uri);
674
675 return success;
676}
677
678/*
679 * Helper for handling flow failures. If anything was put into request->error,
680 * it's added to conn->errorMessage here.
681 */
682static void
684{
686 libpq_gettext("user-defined OAuth flow failed"));
687
688 if (request->error)
689 {
692 }
693
695}
696
697/*
698 * Callback implementation of conn->async_auth() for a user-defined OAuth flow.
699 * Delegates the retrieval of the token to the application's async callback.
700 *
701 * This will be called multiple times as needed; the application is responsible
702 * for setting an altsock to signal and returning the correct PGRES_POLLING_*
703 * statuses for use by PQconnectPoll().
704 */
707{
711
712 if (!request->v1.async)
713 {
715 "user-defined OAuth flow provided neither a token nor an async callback");
717 }
718
719 status = request->v1.async(conn,
721 &conn->altsock);
722
724 {
726 return status;
727 }
728 else if (status == PGRES_POLLING_OK)
729 {
730 /*
731 * We already have a token, so copy it into the conn. (We can't hold
732 * onto the original string, since it may not be safe for us to free()
733 * it.)
734 */
735 if (!request->v1.token)
736 {
738 "user-defined OAuth flow did not provide a token");
740 }
741
742 conn->oauth_token = strdup(request->v1.token);
743 if (!conn->oauth_token)
744 {
745 libpq_append_conn_error(conn, "out of memory");
747 }
748
749 return PGRES_POLLING_OK;
750 }
751
752 /* The hook wants the client to poll the altsock. Make sure it set one. */
754 {
756 "user-defined OAuth flow did not provide a socket for polling");
758 }
759
760 return status;
761}
762
763/*
764 * Cleanup callback for the async user flow. Delegates most of its job to
765 * PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
766 * request itself.
767 */
768static void
770{
773
775
776 if (request->v1.cleanup)
777 request->v1.cleanup(conn, (PGoauthBearerRequest *) request);
779
780 free(request);
781 state->async_ctx = NULL;
782}
783
784/*-------------
785 * Builtin Flow
786 *
787 * There are three potential implementations of use_builtin_flow:
788 *
789 * 1) If the OAuth client is disabled at configuration time, return false.
790 * Dependent clients must provide their own flow.
791 * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
792 * the libpq-oauth plugin and use its implementation.
793 * 3) Otherwise, use flow callbacks that are statically linked into the
794 * executable.
795 */
796
797#if !defined(USE_LIBCURL)
798
799/*
800 * This configuration doesn't support the builtin flow.
801 */
802
803bool
805{
806 return false;
807}
808
809#elif defined(USE_DYNAMIC_OAUTH)
810
811/*
812 * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
813 */
814
815typedef char *(*libpq_gettext_func) (const char *msgid);
816
817/*
818 * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
819 * depend on the offsets within PGconn. (These have changed during minor version
820 * updates in the past.)
821 */
822
823#define DEFINE_GETTER(TYPE, MEMBER) \
824 typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
825 static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
826
827/* Like DEFINE_GETTER, but returns a pointer to the member. */
828#define DEFINE_GETTER_P(TYPE, MEMBER) \
829 typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
830 static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
831
832#define DEFINE_SETTER(TYPE, MEMBER) \
833 typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
834 static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
835
836DEFINE_GETTER_P(PQExpBuffer, errorMessage);
837DEFINE_GETTER(char *, oauth_client_id);
838DEFINE_GETTER(char *, oauth_client_secret);
839DEFINE_GETTER(char *, oauth_discovery_uri);
840DEFINE_GETTER(char *, oauth_issuer_id);
841DEFINE_GETTER(char *, oauth_scope);
842DEFINE_GETTER(fe_oauth_state *, sasl_state);
843
844DEFINE_SETTER(pgsocket, altsock);
845DEFINE_SETTER(char *, oauth_token);
846
847/*
848 * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
849 * callbacks into the connection's async auth handlers.
850 *
851 * Failure to load here results in a relatively quiet connection error, to
852 * handle the use case where the build supports loading a flow but a user does
853 * not want to install it. Troubleshooting of linker/loader failures can be done
854 * via PGOAUTHDEBUG.
855 */
856bool
858{
859 static bool initialized = false;
861 int lockerr;
862
875 void (*cleanup) (PGconn *conn);
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-" PG_MAJORVERSION DLSUFFIX;
889#else
890 "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
891#endif
892
893 state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
894 if (!state->builtin_flow)
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 false;
907 }
908
909 if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
910 || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
911 || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
912 {
913 /*
914 * This is more of an error condition than the one above, but due to
915 * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
916 */
918 fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
919
920 dlclose(state->builtin_flow);
921 return false;
922 }
923
924 /*
925 * Past this point, we do not unload the module. It stays in the process
926 * permanently.
927 */
928
929 /*
930 * We need to inject necessary function pointers into the module. This
931 * only needs to be done once -- even if the pointers are constant,
932 * assigning them while another thread is executing the flows feels like
933 * tempting fate.
934 */
936 {
937 /* Should not happen... but don't continue if it does. */
938 Assert(false);
939
940 libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
941 return false;
942 }
943
944 if (!initialized)
945 {
949#else
950 NULL,
951#endif
961
962 initialized = true;
963 }
964
966
967 /* Set our asynchronous callbacks. */
970
971 return true;
972}
973
974#else
975
976/*
977 * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
978 */
979
982
983bool
985{
986 /* Set our asynchronous callbacks. */
989
990 return true;
991}
992
993#endif /* USE_LIBCURL */
994
995
996/*
997 * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
998 * token for presentation to the server.
999 *
1000 * If the application has registered a custom flow handler using
1001 * PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
1002 * (e.g. if it has one cached for immediate use), or set up for a series of
1003 * asynchronous callbacks which will be managed by run_user_oauth_flow().
1004 *
1005 * If the default handler is used instead, a Device Authorization flow is used
1006 * for the connection if support has been compiled in. (See
1007 * fe-auth-oauth-curl.c for implementation details.)
1008 *
1009 * If neither a custom handler nor the builtin flow is available, the connection
1010 * fails here.
1011 */
1012static bool
1014{
1015 int res;
1017 .v1 = {
1019 .scope = conn->oauth_scope,
1020 },
1021 .issuer = conn->oauth_issuer_id,
1022 };
1023
1024 Assert(request.v1.openid_configuration);
1025 Assert(request.issuer);
1026
1027 /*
1028 * The client may have overridden the OAuth flow. Try the v2 hook first,
1029 * then fall back to the v1 implementation.
1030 */
1032 if (res == 0)
1034
1035 if (res > 0)
1036 {
1038
1039 if (request.v1.token)
1040 {
1041 /*
1042 * We already have a token, so copy it into the conn. (We can't
1043 * hold onto the original string, since it may not be safe for us
1044 * to free() it.)
1045 */
1046 conn->oauth_token = strdup(request.v1.token);
1047 if (!conn->oauth_token)
1048 {
1049 libpq_append_conn_error(conn, "out of memory");
1050 goto fail;
1051 }
1052
1053 /* short-circuit */
1054 if (request.v1.cleanup)
1055 request.v1.cleanup(conn, (PGoauthBearerRequest *) &request);
1056 return true;
1057 }
1058
1059 request_copy = malloc(sizeof(*request_copy));
1060 if (!request_copy)
1061 {
1062 libpq_append_conn_error(conn, "out of memory");
1063 goto fail;
1064 }
1065
1067
1070 state->async_ctx = request_copy;
1071 }
1072 else if (res < 0)
1073 {
1075 goto fail;
1076 }
1077 else if (!use_builtin_flow(conn, state))
1078 {
1079 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1080 goto fail;
1081 }
1082
1083 return true;
1084
1085fail:
1086 if (request.v1.cleanup)
1087 request.v1.cleanup(conn, (PGoauthBearerRequest *) &request);
1088 return false;
1089}
1090
1091/*
1092 * Fill in our issuer identifier (and discovery URI, if possible) using the
1093 * connection parameters. If conn->oauth_discovery_uri can't be populated in
1094 * this function, it will be requested from the server.
1095 */
1096static bool
1098{
1099 /*
1100 * This is the only function that sets conn->oauth_issuer_id. If a
1101 * previous connection attempt has already computed it, don't overwrite it
1102 * or the discovery URI. (There's no reason for them to change once
1103 * they're set, and handle_oauth_sasl_error() will fail the connection if
1104 * the server attempts to switch them on us later.)
1105 */
1106 if (conn->oauth_issuer_id)
1107 return true;
1108
1109 /*---
1110 * To talk to a server, we require the user to provide issuer and client
1111 * identifiers.
1112 *
1113 * While it's possible for an OAuth client to support multiple issuers, it
1114 * requires additional effort to make sure the flows in use are safe -- to
1115 * quote RFC 9207,
1116 *
1117 * OAuth clients that interact with only one authorization server are
1118 * not vulnerable to mix-up attacks. However, when such clients decide
1119 * to add support for a second authorization server in the future, they
1120 * become vulnerable and need to apply countermeasures to mix-up
1121 * attacks.
1122 *
1123 * For now, we allow only one.
1124 */
1126 {
1128 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1129 return false;
1130 }
1131
1132 /*
1133 * oauth_issuer is interpreted differently if it's a well-known discovery
1134 * URI rather than just an issuer identifier.
1135 */
1137 {
1138 /*
1139 * Convert the URI back to an issuer identifier. (This also performs
1140 * validation of the URI format.)
1141 */
1144 if (!conn->oauth_issuer_id)
1145 return false; /* error message already set */
1146
1149 {
1150 libpq_append_conn_error(conn, "out of memory");
1151 return false;
1152 }
1153 }
1154 else
1155 {
1156 /*
1157 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1158 * for the discovery URI.
1159 */
1161 if (!conn->oauth_issuer_id)
1162 {
1163 libpq_append_conn_error(conn, "out of memory");
1164 return false;
1165 }
1166 }
1167
1168 return true;
1169}
1170
1171/*
1172 * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
1173 *
1174 * If the necessary OAuth parameters are set up on the connection, this will run
1175 * the client flow asynchronously and present the resulting token to the server.
1176 * Otherwise, an empty discovery response will be sent and any parameters sent
1177 * back by the server will be stored for a second attempt.
1178 *
1179 * For a full description of the API, see libpq/sasl.h.
1180 */
1181static SASLStatus
1182oauth_exchange(void *opaq, bool final,
1183 char *input, int inputlen,
1184 char **output, int *outputlen)
1185{
1187 PGconn *conn = state->conn;
1188 bool discover = false;
1189
1190 *output = NULL;
1191 *outputlen = 0;
1192
1193 switch (state->step)
1194 {
1195 case FE_OAUTH_INIT:
1196 /* We begin in the initial response phase. */
1197 Assert(inputlen == -1);
1198
1200 return SASL_FAILED;
1201
1202 if (conn->oauth_token)
1203 {
1204 /*
1205 * A previous connection already fetched the token; we'll use
1206 * it below.
1207 */
1208 }
1209 else if (conn->oauth_discovery_uri)
1210 {
1211 /*
1212 * We don't have a token, but we have a discovery URI already
1213 * stored. Decide whether we're using a user-provided OAuth
1214 * flow or the one we have built in.
1215 */
1217 return SASL_FAILED;
1218
1219 if (conn->oauth_token)
1220 {
1221 /*
1222 * A really smart user implementation may have already
1223 * given us the token (e.g. if there was an unexpired copy
1224 * already cached), and we can use it immediately.
1225 */
1226 }
1227 else
1228 {
1229 /*
1230 * Otherwise, we'll have to hand the connection over to
1231 * our OAuth implementation.
1232 *
1233 * This could take a while, since it generally involves a
1234 * user in the loop. To avoid consuming the server's
1235 * authentication timeout, we'll continue this handshake
1236 * to the end, so that the server can close its side of
1237 * the connection. We'll open a second connection later
1238 * once we've retrieved a token.
1239 */
1240 discover = true;
1241 }
1242 }
1243 else
1244 {
1245 /*
1246 * If we don't have a token, and we don't have a discovery URI
1247 * to be able to request a token, we ask the server for one
1248 * explicitly.
1249 */
1250 discover = true;
1251 }
1252
1253 /*
1254 * Generate an initial response. This either contains a token, if
1255 * we have one, or an empty discovery response which is doomed to
1256 * fail.
1257 */
1259 if (!*output)
1260 return SASL_FAILED;
1261
1262 *outputlen = strlen(*output);
1264
1265 if (conn->oauth_token)
1266 {
1267 /*
1268 * For the purposes of require_auth, our side of
1269 * authentication is done at this point; the server will
1270 * either accept the connection or send an error. Unlike
1271 * SCRAM, there is no additional server data to check upon
1272 * success.
1273 */
1274 conn->client_finished_auth = true;
1275 }
1276
1277 return SASL_CONTINUE;
1278
1280 if (final)
1281 {
1282 /*
1283 * OAUTHBEARER does not make use of additional data with a
1284 * successful SASL exchange, so we shouldn't get an
1285 * AuthenticationSASLFinal message.
1286 */
1288 "server sent unexpected additional OAuth data");
1289 return SASL_FAILED;
1290 }
1291
1292 /*
1293 * An error message was sent by the server. Respond with the
1294 * required dummy message (RFC 7628, sec. 3.2.3).
1295 */
1296 *output = strdup(kvsep);
1297 if (unlikely(!*output))
1298 {
1299 libpq_append_conn_error(conn, "out of memory");
1300 return SASL_FAILED;
1301 }
1302 *outputlen = strlen(*output); /* == 1 */
1303
1304 /* Grab the settings from discovery. */
1306 return SASL_FAILED;
1307
1308 if (conn->oauth_token)
1309 {
1310 /*
1311 * The server rejected our token. Continue onwards towards the
1312 * expected FATAL message, but mark our state to catch any
1313 * unexpected "success" from the server.
1314 */
1316 return SASL_CONTINUE;
1317 }
1318
1319 if (!conn->async_auth)
1320 {
1321 /*
1322 * No OAuth flow is set up yet. Did we get enough information
1323 * from the server to create one?
1324 */
1326 {
1328 "server requires OAuth authentication, but no discovery metadata was provided");
1329 return SASL_FAILED;
1330 }
1331
1332 /* Yes. Set up the flow now. */
1334 return SASL_FAILED;
1335
1336 if (conn->oauth_token)
1337 {
1338 /*
1339 * A token was available in a custom flow's cache. Skip
1340 * the asynchronous processing.
1341 */
1342 goto reconnect;
1343 }
1344 }
1345
1346 /*
1347 * Time to retrieve a token. This involves a number of HTTP
1348 * connections and timed waits, so we escape the synchronous auth
1349 * processing and tell PQconnectPoll to transfer control to our
1350 * async implementation.
1351 */
1352 Assert(conn->async_auth); /* should have been set already */
1354 return SASL_ASYNC;
1355
1357
1358 /*
1359 * We've returned successfully from token retrieval. Double-check
1360 * that we have what we need for the next connection.
1361 */
1362 if (!conn->oauth_token)
1363 {
1364 Assert(false); /* should have failed before this point! */
1366 "internal error: OAuth flow did not set a token");
1367 return SASL_FAILED;
1368 }
1369
1370 goto reconnect;
1371
1373
1374 /*
1375 * After an error, the server should send an error response to
1376 * fail the SASL handshake, which is handled in higher layers.
1377 *
1378 * If we get here, the server either sent *another* challenge
1379 * which isn't defined in the RFC, or completed the handshake
1380 * successfully after telling us it was going to fail. Neither is
1381 * acceptable.
1382 */
1384 "server sent additional OAuth data after error");
1385 return SASL_FAILED;
1386
1387 default:
1388 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1389 break;
1390 }
1391
1392 Assert(false); /* should never get here */
1393 return SASL_FAILED;
1394
1395reconnect:
1396
1397 /*
1398 * Despite being a failure from the point of view of SASL, we have enough
1399 * information to restart with a new connection.
1400 */
1401 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1402 conn->oauth_want_retry = true;
1403 return SASL_FAILED;
1404}
1405
1406static bool
1408{
1409 /* This mechanism does not support channel binding. */
1410 return false;
1411}
1412
1413/*
1414 * Fully clears out any stored OAuth token. This is done proactively upon
1415 * successful connection as well as during pqClosePGconn().
1416 */
1417void
1427
1428/*
1429 * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
1430 */
1431bool
1433{
1434 const char *env = getenv("PGOAUTHDEBUG");
1435
1436 return (env && strcmp(env, "UNSAFE") == 0);
1437}
static void cleanup(void)
Definition bootstrap.c:878
#define Assert(condition)
Definition c.h:906
#define unlikely(x)
Definition c.h:424
#define fprintf(file, fmt, msg)
Definition cubescan.l:21
void err(int eval, const char *fmt,...)
Definition err.c:43
#define HTTP_SCHEME
#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)
static void cleanup_user_oauth_flow(PGconn *conn)
static void report_user_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
#define oauth_json_set_error(ctx, fmt,...)
static bool setup_oauth_parameters(PGconn *conn)
#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)
const pg_fe_sasl_mech pg_oauth_mech
#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)
#define oauth_json_has_error(ctx)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
static void oauth_free(void *opaq)
#define ERROR_OPENID_CONFIGURATION_FIELD
#define oauth_json_set_error_internal(ctx,...)
void pqClearOAuthToken(PGconn *conn)
static void * oauth_init(PGconn *conn, const char *password, const char *sasl_mechanism)
#define kvsep
static char * client_initial_response(PGconn *conn, bool discover)
bool use_builtin_flow(PGconn *conn, fe_oauth_state *state)
#define ERROR_STATUS_FIELD
#define MAX_SASL_NESTING_LEVEL
static JsonParseErrorType oauth_json_object_start(void *state)
static PostgresPollingStatusType run_user_oauth_flow(PGconn *conn)
bool oauth_unsafe_debugging_enabled(void)
@ 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
FILE * input
FILE * output
static bool success
Definition initdb.c:187
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:2404
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
void(* pgthreadlock_t)(int acquire)
Definition libpq-fe.h:477
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 OAUTHBEARER_NAME
PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn)
void pg_fe_cleanup_oauth_flow(PGconn *conn)
Definition oauth-curl.c:354
#define conn_oauth_issuer_id(CONN)
Definition oauth-curl.c:57
#define conn_oauth_discovery_uri(CONN)
Definition oauth-curl.c:56
#define conn_oauth_scope(CONN)
Definition oauth-curl.c:58
#define conn_oauth_client_id(CONN)
Definition oauth-curl.c:54
#define set_conn_altsock(CONN, VAL)
Definition oauth-curl.c:61
#define conn_oauth_client_secret(CONN)
Definition oauth-curl.c:55
#define set_conn_oauth_token(CONN, VAL)
Definition oauth-curl.c:62
#define conn_sasl_state(CONN)
Definition oauth-curl.c:59
#define conn_errorMessage(CONN)
Definition oauth-curl.c:53
pgthreadlock_t pg_g_threadlock
Definition oauth-utils.c:35
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition oauth-utils.c:95
#define libpq_gettext(x)
Definition oauth-utils.h:86
char *(* libpq_gettext_func)(const char *msgid)
Definition oauth-utils.h:51
static char * errmsg
static char buf[DEFAULT_XLOG_SEG_SIZE]
void explicit_bzero(void *buf, size_t len)
int pgsocket
Definition port.h:29
#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 calloc(a, b)
#define free(a)
#define malloc(a)
#define init()
static char * password
Definition streamutil.c:51
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:826
const char * openid_configuration
Definition libpq-fe.h:767
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:441
char * oauth_scope
Definition libpq-int.h:445
void(* cleanup_async_auth)(PGconn *conn)
Definition libpq-int.h:529
bool client_finished_auth
Definition libpq-int.h:521
char * oauth_client_id
Definition libpq-int.h:443
char * oauth_issuer
Definition libpq-int.h:439
bool oauth_want_retry
Definition libpq-int.h:447
char * oauth_token
Definition libpq-int.h:446
char * oauth_issuer_id
Definition libpq-int.h:440
PQExpBufferData errorMessage
Definition libpq-int.h:683
pgsocket altsock
Definition libpq-int.h:530
PostgresPollingStatusType(* async_auth)(PGconn *conn)
Definition libpq-int.h:528
void * sasl_state
Definition libpq-int.h:612
static JsonSemAction sem
const char * type
const char * name
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition wchar.c:2224
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