Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * validator.c
4 : : * Test module for serverside OAuth token validation callbacks
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * src/test/modules/oauth_validator/validator.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : :
14 : : #include "postgres.h"
15 : :
16 : : #include "fmgr.h"
17 : : #include "libpq/oauth.h"
18 : : #include "miscadmin.h"
19 : : #include "utils/guc.h"
20 : : #include "utils/memutils.h"
21 : :
439 dgustafsson@postgres 22 :CBC 109 : PG_MODULE_MAGIC;
23 : :
24 : : static void validator_startup(ValidatorModuleState *state);
25 : : static void validator_shutdown(ValidatorModuleState *state);
26 : : static bool validate_token(const ValidatorModuleState *state,
27 : : const char *token,
28 : : const char *role,
29 : : ValidatorModuleResult *res);
30 : :
31 : : /* Callback implementations (exercise all three) */
32 : : static const OAuthValidatorCallbacks validator_callbacks = {
33 : : PG_OAUTH_VALIDATOR_MAGIC,
34 : :
35 : : .startup_cb = validator_startup,
36 : : .shutdown_cb = validator_shutdown,
37 : : .validate_cb = validate_token
38 : : };
39 : :
40 : : /* GUCs */
41 : : static char *authn_id = NULL;
42 : : static bool authorize_tokens = true;
43 : : static char *error_detail = NULL;
44 : : static bool internal_error = false;
45 : : static bool invalid_hba = false;
46 : :
47 : : /* HBA options */
48 : : static const char *hba_opts[] = {
49 : : "authn_id", /* overrides the default authn_id */
50 : : "log", /* logs an arbitrary string */
51 : : };
52 : :
53 : : /*---
54 : : * Extension entry point. Sets up GUCs for use by tests:
55 : : *
56 : : * - oauth_validator.authn_id Sets the user identifier to return during token
57 : : * validation. Defaults to the username in the
58 : : * startup packet, or the validator.authn_id HBA
59 : : * option if it is set.
60 : : *
61 : : * - oauth_validator.authorize_tokens
62 : : * Sets whether to successfully validate incoming
63 : : * tokens. Defaults to true.
64 : : *
65 : : * - oauth_validator.error_detail
66 : : * Sets an error message to be included as a
67 : : * DETAIL on failure.
68 : : *
69 : : * - oauth_validator.internal_error
70 : : * Reports an internal error to the server.
71 : : */
72 : : void
73 : 109 : _PG_init(void)
74 : : {
75 : 109 : DefineCustomStringVariable("oauth_validator.authn_id",
76 : : "Authenticated identity to use for future connections",
77 : : NULL,
78 : : &authn_id,
79 : : NULL,
80 : : PGC_SIGHUP,
81 : : 0,
82 : : NULL, NULL, NULL);
83 : 109 : DefineCustomBoolVariable("oauth_validator.authorize_tokens",
84 : : "Should tokens be marked valid?",
85 : : NULL,
86 : : &authorize_tokens,
87 : : true,
88 : : PGC_SIGHUP,
89 : : 0,
90 : : NULL, NULL, NULL);
32 jchampion@postgresql 91 :GNC 109 : DefineCustomStringVariable("oauth_validator.error_detail",
92 : : "Error message to print during failures",
93 : : NULL,
94 : : &error_detail,
95 : : NULL,
96 : : PGC_SIGHUP,
97 : : 0,
98 : : NULL, NULL, NULL);
99 : 109 : DefineCustomBoolVariable("oauth_validator.internal_error",
100 : : "Should the validator report an internal error?",
101 : : NULL,
102 : : &internal_error,
103 : : false,
104 : : PGC_SIGHUP,
105 : : 0,
106 : : NULL, NULL, NULL);
28 107 : 109 : DefineCustomBoolVariable("oauth_validator.invalid_hba",
108 : : "Should the validator register an invalid option?",
109 : : NULL,
110 : : &invalid_hba,
111 : : false,
112 : : PGC_SIGHUP,
113 : : 0,
114 : : NULL, NULL, NULL);
115 : :
439 dgustafsson@postgres 116 :CBC 109 : MarkGUCPrefixReserved("oauth_validator");
117 : 109 : }
118 : :
119 : : /*
120 : : * Validator module entry point.
121 : : */
122 : : const OAuthValidatorCallbacks *
123 : 109 : _PG_oauth_validator_module_init(void)
124 : : {
125 : 109 : return &validator_callbacks;
126 : : }
127 : :
128 : : #define PRIVATE_COOKIE ((void *) 13579)
129 : :
130 : : /*
131 : : * Startup callback, to set up private data for the validator.
132 : : */
133 : : static void
134 : 109 : validator_startup(ValidatorModuleState *state)
135 : : {
136 : : /*
137 : : * Make sure the server is correctly setting sversion. (Real modules
138 : : * should not do this; it would defeat upgrade compatibility.)
139 : : */
140 [ - + ]: 109 : if (state->sversion != PG_VERSION_NUM)
439 dgustafsson@postgres 141 [ # # ]:UBC 0 : elog(ERROR, "oauth_validator: sversion set to %d", state->sversion);
142 : :
143 : : /*
144 : : * Test the behavior of custom HBA options. Registered options should not
145 : : * be retrievable during startup (we want to discourage modules from
146 : : * relying on the relative order of client connections and the
147 : : * startup_cb).
148 : : */
28 jchampion@postgresql 149 :GNC 109 : RegisterOAuthHBAOptions(state, lengthof(hba_opts), hba_opts);
150 [ + + ]: 327 : for (int i = 0; i < lengthof(hba_opts); i++)
151 : : {
152 [ - + ]: 218 : if (GetOAuthHBAOption(state, hba_opts[i]))
28 jchampion@postgresql 153 [ # # ]:UNC 0 : elog(ERROR,
154 : : "oauth_validator: GetOAuthValidatorOption(\"%s\") was non-NULL during startup_cb",
155 : : hba_opts[i]);
156 : : }
157 : :
28 jchampion@postgresql 158 [ + + ]:GNC 109 : if (invalid_hba)
159 : : {
160 : : /* Register a bad option, which should print a WARNING to the logs. */
161 : 2 : const char *invalid = "bad option name";
162 : :
163 : 2 : RegisterOAuthHBAOptions(state, 1, &invalid);
164 : : }
165 : :
439 dgustafsson@postgres 166 :CBC 109 : state->private_data = PRIVATE_COOKIE;
167 : 109 : }
168 : :
169 : : /*
170 : : * Shutdown callback, to tear down the validator.
171 : : */
172 : : static void
173 : 109 : validator_shutdown(ValidatorModuleState *state)
174 : : {
175 : : /* Check to make sure our private state still exists. */
176 [ - + ]: 109 : if (state->private_data != PRIVATE_COOKIE)
439 dgustafsson@postgres 177 [ # # ]:UBC 0 : elog(PANIC, "oauth_validator: private state cookie changed to %p in shutdown",
178 : : state->private_data);
439 dgustafsson@postgres 179 :CBC 109 : }
180 : :
181 : : /*
182 : : * Validator implementation. Logs the incoming data and authorizes the token by
183 : : * default; the behavior can be modified via the module's GUC and HBA settings.
184 : : */
185 : : static bool
186 : 40 : validate_token(const ValidatorModuleState *state,
187 : : const char *token, const char *role,
188 : : ValidatorModuleResult *res)
189 : : {
190 : : /* Check to make sure our private state still exists. */
191 [ - + ]: 40 : if (state->private_data != PRIVATE_COOKIE)
439 dgustafsson@postgres 192 [ # # ]:UBC 0 : elog(ERROR, "oauth_validator: private state cookie changed to %p in validate",
193 : : state->private_data);
194 : :
28 jchampion@postgresql 195 [ + + ]:GNC 40 : if (GetOAuthHBAOption(state, "log"))
196 [ + - ]: 1 : elog(LOG, "%s", GetOAuthHBAOption(state, "log"));
197 : :
439 dgustafsson@postgres 198 [ + - ]:CBC 40 : elog(LOG, "oauth_validator: token=\"%s\", role=\"%s\"", token, role);
199 [ + - ]: 40 : elog(LOG, "oauth_validator: issuer=\"%s\", scope=\"%s\"",
200 : : MyProcPort->hba->oauth_issuer,
201 : : MyProcPort->hba->oauth_scope);
202 : :
32 jchampion@postgresql 203 :GNC 40 : res->error_detail = error_detail; /* only relevant for failures */
204 [ + + ]: 40 : if (internal_error)
205 : 1 : return false;
206 : :
439 dgustafsson@postgres 207 :CBC 39 : res->authorized = authorize_tokens;
208 [ + + ]: 39 : if (authn_id)
209 : 6 : res->authn_id = pstrdup(authn_id);
28 jchampion@postgresql 210 [ + + ]:GNC 33 : else if (GetOAuthHBAOption(state, "authn_id"))
211 : 1 : res->authn_id = pstrdup(GetOAuthHBAOption(state, "authn_id"));
212 : : else
439 dgustafsson@postgres 213 :CBC 32 : res->authn_id = pstrdup(role);
214 : :
215 : 39 : return true;
216 : : }
|