PostgreSQL Source Code git master
oauth_server.OAuthHandler Class Reference

Public Member Functions

def do_GET (self)
 
str client_id (self)
 
def do_POST (self)
 
JsonObject config (self)
 
JsonObject authorization (self)
 
JsonObject token (self)
 

Data Fields

 path
 

Static Public Attributes

 JsonObject = Dict[str, object]
 

Private Member Functions

def _check_issuer (self)
 
def _check_authn (self)
 
Dict[str, str_parse_params (self)
 
bool _should_modify (self)
 
def _get_param (self, name, default)
 
str _content_type (self)
 
int _interval (self)
 
str _retry_code (self)
 
str _uri_spelling (self)
 
def _response_padding (self)
 
def _access_token (self)
 
None _send_json (self, JsonObject js)
 
def _token_state (self)
 
def _remove_token_state (self)
 

Private Attributes

 _alt_issuer
 
 _parameterized
 
 _response_code
 
 _params
 
 _test_params
 

Detailed Description

Core implementation of the authorization server. The API is
inheritance-based, with entry points at do_GET() and do_POST(). See the
documentation for BaseHTTPRequestHandler.

Definition at line 20 of file oauth_server.py.

Member Function Documentation

◆ _access_token()

def oauth_server.OAuthHandler._access_token (   self)
private
The actual Bearer token sent back to the client on success. Tests may
override this with the "token" test parameter.

Definition at line 226 of file oauth_server.py.

226 def _access_token(self):
227 """
228 The actual Bearer token sent back to the client on success. Tests may
229 override this with the "token" test parameter.
230 """
231 token = self._get_param("token", None)
232 if token is not None:
233 return token
234
235 token = "9243959234"
236 if self._alt_issuer:
237 token += "-alt"
238
239 return token
240

References oauth_server.OAuthHandler._alt_issuer, and oauth_server.OAuthHandler._get_param().

Referenced by oauth_server.OAuthHandler.token().

◆ _check_authn()

def oauth_server.OAuthHandler._check_authn (   self)
private
Checks the expected value of the Authorization header, if any.

Definition at line 50 of file oauth_server.py.

50 def _check_authn(self):
51 """
52 Checks the expected value of the Authorization header, if any.
53 """
54 secret = self._get_param("expected_secret", None)
55 if secret is None:
56 return
57
58 assert "Authorization" in self.headers
59 method, creds = self.headers["Authorization"].split()
60
61 if method != "Basic":
62 raise RuntimeError(f"client used {method} auth; expected Basic")
63
64 # TODO: Remove "~" from the safe list after Py3.6 support is removed.
65 # 3.7 does this by default.
66 username = urllib.parse.quote_plus(self.client_id, safe="~")
67 password = urllib.parse.quote_plus(secret, safe="~")
68 expected_creds = f"{username}:{password}"
69
70 if creds.encode() != base64.b64encode(expected_creds.encode()):
71 raise RuntimeError(
72 f"client sent '{creds}'; expected b64encode('{expected_creds}')"
73 )
74

References oauth_server.OAuthHandler._get_param(), oauth_server.OAuthHandler.client_id(), printTableContent.headers, and async_ctx.headers.

◆ _check_issuer()

def oauth_server.OAuthHandler._check_issuer (   self)
private
Switches the behavior of the provider depending on the issuer URI.

Definition at line 29 of file oauth_server.py.

29 def _check_issuer(self):
30 """
31 Switches the behavior of the provider depending on the issuer URI.
32 """
33 self._alt_issuer = (
34 self.path.startswith("/alternate/")
35 or self.path == "/.well-known/oauth-authorization-server/alternate"
36 )
37 self._parameterized = self.path.startswith("/param/")
38
39 # Strip off the magic path segment. (The more readable
40 # str.removeprefix()/removesuffix() aren't available until Py3.9.)
41 if self._alt_issuer:
42 # The /alternate issuer uses IETF-style .well-known URIs.
43 if self.path.startswith("/.well-known/"):
44 self.path = self.path[: -len("/alternate")]
45 else:
46 self.path = self.path[len("/alternate") :]
47 elif self._parameterized:
48 self.path = self.path[len("/param") :]
49
const void size_t len

Referenced by oauth_server.OAuthHandler.do_POST().

◆ _content_type()

str oauth_server.OAuthHandler._content_type (   self)
private
Returns "application/json" unless the test has requested something
different.

Definition at line 183 of file oauth_server.py.

183 def _content_type(self) -> str:
184 """
185 Returns "application/json" unless the test has requested something
186 different.
187 """
188 return self._get_param("content_type", "application/json")
189

References oauth_server.OAuthHandler._get_param().

Referenced by oauth_server.OAuthHandler._send_json().

◆ _get_param()

def oauth_server.OAuthHandler._get_param (   self,
  name,
  default 
)
private
If the client has requested a modification to this stage (see
_should_modify()), this method searches the provided test parameters for
a key of the given name, and returns it if found. Otherwise the provided
default is returned.

Definition at line 170 of file oauth_server.py.

170 def _get_param(self, name, default):
171 """
172 If the client has requested a modification to this stage (see
173 _should_modify()), this method searches the provided test parameters for
174 a key of the given name, and returns it if found. Otherwise the provided
175 default is returned.
176 """
177 if self._should_modify() and name in self._test_params:
178 return self._test_params[name]
179
180 return default
181

References oauth_server.OAuthHandler._should_modify(), and oauth_server.OAuthHandler._test_params.

Referenced by oauth_server.OAuthHandler._access_token(), oauth_server.OAuthHandler._check_authn(), oauth_server.OAuthHandler._content_type(), oauth_server.OAuthHandler._interval(), oauth_server.OAuthHandler._response_padding(), oauth_server.OAuthHandler._retry_code(), oauth_server.OAuthHandler._uri_spelling(), and oauth_server.OAuthHandler.token().

◆ _interval()

int oauth_server.OAuthHandler._interval (   self)
private
Returns 0 unless the test has requested something different.

Definition at line 191 of file oauth_server.py.

191 def _interval(self) -> int:
192 """
193 Returns 0 unless the test has requested something different.
194 """
195 return self._get_param("interval", 0)
196

References oauth_server.OAuthHandler._get_param().

Referenced by oauth_server.OAuthHandler.authorization().

◆ _parse_params()

Dict[str, str] oauth_server.OAuthHandler._parse_params (   self)
private
Parses apart the form-urlencoded request body and returns the resulting
dict. For use by do_POST().

Definition at line 91 of file oauth_server.py.

91 def _parse_params(self) -> Dict[str, str]:
92 """
93 Parses apart the form-urlencoded request body and returns the resulting
94 dict. For use by do_POST().
95 """
96 size = int(self.headers["Content-Length"])
97 form = self.rfile.read(size)
98
99 assert self.headers["Content-Type"] == "application/x-www-form-urlencoded"
100 return urllib.parse.parse_qs(
101 form.decode("utf-8"),
102 strict_parsing=True,
103 keep_blank_values=True,
104 encoding="utf-8",
105 errors="strict",
106 )
107
#define read(a, b, c)
Definition: win32.h:13

References oauth_server.OAuthHandler.do_POST(), printTableContent.headers, async_ctx.headers, and read.

Referenced by oauth_server.OAuthHandler.client_id().

◆ _remove_token_state()

def oauth_server.OAuthHandler._remove_token_state (   self)
private
Removes any cached _TokenState for the current client_id. Call this
after the token exchange ends to get rid of unnecessary state.

Definition at line 289 of file oauth_server.py.

289 def _remove_token_state(self):
290 """
291 Removes any cached _TokenState for the current client_id. Call this
292 after the token exchange ends to get rid of unnecessary state.
293 """
294 if self.client_id in self.server.token_state:
295 del self.server.token_state[self.client_id]
296

References oauth_server.OAuthHandler.client_id(), and PgFdwRelationInfo.server.

Referenced by oauth_server.OAuthHandler.token().

◆ _response_padding()

def oauth_server.OAuthHandler._response_padding (   self)
private
If the huge_response test parameter is set to True, returns a dict
containing a gigantic string value, which can then be folded into a JSON
response.

Definition at line 214 of file oauth_server.py.

214 def _response_padding(self):
215 """
216 If the huge_response test parameter is set to True, returns a dict
217 containing a gigantic string value, which can then be folded into a JSON
218 response.
219 """
220 if not self._get_param("huge_response", False):
221 return dict()
222
223 return {"_pad_": "x" * 1024 * 1024}
224

References oauth_server.OAuthHandler._get_param().

Referenced by oauth_server.OAuthHandler.authorization(), and oauth_server.OAuthHandler.token().

◆ _retry_code()

str oauth_server.OAuthHandler._retry_code (   self)
private
Returns "authorization_pending" unless the test has requested something
different.

Definition at line 198 of file oauth_server.py.

198 def _retry_code(self) -> str:
199 """
200 Returns "authorization_pending" unless the test has requested something
201 different.
202 """
203 return self._get_param("retry_code", "authorization_pending")
204

References oauth_server.OAuthHandler._get_param().

Referenced by oauth_server.OAuthHandler.token().

◆ _send_json()

None oauth_server.OAuthHandler._send_json (   self,
JsonObject  js 
)
private
Sends the provided JSON dict as an application/json response.
self._response_code can be modified to send JSON error responses.

Definition at line 241 of file oauth_server.py.

241 def _send_json(self, js: JsonObject) -> None:
242 """
243 Sends the provided JSON dict as an application/json response.
244 self._response_code can be modified to send JSON error responses.
245 """
246 resp = json.dumps(js).encode("ascii")
247 self.log_message("sending JSON response: %s", resp)
248
249 self.send_response(self._response_code)
250 self.send_header("Content-Type", self._content_type)
251 self.send_header("Content-Length", str(len(resp)))
252 self.end_headers()
253
254 self.wfile.write(resp)
255
const char * str
#define write(a, b, c)
Definition: win32.h:14

References oauth_server.OAuthHandler._content_type(), oauth_server.OAuthHandler._response_code, len, str, and write.

◆ _should_modify()

bool oauth_server.OAuthHandler._should_modify (   self)
private
Returns True if the client has requested a modification to this stage of
the exchange.

Definition at line 150 of file oauth_server.py.

150 def _should_modify(self) -> bool:
151 """
152 Returns True if the client has requested a modification to this stage of
153 the exchange.
154 """
155 if not hasattr(self, "_test_params"):
156 return False
157
158 stage = self._test_params.get("stage")
159
160 return (
161 stage == "all"
162 or (
163 stage == "discovery"
164 and self.path == "/.well-known/openid-configuration"
165 )
166 or (stage == "device" and self.path == "/authorize")
167 or (stage == "token" and self.path == "/token")
168 )
169

References oauth_server.OAuthHandler._test_params, RewriteMappingFile.path, backup_file_entry.path, PathClauseUsage.path, JsonTablePlanState.path, keepwal_entry.path, file_entry_t.path, fetch_range_request.path, UpgradeTaskReport.path, tablespaceinfo.path, IndexPath.path, BitmapHeapPath.path, BitmapAndPath.path, BitmapOrPath.path, TidPath.path, TidRangePath.path, SubqueryScanPath.path, ForeignPath.path, CustomPath.path, AppendPath.path, MergeAppendPath.path, GroupResultPath.path, MaterialPath.path, MemoizePath.path, UniquePath.path, GatherPath.path, GatherMergePath.path, ProjectionPath.path, ProjectSetPath.path, SortPath.path, GroupPath.path, UpperUniquePath.path, AggPath.path, GroupingSetsPath.path, MinMaxAggPath.path, WindowAggPath.path, SetOpPath.path, RecursiveUnionPath.path, LockRowsPath.path, ModifyTablePath.path, LimitPath.path, MinMaxAggInfo.path, JsonTablePathScan.path, MemoryStatsEntry.path, _include_path.path, and oauth_server.OAuthHandler.path.

Referenced by oauth_server.OAuthHandler._get_param(), and oauth_server.OAuthHandler.token().

◆ _token_state()

def oauth_server.OAuthHandler._token_state (   self)
private
A cached _TokenState object for the connected client (as determined by
the request's client_id), or a new one if it doesn't already exist.

This relies on the existence of a defaultdict attached to the server;
see main() below.

Definition at line 279 of file oauth_server.py.

279 def _token_state(self):
280 """
281 A cached _TokenState object for the connected client (as determined by
282 the request's client_id), or a new one if it doesn't already exist.
283
284 This relies on the existence of a defaultdict attached to the server;
285 see main() below.
286 """
287 return self.server.token_state[self.client_id]
288

References oauth_server.OAuthHandler.client_id(), oauth_server.main(), and PgFdwRelationInfo.server.

Referenced by oauth_server.OAuthHandler.authorization(), and oauth_server.OAuthHandler.token().

◆ _uri_spelling()

str oauth_server.OAuthHandler._uri_spelling (   self)
private
Returns "verification_uri" unless the test has requested something
different.

Definition at line 206 of file oauth_server.py.

206 def _uri_spelling(self) -> str:
207 """
208 Returns "verification_uri" unless the test has requested something
209 different.
210 """
211 return self._get_param("uri_spelling", "verification_uri")
212

References oauth_server.OAuthHandler._get_param().

Referenced by oauth_server.OAuthHandler.authorization().

◆ authorization()

JsonObject oauth_server.OAuthHandler.authorization (   self)

Definition at line 297 of file oauth_server.py.

297 def authorization(self) -> JsonObject:
298 uri = "https://example.com/"
299 if self._alt_issuer:
300 uri = "https://example.org/"
301
302 resp = {
303 "device_code": "postgres",
304 "user_code": "postgresuser",
305 self._uri_spelling: uri,
306 "expires_in": 5,
307 **self._response_padding,
308 }
309
310 interval = self._interval
311 if interval is not None:
312 resp["interval"] = interval
313 self._token_state.min_delay = interval
314 else:
315 self._token_state.min_delay = 5 # default
316
317 # Check the scope.
318 if "scope" in self._params:
319 assert self._params["scope"][0], "empty scopes should be omitted"
320
321 return resp
322

References oauth_server.OAuthHandler._alt_issuer, oauth_server.OAuthHandler._interval(), oauth_server.OAuthHandler._params, oauth_server.OAuthHandler._response_padding(), oauth_server.OAuthHandler._token_state(), and oauth_server.OAuthHandler._uri_spelling().

◆ client_id()

str oauth_server.OAuthHandler.client_id (   self)
Returns the client_id sent in the POST body or the Authorization header.
self._parse_params() must have been called first.

Definition at line 109 of file oauth_server.py.

109 def client_id(self) -> str:
110 """
111 Returns the client_id sent in the POST body or the Authorization header.
112 self._parse_params() must have been called first.
113 """
114 if "client_id" in self._params:
115 return self._params["client_id"][0]
116
117 if "Authorization" not in self.headers:
118 raise RuntimeError("client did not send any client_id")
119
120 _, creds = self.headers["Authorization"].split()
121
122 decoded = base64.b64decode(creds).decode("utf-8")
123 username, _ = decoded.split(":", 1)
124
125 return urllib.parse.unquote_plus(username)
126

References oauth_server.OAuthHandler._params, oauth_server.OAuthHandler._parse_params(), printTableContent.headers, and async_ctx.headers.

Referenced by oauth_server.OAuthHandler._check_authn(), oauth_server.OAuthHandler._remove_token_state(), and oauth_server.OAuthHandler._token_state().

◆ config()

JsonObject oauth_server.OAuthHandler.config (   self)

Definition at line 256 of file oauth_server.py.

256 def config(self) -> JsonObject:
257 port = self.server.socket.getsockname()[1]
258
259 issuer = f"http://127.0.0.1:{port}"
260 if self._alt_issuer:
261 issuer += "/alternate"
262 elif self._parameterized:
263 issuer += "/param"
264
265 return {
266 "issuer": issuer,
267 "token_endpoint": issuer + "/token",
268 "device_authorization_endpoint": issuer + "/authorize",
269 "response_types_supported": ["token"],
270 "subject_types_supported": ["public"],
271 "id_token_signing_alg_values_supported": ["RS256"],
272 "grant_types_supported": [
273 "authorization_code",
274 "urn:ietf:params:oauth:grant-type:device_code",
275 ],
276 }
277

References oauth_server.OAuthHandler._alt_issuer, oauth_server.OAuthHandler._parameterized, and PgFdwRelationInfo.server.

◆ do_GET()

def oauth_server.OAuthHandler.do_GET (   self)

Definition at line 75 of file oauth_server.py.

75 def do_GET(self):
76 self._response_code = 200
77 self._check_issuer()
78
79 config_path = "/.well-known/openid-configuration"
80 if self._alt_issuer:
81 config_path = "/.well-known/oauth-authorization-server"
82
83 if self.path == config_path:
84 resp = self.config()
85 else:
86 self.send_error(404, "Not Found")
87 return
88
89 self._send_json(resp)
90

◆ do_POST()

def oauth_server.OAuthHandler.do_POST (   self)

Definition at line 127 of file oauth_server.py.

127 def do_POST(self):
128 self._response_code = 200
129 self._check_issuer()
130
131 self._params = self._parse_params()
132 if self._parameterized:
133 # Pull encoded test parameters out of the peer's client_id field.
134 # This is expected to be Base64-encoded JSON.
135 js = base64.b64decode(self.client_id)
136 self._test_params = json.loads(js)
137
138 self._check_authn()
139
140 if self.path == "/authorize":
141 resp = self.authorization()
142 elif self.path == "/token":
143 resp = self.token()
144 else:
145 self.send_error(404)
146 return
147
148 self._send_json(resp)
149

References oauth_server.OAuthHandler._check_issuer(), and oauth_server.OAuthHandler._response_code.

Referenced by oauth_server.OAuthHandler._parse_params().

◆ token()

JsonObject oauth_server.OAuthHandler.token (   self)

Definition at line 323 of file oauth_server.py.

323 def token(self) -> JsonObject:
324 err = self._get_param("error_code", None)
325 if err:
326 self._response_code = self._get_param("error_status", 400)
327
328 resp = {"error": err}
329
330 desc = self._get_param("error_desc", "")
331 if desc:
332 resp["error_description"] = desc
333
334 return resp
335
336 if self._should_modify() and "retries" in self._test_params:
337 retries = self._test_params["retries"]
338
339 # Check to make sure the token interval is being respected.
340 now = time.monotonic()
341 if self._token_state.last_try is not None:
342 delay = now - self._token_state.last_try
343 assert (
344 delay > self._token_state.min_delay
345 ), f"client waited only {delay} seconds between token requests (expected {self._token_state.min_delay})"
346
347 self._token_state.last_try = now
348
349 # If we haven't reached the required number of retries yet, return a
350 # "pending" response.
351 if self._token_state.retries < retries:
352 self._token_state.retries += 1
353
354 self._response_code = 400
355 return {"error": self._retry_code}
356
357 # Clean up any retry tracking state now that the exchange is ending.
358 self._remove_token_state()
359
360 return {
361 "access_token": self._access_token,
362 "token_type": "bearer",
363 **self._response_padding,
364 }
365
366
#define token
Definition: indent_globs.h:126

References oauth_server.OAuthHandler._access_token(), oauth_server.OAuthHandler._get_param(), oauth_server.OAuthHandler._remove_token_state(), oauth_server.OAuthHandler._response_code, oauth_server.OAuthHandler._response_padding(), oauth_server.OAuthHandler._retry_code(), oauth_server.OAuthHandler._should_modify(), oauth_server.OAuthHandler._test_params, and oauth_server.OAuthHandler._token_state().

Field Documentation

◆ _alt_issuer

oauth_server.OAuthHandler._alt_issuer
private

◆ _parameterized

oauth_server.OAuthHandler._parameterized
private

Definition at line 37 of file oauth_server.py.

Referenced by oauth_server.OAuthHandler.config().

◆ _params

oauth_server.OAuthHandler._params
private

◆ _response_code

oauth_server.OAuthHandler._response_code
private

◆ _test_params

oauth_server.OAuthHandler._test_params
private

◆ JsonObject

oauth_server.OAuthHandler.JsonObject = Dict[str, object]
static

Definition at line 27 of file oauth_server.py.

◆ path

oauth_server.OAuthHandler.path

Definition at line 35 of file oauth_server.py.

Referenced by oauth_server.OAuthHandler._should_modify().


The documentation for this class was generated from the following file: