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