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

◆ HTTPS_SCHEME

#define HTTPS_SCHEME   "https://"

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

◆ kvsep

#define kvsep   "\x01"

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

◆ oauth_json_has_error

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

Definition at line 175 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 178 of file fe-auth-oauth.c.

◆ OAUTH_WK_SUFFIX

#define OAUTH_WK_SUFFIX   "oauth-authorization-server"

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

◆ OPENID_WK_SUFFIX

#define OPENID_WK_SUFFIX   "openid-configuration"

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

◆ WK_PREFIX

#define WK_PREFIX   "/.well-known/"

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

Function Documentation

◆ cleanup_user_oauth_flow()

static void cleanup_user_oauth_flow ( PGconn conn)
static

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

715{
717 PGoauthBearerRequest *request = state->async_ctx;
718
719 Assert(request);
720
721 if (request->cleanup)
722 request->cleanup(conn, request);
724
725 free(request);
726 state->async_ctx = NULL;
727}
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 482 of file fe-auth-oauth.c.

483{
484 JsonLexContext *lex;
485 JsonSemAction sem = {0};
487 struct json_ctx ctx = {0};
488 char *errmsg = NULL;
489 bool success = false;
490
491 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
492
493 /* Sanity check. */
494 if (strlen(msg) != msglen)
495 {
497 "server's error message contained an embedded NULL, and was discarded");
498 return false;
499 }
500
501 /*
502 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
503 * that up front.
504 */
505 if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
506 {
508 "server's error response is not valid UTF-8");
509 return false;
510 }
511
512 lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
513 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
514
516 sem.semstate = &ctx;
517
523
524 err = pg_parse_json(lex, &sem);
525
527 {
529 errmsg = libpq_gettext("out of memory");
530 else if (ctx.errmsg)
531 errmsg = ctx.errmsg;
532 else
533 {
534 /*
535 * Developer error: one of the action callbacks didn't call
536 * oauth_json_set_error() before erroring out.
537 */
539 errmsg = "<unexpected empty error>";
540 }
541 }
542 else if (err != JSON_SUCCESS)
543 errmsg = json_errdetail(err, lex);
544
545 if (errmsg)
547 "failed to parse server's error response: %s",
548 errmsg);
549
550 /* Don't need the error buffer or the JSON lexer anymore. */
553
554 if (errmsg)
555 goto cleanup;
556
557 if (ctx.discovery_uri)
558 {
559 char *discovery_issuer;
560
561 /*
562 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
563 *
564 * Issuer comparison is done byte-wise, rather than performing any URL
565 * normalization; this follows the suggestions for issuer comparison
566 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
567 * vastly simplifies things. Since this is the key protection against
568 * a rogue server sending the client to an untrustworthy location,
569 * simpler is better.
570 */
571 discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
572 if (!discovery_issuer)
573 goto cleanup; /* error message already set */
574
575 if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
576 {
578 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
579 ctx.discovery_uri, discovery_issuer,
581
582 free(discovery_issuer);
583 goto cleanup;
584 }
585
586 free(discovery_issuer);
587
589 {
591 ctx.discovery_uri = NULL;
592 }
593 else
594 {
595 /* This must match the URI we'd previously determined. */
596 if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
597 {
599 "server's discovery document has moved to %s (previous location was %s)",
600 ctx.discovery_uri,
602 goto cleanup;
603 }
604 }
605 }
606
607 if (ctx.scope)
608 {
609 /* Servers may not override a previously set oauth_scope. */
610 if (!conn->oauth_scope)
611 {
612 conn->oauth_scope = ctx.scope;
613 ctx.scope = NULL;
614 }
615 }
616
617 if (!ctx.status)
618 {
620 "server sent error response without a status");
621 goto cleanup;
622 }
623
624 if (strcmp(ctx.status, "invalid_token") != 0)
625 {
626 /*
627 * invalid_token is the only error code we'll automatically retry for;
628 * otherwise, just bail out now.
629 */
631 "server rejected OAuth bearer token: %s",
632 ctx.status);
633 goto cleanup;
634 }
635
636 success = true;
637
638cleanup:
639 free(ctx.status);
640 free(ctx.scope);
641 free(ctx.discovery_uri);
642
643 return success;
644}
static void cleanup(void)
Definition: bootstrap.c:713
void err(int eval, const char *fmt,...)
Definition: err.c:43
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 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_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_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 335 of file fe-auth-oauth.c.

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

