iaptoolkit 0.1.0__tar.gz → 0.2.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.
- iaptoolkit-0.2.0/PKG-INFO +69 -0
- iaptoolkit-0.2.0/README.md +51 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/pyproject.toml +2 -2
- iaptoolkit-0.2.0/src/iaptoolkit/__init__.py +215 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/tokens/token_datastore.py +2 -5
- iaptoolkit-0.1.0/PKG-INFO +0 -40
- iaptoolkit-0.1.0/README.md +0 -22
- iaptoolkit-0.1.0/src/iaptoolkit/__init__.py +0 -130
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/LICENSE +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/constants.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/exceptions.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/headers.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/tokens/__init__.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/tokens/service_account.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/tokens/structs.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/utils/__init__.py +0 -0
- {iaptoolkit-0.1.0 → iaptoolkit-0.2.0}/src/iaptoolkit/utils/urls.py +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: iaptoolkit
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Library of common utils for interacting with Identity-Aware Proxies
|
|
5
|
+
Author: Rob Voigt
|
|
6
|
+
Author-email: code@ravoigt.com
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Dist: google-auth (>=2.29.0,<3.0.0)
|
|
12
|
+
Requires-Dist: kvcommon (>=0.1.1,<0.2.0)
|
|
13
|
+
Requires-Dist: pytest (>=7.4.4,<8.0.0)
|
|
14
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
15
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# IAP Toolkit
|
|
19
|
+
|
|
20
|
+
A library of utils to ease programmatic authentication with Google IAP (and ideally other IAPs in future).
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Quick Start / Example Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import requests
|
|
27
|
+
|
|
28
|
+
from iaptoolkit import IAPToolkit
|
|
29
|
+
|
|
30
|
+
iaptk = IAPToolkit(google_iap_client_id="EXAMPLE_ID_123456789ABCDEF")
|
|
31
|
+
allowed_domains = ["example.com", ]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Example #1 - Combined Calls
|
|
35
|
+
def example1(url: str):
|
|
36
|
+
headers = dict()
|
|
37
|
+
result = iaptk.check_url_and_add_token_header(
|
|
38
|
+
url=url,
|
|
39
|
+
request_headers=headers,
|
|
40
|
+
valid_domains=allowed_domains
|
|
41
|
+
)
|
|
42
|
+
# result.token_added (bool) indicates if the token was added, depending on whether or not URL was valid
|
|
43
|
+
# headers dict now contains the appropriate Bearer Token header for Google IAP
|
|
44
|
+
|
|
45
|
+
# Make HTTP GET request with requests lib, with our headers containing bearer token to auth with IAP
|
|
46
|
+
response = requests.request("GET", url, headers=headers)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Example #2 - Separate Calls - Functionally the same as Example 1 but more flexibility in URL validation
|
|
50
|
+
def example1(url: str):
|
|
51
|
+
is_url_safe: bool = iaptk.is_url_safe_for_token(url=url, valid_domains=valid_domains)
|
|
52
|
+
|
|
53
|
+
if not is_url_safe:
|
|
54
|
+
raise ExampleBadURLException("This URL isn't safe to send token headers to!")
|
|
55
|
+
|
|
56
|
+
headers = dict()
|
|
57
|
+
token_is_fresh: bool = iaptk.get_token_and_add_to_headers(request_headers=headers)
|
|
58
|
+
# token_is_fresh indicates if token was newly retrieved (True), or if a cached token was reused (False)
|
|
59
|
+
# headers dict now contains the appropriate Bearer Token header for Google IAP
|
|
60
|
+
|
|
61
|
+
# Make HTTP GET request with requests lib, with our headers containing bearer token to auth with IAP
|
|
62
|
+
response = requests.request("GET", url, headers=headers)
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Disclaimer
|
|
67
|
+
|
|
68
|
+
This project is not affiliated with Google. No trademark infringement intended.
|
|
69
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# IAP Toolkit
|
|
2
|
+
|
|
3
|
+
A library of utils to ease programmatic authentication with Google IAP (and ideally other IAPs in future).
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Quick Start / Example Usage
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from iaptoolkit import IAPToolkit
|
|
12
|
+
|
|
13
|
+
iaptk = IAPToolkit(google_iap_client_id="EXAMPLE_ID_123456789ABCDEF")
|
|
14
|
+
allowed_domains = ["example.com", ]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Example #1 - Combined Calls
|
|
18
|
+
def example1(url: str):
|
|
19
|
+
headers = dict()
|
|
20
|
+
result = iaptk.check_url_and_add_token_header(
|
|
21
|
+
url=url,
|
|
22
|
+
request_headers=headers,
|
|
23
|
+
valid_domains=allowed_domains
|
|
24
|
+
)
|
|
25
|
+
# result.token_added (bool) indicates if the token was added, depending on whether or not URL was valid
|
|
26
|
+
# headers dict now contains the appropriate Bearer Token header for Google IAP
|
|
27
|
+
|
|
28
|
+
# Make HTTP GET request with requests lib, with our headers containing bearer token to auth with IAP
|
|
29
|
+
response = requests.request("GET", url, headers=headers)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Example #2 - Separate Calls - Functionally the same as Example 1 but more flexibility in URL validation
|
|
33
|
+
def example1(url: str):
|
|
34
|
+
is_url_safe: bool = iaptk.is_url_safe_for_token(url=url, valid_domains=valid_domains)
|
|
35
|
+
|
|
36
|
+
if not is_url_safe:
|
|
37
|
+
raise ExampleBadURLException("This URL isn't safe to send token headers to!")
|
|
38
|
+
|
|
39
|
+
headers = dict()
|
|
40
|
+
token_is_fresh: bool = iaptk.get_token_and_add_to_headers(request_headers=headers)
|
|
41
|
+
# token_is_fresh indicates if token was newly retrieved (True), or if a cached token was reused (False)
|
|
42
|
+
# headers dict now contains the appropriate Bearer Token header for Google IAP
|
|
43
|
+
|
|
44
|
+
# Make HTTP GET request with requests lib, with our headers containing bearer token to auth with IAP
|
|
45
|
+
response = requests.request("GET", url, headers=headers)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Disclaimer
|
|
50
|
+
|
|
51
|
+
This project is not affiliated with Google. No trademark infringement intended.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "iaptoolkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Library of common utils for interacting with Identity-Aware Proxies"
|
|
5
5
|
authors = ["Rob Voigt <code@ravoigt.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -28,7 +28,7 @@ google-auth = "^2.29.0"
|
|
|
28
28
|
requests = "^2.31.0"
|
|
29
29
|
pytest = "^7.4.4"
|
|
30
30
|
toml = "^0.10.2"
|
|
31
|
-
kvcommon = "^0.1.
|
|
31
|
+
kvcommon = "^0.1.1"
|
|
32
32
|
|
|
33
33
|
[tool.poetry.dev-dependencies]
|
|
34
34
|
black = "*"
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
6
|
+
|
|
7
|
+
import typing as t
|
|
8
|
+
from urllib.parse import ParseResult
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from kvcommon import logger
|
|
12
|
+
|
|
13
|
+
from iaptoolkit import headers
|
|
14
|
+
from iaptoolkit.exceptions import ServiceAccountTokenException
|
|
15
|
+
from iaptoolkit.tokens.service_account import ServiceAccount
|
|
16
|
+
from iaptoolkit.tokens.structs import ResultAddTokenHeader
|
|
17
|
+
|
|
18
|
+
from iaptoolkit.tokens.structs import TokenRefreshStruct
|
|
19
|
+
from iaptoolkit.utils.urls import is_url_safe_for_token
|
|
20
|
+
|
|
21
|
+
LOG = logger.get_logger("iaptk")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IAPToolkit:
|
|
25
|
+
"""
|
|
26
|
+
Class to encapsulate client-specific vars and forward them to static functions
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_GOOGLE_IAP_CLIENT_ID: str
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
google_iap_client_id: str,
|
|
34
|
+
) -> None:
|
|
35
|
+
self._GOOGLE_IAP_CLIENT_ID = google_iap_client_id
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def sanitize_request_headers(request_headers: dict) -> dict:
|
|
39
|
+
return headers.sanitize_request_headers(request_headers)
|
|
40
|
+
|
|
41
|
+
def get_token_oidc(self, bypass_cached: bool = False) -> TokenRefreshStruct:
|
|
42
|
+
try:
|
|
43
|
+
return ServiceAccount.get_token(
|
|
44
|
+
iap_client_id=self._GOOGLE_IAP_CLIENT_ID, bypass_cached=bypass_cached
|
|
45
|
+
)
|
|
46
|
+
except ServiceAccountTokenException as ex:
|
|
47
|
+
LOG.debug(ex)
|
|
48
|
+
raise
|
|
49
|
+
|
|
50
|
+
def get_token_oidc_str(self, bypass_cached: bool = False) -> str:
|
|
51
|
+
struct = self.get_token_oidc(bypass_cached=bypass_cached)
|
|
52
|
+
return struct.id_token
|
|
53
|
+
|
|
54
|
+
def get_token_oauth2(self, bypass_cached: bool = False) -> TokenRefreshStruct:
|
|
55
|
+
# TODO
|
|
56
|
+
raise NotImplementedError()
|
|
57
|
+
|
|
58
|
+
def get_token_oauth2_str(self, bypass_cached: bool = False) -> str:
|
|
59
|
+
struct = self.get_token_oauth2(bypass_cached=bypass_cached)
|
|
60
|
+
return struct.id_token
|
|
61
|
+
|
|
62
|
+
def get_token_and_add_to_headers(
|
|
63
|
+
self, request_headers: dict, use_oauth2: bool = False, use_auth_header: bool = False
|
|
64
|
+
) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
Retrieves an auth token and inserts it into the supplied request_headers dict.
|
|
67
|
+
request_headers is modified in-place
|
|
68
|
+
|
|
69
|
+
Params:
|
|
70
|
+
request_headers: dict of headers to insert into
|
|
71
|
+
use_oauth2: Use OAuth2.0 credentials and respective token, else use OIDC (default)
|
|
72
|
+
As a general guideline, OIDC is the assumed default approach for ServiceAccounts.
|
|
73
|
+
use_auth_header: If true, use the 'Authorization' header instead of 'Proxy-Authorization'
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
if not use_oauth2:
|
|
78
|
+
token_refresh_struct: TokenRefreshStruct = self.get_token_oidc()
|
|
79
|
+
else:
|
|
80
|
+
token_refresh_struct: TokenRefreshStruct = self.get_token_oauth2()
|
|
81
|
+
|
|
82
|
+
headers.add_token_to_request_headers(
|
|
83
|
+
request_headers=request_headers,
|
|
84
|
+
id_token=token_refresh_struct.id_token,
|
|
85
|
+
use_auth_header=use_auth_header,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return token_refresh_struct.token_is_new
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def is_url_safe_for_token(
|
|
92
|
+
url: str | ParseResult,
|
|
93
|
+
valid_domains: t.Optional[t.List[str] | t.Set[str] | t.Tuple[str]] = None,
|
|
94
|
+
):
|
|
95
|
+
if not isinstance(url, ParseResult):
|
|
96
|
+
url = urlparse(url)
|
|
97
|
+
|
|
98
|
+
return is_url_safe_for_token(url_parts=url, allowed_domains=valid_domains)
|
|
99
|
+
|
|
100
|
+
def check_url_and_add_token_header(
|
|
101
|
+
self,
|
|
102
|
+
url: str | ParseResult,
|
|
103
|
+
request_headers: dict,
|
|
104
|
+
valid_domains: t.List[str] | None = None,
|
|
105
|
+
use_oauth2: bool = False,
|
|
106
|
+
use_auth_header: bool = False,
|
|
107
|
+
) -> ResultAddTokenHeader:
|
|
108
|
+
"""
|
|
109
|
+
Checks that the supplied URL is valid (i.e.; in valid_domains) and if so, retrieves a
|
|
110
|
+
token and adds it to request_headers.
|
|
111
|
+
|
|
112
|
+
i.e.; A convenience wrapper with logging for is_url_safe_for_token() and get_token_and_add_to_headers()
|
|
113
|
+
|
|
114
|
+
Params:
|
|
115
|
+
url: URL string or urllib.ParseResult to check for validity
|
|
116
|
+
request_headers: Dict of headers to insert into
|
|
117
|
+
valid_domains: List of domains to validate URL against
|
|
118
|
+
use_oauth2: Passed to get_token_and_add_to_headers() to determine if OAuth2.0 is used or OIDC (default)
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
if self.is_url_safe_for_token(url=url, valid_domains=valid_domains):
|
|
122
|
+
token_is_fresh = self.get_token_and_add_to_headers(
|
|
123
|
+
request_headers=request_headers,
|
|
124
|
+
use_oauth2=use_oauth2,
|
|
125
|
+
use_auth_header=use_auth_header,
|
|
126
|
+
)
|
|
127
|
+
return ResultAddTokenHeader(token_added=True, token_is_fresh=token_is_fresh)
|
|
128
|
+
else:
|
|
129
|
+
LOG.warn(
|
|
130
|
+
"URL is not approved: %s - Token will not be added to headers. Valid domains are: %s",
|
|
131
|
+
url,
|
|
132
|
+
valid_domains,
|
|
133
|
+
)
|
|
134
|
+
return ResultAddTokenHeader(token_added=False, token_is_fresh=False)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class IAPToolkit_OIDC(IAPToolkit):
|
|
138
|
+
"""
|
|
139
|
+
Convenience subclass of IAPToolkit for scenarios where OIDC will always be used, never OAuth2
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def get_token_oauth2(self, *args, **kwargs):
|
|
143
|
+
raise NotImplementedError("Cannot call OAuth2 methods on OIDC-only instance of IAPToolkit.")
|
|
144
|
+
|
|
145
|
+
def get_token_oauth2_str(self, *args, **kwargs):
|
|
146
|
+
raise NotImplementedError("Cannot call OAuth2 methods on OIDC-only instance of IAPToolkit.")
|
|
147
|
+
|
|
148
|
+
def get_token_and_add_to_headers(
|
|
149
|
+
self, request_headers: dict, use_auth_header: bool = False
|
|
150
|
+
) -> bool:
|
|
151
|
+
return super().get_token_and_add_to_headers(
|
|
152
|
+
request_headers=request_headers, use_oauth2=False, use_auth_header=use_auth_header
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def check_url_and_add_token_header(
|
|
156
|
+
self,
|
|
157
|
+
url: str | ParseResult,
|
|
158
|
+
request_headers: dict,
|
|
159
|
+
valid_domains: t.List[str] | None = None,
|
|
160
|
+
use_auth_header: bool = False,
|
|
161
|
+
) -> ResultAddTokenHeader:
|
|
162
|
+
return super().check_url_and_add_token_header(
|
|
163
|
+
url,
|
|
164
|
+
request_headers=request_headers,
|
|
165
|
+
valid_domains=valid_domains,
|
|
166
|
+
use_oauth2=False,
|
|
167
|
+
use_auth_header=use_auth_header,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class IAPToolkit_OAuth2(IAPToolkit):
|
|
172
|
+
"""
|
|
173
|
+
Convenience subclass of IAPToolkit for scenarios where OAuth2 will always be used, never OIDC
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
_GOOGLE_CLIENT_ID: str
|
|
177
|
+
_GOOGLE_CLIENT_SECRET: str
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
google_iap_client_id: str,
|
|
182
|
+
google_client_id: str,
|
|
183
|
+
google_client_secret: str,
|
|
184
|
+
) -> None:
|
|
185
|
+
super().__init__(google_iap_client_id=google_iap_client_id)
|
|
186
|
+
self._GOOGLE_CLIENT_ID = google_client_id
|
|
187
|
+
self._GOOGLE_CLIENT_SECRET = google_client_secret
|
|
188
|
+
|
|
189
|
+
def get_token_oidc(self, *args, **kwargs):
|
|
190
|
+
raise NotImplementedError("Cannot call OIDC methods on OAuth2-only instance of IAPToolkit.")
|
|
191
|
+
|
|
192
|
+
def get_token_oidc_str(self, *args, **kwargs):
|
|
193
|
+
raise NotImplementedError("Cannot call OIDC methods on OAuth2-only instance of IAPToolkit.")
|
|
194
|
+
|
|
195
|
+
def get_token_and_add_to_headers(
|
|
196
|
+
self, request_headers: dict, use_auth_header: bool = False
|
|
197
|
+
) -> bool:
|
|
198
|
+
return super().get_token_and_add_to_headers(
|
|
199
|
+
request_headers=request_headers, use_oauth2=True, use_auth_header=use_auth_header
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def check_url_and_add_token_header(
|
|
203
|
+
self,
|
|
204
|
+
url: str | ParseResult,
|
|
205
|
+
request_headers: dict,
|
|
206
|
+
valid_domains: t.List[str] | None = None,
|
|
207
|
+
use_auth_header: bool = False,
|
|
208
|
+
) -> ResultAddTokenHeader:
|
|
209
|
+
return super().check_url_and_add_token_header(
|
|
210
|
+
url=url,
|
|
211
|
+
request_headers=request_headers,
|
|
212
|
+
valid_domains=valid_domains,
|
|
213
|
+
use_oauth2=True,
|
|
214
|
+
use_auth_header=use_auth_header,
|
|
215
|
+
)
|
|
@@ -4,13 +4,10 @@ import typing as t
|
|
|
4
4
|
from kvcommon import logger
|
|
5
5
|
from kvcommon.datastore.backend import DatastoreBackend
|
|
6
6
|
from kvcommon.datastore.backend import DictBackend
|
|
7
|
-
from kvcommon.datastore.backend import TOMLBackend
|
|
7
|
+
# from kvcommon.datastore.backend import TOMLBackend
|
|
8
8
|
from kvcommon.datastore import VersionedDatastore
|
|
9
9
|
|
|
10
10
|
from iaptoolkit.constants import IAPTOOLKIT_CONFIG_VERSION
|
|
11
|
-
# from iaptoolkit.vars import PERSISTENT_DATASTORE_ENABLED
|
|
12
|
-
# from iaptoolkit.vars import PERSISTENT_DATASTORE_PATH
|
|
13
|
-
# from iaptoolkit.vars import PERSISTENT_DATASTORE_USERNAME
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
LOG = logger.get_logger("iaptk-ds")
|
|
@@ -30,7 +27,7 @@ class TokenDatastore(VersionedDatastore):
|
|
|
30
27
|
self.set_value("tokens", tokens_dict)
|
|
31
28
|
|
|
32
29
|
def discard_existing_tokens(self):
|
|
33
|
-
LOG.
|
|
30
|
+
LOG.debug("Discarding existing tokens.")
|
|
34
31
|
self.update_data(tokens={})
|
|
35
32
|
|
|
36
33
|
def get_stored_service_account_token(self, iap_client_id: str) -> t.Optional[dict]:
|
iaptoolkit-0.1.0/PKG-INFO
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: iaptoolkit
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Library of common utils for interacting with Identity-Aware Proxies
|
|
5
|
-
Author: Rob Voigt
|
|
6
|
-
Author-email: code@ravoigt.com
|
|
7
|
-
Requires-Python: >=3.11,<4.0
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
Requires-Dist: google-auth (>=2.29.0,<3.0.0)
|
|
12
|
-
Requires-Dist: kvcommon (>=0.1.0,<0.2.0)
|
|
13
|
-
Requires-Dist: pytest (>=7.4.4,<8.0.0)
|
|
14
|
-
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
15
|
-
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
# IAP Toolkit
|
|
19
|
-
|
|
20
|
-
A library of utils to ease programmatic authentication with Google IAP (and ideally other IAPs in future).
|
|
21
|
-
|
|
22
|
-
## Configuration & Env Vars
|
|
23
|
-
|
|
24
|
-
| Env Var | Default | Type | Description|
|
|
25
|
-
|---|---|---|---|
|
|
26
|
-
|`KVC_LOG_FORMAT`|`"%(asctime)s - [%(levelname)s] - %(name)s - %(message)s"`|String|Sets log format for internal logger|
|
|
27
|
-
|`KVC_LOG_DATEFMT`|`"%Y-%m-%d %H:%M:%S"`|String|Sets log datetime format for internal logger|
|
|
28
|
-
|`IAPTOOLKIT_USE_AUTH_HEADER`|`False`|Boolean|If False, only adds Google IAP auth tokens to the `Proxy-Authorization` header. If True, adds tokens in the `Authorization` header if available/unused, falling back to the `Proxy-Authorization` header if needed. |
|
|
29
|
-
|`IAPTOOLKIT_PERSISTENT_DATASTORE_ENABLED`|`False`|Boolean|If true, the TOML-backed datastore for tokens is enabled.|
|
|
30
|
-
|`IAPTOOLKIT_PERSISTENT_DATASTORE_PATH`|`"~/.iaptoolkit"`|String|Path to dir where TOML-backed datastore|
|
|
31
|
-
|`IAPTOOLKIT_PERSISTENT_DATASTORE_USERNAME`|`"user.toml"`|String|Filename for TOML-backed datastore|
|
|
32
|
-
|`IAPTOOLKIT_CONFIG_VERSION`|`False`|Boolean|(Unused) Schema version for token storage|
|
|
33
|
-
|`GOOGLE_IAP_CLIENT_ID`|None|String|#TODO|
|
|
34
|
-
|`GOOGLE_CLIENT_ID`|None|String|#TODO|
|
|
35
|
-
|`GOOGLE_CLIENT_SECRET`|None|String|#TODO|
|
|
36
|
-
|
|
37
|
-
## Disclaimer
|
|
38
|
-
|
|
39
|
-
This project is not affiliated with Google. No trademark infringement intended.
|
|
40
|
-
|
iaptoolkit-0.1.0/README.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# IAP Toolkit
|
|
2
|
-
|
|
3
|
-
A library of utils to ease programmatic authentication with Google IAP (and ideally other IAPs in future).
|
|
4
|
-
|
|
5
|
-
## Configuration & Env Vars
|
|
6
|
-
|
|
7
|
-
| Env Var | Default | Type | Description|
|
|
8
|
-
|---|---|---|---|
|
|
9
|
-
|`KVC_LOG_FORMAT`|`"%(asctime)s - [%(levelname)s] - %(name)s - %(message)s"`|String|Sets log format for internal logger|
|
|
10
|
-
|`KVC_LOG_DATEFMT`|`"%Y-%m-%d %H:%M:%S"`|String|Sets log datetime format for internal logger|
|
|
11
|
-
|`IAPTOOLKIT_USE_AUTH_HEADER`|`False`|Boolean|If False, only adds Google IAP auth tokens to the `Proxy-Authorization` header. If True, adds tokens in the `Authorization` header if available/unused, falling back to the `Proxy-Authorization` header if needed. |
|
|
12
|
-
|`IAPTOOLKIT_PERSISTENT_DATASTORE_ENABLED`|`False`|Boolean|If true, the TOML-backed datastore for tokens is enabled.|
|
|
13
|
-
|`IAPTOOLKIT_PERSISTENT_DATASTORE_PATH`|`"~/.iaptoolkit"`|String|Path to dir where TOML-backed datastore|
|
|
14
|
-
|`IAPTOOLKIT_PERSISTENT_DATASTORE_USERNAME`|`"user.toml"`|String|Filename for TOML-backed datastore|
|
|
15
|
-
|`IAPTOOLKIT_CONFIG_VERSION`|`False`|Boolean|(Unused) Schema version for token storage|
|
|
16
|
-
|`GOOGLE_IAP_CLIENT_ID`|None|String|#TODO|
|
|
17
|
-
|`GOOGLE_CLIENT_ID`|None|String|#TODO|
|
|
18
|
-
|`GOOGLE_CLIENT_SECRET`|None|String|#TODO|
|
|
19
|
-
|
|
20
|
-
## Disclaimer
|
|
21
|
-
|
|
22
|
-
This project is not affiliated with Google. No trademark infringement intended.
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
5
|
-
|
|
6
|
-
import typing as t
|
|
7
|
-
from urllib.parse import ParseResult
|
|
8
|
-
from urllib.parse import urlparse
|
|
9
|
-
|
|
10
|
-
from kvcommon import logger
|
|
11
|
-
|
|
12
|
-
from iaptoolkit import headers
|
|
13
|
-
from iaptoolkit.exceptions import ServiceAccountTokenException
|
|
14
|
-
from iaptoolkit.tokens.service_account import ServiceAccount
|
|
15
|
-
from iaptoolkit.tokens.structs import ResultAddTokenHeader
|
|
16
|
-
|
|
17
|
-
from iaptoolkit.tokens.structs import TokenRefreshStruct
|
|
18
|
-
from iaptoolkit.utils.urls import is_url_safe_for_token
|
|
19
|
-
|
|
20
|
-
LOG = logger.get_logger("iaptk")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class IAPToolkit:
|
|
24
|
-
"""
|
|
25
|
-
Class to encapsulate client-specific vars and forward them to static functions
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
_GOOGLE_IAP_CLIENT_ID: str
|
|
29
|
-
_USE_AUTH_HEADER: bool
|
|
30
|
-
# _GOOGLE_CLIENT_ID: str # TODO: OAuth2
|
|
31
|
-
# _GOOGLE_CLIENT_SECRET: str # TODO: OAuth2
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
google_iap_client_id: str,
|
|
36
|
-
use_auth_header: bool,
|
|
37
|
-
# google_client_id: str,
|
|
38
|
-
# google_client_secret: str,
|
|
39
|
-
) -> None:
|
|
40
|
-
self._GOOGLE_IAP_CLIENT_ID = google_iap_client_id
|
|
41
|
-
self._USE_AUTH_HEADER = use_auth_header
|
|
42
|
-
# self._GOOGLE_CLIENT_ID = google_client_id
|
|
43
|
-
# self._GOOGLE_CLIENT_SECRET = google_client_secret
|
|
44
|
-
|
|
45
|
-
# self.ServiceAccount = GoogleServiceAccount(iap_client_id=google_iap_client_id)
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def sanitize_request_headers(request_headers: dict) -> dict:
|
|
49
|
-
return headers.sanitize_request_headers(request_headers)
|
|
50
|
-
|
|
51
|
-
def get_token_oidc(self, bypass_cached: bool = False) -> TokenRefreshStruct:
|
|
52
|
-
try:
|
|
53
|
-
return ServiceAccount.get_token(
|
|
54
|
-
iap_client_id=self._GOOGLE_IAP_CLIENT_ID, bypass_cached=bypass_cached
|
|
55
|
-
)
|
|
56
|
-
except ServiceAccountTokenException as ex:
|
|
57
|
-
LOG.debug(ex)
|
|
58
|
-
raise
|
|
59
|
-
|
|
60
|
-
def get_token_oauth2(self) -> TokenRefreshStruct:
|
|
61
|
-
# TODO
|
|
62
|
-
raise NotImplementedError()
|
|
63
|
-
|
|
64
|
-
def get_token_and_add_to_headers(self, request_headers: dict, use_oauth2: bool = False) -> bool:
|
|
65
|
-
"""
|
|
66
|
-
Retrieves an auth token and inserts it into the supplied request_headers dict.
|
|
67
|
-
request_headers is modified in-place
|
|
68
|
-
|
|
69
|
-
Params:
|
|
70
|
-
request_headers: dict of headers to insert into
|
|
71
|
-
use_oauth2: Use OAuth2.0 credentials and respective token, else use OIDC (default)
|
|
72
|
-
As a general guideline, OIDC is the assumed default approach for ServiceAccounts.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"""
|
|
76
|
-
if not use_oauth2:
|
|
77
|
-
token_refresh_struct: TokenRefreshStruct = self.get_token_oidc()
|
|
78
|
-
else:
|
|
79
|
-
token_refresh_struct: TokenRefreshStruct = self.get_token_oauth2()
|
|
80
|
-
|
|
81
|
-
headers.add_token_to_request_headers(
|
|
82
|
-
request_headers=request_headers,
|
|
83
|
-
id_token=token_refresh_struct.id_token,
|
|
84
|
-
use_auth_header=self._USE_AUTH_HEADER,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
return token_refresh_struct.token_is_new
|
|
88
|
-
|
|
89
|
-
@staticmethod
|
|
90
|
-
def is_url_safe_for_token(
|
|
91
|
-
url: str | ParseResult,
|
|
92
|
-
valid_domains: t.Optional[t.List[str] | t.Set[str] | t.Tuple[str]] = None,
|
|
93
|
-
):
|
|
94
|
-
if not isinstance(url, ParseResult):
|
|
95
|
-
url = urlparse(url)
|
|
96
|
-
|
|
97
|
-
return is_url_safe_for_token(url_parts=url, allowed_domains=valid_domains)
|
|
98
|
-
|
|
99
|
-
def check_url_and_add_token_header(
|
|
100
|
-
self,
|
|
101
|
-
url: str | ParseResult,
|
|
102
|
-
request_headers: dict,
|
|
103
|
-
valid_domains: t.List[str] | None = None,
|
|
104
|
-
use_oauth2: bool = False,
|
|
105
|
-
) -> ResultAddTokenHeader:
|
|
106
|
-
"""
|
|
107
|
-
Checks that the supplied URL is valid (i.e.; in valid_domains) and if so, retrieves a
|
|
108
|
-
token and adds it to request_headers.
|
|
109
|
-
|
|
110
|
-
i.e.; A convenience wrapper with logging for is_url_safe_for_token() and get_token_and_add_to_headers()
|
|
111
|
-
|
|
112
|
-
Params:
|
|
113
|
-
url: URL string or urllib.ParseResult to check for validity
|
|
114
|
-
request_headers: Dict of headers to insert into
|
|
115
|
-
valid_domains: List of domains to validate URL against
|
|
116
|
-
use_oauth2: Passed to get_token_and_add_to_headers() to determine if OAuth2.0 is used or OIDC (default)
|
|
117
|
-
"""
|
|
118
|
-
|
|
119
|
-
if self.is_url_safe_for_token(url=url, valid_domains=valid_domains):
|
|
120
|
-
token_is_fresh = self.get_token_and_add_to_headers(
|
|
121
|
-
request_headers=request_headers, use_oauth2=use_oauth2
|
|
122
|
-
)
|
|
123
|
-
return ResultAddTokenHeader(token_added=True, token_is_fresh=token_is_fresh)
|
|
124
|
-
else:
|
|
125
|
-
LOG.warn(
|
|
126
|
-
"URL is not approved: %s - Token will not be added to headers. Valid domains are: %s",
|
|
127
|
-
url,
|
|
128
|
-
valid_domains,
|
|
129
|
-
)
|
|
130
|
-
return ResultAddTokenHeader(token_added=False, token_is_fresh=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|