PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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-2025, 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#include "common/base64.h"
19#include "common/hmac.h"
20#include "common/jsonapi.h"
21#include "common/oauth-common.h"
22#include "fe-auth.h"
23#include "fe-auth-oauth.h"
24#include "mb/pg_wchar.h"
25
26/* The exported OAuth callback mechanism. */
27static void *oauth_init(PGconn *conn, const char *password,
28 const char *sasl_mechanism);
29static SASLStatus oauth_exchange(void *opaq, bool final,
30 char *input, int inputlen,
31 char **output, int *outputlen);
32static bool oauth_channel_bound(void *opaq);
33static void oauth_free(void *opaq);
34
40};
41
42/*
43 * Initializes mechanism state for OAUTHBEARER.
44 *
45 * For a full description of the API, see libpq/fe-auth-sasl.h.
46 */
47static void *
49 const char *sasl_mechanism)
50{
52
53 /*
54 * We only support one SASL mechanism here; anything else is programmer
55 * error.
56 */
57 Assert(sasl_mechanism != NULL);
58 Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
59
60 state = calloc(1, sizeof(*state));
61 if (!state)
62 return NULL;
63
64 state->step = FE_OAUTH_INIT;
65 state->conn = conn;
66
67 return state;
68}
69
70/*
71 * Frees the state allocated by oauth_init().
72 *
73 * This handles only mechanism state tied to the connection lifetime; state
74 * stored in state->async_ctx is freed up either immediately after the
75 * authentication handshake succeeds, or before the mechanism is cleaned up on
76 * failure. See pg_fe_cleanup_oauth_flow() and cleanup_user_oauth_flow().
77 */
78static void
79oauth_free(void *opaq)
80{
81 fe_oauth_state *state = opaq;
82
83 /* Any async authentication state should have been cleaned up already. */
84 Assert(!state->async_ctx);
85
86 free(state);
87}
88
89#define kvsep "\x01"
90
91/*
92 * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
93 *
94 * If discover is true, the initial response will contain a request for the
95 * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
96 * be set; it will be sent as the connection's bearer token.
97 *
98 * Returns the response as a null-terminated string, or NULL on error.
99 */
100static char *
102{
103 static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
104
106 const char *authn_scheme;
107 char *response = NULL;
108 const char *token = conn->oauth_token;
109
110 if (discover)
111 {
112 /* Parameter discovery uses a completely empty auth value. */
113 authn_scheme = token = "";
114 }
115 else
116 {
117 /*
118 * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
119 * space is used as a separator.
120 */
121 authn_scheme = "Bearer ";
122
123 /* conn->token must have been set in this case. */
124 if (!token)
125 {
126 Assert(false);
128 "internal error: no OAuth token was set for the connection");
129 return NULL;
130 }
131 }
132
134 appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
135
137 response = strdup(buf.data);
139
140 if (!response)
141 libpq_append_conn_error(conn, "out of memory");
142
143 return response;
144}
145
146/*
147 * JSON Parser (for the OAUTHBEARER error result)
148 */
149
150/* Relevant JSON fields in the error result object. */
151#define ERROR_STATUS_FIELD "status"
152#define ERROR_SCOPE_FIELD "scope"
153#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
154
156{
157 char *errmsg; /* any non-NULL value stops all processing */
158 PQExpBufferData errbuf; /* backing memory for errmsg */
159 int nested; /* nesting level (zero is the top) */
160
161 const char *target_field_name; /* points to a static allocation */
162 char **target_field; /* see below */
163
164 /* target_field, if set, points to one of the following: */
165 char *status;
166 char *scope;
168};
169
170#define oauth_json_has_error(ctx) \
171 (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
172
173#define oauth_json_set_error(ctx, ...) \
174 do { \
175 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
176 (ctx)->errmsg = (ctx)->errbuf.data; \
177 } while (0)
178
181{
182 struct json_ctx *ctx = state;
183
184 if (ctx->target_field)
185 {
186 Assert(ctx->nested == 1);
187
189 libpq_gettext("field \"%s\" must be a string"),
190 ctx->target_field_name);
191 }
192
193 ++ctx->nested;
195}
196
199{
200 struct json_ctx *ctx = state;
201
202 --ctx->nested;
203 return JSON_SUCCESS;
204}
205
207oauth_json_object_field_start(void *state, char *name, bool isnull)
208{
209 struct json_ctx *ctx = state;
210
211 /* Only top-level keys are considered. */
212 if (ctx->nested == 1)
213 {
214 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
215 {
217 ctx->target_field = &ctx->status;
218 }
219 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
220 {
222 ctx->target_field = &ctx->scope;
223 }
224 else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
225 {
227 ctx->target_field = &ctx->discovery_uri;
228 }
229 }
230
231 return JSON_SUCCESS;
232}
233
236{
237 struct json_ctx *ctx = state;
238
239 if (!ctx->nested)
240 {
241 ctx->errmsg = libpq_gettext("top-level element must be an object");
242 }
243 else if (ctx->target_field)
244 {
245 Assert(ctx->nested == 1);
246
248 libpq_gettext("field \"%s\" must be a string"),
249 ctx->target_field_name);
250 }
251
253}
254
257{
258 struct json_ctx *ctx = state;
259
260 if (!ctx->nested)
261 {
262 ctx->errmsg = libpq_gettext("top-level element must be an object");
264 }
265
266 if (ctx->target_field)
267 {
268 if (ctx->nested != 1)
269 {
270 /*
271 * ctx->target_field should not have been set for nested keys.
272 * Assert and don't continue any further for production builds.
273 */
274 Assert(false);
276 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
277 ctx->nested);
279 }
280
281 /*
282 * We don't allow duplicate field names; error out if the target has
283 * already been set.
284 */
285 if (*ctx->target_field)
286 {
288 libpq_gettext("field \"%s\" is duplicated"),
289 ctx->target_field_name);
291 }
292
293 /* The only fields we support are strings. */
294 if (type != JSON_TOKEN_STRING)
295 {
297 libpq_gettext("field \"%s\" must be a string"),
298 ctx->target_field_name);
300 }
301
302 *ctx->target_field = strdup(token);
303 if (!*ctx->target_field)
304 return JSON_OUT_OF_MEMORY;
305
306 ctx->target_field = NULL;
307 ctx->target_field_name = NULL;
308 }
309 else
310 {
311 /* otherwise we just ignore it */
312 }
313
314 return JSON_SUCCESS;
315}
316
317#define HTTPS_SCHEME "https://"
318#define HTTP_SCHEME "http://"
319
320/* We support both well-known suffixes defined by RFC 8414. */
321#define WK_PREFIX "/.well-known/"
322#define OPENID_WK_SUFFIX "openid-configuration"
323#define OAUTH_WK_SUFFIX "oauth-authorization-server"
324
325/*
326 * Derives an issuer identifier from one of our recognized .well-known URIs,
327 * using the rules in RFC 8414.
328 */
329static char *
331{
332 const char *authority_start = NULL;
333 const char *wk_start;
334 const char *wk_end;
335 char *issuer;
336 ptrdiff_t start_offset,
337 end_offset;
338 size_t end_len;
339
340 /*
341 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
342 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
343 * level (but issuer identifier comparison at the level above this is
344 * case-sensitive, so in practice it's probably moot).
345 */
346 if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
347 authority_start = wkuri + strlen(HTTPS_SCHEME);
348
349 if (!authority_start
351 && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
352 {
353 /* Allow http:// for testing only. */
354 authority_start = wkuri + strlen(HTTP_SCHEME);
355 }
356
357 if (!authority_start)
358 {
360 "OAuth discovery URI \"%s\" must use HTTPS",
361 wkuri);
362 return NULL;
363 }
364
365 /*
366 * Well-known URIs in general may support queries and fragments, but the
367 * two types we support here do not. (They must be constructed from the
368 * components of issuer identifiers, which themselves may not contain any
369 * queries or fragments.)
370 *
371 * It's important to check this first, to avoid getting tricked later by a
372 * prefix buried inside a query or fragment.
373 */
374 if (strpbrk(authority_start, "?#") != NULL)
375 {
377 "OAuth discovery URI \"%s\" must not contain query or fragment components",
378 wkuri);
379 return NULL;
380 }
381
382 /*
383 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
384 * this must be at the beginning of the path component, but OIDC defined
385 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
386 * search for it anywhere.
387 */
388 wk_start = strstr(authority_start, WK_PREFIX);
389 if (!wk_start)
390 {
392 "OAuth discovery URI \"%s\" is not a .well-known URI",
393 wkuri);
394 return NULL;
395 }
396
397 /*
398 * Now find the suffix type. We only support the two defined in OIDC
399 * Discovery 1.0 and RFC 8414.
400 */
401 wk_end = wk_start + strlen(WK_PREFIX);
402
403 if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
404 wk_end += strlen(OPENID_WK_SUFFIX);
405 else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
406 wk_end += strlen(OAUTH_WK_SUFFIX);
407 else
408 wk_end = NULL;
409
410 /*
411 * Even if there's a match, we still need to check to make sure the suffix
412 * takes up the entire path segment, to weed out constructions like
413 * "/.well-known/openid-configuration-bad".
414 */
415 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
416 {
418 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
419 wkuri);
420 return NULL;
421 }
422
423 /*
424 * Finally, make sure the .well-known components are provided either as a
425 * prefix (IETF style) or as a postfix (OIDC style). In other words,
426 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
427 * to claim association with "https://localhost/a/b".
428 */
429 if (*wk_end != '\0')
430 {
431 /*
432 * It's not at the end, so it's required to be at the beginning at the
433 * path. Find the starting slash.
434 */
435 const char *path_start;
436
437 path_start = strchr(authority_start, '/');
438 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
439
440 if (wk_start != path_start)
441 {
443 "OAuth discovery URI \"%s\" uses an invalid format",
444 wkuri);
445 return NULL;
446 }
447 }
448
449 /* Checks passed! Now build the issuer. */
450 issuer = strdup(wkuri);
451 if (!issuer)
452 {
453 libpq_append_conn_error(conn, "out of memory");
454 return NULL;
455 }
456
457 /*
458 * The .well-known components are from [wk_start, wk_end). Remove those to
459 * form the issuer ID, by shifting the path suffix (which may be empty)
460 * leftwards.
461 */
462 start_offset = wk_start - wkuri;
463 end_offset = wk_end - wkuri;
464 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
465
466 memmove(issuer + start_offset, issuer + end_offset, end_len);
467
468 return issuer;
469}
470
471/*
472 * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
473 * stores any discovered openid_configuration and scope settings for the
474 * connection.
475 */
476static bool
477handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
478{
479 JsonLexContext lex = {0};
480 JsonSemAction sem = {0};
482 struct json_ctx ctx = {0};
483 char *errmsg = NULL;
484 bool success = false;
485
486 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
487
488 /* Sanity check. */
489 if (strlen(msg) != msglen)
490 {
492 "server's error message contained an embedded NULL, and was discarded");
493 return false;
494 }
495
496 /*
497 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
498 * that up front.
499 */
500 if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
501 {
503 "server's error response is not valid UTF-8");
504 return false;
505 }
506
507 makeJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
508 setJsonLexContextOwnsTokens(&lex, true); /* must not leak on error */
509
511 sem.semstate = &ctx;
512
518
519 err = pg_parse_json(&lex, &sem);
520
522 {
524 errmsg = libpq_gettext("out of memory");
525 else if (ctx.errmsg)
526 errmsg = ctx.errmsg;
527 else
528 {
529 /*
530 * Developer error: one of the action callbacks didn't call
531 * oauth_json_set_error() before erroring out.
532 */
534 errmsg = "<unexpected empty error>";
535 }
536 }
537 else if (err != JSON_SUCCESS)
538 errmsg = json_errdetail(err, &lex);
539
540 if (errmsg)
542 "failed to parse server's error response: %s",
543 errmsg);
544
545 /* Don't need the error buffer or the JSON lexer anymore. */
547 freeJsonLexContext(&lex);
548
549 if (errmsg)
550 goto cleanup;
551
552 if (ctx.discovery_uri)
553 {
554 char *discovery_issuer;
555
556 /*
557 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
558 *
559 * Issuer comparison is done byte-wise, rather than performing any URL
560 * normalization; this follows the suggestions for issuer comparison
561 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
562 * vastly simplifies things. Since this is the key protection against
563 * a rogue server sending the client to an untrustworthy location,
564 * simpler is better.
565 */
566 discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
567 if (!discovery_issuer)
568 goto cleanup; /* error message already set */
569
570 if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
571 {
573 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
574 ctx.discovery_uri, discovery_issuer,
576
577 free(discovery_issuer);
578 goto cleanup;
579 }
580
581 free(discovery_issuer);
582
584 {
586 ctx.discovery_uri = NULL;
587 }
588 else
589 {
590 /* This must match the URI we'd previously determined. */
591 if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
592 {
594 "server's discovery document has moved to %s (previous location was %s)",
595 ctx.discovery_uri,
597 goto cleanup;
598 }
599 }
600 }
601
602 if (ctx.scope)
603 {
604 /* Servers may not override a previously set oauth_scope. */
605 if (!conn->oauth_scope)
606 {
607 conn->oauth_scope = ctx.scope;
608 ctx.scope = NULL;
609 }
610 }
611
612 if (!ctx.status)
613 {
615 "server sent error response without a status");
616 goto cleanup;
617 }
618
619 if (strcmp(ctx.status, "invalid_token") != 0)
620 {
621 /*
622 * invalid_token is the only error code we'll automatically retry for;
623 * otherwise, just bail out now.
624 */
626 "server rejected OAuth bearer token: %s",
627 ctx.status);
628 goto cleanup;
629 }
630
631 success = true;
632
633cleanup:
634 free(ctx.status);
635 free(ctx.scope);
636 free(ctx.discovery_uri);
637
638 return success;
639}
640
641/*
642 * Callback implementation of conn->async_auth() for a user-defined OAuth flow.
643 * Delegates the retrieval of the token to the application's async callback.
644 *
645 * This will be called multiple times as needed; the application is responsible
646 * for setting an altsock to signal and returning the correct PGRES_POLLING_*
647 * statuses for use by PQconnectPoll().
648 */
651{
653 PGoauthBearerRequest *request = state->async_ctx;
655
656 if (!request->async)
657 {
659 "user-defined OAuth flow provided neither a token nor an async callback");
661 }
662
663 status = request->async(conn, request, &conn->altsock);
665 {
666 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
667 return status;
668 }
669 else if (status == PGRES_POLLING_OK)
670 {
671 /*
672 * We already have a token, so copy it into the conn. (We can't hold
673 * onto the original string, since it may not be safe for us to free()
674 * it.)
675 */
676 if (!request->token)
677 {
679 "user-defined OAuth flow did not provide a token");
681 }
682
683 conn->oauth_token = strdup(request->token);
684 if (!conn->oauth_token)
685 {
686 libpq_append_conn_error(conn, "out of memory");
688 }
689
690 return PGRES_POLLING_OK;
691 }
692
693 /* The hook wants the client to poll the altsock. Make sure it set one. */
695 {
697 "user-defined OAuth flow did not provide a socket for polling");
699 }
700
701 return status;
702}
703
704/*
705 * Cleanup callback for the async user flow. Delegates most of its job to the
706 * user-provided cleanup implementation, then disconnects the altsock.
707 */
708static void
710{
712 PGoauthBearerRequest *request = state->async_ctx;
713
714 Assert(request);
715
716 if (request->cleanup)
717 request->cleanup(conn, request);
719
720 free(request);
721 state->async_ctx = NULL;
722}
723
724/*
725 * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
726 * token for presentation to the server.
727 *
728 * If the application has registered a custom flow handler using
729 * PQAUTHDATA_OAUTH_BEARER_TOKEN, it may either return a token immediately (e.g.
730 * if it has one cached for immediate use), or set up for a series of
731 * asynchronous callbacks which will be managed by run_user_oauth_flow().
732 *
733 * If the default handler is used instead, a Device Authorization flow is used
734 * for the connection if support has been compiled in. (See
735 * fe-auth-oauth-curl.c for implementation details.)
736 *
737 * If neither a custom handler nor the builtin flow is available, the connection
738 * fails here.
739 */
740static bool
742{
743 int res;
744 PGoauthBearerRequest request = {
746 .scope = conn->oauth_scope,
747 };
748
750
751 /* The client may have overridden the OAuth flow. */
753 if (res > 0)
754 {
755 PGoauthBearerRequest *request_copy;
756
757 if (request.token)
758 {
759 /*
760 * We already have a token, so copy it into the conn. (We can't
761 * hold onto the original string, since it may not be safe for us
762 * to free() it.)
763 */
764 conn->oauth_token = strdup(request.token);
765 if (!conn->oauth_token)
766 {
767 libpq_append_conn_error(conn, "out of memory");
768 goto fail;
769 }
770
771 /* short-circuit */
772 if (request.cleanup)
773 request.cleanup(conn, &request);
774 return true;
775 }
776
777 request_copy = malloc(sizeof(*request_copy));
778 if (!request_copy)
779 {
780 libpq_append_conn_error(conn, "out of memory");
781 goto fail;
782 }
783
784 memcpy(request_copy, &request, sizeof(request));
785
788 state->async_ctx = request_copy;
789 }
790 else if (res < 0)
791 {
792 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
793 goto fail;
794 }
795 else
796 {
797#if USE_LIBCURL
798 /* Hand off to our built-in OAuth flow. */
801
802#else
803 libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
804 goto fail;
805
806#endif
807 }
808
809 return true;
810
811fail:
812 if (request.cleanup)
813 request.cleanup(conn, &request);
814 return false;
815}
816
817/*
818 * Fill in our issuer identifier (and discovery URI, if possible) using the
819 * connection parameters. If conn->oauth_discovery_uri can't be populated in
820 * this function, it will be requested from the server.
821 */
822static bool
824{
825 /*
826 * This is the only function that sets conn->oauth_issuer_id. If a
827 * previous connection attempt has already computed it, don't overwrite it
828 * or the discovery URI. (There's no reason for them to change once
829 * they're set, and handle_oauth_sasl_error() will fail the connection if
830 * the server attempts to switch them on us later.)
831 */
833 return true;
834
835 /*---
836 * To talk to a server, we require the user to provide issuer and client
837 * identifiers.
838 *
839 * While it's possible for an OAuth client to support multiple issuers, it
840 * requires additional effort to make sure the flows in use are safe -- to
841 * quote RFC 9207,
842 *
843 * OAuth clients that interact with only one authorization server are
844 * not vulnerable to mix-up attacks. However, when such clients decide
845 * to add support for a second authorization server in the future, they
846 * become vulnerable and need to apply countermeasures to mix-up
847 * attacks.
848 *
849 * For now, we allow only one.
850 */
852 {
854 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
855 return false;
856 }
857
858 /*
859 * oauth_issuer is interpreted differently if it's a well-known discovery
860 * URI rather than just an issuer identifier.
861 */
862 if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
863 {
864 /*
865 * Convert the URI back to an issuer identifier. (This also performs
866 * validation of the URI format.)
867 */
870 if (!conn->oauth_issuer_id)
871 return false; /* error message already set */
872
875 {
876 libpq_append_conn_error(conn, "out of memory");
877 return false;
878 }
879 }
880 else
881 {
882 /*
883 * Treat oauth_issuer as an issuer identifier. We'll ask the server
884 * for the discovery URI.
885 */
887 if (!conn->oauth_issuer_id)
888 {
889 libpq_append_conn_error(conn, "out of memory");
890 return false;
891 }
892 }
893
894 return true;
895}
896
897/*
898 * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
899 *
900 * If the necessary OAuth parameters are set up on the connection, this will run
901 * the client flow asynchronously and present the resulting token to the server.
902 * Otherwise, an empty discovery response will be sent and any parameters sent
903 * back by the server will be stored for a second attempt.
904 *
905 * For a full description of the API, see libpq/sasl.h.
906 */
907static SASLStatus
908oauth_exchange(void *opaq, bool final,
909 char *input, int inputlen,
910 char **output, int *outputlen)
911{
912 fe_oauth_state *state = opaq;
913 PGconn *conn = state->conn;
914 bool discover = false;
915
916 *output = NULL;
917 *outputlen = 0;
918
919 switch (state->step)
920 {
921 case FE_OAUTH_INIT:
922 /* We begin in the initial response phase. */
923 Assert(inputlen == -1);
924
926 return SASL_FAILED;
927
928 if (conn->oauth_token)
929 {
930 /*
931 * A previous connection already fetched the token; we'll use
932 * it below.
933 */
934 }
935 else if (conn->oauth_discovery_uri)
936 {
937 /*
938 * We don't have a token, but we have a discovery URI already
939 * stored. Decide whether we're using a user-provided OAuth
940 * flow or the one we have built in.
941 */
943 return SASL_FAILED;
944
945 if (conn->oauth_token)
946 {
947 /*
948 * A really smart user implementation may have already
949 * given us the token (e.g. if there was an unexpired copy
950 * already cached), and we can use it immediately.
951 */
952 }
953 else
954 {
955 /*
956 * Otherwise, we'll have to hand the connection over to
957 * our OAuth implementation.
958 *
959 * This could take a while, since it generally involves a
960 * user in the loop. To avoid consuming the server's
961 * authentication timeout, we'll continue this handshake
962 * to the end, so that the server can close its side of
963 * the connection. We'll open a second connection later
964 * once we've retrieved a token.
965 */
966 discover = true;
967 }
968 }
969 else
970 {
971 /*
972 * If we don't have a token, and we don't have a discovery URI
973 * to be able to request a token, we ask the server for one
974 * explicitly.
975 */
976 discover = true;
977 }
978
979 /*
980 * Generate an initial response. This either contains a token, if
981 * we have one, or an empty discovery response which is doomed to
982 * fail.
983 */
984 *output = client_initial_response(conn, discover);
985 if (!*output)
986 return SASL_FAILED;
987
988 *outputlen = strlen(*output);
990
991 if (conn->oauth_token)
992 {
993 /*
994 * For the purposes of require_auth, our side of
995 * authentication is done at this point; the server will
996 * either accept the connection or send an error. Unlike
997 * SCRAM, there is no additional server data to check upon
998 * success.
999 */
1000 conn->client_finished_auth = true;
1001 }
1002
1003 return SASL_CONTINUE;
1004
1006 if (final)
1007 {
1008 /*
1009 * OAUTHBEARER does not make use of additional data with a
1010 * successful SASL exchange, so we shouldn't get an
1011 * AuthenticationSASLFinal message.
1012 */
1014 "server sent unexpected additional OAuth data");
1015 return SASL_FAILED;
1016 }
1017
1018 /*
1019 * An error message was sent by the server. Respond with the
1020 * required dummy message (RFC 7628, sec. 3.2.3).
1021 */
1022 *output = strdup(kvsep);
1023 if (unlikely(!*output))
1024 {
1025 libpq_append_conn_error(conn, "out of memory");
1026 return SASL_FAILED;
1027 }
1028 *outputlen = strlen(*output); /* == 1 */
1029
1030 /* Grab the settings from discovery. */
1031 if (!handle_oauth_sasl_error(conn, input, inputlen))
1032 return SASL_FAILED;
1033
1034 if (conn->oauth_token)
1035 {
1036 /*
1037 * The server rejected our token. Continue onwards towards the
1038 * expected FATAL message, but mark our state to catch any
1039 * unexpected "success" from the server.
1040 */
1042 return SASL_CONTINUE;
1043 }
1044
1045 if (!conn->async_auth)
1046 {
1047 /*
1048 * No OAuth flow is set up yet. Did we get enough information
1049 * from the server to create one?
1050 */
1052 {
1054 "server requires OAuth authentication, but no discovery metadata was provided");
1055 return SASL_FAILED;
1056 }
1057
1058 /* Yes. Set up the flow now. */
1060 return SASL_FAILED;
1061
1062 if (conn->oauth_token)
1063 {
1064 /*
1065 * A token was available in a custom flow's cache. Skip
1066 * the asynchronous processing.
1067 */
1068 goto reconnect;
1069 }
1070 }
1071
1072 /*
1073 * Time to retrieve a token. This involves a number of HTTP
1074 * connections and timed waits, so we escape the synchronous auth
1075 * processing and tell PQconnectPoll to transfer control to our
1076 * async implementation.
1077 */
1078 Assert(conn->async_auth); /* should have been set already */
1080 return SASL_ASYNC;
1081
1083
1084 /*
1085 * We've returned successfully from token retrieval. Double-check
1086 * that we have what we need for the next connection.
1087 */
1088 if (!conn->oauth_token)
1089 {
1090 Assert(false); /* should have failed before this point! */
1092 "internal error: OAuth flow did not set a token");
1093 return SASL_FAILED;
1094 }
1095
1096 goto reconnect;
1097
1099
1100 /*
1101 * After an error, the server should send an error response to
1102 * fail the SASL handshake, which is handled in higher layers.
1103 *
1104 * If we get here, the server either sent *another* challenge
1105 * which isn't defined in the RFC, or completed the handshake
1106 * successfully after telling us it was going to fail. Neither is
1107 * acceptable.
1108 */
1110 "server sent additional OAuth data after error");
1111 return SASL_FAILED;
1112
1113 default:
1114 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1115 break;
1116 }
1117
1118 Assert(false); /* should never get here */
1119 return SASL_FAILED;
1120
1121reconnect:
1122
1123 /*
1124 * Despite being a failure from the point of view of SASL, we have enough
1125 * information to restart with a new connection.
1126 */
1127 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1128 conn->oauth_want_retry = true;
1129 return SASL_FAILED;
1130}
1131
1132static bool
1134{
1135 /* This mechanism does not support channel binding. */
1136 return false;
1137}
1138
1139/*
1140 * Fully clears out any stored OAuth token. This is done proactively upon
1141 * successful connection as well as during pqClosePGconn().
1142 */
1143void
1145{
1146 if (!conn->oauth_token)
1147 return;
1148
1151 conn->oauth_token = NULL;
1152}
1153
1154/*
1155 * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
1156 */
1157bool
1159{
1160 const char *env = getenv("PGOAUTHDEBUG");
1161
1162 return (env && strcmp(env, "UNSAFE") == 0);
1163}
static void cleanup(void)
Definition: bootstrap.c:713
#define unlikely(x)
Definition: c.h:347
int errmsg(const char *fmt,...)
Definition: elog.c:1070
void err(int eval, const char *fmt,...)
Definition: err.c:43
PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn)
void pg_fe_cleanup_oauth_flow(PGconn *conn)
#define HTTP_SCHEME
#define ERROR_SCOPE_FIELD
static bool setup_token_request(PGconn *conn, fe_oauth_state *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 bool setup_oauth_parameters(PGconn *conn)
#define oauth_json_set_error(ctx,...)
#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
Definition: fe-auth-oauth.c:35
#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)
Definition: fe-auth-oauth.c:79
#define ERROR_OPENID_CONFIGURATION_FIELD
void pqClearOAuthToken(PGconn *conn)
static void * oauth_init(PGconn *conn, const char *password, const char *sasl_mechanism)
Definition: fe-auth-oauth.c:48
#define kvsep
Definition: fe-auth-oauth.c:89
static char * client_initial_response(PGconn *conn, bool discover)
#define ERROR_STATUS_FIELD
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
Definition: fe-auth-oauth.h:26
@ FE_OAUTH_SERVER_ERROR
Definition: fe-auth-oauth.h:27
@ FE_OAUTH_INIT
Definition: fe-auth-oauth.h:24
@ FE_OAUTH_BEARER_SENT
Definition: fe-auth-oauth.h:25
SASLStatus
Definition: fe-auth-sasl.h:29
@ SASL_ASYNC
Definition: fe-auth-sasl.h:33
@ SASL_CONTINUE
Definition: fe-auth-sasl.h:32
@ SASL_FAILED
Definition: fe-auth-sasl.h:31
PQauthDataHook_type PQauthDataHook
Definition: fe-auth.c:1586
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: fe-misc.c:1381
Assert(PointerIsAligned(start, uint64))
#define calloc(a, b)
Definition: header.h:55
#define free(a)
Definition: header.h:65
#define malloc(a)
Definition: header.h:50
FILE * input
FILE * output
static bool success
Definition: initdb.c:186
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:2401
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:113
@ PGRES_POLLING_OK
Definition: libpq-fe.h:117
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:114
@ PQAUTHDATA_OAUTH_BEARER_TOKEN
Definition: libpq-fe.h:195
#define libpq_gettext(x)
Definition: libpq-int.h:934
#define OAUTHBEARER_NAME
Definition: oauth-common.h:17
static char * buf
Definition: pg_test_fsync.c:72
@ PG_UTF8
Definition: pg_wchar.h:232
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)
Definition: pgstrcasecmp.c:69
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129
#define PQExpBufferDataBroken(buf)
Definition: pqexpbuffer.h:67
static char * password
Definition: streamutil.c:51
PGconn * conn
Definition: streamutil.c:52
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
void(* cleanup)(PGconn *conn, struct PGoauthBearerRequest *request)
Definition: libpq-fe.h:783
const char * openid_configuration
Definition: libpq-fe.h:751
PostgresPollingStatusType(* async)(PGconn *conn, struct PGoauthBearerRequest *request, SOCKTYPE *altsock)
Definition: libpq-fe.h:772
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:529
bool client_finished_auth
Definition: libpq-int.h:521
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:449
char * oauth_token
Definition: libpq-int.h:448
char * oauth_issuer_id
Definition: libpq-int.h:442
pgsocket altsock
Definition: libpq-int.h:530
PostgresPollingStatusType(* async_auth)(PGconn *conn)
Definition: libpq-int.h:528
void * sasl_state
Definition: libpq-int.h:600
Definition: regguts.h:323
static JsonSemAction sem
const char * type
const char * name
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2163