PostgreSQL Source Code git master
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 "pg_config_paths.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, ...)
 
#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"
 

Functions

static void * oauth_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 char * client_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 char * issuer_from_well_known_uri (PGconn *conn, const char *wkuri)
 
static bool handle_oauth_sasl_error (PGconn *conn, const char *msg, int msglen)
 
static PostgresPollingStatusType run_user_oauth_flow (PGconn *conn)
 
static void cleanup_user_oauth_flow (PGconn *conn)
 
bool use_builtin_flow (PGconn *conn, fe_oauth_state *state)
 
static bool setup_token_request (PGconn *conn, fe_oauth_state *state)
 
static bool setup_oauth_parameters (PGconn *conn)
 
void pqClearOAuthToken (PGconn *conn)
 
bool oauth_unsafe_debugging_enabled (void)
 

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 158 of file fe-auth-oauth.c.

◆ ERROR_SCOPE_FIELD

#define ERROR_SCOPE_FIELD   "scope"

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

◆ ERROR_STATUS_FIELD

#define ERROR_STATUS_FIELD   "status"

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

◆ HTTP_SCHEME

#define HTTP_SCHEME   "http://"

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

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

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

◆ kvsep

#define kvsep   "\x01"

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

◆ MAX_SASL_NESTING_LEVEL

#define MAX_SASL_NESTING_LEVEL   8

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

◆ oauth_json_has_error

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

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

◆ oauth_json_set_error

#define oauth_json_set_error (   ctx,
  ... 
)
Value:
do { \
appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
(ctx)->errmsg = (ctx)->errbuf.data; \
} while (0)
int errmsg(const char *fmt,...)
Definition: elog.c:1071

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

◆ OAUTH_WK_SUFFIX

#define OAUTH_WK_SUFFIX   "oauth-authorization-server"

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

◆ OPENID_WK_SUFFIX

#define OPENID_WK_SUFFIX   "openid-configuration"

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

◆ WK_PREFIX

#define WK_PREFIX   "/.well-known/"

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

Function Documentation

◆ cleanup_user_oauth_flow()

static void cleanup_user_oauth_flow ( PGconn conn)
static

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

740{
742 PGoauthBearerRequest *request = state->async_ctx;
743
744 Assert(request);
745
746 if (request->cleanup)
747 request->cleanup(conn, request);
749
750 free(request);
751 state->async_ctx = NULL;
752}
Assert(PointerIsAligned(start, uint64))
#define free(a)
Definition: header.h:65
#define PGINVALID_SOCKET
Definition: port.h:31
PGconn * conn
Definition: streamutil.c:52
void(* cleanup)(PGconn *conn, struct PGoauthBearerRequest *request)
Definition: libpq-fe.h:787
pgsocket altsock
Definition: libpq-int.h:527
void * sasl_state
Definition: libpq-int.h:600
Definition: regguts.h:323

References pg_conn::altsock, Assert(), PGoauthBearerRequest::cleanup, conn, 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 106 of file fe-auth-oauth.c.

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
139 appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
140
142 response = strdup(buf.data);
144
145 if (!response)
146 libpq_append_conn_error(conn, "out of memory");
147
148 return response;
149}
#define kvsep
Definition: fe-auth-oauth.c:94
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: oauth-utils.c:95
static char * buf
Definition: pg_test_fsync.c:72
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
char * oauth_token
Definition: libpq-int.h:443

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

Referenced by oauth_exchange().

◆ handle_oauth_sasl_error()

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

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

