oauth2-cli-auth 1.4.0__tar.gz → 1.6.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,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: oauth2-cli-auth
3
- Version: 1.4.0
3
+ Version: 1.6.0
4
4
  Summary: Authenticate against OAuth2 Provider in Python CLIs
5
- Home-page: https://github.com/timo-reymann/python-oauth2-cli-auth
6
5
  License: MIT
7
6
  Author: Timo Reymann
8
7
  Author-email: mail@timo-reymann.de
9
- Requires-Python: >=3.9,<4.0
8
+ Requires-Python: >=3.9
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: Environment :: Console
12
11
  Classifier: Environment :: MacOS X
@@ -20,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
23
  Project-URL: Bug Tracker, https://github.com/timo-reymann/python-oauth2-cli-auth/issues
24
24
  Project-URL: Repository, https://github.com/timo-reymann/python-oauth2-cli-auth
25
25
  Description-Content-Type: text/markdown
@@ -30,6 +30,7 @@ oauth2-cli-auth
30
30
  [![CircleCI](https://circleci.com/gh/timo-reymann/python-oauth2-cli-auth.svg?style=shield)](https://app.circleci.com/pipelines/github/timo-reymann/python-oauth2-cli-auth)
31
31
  [![GitHub Release](https://img.shields.io/github/v/tag/timo-reymann/python-oauth2-cli-auth?label=version)](https://github.com/timo-reymann/python-oauth2-cli-auth/releases)
32
32
  [![PyPI version](https://badge.fury.io/py/oauth2-cli-auth.svg)](https://pypi.org/project/oauth2-cli-auth)
33
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/oauth2-cli-auth)
33
34
  [![codecov](https://codecov.io/gh/timo-reymann/python-oauth2-cli-auth/graph/badge.svg?token=zxY1pKcBaj)](https://codecov.io/gh/timo-reymann/python-oauth2-cli-auth)
34
35
  [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=timo-reymann_python-oauth2-cli-auth&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=timo-reymann_python-oauth2-cli-auth)
35
36
  [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=timo-reymann_python-oauth2-cli-auth&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=timo-reymann_python-oauth2-cli-auth)
@@ -111,7 +112,7 @@ So I created this small library without any dependencies besides the python stan
111
112
 
112
113
  ## Documentation
113
114
 
114
- - [Library documentation](https://timo-reymann.github.io/python-oauth2-cli-auth/oauth2_cli_auth/)
115
+ - [Library documentation](https://timo-reymann.github.io/python-oauth2-cli-auth)
115
116
 
116
117
  ## Contributing
117
118
 
@@ -4,6 +4,7 @@ oauth2-cli-auth
4
4
  [![CircleCI](https://circleci.com/gh/timo-reymann/python-oauth2-cli-auth.svg?style=shield)](https://app.circleci.com/pipelines/github/timo-reymann/python-oauth2-cli-auth)
5
5
  [![GitHub Release](https://img.shields.io/github/v/tag/timo-reymann/python-oauth2-cli-auth?label=version)](https://github.com/timo-reymann/python-oauth2-cli-auth/releases)
6
6
  [![PyPI version](https://badge.fury.io/py/oauth2-cli-auth.svg)](https://pypi.org/project/oauth2-cli-auth)
7
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/oauth2-cli-auth)
7
8
  [![codecov](https://codecov.io/gh/timo-reymann/python-oauth2-cli-auth/graph/badge.svg?token=zxY1pKcBaj)](https://codecov.io/gh/timo-reymann/python-oauth2-cli-auth)
8
9
  [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=timo-reymann_python-oauth2-cli-auth&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=timo-reymann_python-oauth2-cli-auth)
9
10
  [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=timo-reymann_python-oauth2-cli-auth&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=timo-reymann_python-oauth2-cli-auth)
@@ -85,7 +86,7 @@ So I created this small library without any dependencies besides the python stan
85
86
 
86
87
  ## Documentation
87
88
 
88
- - [Library documentation](https://timo-reymann.github.io/python-oauth2-cli-auth/oauth2_cli_auth/)
89
+ - [Library documentation](https://timo-reymann.github.io/python-oauth2-cli-auth)
89
90
 
90
91
  ## Contributing
91
92
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Authenticate against OAuth2 Provider in Python CLIs
3
3
  """
4
-
4
+ from oauth2_cli_auth.__version__ import __version__
5
5
  from oauth2_cli_auth.http_server import OAuthCallbackHttpServer
6
6
  from oauth2_cli_auth.code_grant import OAuth2ClientInfo, exchange_code_for_access_token, get_auth_url, open_browser, load_oidc_config
7
7
  from oauth2_cli_auth.simplified_flow import get_access_token_with_browser_open
@@ -0,0 +1 @@
1
+ __version__ = "1.6.0"
@@ -18,15 +18,27 @@ class OAuth2ClientInfo:
18
18
  """
19
19
  authorization_url: str
20
20
  """Authorization URL to redirect the user to"""
21
+
21
22
  token_url: str
22
23
  """Token URL for fetching the access token"""
24
+
23
25
  client_id: str
24
26
  """Id of the client to request for"""
27
+
25
28
  scopes: list[str]
26
29
  """List of scopes to request"""
27
30
 
28
31
  @staticmethod
29
- def from_oidc_endpoint(oidc_config_endpoint: str, client_id: str, scopes: list[str]):
32
+ def from_oidc_endpoint(oidc_config_endpoint: str, client_id: str, scopes: list[str]) -> "OAuth2ClientInfo":
33
+ """
34
+ Create client information object from well known endpoint in format as specified in
35
+ https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
36
+
37
+ :param oidc_config_endpoint: Endpoint to load
38
+ :param client_id: Client ID to use
39
+ :param scopes: Scopes to add to OAuth2 authorization requests
40
+ :return: OAuth2ClientInfo containing parsed information from endpoint
41
+ """
30
42
  config = load_oidc_config(oidc_config_endpoint)
31
43
  return OAuth2ClientInfo(
32
44
  authorization_url=config.get("authorization_endpoint"),
@@ -49,6 +61,7 @@ def load_oidc_config(odic_well_known_endpoint: str) -> dict:
49
61
  def open_browser(url: str, print_open_browser_instruction: Callable[[str], None] | None = print) -> None:
50
62
  """
51
63
  Open browser using webbrowser module and show message about URL open
64
+
52
65
  :param print_open_browser_instruction: Callback to print the instructions to open the browser. Set to None in order to supress the output.
53
66
  :param url: URL to open and display
54
67
  :return: None
@@ -0,0 +1,273 @@
1
+ """
2
+ Local HTTP server logic
3
+ """
4
+ from http.server import BaseHTTPRequestHandler, HTTPServer
5
+ from string import Template
6
+ from typing import Optional
7
+ from urllib.parse import parse_qs, urlparse
8
+
9
+ from oauth2_cli_auth._timeout import TimeoutException, _method_with_timeout
10
+
11
+
12
+ class CallbackPageTemplate:
13
+ """
14
+ Holder for callback page assets and template
15
+ """
16
+ SUCCESS_SVG = """
17
+ <svg xmlns="http://www.w3.org/2000/svg" width="154px" height="154px">
18
+ <g fill="none" stroke="#22AE73" stroke-width="2">
19
+ <circle cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
20
+ <circle id="colored" fill="#22AE73" cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
21
+ <polyline class="st0" stroke="#fff" stroke-width="10" points="43.5,77.8 63.7,97.9 112.2,49.4 " style="stroke-dasharray:100px, 100px; stroke-dashoffset: 200px;"/>
22
+ </g>
23
+ </svg>
24
+ """
25
+ """SVG to display checkmark"""
26
+
27
+ ERROR_SVG = """
28
+ <svg xmlns="http://www.w3.org/2000/svg" width="154px" height="154px">
29
+ <g fill="none" stroke="#F44812" stroke-width="2">
30
+ <circle cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
31
+ <circle id="colored" fill="#F44812" cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
32
+ <polyline class="st0" stroke="#fff" stroke-width="10" points="43.5,77.8 112.2,77.8 " style="stroke-dasharray:100px, 100px; stroke-dashoffset: 200px;"/>
33
+ </g>
34
+ </svg>
35
+ """
36
+ """SVG to display error icon"""
37
+
38
+ PAGE_TEMPLATE = Template(
39
+ """
40
+ <html lang="$lang">
41
+ <head>
42
+ <title>$title</title>
43
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
44
+ <meta name="charset" content="utf-8">
45
+ <style>
46
+ * {
47
+ margin: 0;
48
+ padding: 0;
49
+ }
50
+
51
+ body {
52
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
53
+ }
54
+
55
+ @media (prefers-color-scheme: dark) {
56
+ body {
57
+ background: rgb(34, 39, 46);
58
+ color: rgb(173, 186, 199);
59
+ }
60
+ }
61
+
62
+ html, body {
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ height: 100%;
67
+ }
68
+
69
+ h1 {
70
+ font-size: 4rem;
71
+ }
72
+
73
+ p {
74
+ font-size: 1.4rem;
75
+ max-width: 70ch;
76
+ }
77
+
78
+ .message {
79
+ text-align: center;
80
+ }
81
+
82
+ .animation-ctn {
83
+ text-align: center;
84
+ }
85
+
86
+ @keyframes checkmark {
87
+ 0% {
88
+ stroke-dashoffset: 100px
89
+ }
90
+
91
+ 100% {
92
+ stroke-dashoffset: 0px
93
+ }
94
+ }
95
+
96
+ @keyframes checkmark-circle {
97
+ 0% {
98
+ stroke-dashoffset: 480px
99
+ }
100
+
101
+ 100% {
102
+ stroke-dashoffset: 960px
103
+ }
104
+ }
105
+
106
+ @keyframes colored-circle {
107
+ 0% {
108
+ opacity: 0
109
+ }
110
+
111
+ 100% {
112
+ opacity: 100
113
+ }
114
+ }
115
+
116
+ .icon svg {
117
+ padding: 1rem;
118
+ }
119
+
120
+ .icon svg polyline {
121
+ -webkit-animation: checkmark 0.25s ease-in-out 0.7s backwards;
122
+ animation: checkmark 0.25s ease-in-out 0.7s backwards
123
+ }
124
+
125
+ .icon svg circle {
126
+ -webkit-animation: checkmark-circle 0.6s ease-in-out backwards;
127
+ animation: checkmark-circle 0.6s ease-in-out backwards;
128
+ }
129
+
130
+ .icon svg circle#colored {
131
+ -webkit-animation: colored-circle 0.6s ease-in-out 0.7s backwards;
132
+ animation: colored-circle 0.6s ease-in-out 0.7s backwards;
133
+ }
134
+ </style>
135
+ </head>
136
+
137
+ <body>
138
+ <div class="message">
139
+ <div class="animation-ctn">
140
+ <div class="icon">
141
+ $svg
142
+ </div>
143
+ </div>
144
+
145
+ <h1>$title</h1>
146
+ <p>$message</p>
147
+ </div>
148
+ </body>
149
+ </html>
150
+ """
151
+ )
152
+ """Template for callback HTML page"""
153
+
154
+ def render(self, title: str, message: str, lang: str = "en", has_error: bool = False):
155
+ """
156
+ Render callback page
157
+
158
+ :param title: Page title
159
+ :param message: Message to display under the icon
160
+ :param lang: Langauge for HTML tag
161
+ :param has_error: Indicates weather checkmark (false) or cross (true) should be displayed
162
+ :return: Rendered HTML
163
+ """
164
+ return self.PAGE_TEMPLATE.substitute(
165
+ lang=lang,
166
+ title=title,
167
+ message=message,
168
+ svg=self.ERROR_SVG if has_error else self.SUCCESS_SVG,
169
+ )
170
+
171
+
172
+ class OAuthRedirectHandler(BaseHTTPRequestHandler):
173
+ """
174
+ HTTPRequest Handler that is intended to be used as oauth2 callback page
175
+ """
176
+ _callback_template = CallbackPageTemplate()
177
+ LANGUAGE = "en"
178
+ """HTML language to render for callback page"""
179
+
180
+ def log_message(self, format, *args):
181
+ """
182
+ Disables logging for BaseHTTPRequestHandler
183
+ """
184
+ # silence the log messages
185
+ pass
186
+
187
+ def _serve_html(self, html: str):
188
+ self.send_header("Content-type", "text/html")
189
+ self.end_headers()
190
+ self.wfile.write(html.encode("utf-8"))
191
+
192
+ def _serve_callback_page(self, title: str, message: str, has_error: bool):
193
+ rendered_callback_page = self._callback_template.render(
194
+ lang=self.LANGUAGE,
195
+ title=title,
196
+ message=message,
197
+ has_error=has_error
198
+ )
199
+ self._serve_html(rendered_callback_page)
200
+
201
+ # noinspection PyPep8Naming
202
+ def do_GET(self) -> None:
203
+ """
204
+ Provides callback page for the oauth2 redirect
205
+ """
206
+ params = parse_qs(urlparse(self.path).query)
207
+
208
+ has_error = ("code" not in params
209
+ or len(params['code']) != 1
210
+ or params['code'][0].strip() == "")
211
+
212
+ if has_error:
213
+ self.send_response(400)
214
+ self._serve_callback_page(
215
+ title="Oh snap!",
216
+ message="Something went wrong trying to authenticate you. Please try going back in your browser, or restart the auth process.",
217
+ has_error=True,
218
+ )
219
+ else:
220
+ self.send_response(200)
221
+ self.server._code = params["code"][0]
222
+ self._serve_callback_page(
223
+ title="Success",
224
+ message="You have been authenticated successfully. You may close this browser window now and go back to the terminal",
225
+ has_error=False,
226
+ )
227
+
228
+
229
+ class OAuthCallbackHttpServer(HTTPServer):
230
+ """
231
+ Simplistic HTTP Server to provide local callback URL for oauth2 provider
232
+ """
233
+
234
+ def __init__(self, port):
235
+ super().__init__(("", port), OAuthRedirectHandler)
236
+
237
+ self._code: str | None = None
238
+
239
+ def get_code(self) -> str | None:
240
+ """
241
+ This method should only be called after the request was done and might be None when no token is given.
242
+
243
+ :return: Authorization code or None if the request was not performed yet
244
+ """
245
+ return self._code
246
+
247
+ @property
248
+ def callback_url(self) -> str:
249
+ """
250
+ Callback URL for the HTTP-Server
251
+ """
252
+ return f"http://localhost:{self.server_port}"
253
+
254
+ def wait_for_code(self, attempts: int = 3, timeout_per_attempt=10) -> Optional[str]:
255
+ """
256
+ Wait for the server to open the callback page containing the code query parameter.
257
+
258
+ It tries for #attempts with a timeout of #timeout_per_attempts for each attempt.
259
+ This prevents the CLI from getting stuck by unsolved callback URls
260
+
261
+ :param attempts: Amount of attempts
262
+ :param timeout_per_attempt: Timeout for each attempt to be successful
263
+ :return: Code from callback page or None if the callback page is not called successfully
264
+ """
265
+ for _ in range(0, attempts):
266
+ try:
267
+ _method_with_timeout(self.handle_request, timeout_seconds=timeout_per_attempt)
268
+ except TimeoutException:
269
+ continue
270
+ if self.get_code() is not None:
271
+ return self.get_code()
272
+
273
+ return None
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "oauth2-cli-auth"
3
- version = "1.4.0"
3
+ version = "1.6.0"
4
4
  description = "Authenticate against OAuth2 Provider in Python CLIs"
5
5
  authors = ["Timo Reymann <mail@timo-reymann.de>"]
6
6
  license = "MIT"
@@ -26,7 +26,7 @@ include = [
26
26
  "LICENSE"
27
27
  ]
28
28
  [tool.poetry.dependencies]
29
- python = "^3.9"
29
+ python = ">=3.9"
30
30
 
31
31
  [tool.poetry.urls]
32
32
  "Bug Tracker" = "https://github.com/timo-reymann/python-oauth2-cli-auth/issues"
@@ -37,11 +37,10 @@ url = "https://test.pypi.org/legacy/"
37
37
  priority="explicit"
38
38
 
39
39
  [tool.poetry.group.dev.dependencies]
40
- pytest = "^7.4.3"
40
+ pytest = "^8.0.0"
41
41
  coverage = "^7.3.2"
42
- pdoc3 = "^0.10.0"
43
- setuptools = "^69.5.1"
44
- pydoctor = "^23.9.1"
42
+ setuptools = "^77.0.0"
43
+ pydoctor = "^24.0.0"
45
44
 
46
45
  [tool.coverage.run]
47
46
  omit = [".*", "*/site-packages/*", "*/*_test.py"]
@@ -1,232 +0,0 @@
1
- """
2
- Local HTTP server logic
3
- """
4
- from http.server import BaseHTTPRequestHandler, HTTPServer
5
- from string import Template
6
- from typing import Optional
7
- from urllib.parse import parse_qs, urlparse
8
- from oauth2_cli_auth._timeout import _method_with_timeout, TimeoutException
9
-
10
-
11
- class CallbackPageTemplate:
12
- """
13
- Holder for callback page assets and template
14
- """
15
- SUCCESS_SVG = """
16
- <svg xmlns="http://www.w3.org/2000/svg" width="154px" height="154px">
17
- <g fill="none" stroke="#22AE73" stroke-width="2">
18
- <circle cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
19
- <circle id="colored" fill="#22AE73" cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
20
- <polyline class="st0" stroke="#fff" stroke-width="10" points="43.5,77.8 63.7,97.9 112.2,49.4 " style="stroke-dasharray:100px, 100px; stroke-dashoffset: 200px;"/>
21
- </g>
22
- </svg>
23
- """
24
-
25
- ERROR_SVG = """
26
- <svg xmlns="http://www.w3.org/2000/svg" width="154px" height="154px">
27
- <g fill="none" stroke="#F44812" stroke-width="2">
28
- <circle cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
29
- <circle id="colored" fill="#F44812" cx="77" cy="77" r="72" style="stroke-dasharray:480px, 480px; stroke-dashoffset: 960px;"></circle>
30
- <polyline class="st0" stroke="#fff" stroke-width="10" points="43.5,77.8 112.2,77.8 " style="stroke-dasharray:100px, 100px; stroke-dashoffset: 200px;"/>
31
- </g>
32
- </svg>
33
- """
34
-
35
- PAGE_TEMPLATE = Template("""
36
- <html lang="$lang">
37
- <head>
38
- <title>$title</title>
39
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
40
- <meta name="charset" content="utf-8">
41
- <style>
42
- * {
43
- margin: 0;
44
- padding: 0;
45
- }
46
-
47
- body {
48
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
49
- }
50
-
51
- @media (prefers-color-scheme: dark) {
52
- body {
53
- background: rgb(34, 39, 46);
54
- color: rgb(173, 186, 199);
55
- }
56
- }
57
-
58
- html, body {
59
- display: flex;
60
- align-items: center;
61
- justify-content: center;
62
- height: 100%;
63
- }
64
-
65
- h1 {
66
- font-size: 4rem;
67
- }
68
-
69
- p {
70
- font-size: 1.4rem;
71
- max-width: 70ch;
72
- }
73
-
74
- .message {
75
- text-align: center;
76
- }
77
-
78
- .animation-ctn {
79
- text-align: center;
80
- }
81
-
82
- @keyframes checkmark {
83
- 0% {
84
- stroke-dashoffset: 100px
85
- }
86
-
87
- 100% {
88
- stroke-dashoffset: 0px
89
- }
90
- }
91
-
92
- @keyframes checkmark-circle {
93
- 0% {
94
- stroke-dashoffset: 480px
95
- }
96
-
97
- 100% {
98
- stroke-dashoffset: 960px
99
- }
100
- }
101
-
102
- @keyframes colored-circle {
103
- 0% {
104
- opacity: 0
105
- }
106
-
107
- 100% {
108
- opacity: 100
109
- }
110
- }
111
-
112
- .icon svg {
113
- padding: 1rem;
114
- }
115
-
116
- .icon svg polyline {
117
- -webkit-animation: checkmark 0.25s ease-in-out 0.7s backwards;
118
- animation: checkmark 0.25s ease-in-out 0.7s backwards
119
- }
120
-
121
- .icon svg circle {
122
- -webkit-animation: checkmark-circle 0.6s ease-in-out backwards;
123
- animation: checkmark-circle 0.6s ease-in-out backwards;
124
- }
125
-
126
- .icon svg circle#colored {
127
- -webkit-animation: colored-circle 0.6s ease-in-out 0.7s backwards;
128
- animation: colored-circle 0.6s ease-in-out 0.7s backwards;
129
- }
130
- </style>
131
- </head>
132
-
133
- <body>
134
- <div class="message">
135
- <div class="animation-ctn">
136
- <div class="icon">
137
- $svg
138
- </div>
139
- </div>
140
-
141
- <h1>$title</h1>
142
- <p>$message</p>
143
- </div>
144
- </body>
145
- </html>
146
- """)
147
-
148
- def render(self, title: str, message: str, lang: str = "en", has_error: bool = False):
149
- return self.PAGE_TEMPLATE.substitute(
150
- lang=lang,
151
- title=title,
152
- message=message,
153
- svg=self.ERROR_SVG if has_error else self.SUCCESS_SVG,
154
- )
155
-
156
-
157
- class OAuthRedirectHandler(BaseHTTPRequestHandler):
158
- """
159
- HTTPRequest Handler that is intended to be used as oauth2 callback page
160
- """
161
- callback_template = CallbackPageTemplate()
162
-
163
- def log_message(self, format, *args):
164
- # silence the log messages
165
- pass
166
-
167
- def do_GET(self):
168
- params = parse_qs(urlparse(self.path).query)
169
-
170
- has_error = "code" not in params or len(params['code']) != 1 or params['code'][0].strip() == ""
171
-
172
- if has_error:
173
- self.send_response(400)
174
- title = "Oh snap!"
175
- message = "Something went wrong trying to authenticate you. Please try going back in your browser, or restart the auth process."
176
- else:
177
- self.send_response(200)
178
- self.server._code = params["code"][0]
179
- title = "Success"
180
- message = "You have been authenticated successfully. You may close this browser window now and go back to the terminal"
181
-
182
- self.send_header("Content-type", "text/html")
183
- self.end_headers()
184
- self.wfile.write(
185
- self.callback_template
186
- .render(
187
- lang="en",
188
- title=title,
189
- message=message,
190
- has_error=has_error
191
- )
192
- .encode("utf-8")
193
- )
194
-
195
-
196
- class OAuthCallbackHttpServer(HTTPServer):
197
- """
198
- Simplistic HTTP Server to provide local callback URL for oauth2 provider
199
- """
200
-
201
- def __init__(self, port):
202
- super().__init__(("", port), OAuthRedirectHandler)
203
-
204
- self._code = None
205
-
206
- def get_code(self):
207
- return self._code
208
-
209
- @property
210
- def callback_url(self):
211
- return f"http://localhost:{self.server_port}"
212
-
213
- def wait_for_code(self, attempts: int = 3, timeout_per_attempt=10) -> Optional[str]:
214
- """
215
- Wait for the server to open the callback page containing the code query parameter.
216
-
217
- It tries for #attempts with a timeout of #timeout_per_attempts for each attempt.
218
- This prevents the CLI from getting stuck by unsolved callback URls
219
-
220
- :param attempts: Amount of attempts
221
- :param timeout_per_attempt: Timeout for each attempt to be successful
222
- :return: Code from callback page or None if the callback page is not called successfully
223
- """
224
- for i in range(0, attempts):
225
- try:
226
- _method_with_timeout(self.handle_request, timeout_seconds=timeout_per_attempt)
227
- except TimeoutException:
228
- continue
229
- if self.get_code() is not None:
230
- return self.get_code()
231
-
232
- return None
File without changes