1343{
1344 /* This mechanism does not support channel binding. */
1345 return false;
1346}

◆ oauth_exchange()

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

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

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

static JsonParseErrorType oauth_json_array_start ( void *  state)
static

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

241{
242 struct json_ctx *ctx = state;
243
244 if (!ctx->nested)
245 {
246 ctx->errmsg = libpq_gettext("top-level element must be an object");
247 }
248 else if (ctx->target_field)
249 {
250 Assert(ctx->nested == 1);
251
253 libpq_gettext("field \"%s\" must be a string"),
254 ctx->target_field_name);
255 }
256
258}
#define oauth_json_set_error(ctx,...)
const char * target_field_name
char ** target_field

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

204{
205 struct json_ctx *ctx = state;
206
207 --ctx->nested;
208 return JSON_SUCCESS;
209}

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

213{
214 struct json_ctx *ctx = state;
215
216 /* Only top-level keys are considered. */
217 if (ctx->nested == 1)
218 {
219 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
220 {
222 ctx->target_field = &ctx->status;
223 }
224 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
225 {
227 ctx->target_field = &ctx->scope;
228 }
229 else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
230 {
232 ctx->target_field = &ctx->discovery_uri;
233 }
234 }
235
236 return JSON_SUCCESS;
237}
#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 185 of file fe-auth-oauth.c.

186{
187 struct json_ctx *ctx = state;
188
189 if (ctx->target_field)
190 {
191 Assert(ctx->nested == 1);
192
194 libpq_gettext("field \"%s\" must be a string"),
195 ctx->target_field_name);
196 }
197
198 ++ctx->nested;
200}

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

262{
263 struct json_ctx *ctx = state;
264
265 if (!ctx->nested)
266 {
267 ctx->errmsg = libpq_gettext("top-level element must be an object");
269 }
270
271 if (ctx->target_field)
272 {
273 if (ctx->nested != 1)
274 {
275 /*
276 * ctx->target_field should not have been set for nested keys.
277 * Assert and don't continue any further for production builds.
278 */
279 Assert(false);
281 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
282 ctx->nested);
284 }
285
286 /*
287 * We don't allow duplicate field names; error out if the target has
288 * already been set.
289 */
290 if (*ctx->target_field)
291 {
293 libpq_gettext("field \"%s\" is duplicated"),
294 ctx->target_field_name);
296 }
297
298 /* The only fields we support are strings. */
299 if (type != JSON_TOKEN_STRING)
300 {
302 libpq_gettext("field \"%s\" must be a string"),
303 ctx->target_field_name);
305 }
306
307 *ctx->target_field = strdup(token);
308 if (!*ctx->target_field)
309 return JSON_OUT_OF_MEMORY;
310
311 ctx->target_field = NULL;
312 ctx->target_field_name = NULL;
313 }
314 else
315 {
316 /* otherwise we just ignore it */
317 }
318
319 return JSON_SUCCESS;
320}
@ 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 1367 of file fe-auth-oauth.c.

1368{
1369 const char *env = getenv("PGOAUTHDEBUG");
1370
1371 return (env && strcmp(env, "UNSAFE") == 0);
1372}

Referenced by issuer_from_well_known_uri().

◆ pqClearOAuthToken()

void pqClearOAuthToken ( PGconn conn)

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

1354{
1355 if (!conn->oauth_token)
1356 return;
1357
1360 conn->oauth_token = NULL;
1361}
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 655 of file fe-auth-oauth.c.