507{
508 JsonLexContext *lex;
509 JsonSemAction sem = {0};
511 struct json_ctx ctx = {0};
512 char *errmsg = NULL;
513 bool success = false;
514
515 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
516
517 /* Sanity check. */
518 if (strlen(msg) != msglen)
519 {
521 "server's error message contained an embedded NULL, and was discarded");
522 return false;
523 }
524
525 /*
526 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
527 * that up front.
528 */
529 if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
530 {
532 "server's error response is not valid UTF-8");
533 return false;
534 }
535
536 lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
537 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
538
540 sem.semstate = &ctx;
541
548
549 err = pg_parse_json(lex, &sem);
550
552 {
554 errmsg = libpq_gettext("out of memory");
555 else if (ctx.errmsg)
556 errmsg = ctx.errmsg;
557 else
558 {
559 /*
560 * Developer error: one of the action callbacks didn't call
561 * oauth_json_set_error() before erroring out.
562 */
564 errmsg = "<unexpected empty error>";
565 }
566 }
567 else if (err != JSON_SUCCESS)
568 errmsg = json_errdetail(err, lex);
569
570 if (errmsg)
572 "failed to parse server's error response: %s",
573 errmsg);
574
575 /* Don't need the error buffer or the JSON lexer anymore. */
578
579 if (errmsg)
580 goto cleanup;
581
582 if (ctx.discovery_uri)
583 {
584 char *discovery_issuer;
585
586 /*
587 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
588 *
589 * Issuer comparison is done byte-wise, rather than performing any URL
590 * normalization; this follows the suggestions for issuer comparison
591 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
592 * vastly simplifies things. Since this is the key protection against
593 * a rogue server sending the client to an untrustworthy location,
594 * simpler is better.
595 */
596 discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
597 if (!discovery_issuer)
598 goto cleanup; /* error message already set */
599
600 if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
601 {
603 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
604 ctx.discovery_uri, discovery_issuer,
606
607 free(discovery_issuer);
608 goto cleanup;
609 }
610
611 free(discovery_issuer);
612
614 {
616 ctx.discovery_uri = NULL;
617 }
618 else
619 {
620 /* This must match the URI we'd previously determined. */
621 if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
622 {
624 "server's discovery document has moved to %s (previous location was %s)",
625 ctx.discovery_uri,
627 goto cleanup;
628 }
629 }
630 }
631
632 if (ctx.scope)
633 {
634 /* Servers may not override a previously set oauth_scope. */
635 if (!conn->oauth_scope)
636 {
637 conn->oauth_scope = ctx.scope;
638 ctx.scope = NULL;
639 }
640 }
641
642 if (!ctx.status)
643 {
645 "server sent error response without a status");
646 goto cleanup;
647 }
648
649 if (strcmp(ctx.status, "invalid_token") != 0)
650 {
651 /*
652 * invalid_token is the only error code we'll automatically retry for;
653 * otherwise, just bail out now.
654 */
656 "server rejected OAuth bearer token: %s",
657 ctx.status);
658 goto cleanup;
659 }
660
661 success = true;
662
663cleanup:
664 free(ctx.status);
665 free(ctx.scope);
666 free(ctx.discovery_uri);
667
668 return success;
669}
static void cleanup(void)
Definition: bootstrap.c:713
void err(int eval, const char *fmt,...)
Definition: err.c:43
static JsonParseErrorType oauth_json_array_end(void *state)
static char * issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
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 oauth_json_has_error(ctx)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
static JsonParseErrorType oauth_json_object_start(void *state)
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_SEM_ACTION_FAILED
Definition: jsonapi.h:59
@ JSON_SUCCESS
Definition: jsonapi.h:36
#define libpq_gettext(x)
Definition: oauth-utils.h:86
@ PG_UTF8
Definition: pg_wchar.h:232
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
char * discovery_uri
char * status
char * scope
PQExpBufferData errbuf
char * errmsg
char * oauth_discovery_uri
Definition: libpq-int.h:438
char * oauth_scope
Definition: libpq-int.h:442
char * oauth_issuer_id
Definition: libpq-int.h:437
static JsonSemAction sem
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2202

References JsonSemAction::array_end, JsonSemAction::array_start, Assert(), cleanup(), conn, json_ctx::discovery_uri, err(), json_ctx::errbuf, errmsg(), json_ctx::errmsg, 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 359 of file fe-auth-oauth.c.

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

References Assert(), conn, HTTP_SCHEME, HTTPS_SCHEME, libpq_append_conn_error(), oauth_unsafe_debugging_enabled(), OAUTH_WK_SUFFIX, 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 1367 of file fe-auth-oauth.c.

1368{
1369 /* This mechanism does not support channel binding. */
1370 return false;
1371}

◆ oauth_exchange()

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

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

1145{
1146 fe_oauth_state *state = opaq;
1147 PGconn *conn = state->conn;
1148 bool discover = false;
1149
1150 *output = NULL;
1151 *outputlen = 0;
1152
1153 switch (state->step)
1154 {
1155 case FE_OAUTH_INIT:
1156 /* We begin in the initial response phase. */
1157 Assert(inputlen == -1);
1158
1160 return SASL_FAILED;
1161
1162 if (conn->oauth_token)
1163 {
1164 /*
1165 * A previous connection already fetched the token; we'll use
1166 * it below.
1167 */
1168 }
1169 else if (conn->oauth_discovery_uri)
1170 {
1171 /*
1172 * We don't have a token, but we have a discovery URI already
1173 * stored. Decide whether we're using a user-provided OAuth
1174 * flow or the one we have built in.
1175 */
1177 return SASL_FAILED;
1178
1179 if (conn->oauth_token)
1180 {
1181 /*
1182 * A really smart user implementation may have already
1183 * given us the token (e.g. if there was an unexpired copy
1184 * already cached), and we can use it immediately.
1185 */
1186 }
1187 else
1188 {
1189 /*
1190 * Otherwise, we'll have to hand the connection over to
1191 * our OAuth implementation.
1192 *
1193 * This could take a while, since it generally involves a
1194 * user in the loop. To avoid consuming the server's
1195 * authentication timeout, we'll continue this handshake
1196 * to the end, so that the server can close its side of
1197 * the connection. We'll open a second connection later
1198 * once we've retrieved a token.
1199 */
1200 discover = true;
1201 }
1202 }
1203 else
1204 {
1205 /*
1206 * If we don't have a token, and we don't have a discovery URI
1207 * to be able to request a token, we ask the server for one
1208 * explicitly.
1209 */
1210 discover = true;
1211 }
1212
1213 /*
1214 * Generate an initial response. This either contains a token, if
1215 * we have one, or an empty discovery response which is doomed to
1216 * fail.
1217 */
1218 *output = client_initial_response(conn, discover);
1219 if (!*output)
1220 return SASL_FAILED;
1221
1222 *outputlen = strlen(*output);
1224
1225 if (conn->oauth_token)
1226 {
1227 /*
1228 * For the purposes of require_auth, our side of
1229 * authentication is done at this point; the server will
1230 * either accept the connection or send an error. Unlike
1231 * SCRAM, there is no additional server data to check upon
1232 * success.
1233 */
1234 conn->client_finished_auth = true;
1235 }
1236
1237 return SASL_CONTINUE;
1238
1240 if (final)
1241 {
1242 /*
1243 * OAUTHBEARER does not make use of additional data with a
1244 * successful SASL exchange, so we shouldn't get an
1245 * AuthenticationSASLFinal message.
1246 */
1248 "server sent unexpected additional OAuth data");
1249 return SASL_FAILED;
1250 }
1251
1252 /*
1253 * An error message was sent by the server. Respond with the
1254 * required dummy message (RFC 7628, sec. 3.2.3).
1255 */
1256 *output = strdup(kvsep);
1257 if (unlikely(!*output))
1258 {
1259 libpq_append_conn_error(conn, "out of memory");
1260 return SASL_FAILED;
1261 }
1262 *outputlen = strlen(*output); /* == 1 */
1263
1264 /* Grab the settings from discovery. */
1265 if (!handle_oauth_sasl_error(conn, input, inputlen))
1266 return SASL_FAILED;
1267
1268 if (conn->oauth_token)
1269 {
1270 /*
1271 * The server rejected our token. Continue onwards towards the
1272 * expected FATAL message, but mark our state to catch any
1273 * unexpected "success" from the server.
1274 */
1276 return SASL_CONTINUE;
1277 }
1278
1279 if (!conn->async_auth)
1280 {
1281 /*
1282 * No OAuth flow is set up yet. Did we get enough information
1283 * from the server to create one?
1284 */
1286 {
1288 "server requires OAuth authentication, but no discovery metadata was provided");
1289 return SASL_FAILED;
1290 }
1291
1292 /* Yes. Set up the flow now. */
1294 return SASL_FAILED;
1295
1296 if (conn->oauth_token)
1297 {
1298 /*
1299 * A token was available in a custom flow's cache. Skip
1300 * the asynchronous processing.
1301 */
1302 goto reconnect;
1303 }
1304 }
1305
1306 /*
1307 * Time to retrieve a token. This involves a number of HTTP
1308 * connections and timed waits, so we escape the synchronous auth
1309 * processing and tell PQconnectPoll to transfer control to our
1310 * async implementation.
1311 */
1312 Assert(conn->async_auth); /* should have been set already */
1314 return SASL_ASYNC;
1315
1317
1318 /*
1319 * We've returned successfully from token retrieval. Double-check
1320 * that we have what we need for the next connection.
1321 */
1322 if (!conn->oauth_token)
1323 {
1324 Assert(false); /* should have failed before this point! */
1326 "internal error: OAuth flow did not set a token");
1327 return SASL_FAILED;
1328 }
1329
1330 goto reconnect;
1331
1333
1334 /*
1335 * After an error, the server should send an error response to
1336 * fail the SASL handshake, which is handled in higher layers.
1337 *
1338 * If we get here, the server either sent *another* challenge
1339 * which isn't defined in the RFC, or completed the handshake
1340 * successfully after telling us it was going to fail. Neither is
1341 * acceptable.
1342 */
1344 "server sent additional OAuth data after error");
1345 return SASL_FAILED;
1346
1347 default:
1348 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1349 break;
1350 }
1351
1352 Assert(false); /* should never get here */
1353 return SASL_FAILED;
1354
1355reconnect:
1356
1357 /*
1358 * Despite being a failure from the point of view of SASL, we have enough
1359 * information to restart with a new connection.
1360 */
1361 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1362 conn->oauth_want_retry = true;
1363 return SASL_FAILED;
1364}
#define unlikely(x)
Definition: c.h:347
static bool setup_token_request(PGconn *conn, fe_oauth_state *state)
static bool handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
static bool setup_oauth_parameters(PGconn *conn)
static char * client_initial_response(PGconn *conn, bool discover)
@ 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
@ SASL_ASYNC
Definition: fe-auth-sasl.h:33
@ SASL_CONTINUE
Definition: fe-auth-sasl.h:32
@ SASL_FAILED
Definition: fe-auth-sasl.h:31
FILE * input
FILE * output
bool client_finished_auth
Definition: libpq-int.h:518
bool oauth_want_retry
Definition: libpq-int.h:444
PostgresPollingStatusType(* async_auth)(PGconn *conn)
Definition: libpq-int.h:525

