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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: iaptoolkit
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: Library of common utils for interacting with Identity-Aware Proxies
5
5
  Home-page: https://github.com/RAVoigt/iaptoolkit
6
6
  Author: Rob Voigt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "iaptoolkit"
3
- version = "0.3.3"
3
+ version = "0.3.4"
4
4
  description = "Library of common utils for interacting with Identity-Aware Proxies"
5
5
  authors = [
6
6
  {name = "Rob Voigt", email = "code@ravoigt.com"}
@@ -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
@@ -52,6 +52,7 @@ class TokenRefreshStruct:
52
52
  def valid(self):
53
53
  return validate_token(self.id_token)
54
54
 
55
+
55
56
  @dataclass(kw_only=True)
56
57
  class TokenStructOAuth2(TokenStruct):
57
58
  refresh_token: str
@@ -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