oauth2-cli-auth 1.2.0__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: oauth2-cli-auth
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: Authenticate against OAuth2 Provider in Python CLIs
5
5
  Home-page: https://github.com/timo-reymann/python-oauth2-cli-auth
6
6
  License: MIT
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
22
23
  Project-URL: Bug Tracker, https://github.com/timo-reymann/python-oauth2-cli-auth/issues
23
24
  Project-URL: Repository, https://github.com/timo-reymann/python-oauth2-cli-auth
24
25
  Description-Content-Type: text/markdown
@@ -16,7 +16,7 @@ def _method_with_timeout(your_method, timeout_seconds=5, *args, **kwargs):
16
16
  try:
17
17
  result = your_method(*args, **kwargs)
18
18
  except TimeoutException as te:
19
- raise te
19
+ raise te
20
20
  finally:
21
21
  signal.alarm(0) # Reset the alarm
22
22
 
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import time
3
3
  import urllib
4
+ from urllib.error import URLError
4
5
 
5
6
 
6
7
  def _urlopen_with_backoff(url, max_retries=3, base_delay=1, timeout=15):
@@ -15,7 +16,7 @@ def _urlopen_with_backoff(url, max_retries=3, base_delay=1, timeout=15):
15
16
  delay = base_delay * (2 ** retries)
16
17
  time.sleep(delay)
17
18
 
18
- raise Exception(f"Failed to open URL after {max_retries} retries")
19
+ raise URLError(f"Failed to open URL after {max_retries} retries")
19
20
 
20
21
 
21
22
  def _load_json(url_or_request: str | urllib.request.Request) -> dict:
@@ -1,8 +1,13 @@
1
+ """
2
+ Functionality to work with OAuth2 code grant
3
+ """
4
+ import base64
5
+ import urllib.parse
6
+ import urllib.request
1
7
  import webbrowser
8
+ from collections.abc import Callable
2
9
  from dataclasses import dataclass
3
- import urllib.request
4
- import urllib.parse
5
- import base64
10
+
6
11
  from oauth2_cli_auth._urllib_util import _load_json
7
12
 
8
13
 
@@ -32,17 +37,24 @@ class OAuth2ClientInfo:
32
37
 
33
38
 
34
39
  def load_oidc_config(odic_well_known_endpoint: str) -> dict:
40
+ """
41
+ Load OIDC configuration from the well known configuration endpoint
42
+
43
+ :param odic_well_known_endpoint: Endpoint to load configuration from
44
+ """
35
45
  config = _load_json(odic_well_known_endpoint)
36
46
  return config
37
47
 
38
48
 
39
- def open_browser(url: str) -> None:
49
+ def open_browser(url: str, print_open_browser_instruction: Callable[[str], None] | None = print) -> None:
40
50
  """
41
51
  Open browser using webbrowser module and show message about URL open
52
+ :param print_open_browser_instruction: Callback to print the instructions to open the browser. Set to None in order to supress the output.
42
53
  :param url: URL to open and display
43
54
  :return: None
44
55
  """
45
- print(f"Open your browser at\n{url}")
56
+ if print_open_browser_instruction is not None:
57
+ print_open_browser_instruction(f"Open your browser at\n{url}")
46
58
  webbrowser.open(url)
47
59
 
48
60
 
@@ -61,17 +73,18 @@ def get_auth_url(client_info: OAuth2ClientInfo, redirect_uri: str) -> str:
61
73
  f"&response_type=code")
62
74
 
63
75
 
64
- def exchange_code_for_access_token(client_info: OAuth2ClientInfo, redirect_uri: str, code: str,
65
- access_token_field: str = "access_token") -> str:
76
+ def exchange_code_for_response(
77
+ client_info: OAuth2ClientInfo,
78
+ redirect_uri: str,
79
+ code: str,
80
+ ) -> dict:
66
81
  """
67
- Exchange a code for an access token using the endpoints from client info
82
+ Exchange a code for an access token using the endpoints from client info and return the entire response
68
83
 
69
84
  :param client_info: Info about oauth2 client
70
85
  :param redirect_uri: Callback URL
71
86
  :param code: Code to redeem
72
- :param access_token_field: Name of the field containing the access token to use. This might differ depending on
73
- the provider you are using. For example for Auth0 you have to set this to id_token
74
- :return: Extracted access token from response
87
+ :return: Response from OAuth2 endpoint
75
88
  """