References Assert(), pg_conn::async_auth, pg_conn::client_finished_auth, client_initial_response(), conn, 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 84 of file fe-auth-oauth.c.

85{
86 fe_oauth_state *state = opaq;
87
88 /* Any async authentication state should have been cleaned up already. */
89 Assert(!state->async_ctx);
90
91 free(state);
92}

References Assert(), and free.

◆ oauth_init()

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

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

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}
#define calloc(a, b)
Definition: header.h:55
#define OAUTHBEARER_NAME
Definition: oauth-common.h:17

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

◆ oauth_json_array_end()

static JsonParseErrorType oauth_json_array_end ( void *  state)
static

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

277{
278 struct json_ctx *ctx = state;
279
280 --ctx->nested;
281 return JSON_SUCCESS;
282}

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 251 of file fe-auth-oauth.c.

252{
253 struct json_ctx *ctx = state;
254
255 if (!ctx->nested)
256 {
257 ctx->errmsg = libpq_gettext("top-level element must be an object");
258 }
259 else if (ctx->target_field)
260 {
261 Assert(ctx->nested == 1);
262
264 libpq_gettext("field \"%s\" must be a string"),
265 ctx->target_field_name);
266 }
267
268 ++ctx->nested;
270 oauth_json_set_error(ctx, libpq_gettext("JSON is too deeply nested"));
271
273}
#define oauth_json_set_error(ctx,...)
#define MAX_SASL_NESTING_LEVEL
const char * target_field_name
char ** target_field

