Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * auth-oauth.c
4 : : * Server-side implementation of the SASL OAUTHBEARER mechanism.
5 : : *
6 : : * See the following RFC for more details:
7 : : * - RFC 7628: https://datatracker.ietf.org/doc/html/rfc7628
8 : : *
9 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
10 : : * Portions Copyright (c) 1994, Regents of the University of California
11 : : *
12 : : * src/backend/libpq/auth-oauth.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : : #include "postgres.h"
17 : :
18 : : #include <unistd.h>
19 : : #include <fcntl.h>
20 : :
21 : : #include "common/oauth-common.h"
22 : : #include "fmgr.h"
23 : : #include "lib/stringinfo.h"
24 : : #include "libpq/auth.h"
25 : : #include "libpq/hba.h"
26 : : #include "libpq/oauth.h"
27 : : #include "libpq/sasl.h"
28 : : #include "miscadmin.h"
29 : : #include "storage/fd.h"
30 : : #include "storage/ipc.h"
31 : : #include "utils/json.h"
32 : : #include "utils/varlena.h"
33 : :
34 : : /* GUC */
35 : : char *oauth_validator_libraries_string = NULL;
36 : :
37 : : static void oauth_get_mechanisms(Port *port, StringInfo buf);
38 : : static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
39 : : static int oauth_exchange(void *opaq, const char *input, int inputlen,
40 : : char **output, int *outputlen, const char **logdetail);
41 : :
42 : : static void load_validator_library(const char *libname);
43 : : static void shutdown_validator_library(void *arg);
44 : : static bool check_validator_hba_options(Port *port, const char **logdetail);
45 : :
46 : : static ValidatorModuleState *validator_module_state;
47 : : static const OAuthValidatorCallbacks *ValidatorCallbacks;
48 : :
49 : : static MemoryContext ValidatorMemoryContext;
50 : : static List *ValidatorOptions;
51 : : static bool ValidatorOptionsChecked;
52 : :
53 : : /* Mechanism declaration */
54 : : const pg_be_sasl_mech pg_be_oauth_mech = {
55 : : .get_mechanisms = oauth_get_mechanisms,
56 : : .init = oauth_init,
57 : : .exchange = oauth_exchange,
58 : :
59 : : .max_message_length = PG_MAX_AUTH_TOKEN_LENGTH,
60 : : };
61 : :
62 : : /* Valid states for the oauth_exchange() machine. */
63 : : enum oauth_state
64 : : {
65 : : OAUTH_STATE_INIT = 0,
66 : : OAUTH_STATE_ERROR,
67 : : OAUTH_STATE_ERROR_DISCOVERY,
68 : : OAUTH_STATE_FINISHED,
69 : : };
70 : :
71 : : /* Mechanism callback state. */
72 : : struct oauth_ctx
73 : : {
74 : : enum oauth_state state;
75 : : Port *port;
76 : : const char *issuer;
77 : : const char *scope;
78 : : };
79 : :
80 : : static char *sanitize_char(char c);
81 : : static char *parse_kvpairs_for_auth(char **input);
82 : : static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
83 : : static bool validate(Port *port, const char *auth, const char **logdetail);
84 : :
85 : : /* Constants seen in an OAUTHBEARER client initial response. */
86 : : #define KVSEP 0x01 /* separator byte for key/value pairs */
87 : : #define AUTH_KEY "auth" /* key containing the Authorization header */
88 : : #define BEARER_SCHEME "Bearer " /* required header scheme (case-insensitive!) */
89 : :
90 : : /*
91 : : * Retrieves the OAUTHBEARER mechanism list (currently a single item).
92 : : *
93 : : * For a full description of the API, see libpq/sasl.h.
94 : : */
95 : : static void
439 dgustafsson@postgres 96 :CBC 119 : oauth_get_mechanisms(Port *port, StringInfo buf)
97 : : {
98 : : /* Only OAUTHBEARER is supported. */
99 : 119 : appendStringInfoString(buf, OAUTHBEARER_NAME);
100 : 119 : appendStringInfoChar(buf, '\0');
101 : 119 : }
102 : :
103 : : /*
104 : : * Initializes mechanism state and loads the configured validator module.
105 : : *
106 : : * For a full description of the API, see libpq/sasl.h.
107 : : */
108 : : static void *
109 : 114 : oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
110 : : {
111 : : struct oauth_ctx *ctx;
112 : :
113 [ - + ]: 114 : if (strcmp(selected_mech, OAUTHBEARER_NAME) != 0)
439 dgustafsson@postgres 114 [ # # ]:UBC 0 : ereport(ERROR,
115 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
116 : : errmsg("client selected an invalid SASL authentication mechanism"));
117 : :
118 : : /* Save our memory context for later use by client API calls. */
28 jchampion@postgresql 119 :GNC 114 : ValidatorMemoryContext = CurrentMemoryContext;
120 : :
146 michael@paquier.xyz 121 : 114 : ctx = palloc0_object(struct oauth_ctx);
122 : :
439 dgustafsson@postgres 123 :CBC 114 : ctx->state = OAUTH_STATE_INIT;
124 : 114 : ctx->port = port;
125 : :
126 [ - + ]: 114 : Assert(port->hba);
127 : 114 : ctx->issuer = port->hba->oauth_issuer;
128 : 114 : ctx->scope = port->hba->oauth_scope;
129 : :
130 : 114 : load_validator_library(port->hba->oauth_validator);
131 : :
132 : 113 : return ctx;
133 : : }
134 : :
135 : : /*
136 : : * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2). This pulls
137 : : * apart the client initial response and validates the Bearer token. It also
138 : : * handles the dummy error response for a failed handshake, as described in
139 : : * Sec. 3.2.3.
140 : : *
141 : : * For a full description of the API, see libpq/sasl.h.
142 : : */
143 : : static int
144 : 191 : oauth_exchange(void *opaq, const char *input, int inputlen,
145 : : char **output, int *outputlen, const char **logdetail)
146 : : {
147 : : char *input_copy;
148 : : char *p;
149 : : char cbind_flag;
150 : : char *auth;
151 : : int status;
152 : :
153 : 191 : struct oauth_ctx *ctx = opaq;
154 : :
155 : 191 : *output = NULL;
156 : 191 : *outputlen = -1;
157 : :
158 : : /*
159 : : * If the client didn't include an "Initial Client Response" in the
160 : : * SASLInitialResponse message, send an empty challenge, to which the
161 : : * client will respond with the same data that usually comes in the
162 : : * Initial Client Response.
163 : : */
164 [ - + ]: 191 : if (input == NULL)
165 : : {
439 dgustafsson@postgres 166 [ # # ]:UBC 0 : Assert(ctx->state == OAUTH_STATE_INIT);
167 : :
168 : 0 : *output = pstrdup("");
169 : 0 : *outputlen = 0;
170 : 0 : return PG_SASL_EXCHANGE_CONTINUE;
171 : : }
172 : :
173 : : /*
174 : : * Check that the input length agrees with the string length of the input.
175 : : */
439 dgustafsson@postgres 176 [ - + ]:CBC 191 : if (inputlen == 0)
439 dgustafsson@postgres 177 [ # # ]:UBC 0 : ereport(ERROR,
178 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
179 : : errmsg("malformed OAUTHBEARER message"),
180 : : errdetail("The message is empty."));
439 dgustafsson@postgres 181 [ - + ]:CBC 191 : if (inputlen != strlen(input))
439 dgustafsson@postgres 182 [ # # ]:UBC 0 : ereport(ERROR,
183 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
184 : : errmsg("malformed OAUTHBEARER message"),
185 : : errdetail("Message length does not match input length."));
186 : :
439 dgustafsson@postgres 187 [ + + - ]:CBC 191 : switch (ctx->state)
188 : : {
189 : 113 : case OAUTH_STATE_INIT:
190 : : /* Handle this case below. */
191 : 113 : break;
192 : :
193 : 78 : case OAUTH_STATE_ERROR:
194 : : case OAUTH_STATE_ERROR_DISCOVERY:
195 : :
196 : : /*
197 : : * Only one response is valid for the client during authentication
198 : : * failure: a single kvsep.
199 : : */
200 [ + - - + ]: 78 : if (inputlen != 1 || *input != KVSEP)
439 dgustafsson@postgres 201 [ # # ]:UBC 0 : ereport(ERROR,
202 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
203 : : errmsg("malformed OAUTHBEARER message"),
204 : : errdetail("Client did not send a kvsep response."));
205 : :
206 : : /*
207 : : * The (failed) handshake is now complete. Don't report discovery
208 : : * requests in the server log unless the log level is high enough.
209 : : */
35 jchampion@postgresql 210 [ + + ]:GNC 78 : if (ctx->state == OAUTH_STATE_ERROR_DISCOVERY)
211 : : {
212 [ + - ]: 69 : ereport(DEBUG1, errmsg("OAuth issuer discovery requested"));
213 : :
214 : 69 : ctx->state = OAUTH_STATE_FINISHED;
215 : 69 : return PG_SASL_EXCHANGE_ABANDONED;
216 : : }
217 : :
218 : : /* We're not in discovery, so this is just a normal auth failure. */
439 dgustafsson@postgres 219 :CBC 9 : ctx->state = OAUTH_STATE_FINISHED;
220 : 9 : return PG_SASL_EXCHANGE_FAILURE;
221 : :
439 dgustafsson@postgres 222 :UBC 0 : default:
223 [ # # ]: 0 : elog(ERROR, "invalid OAUTHBEARER exchange state");
224 : : return PG_SASL_EXCHANGE_FAILURE;
225 : : }
226 : :
227 : : /* Handle the client's initial message. */
439 dgustafsson@postgres 228 :CBC 113 : p = input_copy = pstrdup(input);
229 : :
230 : : /*
231 : : * OAUTHBEARER does not currently define a channel binding (so there is no
232 : : * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a
233 : : * 'y' specifier purely for the remote chance that a future specification
234 : : * could define one; then future clients can still interoperate with this
235 : : * server implementation. 'n' is the expected case.
236 : : */
237 : 113 : cbind_flag = *p;
238 [ - + - ]: 113 : switch (cbind_flag)
239 : : {
439 dgustafsson@postgres 240 :UBC 0 : case 'p':
241 [ # # ]: 0 : ereport(ERROR,
242 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
243 : : errmsg("malformed OAUTHBEARER message"),
244 : : errdetail("The server does not support channel binding for OAuth, but the client message includes channel binding data."));
245 : : break;
246 : :
439 dgustafsson@postgres 247 :CBC 113 : case 'y': /* fall through */
248 : : case 'n':
249 : 113 : p++;
250 [ - + ]: 113 : if (*p != ',')
439 dgustafsson@postgres 251 [ # # ]:UBC 0 : ereport(ERROR,
252 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
253 : : errmsg("malformed OAUTHBEARER message"),
254 : : errdetail("Comma expected, but found character \"%s\".",
255 : : sanitize_char(*p)));
439 dgustafsson@postgres 256 :CBC 113 : p++;
257 : 113 : break;
258 : :
439 dgustafsson@postgres 259 :UBC 0 : default:
260 [ # # ]: 0 : ereport(ERROR,
261 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
262 : : errmsg("malformed OAUTHBEARER message"),
263 : : errdetail("Unexpected channel-binding flag \"%s\".",
264 : : sanitize_char(cbind_flag)));
265 : : }
266 : :
267 : : /*
268 : : * Forbid optional authzid (authorization identity). We don't support it.
269 : : */
439 dgustafsson@postgres 270 [ - + ]:CBC 113 : if (*p == 'a')
439 dgustafsson@postgres 271 [ # # ]:UBC 0 : ereport(ERROR,
272 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
273 : : errmsg("client uses authorization identity, but it is not supported"));
439 dgustafsson@postgres 274 [ - + ]:CBC 113 : if (*p != ',')
439 dgustafsson@postgres 275 [ # # ]:UBC 0 : ereport(ERROR,
276 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
277 : : errmsg("malformed OAUTHBEARER message"),
278 : : errdetail("Unexpected attribute \"%s\" in client-first-message.",
279 : : sanitize_char(*p)));
439 dgustafsson@postgres 280 :CBC 113 : p++;
281 : :
282 : : /* All remaining fields are separated by the RFC's kvsep (\x01). */
283 [ - + ]: 113 : if (*p != KVSEP)
439 dgustafsson@postgres 284 [ # # ]:UBC 0 : ereport(ERROR,
285 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
286 : : errmsg("malformed OAUTHBEARER message"),
287 : : errdetail("Key-value separator expected, but found character \"%s\".",
288 : : sanitize_char(*p)));
439 dgustafsson@postgres 289 :CBC 113 : p++;
290 : :
291 : 113 : auth = parse_kvpairs_for_auth(&p);
292 [ - + ]: 113 : if (!auth)
439 dgustafsson@postgres 293 [ # # ]:UBC 0 : ereport(ERROR,
294 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
295 : : errmsg("malformed OAUTHBEARER message"),
296 : : errdetail("Message does not contain an auth value."));
297 : :
298 : : /* We should be at the end of our message. */
439 dgustafsson@postgres 299 [ - + ]:CBC 113 : if (*p)
439 dgustafsson@postgres 300 [ # # ]:UBC 0 : ereport(ERROR,
301 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
302 : : errmsg("malformed OAUTHBEARER message"),
303 : : errdetail("Message contains additional data after the final terminator."));
304 : :
305 : : /*
306 : : * Make sure all custom HBA options are understood by the validator before
307 : : * continuing, since we couldn't check them during server start/reload.
308 : : */
28 jchampion@postgresql 309 [ + + ]:GNC 113 : if (!check_validator_hba_options(ctx->port, logdetail))
310 : : {
311 : 1 : ctx->state = OAUTH_STATE_FINISHED;
312 : 1 : return PG_SASL_EXCHANGE_FAILURE;
313 : : }
314 : :
35 315 [ + + ]: 112 : if (auth[0] == '\0')
316 : : {
317 : : /*
318 : : * An empty auth value represents a discovery request; the client
319 : : * expects it to fail. Skip validation entirely and move directly to
320 : : * the error response.
321 : : */
322 : 69 : generate_error_response(ctx, output, outputlen);
323 : :
324 : 69 : ctx->state = OAUTH_STATE_ERROR_DISCOVERY;
325 : 69 : status = PG_SASL_EXCHANGE_CONTINUE;
326 : : }
32 327 [ + + ]: 43 : else if (!validate(ctx->port, auth, logdetail))
328 : : {
439 dgustafsson@postgres 329 :CBC 9 : generate_error_response(ctx, output, outputlen);
330 : :
331 : 9 : ctx->state = OAUTH_STATE_ERROR;
332 : 9 : status = PG_SASL_EXCHANGE_CONTINUE;
333 : : }
334 : : else
335 : : {
336 : 33 : ctx->state = OAUTH_STATE_FINISHED;
337 : 33 : status = PG_SASL_EXCHANGE_SUCCESS;
338 : : }
339 : :
340 : : /* Don't let extra copies of the bearer token hang around. */
341 : 111 : explicit_bzero(input_copy, inputlen);
342 : :
343 : 111 : return status;
344 : : }
345 : :
346 : : /*
347 : : * Convert an arbitrary byte to printable form. For error messages.
348 : : *
349 : : * If it's a printable ASCII character, print it as a single character.
350 : : * otherwise, print it in hex.
351 : : *
352 : : * The returned pointer points to a static buffer.
353 : : */
354 : : static char *
439 dgustafsson@postgres 355 :UBC 0 : sanitize_char(char c)
356 : : {
357 : : static char buf[5];
358 : :
359 [ # # # # ]: 0 : if (c >= 0x21 && c <= 0x7E)
360 : 0 : snprintf(buf, sizeof(buf), "'%c'", c);
361 : : else
362 : 0 : snprintf(buf, sizeof(buf), "0x%02x", (unsigned char) c);
363 : 0 : return buf;
364 : : }
365 : :
366 : : /*
367 : : * Performs syntactic validation of a key and value from the initial client
368 : : * response. (Semantic validation of interesting values must be performed
369 : : * later.)
370 : : */
371 : : static void
439 dgustafsson@postgres 372 :CBC 113 : validate_kvpair(const char *key, const char *val)
373 : : {
374 : : /*-----
375 : : * From Sec 3.1:
376 : : * key = 1*(ALPHA)
377 : : */
378 : : static const char *key_allowed_set =
379 : : "abcdefghijklmnopqrstuvwxyz"
380 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
381 : :
382 : : size_t span;
383 : :
384 [ - + ]: 113 : if (!key[0])
439 dgustafsson@postgres 385 [ # # ]:UBC 0 : ereport(ERROR,
386 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
387 : : errmsg("malformed OAUTHBEARER message"),
388 : : errdetail("Message contains an empty key name."));
389 : :
439 dgustafsson@postgres 390 :CBC 113 : span = strspn(key, key_allowed_set);
391 [ - + ]: 113 : if (key[span] != '\0')
439 dgustafsson@postgres 392 [ # # ]:UBC 0 : ereport(ERROR,
393 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
394 : : errmsg("malformed OAUTHBEARER message"),
395 : : errdetail("Message contains an invalid key name."));
396 : :
397 : : /*-----
398 : : * From Sec 3.1:
399 : : * value = *(VCHAR / SP / HTAB / CR / LF )
400 : : *
401 : : * The VCHAR (visible character) class is large; a loop is more
402 : : * straightforward than strspn().
403 : : */
439 dgustafsson@postgres 404 [ + + ]:CBC 833 : for (; *val; ++val)
405 : : {
406 [ + + + - ]: 720 : if (0x21 <= *val && *val <= 0x7E)
407 : 677 : continue; /* VCHAR */
408 : :
409 [ + - ]: 43 : switch (*val)
410 : : {
411 : 43 : case ' ':
412 : : case '\t':
413 : : case '\r':
414 : : case '\n':
415 : 43 : continue; /* SP, HTAB, CR, LF */
416 : :
439 dgustafsson@postgres 417 :UBC 0 : default:
418 [ # # ]: 0 : ereport(ERROR,
419 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
420 : : errmsg("malformed OAUTHBEARER message"),
421 : : errdetail("Message contains an invalid value."));
422 : : }
423 : : }
439 dgustafsson@postgres 424 :CBC 113 : }
425 : :
426 : : /*
427 : : * Consumes all kvpairs in an OAUTHBEARER exchange message. If the "auth" key is
428 : : * found, its value is returned.
429 : : */
430 : : static char *
431 : 113 : parse_kvpairs_for_auth(char **input)
432 : : {
433 : 113 : char *pos = *input;
434 : 113 : char *auth = NULL;
435 : :
436 : : /*----
437 : : * The relevant ABNF, from Sec. 3.1:
438 : : *
439 : : * kvsep = %x01
440 : : * key = 1*(ALPHA)
441 : : * value = *(VCHAR / SP / HTAB / CR / LF )
442 : : * kvpair = key "=" value kvsep
443 : : * ;;gs2-header = See RFC 5801
444 : : * client-resp = (gs2-header kvsep *kvpair kvsep) / kvsep
445 : : *
446 : : * By the time we reach this code, the gs2-header and initial kvsep have
447 : : * already been validated. We start at the beginning of the first kvpair.
448 : : */
449 : :
450 [ + - ]: 226 : while (*pos)
451 : : {
452 : : char *end;
453 : : char *sep;
454 : : char *key;
455 : : char *value;
456 : :
457 : : /*
458 : : * Find the end of this kvpair. Note that input is null-terminated by
459 : : * the SASL code, so the strchr() is bounded.
460 : : */
461 : 226 : end = strchr(pos, KVSEP);
462 [ - + ]: 226 : if (!end)
439 dgustafsson@postgres 463 [ # # ]:UBC 0 : ereport(ERROR,
464 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
465 : : errmsg("malformed OAUTHBEARER message"),
466 : : errdetail("Message contains an unterminated key/value pair."));
439 dgustafsson@postgres 467 :CBC 226 : *end = '\0';
468 : :
469 [ + + ]: 226 : if (pos == end)
470 : : {
471 : : /* Empty kvpair, signifying the end of the list. */
472 : 113 : *input = pos + 1;
473 : 113 : return auth;
474 : : }
475 : :
476 : : /*
477 : : * Find the end of the key name.
478 : : */
479 : 113 : sep = strchr(pos, '=');
480 [ - + ]: 113 : if (!sep)
439 dgustafsson@postgres 481 [ # # ]:UBC 0 : ereport(ERROR,
482 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
483 : : errmsg("malformed OAUTHBEARER message"),
484 : : errdetail("Message contains a key without a value."));
439 dgustafsson@postgres 485 :CBC 113 : *sep = '\0';
486 : :
487 : : /* Both key and value are now safely terminated. */
488 : 113 : key = pos;
489 : 113 : value = sep + 1;
490 : 113 : validate_kvpair(key, value);
491 : :
492 [ + - ]: 113 : if (strcmp(key, AUTH_KEY) == 0)
493 : : {
494 [ - + ]: 113 : if (auth)
439 dgustafsson@postgres 495 [ # # ]:UBC 0 : ereport(ERROR,
496 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
497 : : errmsg("malformed OAUTHBEARER message"),
498 : : errdetail("Message contains multiple auth values."));
499 : :
439 dgustafsson@postgres 500 :CBC 113 : auth = value;
501 : : }
502 : : else
503 : : {
504 : : /*
505 : : * The RFC also defines the host and port keys, but they are not
506 : : * required for OAUTHBEARER and we do not use them. Also, per Sec.
507 : : * 3.1, any key/value pairs we don't recognize must be ignored.
508 : : */
509 : : }
510 : :
511 : : /* Move to the next pair. */
512 : 113 : pos = end + 1;
513 : : }
514 : :
439 dgustafsson@postgres 515 [ # # ]:UBC 0 : ereport(ERROR,
516 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
517 : : errmsg("malformed OAUTHBEARER message"),
518 : : errdetail("Message did not contain a final terminator."));
519 : :
520 : : pg_unreachable();
521 : : return NULL;
522 : : }
523 : :
524 : : /*
525 : : * Builds the JSON response for failed authentication (RFC 7628, Sec. 3.2.2).
526 : : * This contains the required scopes for entry and a pointer to the OAuth/OpenID
527 : : * discovery document, which the client may use to conduct its OAuth flow.
528 : : */
529 : : static void
439 dgustafsson@postgres 530 :CBC 78 : generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
531 : : {
532 : : StringInfoData buf;
533 : : StringInfoData issuer;
534 : :
535 : : /*
536 : : * The admin needs to set an issuer and scope for OAuth to work. There's
537 : : * not really a way to hide this from the user, either, because we can't
538 : : * choose a "default" issuer, so be honest in the failure message. (In
539 : : * practice such configurations are rejected during HBA parsing.)
540 : : */
541 [ + - - + ]: 78 : if (!ctx->issuer || !ctx->scope)
439 dgustafsson@postgres 542 [ # # ]:UBC 0 : ereport(FATAL,
543 : : errcode(ERRCODE_INTERNAL_ERROR),
544 : : errmsg("OAuth is not properly configured for this user"),
545 : : errdetail_log("The issuer and scope parameters must be set in pg_hba.conf."));
546 : :
547 : : /*
548 : : * Build a default .well-known URI based on our issuer, unless the HBA has
549 : : * already provided one.
550 : : */
439 dgustafsson@postgres 551 :CBC 78 : initStringInfo(&issuer);
552 : 78 : appendStringInfoString(&issuer, ctx->issuer);
553 [ + + ]: 78 : if (strstr(ctx->issuer, "/.well-known/") == NULL)
554 : 75 : appendStringInfoString(&issuer, "/.well-known/openid-configuration");
555 : :
556 : 78 : initStringInfo(&buf);
557 : :
558 : : /*
559 : : * Escaping the string here is belt-and-suspenders defensive programming
560 : : * since escapable characters aren't valid in either the issuer URI or the
561 : : * scope list, but the HBA doesn't enforce that yet.
562 : : */
563 : 78 : appendStringInfoString(&buf, "{ \"status\": \"invalid_token\", ");
564 : :
565 : 78 : appendStringInfoString(&buf, "\"openid-configuration\": ");
566 : 78 : escape_json(&buf, issuer.data);
567 : 78 : pfree(issuer.data);
568 : :
569 : 78 : appendStringInfoString(&buf, ", \"scope\": ");
570 : 78 : escape_json(&buf, ctx->scope);
571 : :
572 : 78 : appendStringInfoString(&buf, " }");
573 : :
574 : 78 : *output = buf.data;
575 : 78 : *outputlen = buf.len;
576 : 78 : }
577 : :
578 : : /*-----
579 : : * Validates the provided Authorization header and returns the token from
580 : : * within it. NULL is returned on validation failure.
581 : : *
582 : : * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
583 : : * 2.1:
584 : : *
585 : : * b64token = 1*( ALPHA / DIGIT /
586 : : * "-" / "." / "_" / "~" / "+" / "/" ) *"="
587 : : * credentials = "Bearer" 1*SP b64token
588 : : *
589 : : * The "credentials" construction is what we receive in our auth value.
590 : : *
591 : : * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
592 : : * header format; RFC 9110 Sec. 11), the "Bearer" scheme string must be
593 : : * compared case-insensitively. (This is not mentioned in RFC 6750, but the
594 : : * OAUTHBEARER spec points it out: RFC 7628 Sec. 4.)
595 : : *
596 : : * Invalid formats are technically a protocol violation, but we shouldn't
597 : : * reflect any information about the sensitive Bearer token back to the
598 : : * client; log at COMMERROR instead.
599 : : */
600 : : static const char *
601 : 43 : validate_token_format(const char *header)
602 : : {
603 : : size_t span;
604 : : const char *token;
605 : : static const char *const b64token_allowed_set =
606 : : "abcdefghijklmnopqrstuvwxyz"
607 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
608 : : "0123456789-._~+/";
609 : :
610 : : /* Missing auth headers should be handled by the caller. */
611 [ - + ]: 43 : Assert(header);
612 : : /* Empty auth (discovery) should be handled before calling validate(). */
35 jchampion@postgresql 613 [ - + ]:GNC 43 : Assert(header[0] != '\0');
614 : :
439 dgustafsson@postgres 615 [ - + ]:CBC 43 : if (pg_strncasecmp(header, BEARER_SCHEME, strlen(BEARER_SCHEME)))
616 : : {
439 dgustafsson@postgres 617 [ # # ]:UBC 0 : ereport(COMMERROR,
618 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
619 : : errmsg("malformed OAuth bearer token"),
620 : : errdetail_log("Client response indicated a non-Bearer authentication scheme."));
621 : 0 : return NULL;
622 : : }
623 : :
624 : : /* Pull the bearer token out of the auth value. */
439 dgustafsson@postgres 625 :CBC 43 : token = header + strlen(BEARER_SCHEME);
626 : :
627 : : /* Swallow any additional spaces. */
628 [ - + ]: 43 : while (*token == ' ')
439 dgustafsson@postgres 629 :UBC 0 : token++;
630 : :
631 : : /* Tokens must not be empty. */
439 dgustafsson@postgres 632 [ + + ]:CBC 43 : if (!*token)
633 : : {
634 [ + - ]: 1 : ereport(COMMERROR,
635 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
636 : : errmsg("malformed OAuth bearer token"),
637 : : errdetail_log("Bearer token is empty."));
638 : 1 : return NULL;
639 : : }
640 : :
641 : : /*
642 : : * Make sure the token contains only allowed characters. Tokens may end
643 : : * with any number of '=' characters.
644 : : */
645 : 42 : span = strspn(token, b64token_allowed_set);
646 [ - + ]: 42 : while (token[span] == '=')
439 dgustafsson@postgres 647 :UBC 0 : span++;
648 : :
439 dgustafsson@postgres 649 [ + + ]:CBC 42 : if (token[span] != '\0')
650 : : {
651 : : /*
652 : : * This error message could be more helpful by printing the
653 : : * problematic character(s), but that'd be a bit like printing a piece
654 : : * of someone's password into the logs.
655 : : */
656 [ + - ]: 1 : ereport(COMMERROR,
657 : : errcode(ERRCODE_PROTOCOL_VIOLATION),
658 : : errmsg("malformed OAuth bearer token"),
659 : : errdetail_log("Bearer token is not in the correct format."));
660 : 1 : return NULL;
661 : : }
662 : :
663 : 41 : return token;
664 : : }
665 : :
666 : : /*
667 : : * Checks that the "auth" kvpair in the client response contains a syntactically
668 : : * valid Bearer token, then passes it along to the loaded validator module for
669 : : * authorization. Returns true if validation succeeds.
670 : : */
671 : : static bool
32 jchampion@postgresql 672 :GNC 43 : validate(Port *port, const char *auth, const char **logdetail)
673 : : {
674 : : int map_status;
675 : : ValidatorModuleResult *ret;
676 : : const char *token;
677 : : bool status;
678 : :
679 : : /* Ensure that we have a correct token to validate */
439 dgustafsson@postgres 680 [ + + ]:CBC 43 : if (!(token = validate_token_format(auth)))
681 : 2 : return false;
682 : :
683 : : /*
684 : : * Ensure that we have a validation library loaded, this should always be
685 : : * the case and an error here is indicative of a bug.
686 : : */
687 [ + - - + ]: 41 : if (!ValidatorCallbacks || !ValidatorCallbacks->validate_cb)
439 dgustafsson@postgres 688 [ # # ]:UBC 0 : ereport(FATAL,
689 : : errcode(ERRCODE_INTERNAL_ERROR),
690 : : errmsg("validation of OAuth token requested without a validator loaded"));
691 : :
692 : : /* Call the validation function from the validator module */
146 michael@paquier.xyz 693 :GNC 41 : ret = palloc0_object(ValidatorModuleResult);
439 dgustafsson@postgres 694 [ + + ]:CBC 40 : if (!ValidatorCallbacks->validate_cb(validator_module_state, token,
695 : 41 : port->user_name, ret))
696 : : {
439 dgustafsson@postgres 697 [ + - + - ]:GBC 1 : ereport(WARNING,
698 : : errcode(ERRCODE_INTERNAL_ERROR),
699 : : errmsg("internal error in OAuth validator module"),
700 : : ret->error_detail ? errdetail_log("%s", ret->error_detail) : 0);
701 : :
32 jchampion@postgresql 702 :GNC 1 : *logdetail = ret->error_detail;
439 dgustafsson@postgres 703 :GBC 1 : return false;
704 : : }
705 : :
706 : : /*
707 : : * Log any authentication results even if the token isn't authorized; it
708 : : * might be useful for auditing or troubleshooting.
709 : : */
439 dgustafsson@postgres 710 [ + - ]:CBC 39 : if (ret->authn_id)
711 : 39 : set_authn_id(port, ret->authn_id);
712 : :
713 [ + + ]: 39 : if (!ret->authorized)
714 : : {
32 jchampion@postgresql 715 [ + + ]:GNC 2 : if (ret->error_detail)
716 : 1 : *logdetail = ret->error_detail;
717 : : else
718 : 1 : *logdetail = _("Validator failed to authorize the provided token.");
719 : :
439 dgustafsson@postgres 720 :CBC 2 : status = false;
721 : 2 : goto cleanup;
722 : : }
723 : :
724 [ + + ]: 37 : if (port->hba->oauth_skip_usermap)
725 : : {
726 : : /*
727 : : * If the validator is our authorization authority, we're done.
728 : : * Authentication may or may not have been performed depending on the
729 : : * validator implementation; all that matters is that the validator
730 : : * says the user can log in with the target role.
731 : : */
732 : 2 : status = true;
733 : 2 : goto cleanup;
734 : : }
735 : :
736 : : /* Make sure the validator authenticated the user. */
737 [ + - + + ]: 35 : if (ret->authn_id == NULL || ret->authn_id[0] == '\0')
738 : : {
32 jchampion@postgresql 739 :GNC 1 : *logdetail = _("Validator provided no identity.");
740 : :
439 dgustafsson@postgres 741 :CBC 1 : status = false;
742 : 1 : goto cleanup;
743 : : }
744 : :
745 : : /* Finally, check the user map. */
746 : 34 : map_status = check_usermap(port->hba->usermap, port->user_name,
747 : : MyClientConnectionInfo.authn_id, false);
748 : 34 : status = (map_status == STATUS_OK);
749 : :
750 : 39 : cleanup:
751 : :
752 : : /*
753 : : * Clear and free the validation result from the validator module once
754 : : * we're done with it.
755 : : */
756 [ + - ]: 39 : if (ret->authn_id != NULL)
757 : 39 : pfree(ret->authn_id);
758 : 39 : pfree(ret);
759 : :
760 : 39 : return status;
761 : : }
762 : :
763 : : /*
764 : : * load_validator_library
765 : : *
766 : : * Load the configured validator library in order to perform token validation.
767 : : * There is no built-in fallback since validation is implementation specific. If
768 : : * no validator library is configured, or if it fails to load, then error out
769 : : * since token validation won't be possible.
770 : : */
771 : : static void
772 : 114 : load_validator_library(const char *libname)
773 : : {
774 : : OAuthValidatorModuleInit validator_init;
775 : : MemoryContextCallback *mcb;
776 : :
777 : : /*
778 : : * The presence, and validity, of libname has already been established by
779 : : * check_oauth_validator so we don't need to perform more than Assert
780 : : * level checking here.
781 : : */
782 [ + - - + ]: 114 : Assert(libname && *libname);
783 : :
784 : 114 : validator_init = (OAuthValidatorModuleInit)
785 : 114 : load_external_function(libname, "_PG_oauth_validator_module_init",
786 : : false, NULL);
787 : :
788 : : /*
789 : : * The validator init function is required since it will set the callbacks
790 : : * for the validator library.
791 : : */
792 [ - + ]: 114 : if (validator_init == NULL)
439 dgustafsson@postgres 793 [ # # ]:UBC 0 : ereport(ERROR,
794 : : errmsg("%s module \"%s\" must define the symbol %s",
795 : : "OAuth validator", libname, "_PG_oauth_validator_module_init"));
796 : :
439 dgustafsson@postgres 797 :CBC 114 : ValidatorCallbacks = (*validator_init) ();
798 [ - + ]: 114 : Assert(ValidatorCallbacks);
799 : :
800 : : /*
801 : : * Check the magic number, to protect against break-glass scenarios where
802 : : * the ABI must change within a major version. load_external_function()
803 : : * already checks for compatibility across major versions.
804 : : */
805 [ + + ]: 114 : if (ValidatorCallbacks->magic != PG_OAUTH_VALIDATOR_MAGIC)
806 [ + - ]: 1 : ereport(ERROR,
807 : : errmsg("%s module \"%s\": magic number mismatch",
808 : : "OAuth validator", libname),
809 : : errdetail("Server has magic number 0x%08X, module has 0x%08X.",
810 : : PG_OAUTH_VALIDATOR_MAGIC, ValidatorCallbacks->magic));
811 : :
812 : : /*
813 : : * Make sure all required callbacks are present in the ValidatorCallbacks
814 : : * structure. Right now only the validation callback is required.
815 : : */
816 [ - + ]: 113 : if (ValidatorCallbacks->validate_cb == NULL)
439 dgustafsson@postgres 817 [ # # ]:UBC 0 : ereport(ERROR,
818 : : errmsg("%s module \"%s\" must provide a %s callback",
819 : : "OAuth validator", libname, "validate_cb"));
820 : :
821 : : /* Allocate memory for validator library private state data */
146 michael@paquier.xyz 822 :GNC 113 : validator_module_state = palloc0_object(ValidatorModuleState);
439 dgustafsson@postgres 823 :CBC 113 : validator_module_state->sversion = PG_VERSION_NUM;
824 : :
825 [ + + ]: 113 : if (ValidatorCallbacks->startup_cb != NULL)
826 : 111 : ValidatorCallbacks->startup_cb(validator_module_state);
827 : :
828 : : /* Shut down the library before cleaning up its state. */
146 michael@paquier.xyz 829 :GNC 113 : mcb = palloc0_object(MemoryContextCallback);
439 dgustafsson@postgres 830 :CBC 113 : mcb->func = shutdown_validator_library;
831 : :
832 : 113 : MemoryContextRegisterResetCallback(CurrentMemoryContext, mcb);
833 : 113 : }
834 : :
835 : : /*
836 : : * Call the validator module's shutdown callback, if one is provided. This is
837 : : * invoked during memory context reset.
838 : : */
839 : : static void
840 : 113 : shutdown_validator_library(void *arg)
841 : : {
842 [ + + ]: 113 : if (ValidatorCallbacks->shutdown_cb != NULL)
843 : 111 : ValidatorCallbacks->shutdown_cb(validator_module_state);
844 : :
845 : : /* The backing memory for this is about to disappear. */
28 jchampion@postgresql 846 :GNC 113 : ValidatorOptions = NIL;
439 dgustafsson@postgres 847 :CBC 113 : }
848 : :
849 : : /*
850 : : * Ensure an OAuth validator named in the HBA is permitted by the configuration.
851 : : *
852 : : * If the validator is currently unset and exactly one library is declared in
853 : : * oauth_validator_libraries, then that library will be used as the validator.
854 : : * Otherwise the name must be present in the list of oauth_validator_libraries.
855 : : */
856 : : bool
857 : 39 : check_oauth_validator(HbaLine *hbaline, int elevel, char **err_msg)
858 : : {
859 : 39 : int line_num = hbaline->linenumber;
860 : 39 : const char *file_name = hbaline->sourcefile;
861 : : char *rawstring;
862 : 39 : List *elemlist = NIL;
863 : :
864 : 39 : *err_msg = NULL;
865 : :
866 [ - + ]: 39 : if (oauth_validator_libraries_string[0] == '\0')
867 : : {
439 dgustafsson@postgres 868 [ # # ]:UBC 0 : ereport(elevel,
869 : : errcode(ERRCODE_CONFIG_FILE_ERROR),
870 : : errmsg("oauth_validator_libraries must be set for authentication method %s",
871 : : "oauth"),
872 : : errcontext("line %d of configuration file \"%s\"",
873 : : line_num, file_name));
874 : 0 : *err_msg = psprintf("oauth_validator_libraries must be set for authentication method %s",
875 : : "oauth");
876 : 0 : return false;
877 : : }
878 : :
879 : : /* SplitDirectoriesString needs a modifiable copy */
439 dgustafsson@postgres 880 :CBC 39 : rawstring = pstrdup(oauth_validator_libraries_string);
881 : :
882 [ - + ]: 39 : if (!SplitDirectoriesString(rawstring, ',', &elemlist))
883 : : {
884 : : /* syntax error in list */
439 dgustafsson@postgres 885 [ # # ]:UBC 0 : ereport(elevel,
886 : : errcode(ERRCODE_CONFIG_FILE_ERROR),
887 : : errmsg("invalid list syntax in parameter \"%s\"",
888 : : "oauth_validator_libraries"));
889 : 0 : *err_msg = psprintf("invalid list syntax in parameter \"%s\"",
890 : : "oauth_validator_libraries");
891 : 0 : goto done;
892 : : }
893 : :
439 dgustafsson@postgres 894 [ + + ]:CBC 39 : if (!hbaline->oauth_validator)
895 : : {
896 [ + + ]: 36 : if (elemlist->length == 1)
897 : : {
898 : 35 : hbaline->oauth_validator = pstrdup(linitial(elemlist));
899 : 35 : goto done;
900 : : }
901 : :
902 [ + - ]: 1 : ereport(elevel,
903 : : errcode(ERRCODE_CONFIG_FILE_ERROR),
904 : : errmsg("authentication method \"oauth\" requires argument \"validator\" to be set when oauth_validator_libraries contains multiple options"),
905 : : errcontext("line %d of configuration file \"%s\"",
906 : : line_num, file_name));
907 : 1 : *err_msg = "authentication method \"oauth\" requires argument \"validator\" to be set when oauth_validator_libraries contains multiple options";
908 : 1 : goto done;
909 : : }
910 : :
911 [ + - + - : 4 : foreach_ptr(char, allowed, elemlist)
+ - ]
912 : : {
913 [ + + ]: 4 : if (strcmp(allowed, hbaline->oauth_validator) == 0)
914 : 3 : goto done;
915 : : }
916 : :
439 dgustafsson@postgres 917 [ # # ]:UBC 0 : ereport(elevel,
918 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
919 : : errmsg("validator \"%s\" is not permitted by %s",
920 : : hbaline->oauth_validator, "oauth_validator_libraries"),
921 : : errcontext("line %d of configuration file \"%s\"",
922 : : line_num, file_name));
923 : 0 : *err_msg = psprintf("validator \"%s\" is not permitted by %s",
924 : : hbaline->oauth_validator, "oauth_validator_libraries");
925 : :
439 dgustafsson@postgres 926 :CBC 39 : done:
927 : 39 : list_free_deep(elemlist);
928 : 39 : pfree(rawstring);
929 : :
930 : 39 : return (*err_msg == NULL);
931 : : }
932 : :
933 : : /*
934 : : * Client APIs for validator implementations
935 : : *
936 : : * Since we're currently not threaded, we only allow one validator in the
937 : : * process at a time. So we can make use of globals for now instead of looking
938 : : * up information using the state pointer. We probably shouldn't assume that the
939 : : * module hasn't temporarily changed memory contexts on us, though; functions
940 : : * here should defensively use an appropriate context when making global
941 : : * allocations.
942 : : */
943 : :
944 : : /*
945 : : * Adds to the list of allowed validator.* HBA options. Used during the
946 : : * startup_cb.
947 : : */
948 : : void
28 jchampion@postgresql 949 :GNC 113 : RegisterOAuthHBAOptions(ValidatorModuleState *state, int num,
950 : : const char *opts[])
951 : : {
952 : : MemoryContext oldcontext;
953 : :
954 [ - + ]: 113 : if (!state)
955 : : {
28 jchampion@postgresql 956 :UNC 0 : Assert(false);
957 : : return;
958 : : }
959 : :
28 jchampion@postgresql 960 :GNC 113 : oldcontext = MemoryContextSwitchTo(ValidatorMemoryContext);
961 : :
962 [ + + ]: 337 : for (int i = 0; i < num; i++)
963 : : {
964 [ + + ]: 224 : if (!valid_oauth_hba_option_name(opts[i]))
965 : : {
966 : : /*
967 : : * The user can't set this option in the HBA, so GetOAuthHBAOption
968 : : * would always return NULL.
969 : : */
970 [ + - ]: 2 : ereport(WARNING,
971 : : errmsg("HBA option name \"%s\" is invalid and will be ignored",
972 : : opts[i]),
973 : : /* translator: the second %s is a function name */
974 : : errcontext("validator module \"%s\", in call to %s",
975 : : MyProcPort->hba->oauth_validator,
976 : : "RegisterOAuthHBAOptions"));
977 : 2 : continue;
978 : : }
979 : :
980 : 222 : ValidatorOptions = lappend(ValidatorOptions, pstrdup(opts[i]));
981 : : }
982 : :
983 : 113 : MemoryContextSwitchTo(oldcontext);
984 : :
985 : : /*
986 : : * Wait to validate the HBA against the registered options until later
987 : : * (see check_validator_hba_options()).
988 : : *
989 : : * Delaying allows the validator to make multiple registration calls, to
990 : : * append to the list; it lets us make the check in a place where we can
991 : : * report the error without leaking details to the client; and it avoids
992 : : * exporting the order of operations between HBA matching and the
993 : : * startup_cb call as an API guarantee. (The last issue may become
994 : : * relevant with a threaded model.)
995 : : */
996 : : }
997 : :
998 : : /*
999 : : * Restrict the names available to custom HBA options, so that we don't
1000 : : * accidentally prevent future syntax extensions to HBA files.
1001 : : */
1002 : : bool
1003 : 233 : valid_oauth_hba_option_name(const char *name)
1004 : : {
1005 : : /*
1006 : : * This list is not incredibly principled, since the goal is just to bound
1007 : : * compatibility guarantees for our HBA parser. Alphanumerics seem
1008 : : * obviously fine, and it's difficult to argue against the punctuation
1009 : : * that's already included in some HBA option names and identifiers.
1010 : : */
1011 : : static const char *name_allowed_set =
1012 : : "abcdefghijklmnopqrstuvwxyz"
1013 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1014 : : "0123456789_-";
1015 : :
1016 : : size_t span;
1017 : :
1018 [ + + ]: 233 : if (!name[0])
1019 : 1 : return false;
1020 : :
1021 : 232 : span = strspn(name, name_allowed_set);
1022 : 232 : return name[span] == '\0';
1023 : : }
1024 : :
1025 : : /*
1026 : : * Verifies that all validator.* HBA options specified by the user were actually
1027 : : * registered by the validator library in use.
1028 : : */
1029 : : static bool
1030 : 113 : check_validator_hba_options(Port *port, const char **logdetail)
1031 : : {
1032 : 113 : HbaLine *hba = port->hba;
1033 : :
1034 [ + + + + : 232 : foreach_ptr(char, key, hba->oauth_opt_keys)
+ + ]
1035 : : {
1036 : 8 : bool found = false;
1037 : :
1038 : : /* O(n^2) shouldn't be a problem here in practice. */
1039 [ + - + + : 21 : foreach_ptr(char, optname, ValidatorOptions)
+ + ]
1040 : : {
1041 [ + + ]: 12 : if (strcmp(key, optname) == 0)
1042 : : {
1043 : 7 : found = true;
1044 : 7 : break;
1045 : : }
1046 : : }
1047 : :
1048 [ + + ]: 8 : if (!found)
1049 : : {
1050 : : /*
1051 : : * Unknown option name. Mirror the error messages in hba.c here,
1052 : : * keeping in mind that the original "validator." prefix was
1053 : : * stripped from the key during parsing.
1054 : : *
1055 : : * Since this is affecting live connections, which is unusual for
1056 : : * HBA, be noisy with a WARNING. (Warnings aren't sent to clients
1057 : : * prior to successful authentication, so this won't disclose the
1058 : : * server config.) It'll duplicate some of the information in the
1059 : : * logdetail, but that should make it hard to miss the connection
1060 : : * between the two.
1061 : : */
1062 : 1 : char *name = psprintf("validator.%s", key);
1063 : :
1064 : 1 : *logdetail = psprintf(_("unrecognized authentication option name: \"%s\""),
1065 : : name);
1066 [ + - ]: 1 : ereport(WARNING,
1067 : : errcode(ERRCODE_CONFIG_FILE_ERROR),
1068 : : errmsg("unrecognized authentication option name: \"%s\"",
1069 : : name),
1070 : : /* translator: the first %s is the name of the module */
1071 : : errdetail("The installed validator module (\"%s\") did not define an option named \"%s\".",
1072 : : hba->oauth_validator, key),
1073 : : errhint("All OAuth connections matching this line will fail. Correct the option and reload the server configuration."),
1074 : : errcontext("line %d of configuration file \"%s\"",
1075 : : hba->linenumber, hba->sourcefile));
1076 : :
1077 : 1 : return false;
1078 : : }
1079 : : }
1080 : :
1081 : 112 : ValidatorOptionsChecked = true; /* unfetter GetOAuthHBAOption() */
1082 : 112 : return true;
1083 : : }
1084 : :
1085 : : /*
1086 : : * Retrieves the setting for a validator.* HBA option, or NULL if not found.
1087 : : * This may only be used during the validate_cb and shutdown_cb.
1088 : : */
1089 : : const char *
1090 : 297 : GetOAuthHBAOption(const ValidatorModuleState *state, const char *optname)
1091 : : {
1092 : 297 : HbaLine *hba = MyProcPort->hba;
1093 : : ListCell *lc_k;
1094 : : ListCell *lc_v;
1095 : 297 : const char *ret = NULL;
1096 : :
1097 [ + + ]: 297 : if (!ValidatorOptionsChecked)
1098 : : {
1099 : : /*
1100 : : * Prevent the startup_cb from retrieving HBA options that it has just
1101 : : * registered. This probably seems strange -- why refuse to hand out
1102 : : * information we already know? -- but this lets us reserve the
1103 : : * ability to perform the startup_cb call earlier, before we know
1104 : : * which HBA line is matched by a connection, without breaking this
1105 : : * API.
1106 : : */
1107 : 222 : return NULL;
1108 : : }
1109 : :
1110 [ + - - + ]: 75 : if (!state || !hba)
1111 : : {
28 jchampion@postgresql 1112 :UNC 0 : Assert(false);
1113 : : return NULL;
1114 : : }
1115 : :
28 jchampion@postgresql 1116 [ - + ]:GNC 75 : Assert(list_length(hba->oauth_opt_keys) == list_length(hba->oauth_opt_vals));
1117 : :
1118 [ + + + + : 84 : forboth(lc_k, hba->oauth_opt_keys, lc_v, hba->oauth_opt_vals)
+ + + + +
+ + - +
+ ]
1119 : : {
1120 : 9 : const char *key = lfirst(lc_k);
1121 : 9 : const char *val = lfirst(lc_v);
1122 : :
1123 [ + + ]: 9 : if (strcmp(key, optname) == 0)
1124 : : {
1125 : : /*
1126 : : * Don't return yet -- when regular HBA options are specified more
1127 : : * than once, the last one wins. Do the same for these options.
1128 : : */
1129 : 6 : ret = val;
1130 : : }
1131 : : }
1132 : :
1133 : 75 : return ret;
1134 : : }
|