76
89
  headers = {
77
90
  "Content-Type": "application/x-www-form-urlencoded",
@@ -88,4 +101,25 @@ def exchange_code_for_access_token(client_info: OAuth2ClientInfo, redirect_uri:
88
101
  request = urllib.request.Request(client_info.token_url, data=encoded_data, headers=headers)
89
102
  json_response = _load_json(request)
90
103
 
104
+ return json_response
105
+
106
+
107
+ def exchange_code_for_access_token(
108
+ client_info: OAuth2ClientInfo,
109
+ redirect_uri: str,
110
+ code: str,
111
+ access_token_field: str = "access_token"
112
+ ) -> str:
113
+ """
114
+ Exchange a code for an access token using the endpoints from client info
115
+
116
+ :param client_info: Info about oauth2 client
117
+ :param redirect_uri: Callback URL
118
+ :param code: Code to redeem
119
+ :param access_token_field: Name of the field containing the access token to use. This might differ depending on
120
+ the provider you are using. For example for Auth0 you have to set this to id_token
121
+ :return: Extracted access token from response
122
+ """
123
+ json_response = exchange_code_for_response(client_info, redirect_uri, code)
124
+
91
125
  return json_response.get(access_token_field)
@@ -1,3 +1,6 @@
1
+ """
2
+ Local HTTP server logic
3
+ """
1
4
  from http.server import BaseHTTPRequestHandler, HTTPServer
2
5
  from string import Template
3
6
  from typing import Optional
@@ -6,6 +9,9 @@ from oauth2_cli_auth._timeout import _method_with_timeout, TimeoutException
6
9
 
7
10
 
8
11
  class CallbackPageTemplate:
12
+ """
13
+ Holder for callback page assets and template
14
+ """
9
15
  SUCCESS_SVG = """
10
16
  <svg xmlns="http://www.w3.org/2000/svg" width="154px" height="154px">
11
17
  <g fill="none" stroke="#22AE73" stroke-width="2">
@@ -149,6 +155,9 @@ class CallbackPageTemplate:
149
155
 
150
156
 
151
157
  class OAuthRedirectHandler(BaseHTTPRequestHandler):
158
+ """
159
+ HTTPRequest Handler that is intended to be used as oauth2 callback page
160
+ """
152
161
  callback_template = CallbackPageTemplate()
153
162
 
154
163
  def log_message(self, format, *args):
@@ -201,7 +210,7 @@ class OAuthCallbackHttpServer(HTTPServer):
201
210
  def callback_url(self):
202
211
  return f"http://localhost:{self.server_port}"
203
212
 
204
- def wait_for_code(self, attempts: int = 3, timeout_per_attempt=10) -> Optional[int]:
213
+ def wait_for_code(self, attempts: int = 3, timeout_per_attempt=10) -> Optional[str]:
205
214
  """
206
215
  Wait for the server to open the callback page containing the code query parameter.
207
216
 
@@ -1,3 +1,6 @@
1
+ """
2
+ Ready to use workflows
3
+ """
1
4
  from oauth2_cli_auth import OAuthCallbackHttpServer, get_auth_url, exchange_code_for_access_token, OAuth2ClientInfo, \
2
5
  open_browser
3
6
 
@@ -10,6 +13,7 @@ def get_access_token_with_browser_open(client_info: OAuth2ClientInfo, server_por
10
13
  - Open the browser with the authorization URL
11
14
  - Wait for the code to arrive
12
15
  - Get access token from code
16
+
13
17
  :param client_info: Client Info for Oauth2 Interaction
14
18
  :param server_port: Port of the local web server to spin up
15
19
  :return: Access Token
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "oauth2-cli-auth"
3
- version = "1.2.0"
3
+ version = "1.4.0"
4
4
  description = "Authenticate against OAuth2 Provider in Python CLIs"
5
5
  authors = ["Timo Reymann <mail@timo-reymann.de>"]
6
6
  license = "MIT"
@@ -34,13 +34,14 @@ python = "^3.9"
34
34
  [[tool.poetry.source]]
35
35
  name = "testpypi"
36
36
  url = "https://test.pypi.org/legacy/"
37
- default = false
38
- secondary = false
37
+ priority="explicit"
39
38
 
40
39
  [tool.poetry.group.dev.dependencies]
41
40
  pytest = "^7.4.3"
42
41
  coverage = "^7.3.2"
43
42
  pdoc3 = "^0.10.0"
43
+ setuptools = "^69.5.1"
44
+ pydoctor = "^23.9.1"
44
45
 
45
46
  [tool.coverage.run]
46
47
  omit = [".*", "*/site-packages/*", "*/*_test.py"]
@@ -51,3 +52,22 @@ fail_under = 70
51
52
  [build-system]
52
53
  requires = ["poetry-core"]
53
54
  build-backend = "poetry.core.masonry.api"
55
+
56
+ [tool.pydoctor]
57
+ verbose = 0
58
+ warnings-as-errors = true
59
+
60
+ intersphinx = ["https://docs.python.org/3/objects.inv"]
61
+ docformat = "restructuredtext"
62
+ html-output = "gh-pages"
63
+
64
+ add-package = ["oauth2_cli_auth"]
65
+ project-name = "oauth2_cli_auth"
66
+ project-url = "https://github.com/timo-reymann/python-oauth2-cli-auth"
67
+ privacy = [
68
+ "HIDDEN:oauth2_cli_auth.*_test",
69
+ "HIDDEN:oauth2_cli_auth.conftest"
70
+ ]
71
+
72
+ theme = "base"
73
+ template-dir = "pydoctor-theme"
File without changes