References Assert(), json_ctx::errmsg, JSON_SEM_ACTION_FAILED, JSON_SUCCESS, libpq_gettext, 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 214 of file fe-auth-oauth.c.

215{
216 struct json_ctx *ctx = state;
217
218 --ctx->nested;
219 return JSON_SUCCESS;
220}

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 223 of file fe-auth-oauth.c.

224{
225 struct json_ctx *ctx = state;
226
227 /* Only top-level keys are considered. */
228 if (ctx->nested == 1)
229 {
230 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
231 {
233 ctx->target_field = &ctx->status;
234 }
235 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
236 {
238 ctx->target_field = &ctx->scope;
239 }
240 else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
241 {
243 ctx->target_field = &ctx->discovery_uri;
244 }
245 }
246
247 return JSON_SUCCESS;
248}
#define ERROR_SCOPE_FIELD
#define ERROR_OPENID_CONFIGURATION_FIELD
#define ERROR_STATUS_FIELD
const char * name

References json_ctx::discovery_uri, ERROR_OPENID_CONFIGURATION_FIELD, ERROR_SCOPE_FIELD, ERROR_STATUS_FIELD, 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 193 of file fe-auth-oauth.c.

194{
195 struct json_ctx *ctx = state;
196
197 if (ctx->target_field)
198 {
199 Assert(ctx->nested == 1);
200
202 libpq_gettext("field \"%s\" must be a string"),
203 ctx->target_field_name);
204 }
205
206 ++ctx->nested;
208 oauth_json_set_error(ctx, libpq_gettext("JSON is too deeply nested"));
209
211}

References Assert(), JSON_SEM_ACTION_FAILED, JSON_SUCCESS, libpq_gettext, 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 285 of file fe-auth-oauth.c.

286{
287 struct json_ctx *ctx = state;
288
289 if (!ctx->nested)
290 {
291 ctx->errmsg = libpq_gettext("top-level element must be an object");
293 }
294
295 if (ctx->target_field)
296 {
297 if (ctx->nested != 1)
298 {
299 /*
300 * ctx->target_field should not have been set for nested keys.
301 * Assert and don't continue any further for production builds.
302 */
303 Assert(false);
305 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
306 ctx->nested);
308 }
309
310 /*
311 * We don't allow duplicate field names; error out if the target has
312 * already been set.
313 */
314 if (*ctx->target_field)
315 {
317 libpq_gettext("field \"%s\" is duplicated"),
318 ctx->target_field_name);
320 }
321
322 /* The only fields we support are strings. */
323 if (type != JSON_TOKEN_STRING)
324 {
326 libpq_gettext("field \"%s\" must be a string"),
327 ctx->target_field_name);
329 }
330
331 *ctx->target_field = strdup(token);
332 if (!*ctx->target_field)
333 return JSON_OUT_OF_MEMORY;
334
335 ctx->target_field = NULL;
336 ctx->target_field_name = NULL;
337 }
338 else
339 {
340 /* otherwise we just ignore it */
341 }
342
343 return JSON_SUCCESS;
344}
@ JSON_OUT_OF_MEMORY
Definition: jsonapi.h:52
@ JSON_TOKEN_STRING
Definition: jsonapi.h:20
const char * type

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

Referenced by handle_oauth_sasl_error().

◆ oauth_unsafe_debugging_enabled()

