iaptoolkit 0.1.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iaptoolkit
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Library of common utils for interacting with Identity-Aware Proxies
5
5
  Author: Rob Voigt
6
6
  Author-email: code@ravoigt.com
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Requires-Dist: google-auth (>=2.29.0,<3.0.0)
12
- Requires-Dist: kvcommon (>=0.1.0,<0.2.0)
12
+ Requires-Dist: kvcommon (>=0.1.1,<0.2.0)
13
13
  Requires-Dist: pytest (>=7.4.4,<8.0.0)
14
14
  Requires-Dist: requests (>=2.31.0,<3.0.0)
15
15
  Requires-Dist: toml (>=0.10.2,<0.11.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "iaptoolkit"
3
- version = "0.1.1"
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.0"
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.info("Discarding existing tokens.")
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]:
@@ -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