656{
658 PGoauthBearerRequest *request = state->async_ctx;
660
661 if (!request->async)
662 {
664 "user-defined OAuth flow provided neither a token nor an async callback");
666 }
667
668 status = request->async(conn, request, &conn->altsock);
669 if (status == PGRES_POLLING_FAILED)
670 {
671 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
672 return status;
673 }
674 else if (status == PGRES_POLLING_OK)
675 {
676 /*
677 * We already have a token, so copy it into the conn. (We can't hold
678 * onto the original string, since it may not be safe for us to free()
679 * it.)
680 */
681 if (!request->token)
682 {
684 "user-defined OAuth flow did not provide a token");
686 }
687
688 conn->oauth_token = strdup(request->token);
689 if (!conn->oauth_token)
690 {
691 libpq_append_conn_error(conn, "out of memory");
693 }
694
695 return PGRES_POLLING_OK;
696 }
697
698 /* The hook wants the client to poll the altsock. Make sure it set one. */
700 {
702 "user-defined OAuth flow did not provide a socket for polling");
704 }
705
706 return status;
707}
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 1032 of file fe-auth-oauth.c.

1033{
1034 /*
1035 * This is the only function that sets conn->oauth_issuer_id. If a
1036 * previous connection attempt has already computed it, don't overwrite it
1037 * or the discovery URI. (There's no reason for them to change once
1038 * they're set, and handle_oauth_sasl_error() will fail the connection if
1039 * the server attempts to switch them on us later.)
1040 */
1041 if (conn->oauth_issuer_id)
1042 return true;
1043
1044 /*---
1045 * To talk to a server, we require the user to provide issuer and client
1046 * identifiers.
1047 *
1048 * While it's possible for an OAuth client to support multiple issuers, it
1049 * requires additional effort to make sure the flows in use are safe -- to
1050 * quote RFC 9207,
1051 *
1052 * OAuth clients that interact with only one authorization server are
1053 * not vulnerable to mix-up attacks. However, when such clients decide
1054 * to add support for a second authorization server in the future, they
1055 * become vulnerable and need to apply countermeasures to mix-up
1056 * attacks.
1057 *
1058 * For now, we allow only one.
1059 */
1061 {
1063 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1064 return false;
1065 }
1066
1067 /*
1068 * oauth_issuer is interpreted differently if it's a well-known discovery
1069 * URI rather than just an issuer identifier.
1070 */
1071 if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
1072 {
1073 /*
1074 * Convert the URI back to an issuer identifier. (This also performs
1075 * validation of the URI format.)
1076 */
1079 if (!conn->oauth_issuer_id)
1080 return false; /* error message already set */
1081
1084 {
1085 libpq_append_conn_error(conn, "out of memory");
1086 return false;
1087 }
1088 }
1089 else
1090 {
1091 /*
1092 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1093 * for the discovery URI.
1094 */
1096 if (!conn->oauth_issuer_id)
1097 {
1098 libpq_append_conn_error(conn, "out of memory");
1099 return false;
1100 }
1101 }
1102
1103 return true;
1104}
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 958 of file fe-auth-oauth.c.

959{
960 int res;
961 PGoauthBearerRequest request = {
963 .scope = conn->oauth_scope,
964 };
965
967
968 /* The client may have overridden the OAuth flow. */
970 if (res > 0)
971 {
972 PGoauthBearerRequest *request_copy;
973
974 if (request.token)
975 {
976 /*
977 * We already have a token, so copy it into the conn. (We can't
978 * hold onto the original string, since it may not be safe for us
979 * to free() it.)
980 */
981 conn->oauth_token = strdup(request.token);
982 if (!conn->oauth_token)
983 {
984 libpq_append_conn_error(conn, "out of memory");
985 goto fail;
986 }
987
988 /* short-circuit */
989 if (request.cleanup)
990 request.cleanup(conn, &request);
991 return true;
992 }
993
994 request_copy = malloc(sizeof(*request_copy));
995 if (!request_copy)
996 {
997 libpq_append_conn_error(conn, "out of memory");
998 goto fail;
999 }
1000
1001 *request_copy = request;
1002
1005 state->async_ctx = request_copy;
1006 }
1007 else if (res < 0)
1008 {
1009 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
1010 goto fail;
1011 }
1012 else if (!use_builtin_flow(conn, state))
1013 {
1014 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1015 goto fail;
1016 }
1017
1018 return true;
1019
1020fail:
1021 if (request.cleanup)
1022 request.cleanup(conn, &request);
1023 return false;
1024}
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 749 of file fe-auth-oauth.c.

750{
751 return false;
752}

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