bool oauth_unsafe_debugging_enabled ( void  )

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

1393{
1394 const char *env = getenv("PGOAUTHDEBUG");
1395
1396 return (env && strcmp(env, "UNSAFE") == 0);
1397}

Referenced by issuer_from_well_known_uri().

◆ pqClearOAuthToken()

void pqClearOAuthToken ( PGconn conn)

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

1379{
1380 if (!conn->oauth_token)
1381 return;
1382
1385 conn->oauth_token = NULL;
1386}
void explicit_bzero(void *buf, size_t len)

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

Referenced by pqClosePGconn(), and PQconnectPoll().

◆ run_user_oauth_flow()

static PostgresPollingStatusType run_user_oauth_flow ( PGconn conn)
static

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

681{
683 PGoauthBearerRequest *request = state->async_ctx;
685
686 if (!request->async)
687 {
689 "user-defined OAuth flow provided neither a token nor an async callback");
691 }
692
693 status = request->async(conn, request, &conn->altsock);
694 if (status == PGRES_POLLING_FAILED)
695 {
696 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
697 return status;
698 }
699 else if (status == PGRES_POLLING_OK)
700 {
701 /*
702 * We already have a token, so copy it into the conn. (We can't hold
703 * onto the original string, since it may not be safe for us to free()
704 * it.)
705 */
706 if (!request->token)
707 {
709 "user-defined OAuth flow did not provide a token");
711 }
712
713 conn->oauth_token = strdup(request->token);
714 if (!conn->oauth_token)
715 {
716 libpq_append_conn_error(conn, "out of memory");
718 }
719
720 return PGRES_POLLING_OK;
721 }
722
723 /* The hook wants the client to poll the altsock. Make sure it set one. */
725 {
727 "user-defined OAuth flow did not provide a socket for polling");
729 }
730
731 return status;
732}
PostgresPollingStatusType
Definition: libpq-fe.h:114
@ PGRES_POLLING_OK
Definition: libpq-fe.h:118
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:115
PostgresPollingStatusType(* async)(PGconn *conn, struct PGoauthBearerRequest *request, SOCKTYPE *altsock)
Definition: libpq-fe.h:776

References pg_conn::altsock, PGoauthBearerRequest::async, conn, libpq_append_conn_error(), pg_conn::oauth_token, PGINVALID_SOCKET, PGRES_POLLING_FAILED, PGRES_POLLING_OK, pg_conn::sasl_state, json_ctx::status, and PGoauthBearerRequest::token.

Referenced by setup_token_request().

◆ setup_oauth_parameters()

static bool setup_oauth_parameters ( PGconn conn)
static

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

1058{
1059 /*
1060 * This is the only function that sets conn->oauth_issuer_id. If a
1061 * previous connection attempt has already computed it, don't overwrite it
1062 * or the discovery URI. (There's no reason for them to change once
1063 * they're set, and handle_oauth_sasl_error() will fail the connection if
1064 * the server attempts to switch them on us later.)
1065 */
1066 if (conn->oauth_issuer_id)
1067 return true;
1068
1069 /*---
1070 * To talk to a server, we require the user to provide issuer and client
1071 * identifiers.
1072 *
1073 * While it's possible for an OAuth client to support multiple issuers, it
1074 * requires additional effort to make sure the flows in use are safe -- to
1075 * quote RFC 9207,
1076 *
1077 * OAuth clients that interact with only one authorization server are
1078 * not vulnerable to mix-up attacks. However, when such clients decide
1079 * to add support for a second authorization server in the future, they
1080 * become vulnerable and need to apply countermeasures to mix-up
1081 * attacks.
1082 *
1083 * For now, we allow only one.
1084 */
1086 {
1088 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1089 return false;
1090 }
1091
1092 /*
1093 * oauth_issuer is interpreted differently if it's a well-known discovery
1094 * URI rather than just an issuer identifier.
1095 */
1096 if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
1097 {
1098 /*
1099 * Convert the URI back to an issuer identifier. (This also performs
1100 * validation of the URI format.)
1101 */
1104 if (!conn->oauth_issuer_id)
1105 return false; /* error message already set */
1106
1109 {
1110 libpq_append_conn_error(conn, "out of memory");
1111 return false;
1112 }
1113 }
1114 else
1115 {
1116 /*
1117 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1118 * for the discovery URI.
1119 */
1121 if (!conn->oauth_issuer_id)
1122 {
1123 libpq_append_conn_error(conn, "out of memory");
1124 return false;
1125 }
1126 }
1127
1128 return true;
1129}
char * oauth_client_id
Definition: libpq-int.h:440
char * oauth_issuer
Definition: libpq-int.h:436

