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, fmt, ...)
 
#define oauth_json_set_error_internal(ctx, ...)
 
#define HTTPS_SCHEME   "https://"
 
#define HTTP_SCHEME   "http://"
 
#define WK_PREFIX   "/.well-known/"
 
#define OPENID_WK_SUFFIX   "openid-configuration"
 
#define OAUTH_WK_SUFFIX   "oauth-authorization-server"
 

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

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

Definition at line 353 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,
  fmt,
  ... 
)
Value:
do { \
appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
(ctx)->errmsg = (ctx)->errbuf.data; \
} while (0)
int errmsg(const char *fmt,...)
Definition: elog.c:1080
#define libpq_gettext(x)
Definition: oauth-utils.h:86

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

◆ oauth_json_set_error_internal

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

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

◆ OAUTH_WK_SUFFIX

#define OAUTH_WK_SUFFIX   "oauth-authorization-server"

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

◆ OPENID_WK_SUFFIX

#define OPENID_WK_SUFFIX   "openid-configuration"

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

◆ WK_PREFIX

#define WK_PREFIX   "/.well-known/"

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

Function Documentation

◆ cleanup_user_oauth_flow()

static void cleanup_user_oauth_flow ( PGconn conn)
static

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

747{
749 PGoauthBearerRequest *request = state->async_ctx;
750
751 Assert(request);
752
753 if (request->cleanup)
754 request->cleanup(conn, request);
756
757 free(request);
758 state->async_ctx = NULL;
759}
Assert(PointerIsAligned(start, uint64))
#define PGINVALID_SOCKET
Definition: port.h:31
#define free(a)
PGconn * conn
Definition: streamutil.c:52
void(* cleanup)(PGconn *conn, struct PGoauthBearerRequest *request)
Definition: libpq-fe.h:790
pgsocket altsock
Definition: libpq-int.h:530
void * sasl_state
Definition: libpq-int.h:612
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[DEFAULT_XLOG_SEG_SIZE]
Definition: pg_test_fsync.c:71
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:446

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

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 */
536 if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
537 {
539 "server's error response is not valid UTF-8");
540 return false;
541 }
542
543 lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
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 */
603 discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
604 if (!discovery_issuer)
605 goto cleanup; /* error message already set */
606
607 if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
608 {
610 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
611 ctx.discovery_uri, discovery_issuer,
613
614 free(discovery_issuer);
615 goto cleanup;
616 }
617
618 free(discovery_issuer);
619
621 {
623 ctx.discovery_uri = NULL;
624 }
625 else
626 {
627 /* This must match the URI we'd previously determined. */
628 if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
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}
static void cleanup(void)
Definition: bootstrap.c:717
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
@ 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:441
char * oauth_scope
Definition: libpq-int.h:445
char * oauth_issuer_id
Definition: libpq-int.h:440
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 366 of file fe-auth-oauth.c.

367{
368 const char *authority_start = NULL;
369 const char *wk_start;
370 const char *wk_end;
371 char *issuer;
372 ptrdiff_t start_offset,
373 end_offset;
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 */
382 if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
383 authority_start = wkuri + strlen(HTTPS_SCHEME);
384
385 if (!authority_start
387 && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
388 {
389 /* Allow http:// for testing only. */
390 authority_start = wkuri + strlen(HTTP_SCHEME);
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 */
424 wk_start = strstr(authority_start, WK_PREFIX);
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 */
437 wk_end = wk_start + strlen(WK_PREFIX);
438
439 if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
440 wk_end += strlen(OPENID_WK_SUFFIX);
441 else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
442 wk_end += strlen(OAUTH_WK_SUFFIX);
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
473 path_start = strchr(authority_start, '/');
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 */
498 start_offset = wk_start - wkuri;
499 end_offset = wk_end - wkuri;
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}
#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:65

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

1375{
1376 /* This mechanism does not support channel binding. */
1377 return false;
1378}

◆ oauth_exchange()

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

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

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

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

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

284{
285 struct json_ctx *ctx = state;
286
287 --ctx->nested;
288 return JSON_SUCCESS;
289}

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

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}
#define oauth_json_set_error(ctx, fmt,...)
#define MAX_SASL_NESTING_LEVEL
const char * target_field_name
char ** target_field

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

Referenced by handle_oauth_sasl_error().

◆ oauth_json_object_end()

static JsonParseErrorType oauth_json_object_end ( void *  state)
static

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

222{
223 struct json_ctx *ctx = state;
224
225 --ctx->nested;
226 return JSON_SUCCESS;
227}

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

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 }
247 else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
248 {
250 ctx->target_field = &ctx->discovery_uri;
251 }
252 }
253
254 return JSON_SUCCESS;
255}
#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 200 of file fe-auth-oauth.c.

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}

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

