iaptoolkit 0.3.3__tar.gz → 0.3.4__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.3.3 → iaptoolkit-0.3.4}/PKG-INFO +1 -1
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/pyproject.toml +1 -1
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/exceptions.py +4 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/tokens/structs.py +1 -0
- iaptoolkit-0.3.4/src/iaptoolkit/utils/urls.py +91 -0
- iaptoolkit-0.3.3/src/iaptoolkit/utils/urls.py +0 -44
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/LICENSE +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/README.md +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/__init__.py +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/constants.py +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/headers.py +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/tokens/__init__.py +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/tokens/service_account.py +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/tokens/token_datastore.py +0 -0
- {iaptoolkit-0.3.3 → iaptoolkit-0.3.4}/src/iaptoolkit/utils/__init__.py +0 -0
|
@@ -22,6 +22,10 @@ class TokenStorageException(TokenException):
|
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
class IAPClientIDException(IAPToolkitBaseException):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
class ServiceAccountTokenException(TokenException):
|
|
26
30
|
def __init__(self, message: str, google_exception: t.Union[DefaultCredentialsError, RefreshError] | None):
|
|
27
31
|
self.google_exception = google_exception
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import requests
|
|
3
|
+
import typing as t
|
|
4
|
+
from urllib.parse import parse_qs
|
|
5
|
+
from urllib.parse import ParseResult
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from kvcommon import logger
|
|
9
|
+
from kvcommon.urls import get_netloc_without_port_from_url_parts
|
|
10
|
+
|
|
11
|
+
from iaptoolkit.exceptions import IAPClientIDException
|
|
12
|
+
from iaptoolkit.exceptions import InvalidDomain
|
|
13
|
+
|
|
14
|
+
LOG = logger.get_logger("iaptk")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_url_safe_for_token(
|
|
18
|
+
url_parts: ParseResult, allowed_domains: t.Optional[t.List[str] | t.Set[str] | t.Tuple[str]] = None
|
|
19
|
+
) -> bool:
|
|
20
|
+
"""Determines if the given url is considered a safe to receive a token in request headers.
|
|
21
|
+
|
|
22
|
+
If URL validation is enabled, check that the URL's netloc is in the list of valid domains.
|
|
23
|
+
"""
|
|
24
|
+
if not isinstance(url_parts, ParseResult):
|
|
25
|
+
raise TypeError(
|
|
26
|
+
f"Invalid url_parts - Expected a ParseResult - Got: "
|
|
27
|
+
f"'{str(url_parts)}' (type#: {type(url_parts).__name__})"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if allowed_domains is not None and not isinstance(allowed_domains, (list, set, tuple)):
|
|
31
|
+
raise TypeError("allowed_domains must be a list, set or tuple if not None")
|
|
32
|
+
|
|
33
|
+
netloc = get_netloc_without_port_from_url_parts(url_parts)
|
|
34
|
+
if not netloc:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
if not allowed_domains:
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
for domain in allowed_domains:
|
|
41
|
+
if domain == "" or not isinstance(domain, str):
|
|
42
|
+
raise InvalidDomain(
|
|
43
|
+
f"Empty or non-string domain in allowed_domains: " f"'{str(domain)}' (type#: {type(domain).__name__})"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if netloc.endswith(domain):
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(kw_only=True)
|
|
53
|
+
class IAPURLState:
|
|
54
|
+
protected: bool = False
|
|
55
|
+
iap_client_id: str | None = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_url_iap_state(url: str) -> IAPURLState:
|
|
59
|
+
# This approach may not be reliable - Undocumented?
|
|
60
|
+
|
|
61
|
+
iap_client_id = None
|
|
62
|
+
requires_iap = False
|
|
63
|
+
|
|
64
|
+
response = requests.get(url, allow_redirects=False)
|
|
65
|
+
if response.status_code == 302:
|
|
66
|
+
location = response.headers.get("location")
|
|
67
|
+
qs = str(urlparse(location).query)
|
|
68
|
+
query = parse_qs(qs) or {}
|
|
69
|
+
if "client_id" in query:
|
|
70
|
+
iap_client_id = str(query["client_id"][0])
|
|
71
|
+
requires_iap = True
|
|
72
|
+
|
|
73
|
+
return IAPURLState(protected=requires_iap, iap_client_id=iap_client_id)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_url_iap_protected(url: str) -> bool:
|
|
77
|
+
url_state: IAPURLState = get_url_iap_state(url)
|
|
78
|
+
return url_state.protected
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_iap_client_id_for_url(url: str) -> str | None:
|
|
82
|
+
url_state: IAPURLState = get_url_iap_state(url)
|
|
83
|
+
if not url_state.protected:
|
|
84
|
+
raise IAPClientIDException(f"URL does not appear to be IAP-protected: '{url}'")
|
|
85
|
+
|
|
86
|
+
iap_client_id = url_state.iap_client_id
|
|
87
|
+
if not iap_client_id:
|
|
88
|
+
raise IAPClientIDException(
|
|
89
|
+
f"No client_id returned in redirect for query when trying to retrieve IAP Client ID for url: '{url}'"
|
|
90
|
+
)
|
|
91
|
+
return iap_client_id
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import typing as t
|
|
2
|
-
from urllib.parse import ParseResult
|
|
3
|
-
|
|
4
|
-
from kvcommon import logger
|
|
5
|
-
from kvcommon.urls import get_netloc_without_port_from_url_parts
|
|
6
|
-
|
|
7
|
-
from iaptoolkit.exceptions import InvalidDomain
|
|
8
|
-
|
|
9
|
-
LOG = logger.get_logger("iaptk")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def is_url_safe_for_token(
|
|
13
|
-
url_parts: ParseResult, allowed_domains: t.Optional[t.List[str] | t.Set[str] | t.Tuple[str]] = None
|
|
14
|
-
) -> bool:
|
|
15
|
-
"""Determines if the given url is considered a safe to receive a token in request headers.
|
|
16
|
-
|
|
17
|
-
If URL validation is enabled, check that the URL's netloc is in the list of valid domains.
|
|
18
|
-
"""
|
|
19
|
-
if not isinstance(url_parts, ParseResult):
|
|
20
|
-
raise TypeError(
|
|
21
|
-
f"Invalid url_parts - Expected a ParseResult - Got: "
|
|
22
|
-
f"'{str(url_parts)}' (type#: {type(url_parts).__name__})"
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
if allowed_domains is not None and not isinstance(allowed_domains, (list, set, tuple)):
|
|
26
|
-
raise TypeError("allowed_domains must be a list, set or tuple if not None")
|
|
27
|
-
|
|
28
|
-
netloc = get_netloc_without_port_from_url_parts(url_parts)
|
|
29
|
-
if not netloc:
|
|
30
|
-
return False
|
|
31
|
-
|
|
32
|
-
if not allowed_domains:
|
|
33
|
-
return True
|
|
34
|
-
|
|
35
|
-
for domain in allowed_domains:
|
|
36
|
-
if domain == "" or not isinstance(domain, str):
|
|
37
|
-
raise InvalidDomain(
|
|
38
|
-
f"Empty or non-string domain in allowed_domains: " f"'{str(domain)}' (type#: {type(domain).__name__})"
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
if netloc.endswith(domain):
|
|
42
|
-
return True
|
|
43
|
-
|
|
44
|
-
return 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
|