References conn, 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 983 of file fe-auth-oauth.c.

984{
985 int res;
986 PGoauthBearerRequest request = {
988 .scope = conn->oauth_scope,
989 };
990
992
993 /* The client may have overridden the OAuth flow. */
995 if (res > 0)
996 {
997 PGoauthBearerRequest *request_copy;
998
999 if (request.token)
1000 {
1001 /*
1002 * We already have a token, so copy it into the conn. (We can't
1003 * hold onto the original string, since it may not be safe for us
1004 * to free() it.)
1005 */
1006 conn->oauth_token = strdup(request.token);
1007 if (!conn->oauth_token)
1008 {
1009 libpq_append_conn_error(conn, "out of memory");
1010 goto fail;
1011 }
1012
1013 /* short-circuit */
1014 if (request.cleanup)
1015 request.cleanup(conn, &request);
1016 return true;
1017 }
1018
1019 request_copy = malloc(sizeof(*request_copy));
1020 if (!request_copy)
1021 {
1022 libpq_append_conn_error(conn, "out of memory");
1023 goto fail;
1024 }
1025
1026 *request_copy = request;
1027
1030 state->async_ctx = request_copy;
1031 }
1032 else if (res < 0)
1033 {
1034 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
1035 goto fail;
1036 }
1037 else if (!use_builtin_flow(conn, state))
1038 {
1039 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1040 goto fail;
1041 }
1042
1043 return true;
1044
1045fail:
1046 if (request.cleanup)
1047 request.cleanup(conn, &request);
1048 return false;
1049}
static void cleanup_user_oauth_flow(PGconn *conn)
bool use_builtin_flow(PGconn *conn, fe_oauth_state *state)
static PostgresPollingStatusType run_user_oauth_flow(PGconn *conn)
PQauthDataHook_type PQauthDataHook
Definition: fe-auth.c:1586
#define malloc(a)
Definition: header.h:50
@ PQAUTHDATA_OAUTH_BEARER_TOKEN
Definition: libpq-fe.h:196
const char * openid_configuration
Definition: libpq-fe.h:755
void(* cleanup_async_auth)(PGconn *conn)
Definition: libpq-int.h:526

References Assert(), pg_conn::async_auth, PGoauthBearerRequest::cleanup, pg_conn::cleanup_async_auth, cleanup_user_oauth_flow(), conn, libpq_append_conn_error(), malloc, pg_conn::oauth_discovery_uri, pg_conn::oauth_scope, pg_conn::oauth_token, PGoauthBearerRequest::openid_configuration, PQAUTHDATA_OAUTH_BEARER_TOKEN, PQauthDataHook, run_user_oauth_flow(), PGoauthBearerRequest::token, and use_builtin_flow().

Referenced by oauth_exchange().

◆ use_builtin_flow()

bool use_builtin_flow ( PGconn conn,
fe_oauth_state state 
)

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

775{
776 return false;
777}

Referenced by setup_token_request().

Variable Documentation

◆ pg_oauth_mech

const pg_fe_sasl_mech pg_oauth_mech
Initial value:
= {
}
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)
Definition: fe-auth-oauth.c:84
static void * oauth_init(PGconn *conn, const char *password, const char *sasl_mechanism)
Definition: fe-auth-oauth.c:53

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

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