Referenced by handle_oauth_sasl_error().

◆ oauth_json_scalar()

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

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

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}
#define oauth_json_set_error_internal(ctx,...)
@ JSON_OUT_OF_MEMORY
Definition: jsonapi.h:52
@ JSON_TOKEN_STRING
Definition: jsonapi.h:20
const char * type

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

Referenced by handle_oauth_sasl_error().

◆ oauth_unsafe_debugging_enabled()

bool oauth_unsafe_debugging_enabled ( void  )

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

1400{
1401 const char *env = getenv("PGOAUTHDEBUG");
1402
1403 return (env && strcmp(env, "UNSAFE") == 0);
1404}

Referenced by issuer_from_well_known_uri().

◆ pqClearOAuthToken()

void pqClearOAuthToken ( PGconn conn)

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

1386{
1387 if (!conn->oauth_token)
1388 return;
1389
1392 conn->oauth_token = NULL;
1393}
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 687 of file fe-auth-oauth.c.

688{
690 PGoauthBearerRequest *request = state->async_ctx;
692
693 if (!request->async)
694 {
696 "user-defined OAuth flow provided neither a token nor an async callback");
698 }
699
700 status = request->async(conn, request, &conn->altsock);
701 if (status == PGRES_POLLING_FAILED)
702 {
703 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
704 return status;
705 }
706 else if (status == PGRES_POLLING_OK)
707 {
708 /*
709 * We already have a token, so copy it into the conn. (We can't hold
710 * onto the original string, since it may not be safe for us to free()
711 * it.)
712 */
713 if (!request->token)
714 {
716 "user-defined OAuth flow did not provide a token");
718 }
719
720 conn->oauth_token = strdup(request->token);
721 if (!conn->oauth_token)
722 {
723 libpq_append_conn_error(conn, "out of memory");
725 }
726
727 return PGRES_POLLING_OK;
728 }
729
730 /* The hook wants the client to poll the altsock. Make sure it set one. */
732 {
734 "user-defined OAuth flow did not provide a socket for polling");
736 }
737
738 return status;
739}
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, PQ_SOCKTYPE *altsock)
Definition: libpq-fe.h:779

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

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

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

991{
992 int res;
993 PGoauthBearerRequest request = {
995 .scope = conn->oauth_scope,
996 };
997
999
1000 /* The client may have overridden the OAuth flow. */
1002 if (res > 0)
1003 {
1004 PGoauthBearerRequest *request_copy;
1005
1006 if (request.token)
1007 {
1008 /*
1009 * We already have a token, so copy it into the conn. (We can't
1010 * hold onto the original string, since it may not be safe for us
1011 * to free() it.)
1012 */
1013 conn->oauth_token = strdup(request.token);
1014 if (!conn->oauth_token)
1015 {
1016 libpq_append_conn_error(conn, "out of memory");
1017 goto fail;
1018 }
1019
1020 /* short-circuit */
1021 if (request.cleanup)
1022 request.cleanup(conn, &request);
1023 return true;
1024 }
1025
1026 request_copy = malloc(sizeof(*request_copy));
1027 if (!request_copy)
1028 {
1029 libpq_append_conn_error(conn, "out of memory");
1030 goto fail;
1031 }
1032
1033 *request_copy = request;
1034
1037 state->async_ctx = request_copy;
1038 }
1039 else if (res < 0)
1040 {
1041 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
1042 goto fail;
1043 }
1044 else if (!use_builtin_flow(conn, state))
1045 {
1046 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1047 goto fail;
1048 }
1049
1050 return true;
1051
1052fail:
1053 if (request.cleanup)
1054 request.cleanup(conn, &request);
1055 return false;
1056}
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
@ PQAUTHDATA_OAUTH_BEARER_TOKEN
Definition: libpq-fe.h:196
#define malloc(a)
const char * openid_configuration
Definition: libpq-fe.h:755
void(* cleanup_async_auth)(PGconn *conn)
Definition: libpq-int.h:529

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

782{
783 return false;
784}

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().