fluidattacks-core 2.2.2__tar.gz → 2.2.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.
Files changed (25) hide show
  1. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/PKG-INFO +2 -2
  2. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/__init__.py +16 -3
  3. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/clone.py +6 -2
  4. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/codecommit_utils.py +10 -4
  5. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/https_utils.py +12 -3
  6. fluidattacks_core-2.2.4/fluidattacks_core/git/remote.py +199 -0
  7. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/pyproject.toml +2 -2
  8. fluidattacks_core-2.2.2/fluidattacks_core/git/remote.py +0 -51
  9. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/README.md +0 -0
  10. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/__init__.py +0 -0
  11. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/authz/__init__.py +0 -0
  12. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/authz/py.typed +0 -0
  13. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/authz/types.py +0 -0
  14. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/classes.py +0 -0
  15. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/delete_files.py +0 -0
  16. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/download_file.py +0 -0
  17. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/download_repo.py +0 -0
  18. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/py.typed +0 -0
  19. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/ssh_utils.py +0 -0
  20. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/utils.py +0 -0
  21. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/git/warp.py +0 -0
  22. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/http/__init__.py +0 -0
  23. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/http/client.py +0 -0
  24. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/http/validations.py +0 -0
  25. {fluidattacks_core-2.2.2 → fluidattacks_core-2.2.4}/fluidattacks_core/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fluidattacks-core
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Fluid Attacks Core Library
5
5
  License: MPL-2.0
6
6
  Author: Development
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Topic :: Software Development :: Libraries
17
17
  Requires-Dist: GitPython (>=3.1.41)
18
18
  Requires-Dist: aiofiles (>=23.2.1)
19
- Requires-Dist: aiohttp (>=3.9.2)
19
+ Requires-Dist: aiohttp (>=3.11.4)
20
20
  Requires-Dist: boto3 (>=1.34)
21
21
  Requires-Dist: certifi (>=2024.8.30)
22
22
  Requires-Dist: pathspec (>=0.12.1)
@@ -89,7 +89,8 @@ async def disable_quotepath(git_path: str) -> None:
89
89
 
90
90
 
91
91
  async def get_last_commit_info_new(
92
- repo_path: str, filename: str
92
+ repo_path: str,
93
+ filename: str,
93
94
  ) -> CommitInfo | None:
94
95
  proc = await asyncio.create_subprocess_exec(
95
96
  "git",
@@ -192,7 +193,9 @@ async def get_modified_filenames(repo_path: str, commit_sha: str) -> list[str]:
192
193
 
193
194
 
194
195
  async def is_commit_in_branch(
195
- repo_path: str, branch: str, commit_sha: str
196
+ repo_path: str,
197
+ branch: str,
198
+ commit_sha: str,
196
199
  ) -> bool:
197
200
  proc = await asyncio.create_subprocess_exec(
198
201
  "git",
@@ -260,7 +263,13 @@ def rebase(
260
263
  except (UnicodeDecodeError, UnicodeEncodeError) as exc:
261
264
  if ignore_errors:
262
265
  LOGGER.exception(
263
- exc, extra={"extra": {"path": path, "new_path": new_path}}
266
+ exc,
267
+ extra={
268
+ "extra": {
269
+ "path": path,
270
+ "new_path": new_path,
271
+ }
272
+ },
264
273
  )
265
274
  return None
266
275
 
@@ -313,6 +322,7 @@ async def clone(
313
322
  is_pat: bool = False,
314
323
  arn: str | None = None,
315
324
  org_external_id: str | None = None,
325
+ follow_redirects: bool = True,
316
326
  ) -> tuple[str | None, str | None]:
317
327
  if credential_key:
318
328
  return await ssh_clone(
@@ -329,6 +339,7 @@ async def clone(
329
339
  temp_dir=temp_dir,
330
340
  token=None,
331
341
  user=user,
342
+ follow_redirects=follow_redirects,
332
343
  )
333
344
  if token is not None:
334
345
  return await https_clone(
@@ -340,6 +351,7 @@ async def clone(
340
351
  user=None,
341
352
  provider=provider,
342
353
  is_pat=is_pat,
354
+ follow_redirects=follow_redirects,
343
355
  )
344
356
  if arn is not None and org_external_id is not None:
345
357
  return await call_codecommit_clone(
@@ -356,6 +368,7 @@ async def clone(
356
368
  branch=repo_branch,
357
369
  repo_url=repo_url,
358
370
  temp_dir=temp_dir,
371
+ follow_redirects=follow_redirects,
359
372
  )
360
373
 
361
374
  raise InvalidParameter()
@@ -80,6 +80,7 @@ async def https_clone(
80
80
  user: str | None = None,
81
81
  provider: str | None = None,
82
82
  is_pat: bool = False,
83
+ follow_redirects: bool = True,
83
84
  ) -> tuple[str | None, str | None]:
84
85
  url = format_url(
85
86
  repo_url=repo_url,
@@ -95,7 +96,7 @@ async def https_clone(
95
96
  "-c",
96
97
  "http.sslVerify=false",
97
98
  "-c",
98
- "http.followRedirects=true",
99
+ f"http.followRedirects={follow_redirects}",
99
100
  *(
100
101
  [
101
102
  "-c",
@@ -131,6 +132,7 @@ async def codecommit_clone(
131
132
  branch: str,
132
133
  repo_url: str,
133
134
  temp_dir: str,
135
+ follow_redirects: bool = True,
134
136
  ) -> tuple[str | None, str | None]:
135
137
  folder_to_clone_root = f"{temp_dir}/{uuid.uuid4()}"
136
138
  proc = await asyncio.create_subprocess_exec(
@@ -138,7 +140,7 @@ async def codecommit_clone(
138
140
  "-c",
139
141
  "http.sslVerify=false",
140
142
  "-c",
141
- "http.followRedirects=true",
143
+ f"http.followRedirects={follow_redirects}",
142
144
  "clone",
143
145
  "--branch",
144
146
  branch,
@@ -167,6 +169,7 @@ async def call_codecommit_clone(
167
169
  temp_dir: str,
168
170
  arn: str,
169
171
  org_external_id: str,
172
+ follow_redirects: bool = True,
170
173
  ) -> tuple[str | None, str | None]:
171
174
  original_access_key = os.environ.get("AWS_ACCESS_KEY_ID")
172
175
  original_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
@@ -191,6 +194,7 @@ async def call_codecommit_clone(
191
194
  branch=branch,
192
195
  repo_url=repo_url,
193
196
  temp_dir=temp_dir,
197
+ follow_redirects=follow_redirects,
194
198
  )
195
199
 
196
200
  except ClientError as exc:
@@ -24,6 +24,7 @@ async def assume_role_and_execute_git(
24
24
  repo_url: str,
25
25
  branch: str,
26
26
  org_external_id: str,
27
+ follow_redirects: bool = True,
27
28
  ) -> str | None:
28
29
  original_access_key = os.environ.get("AWS_ACCESS_KEY_ID")
29
30
  original_secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
@@ -44,7 +45,9 @@ async def assume_role_and_execute_git(
44
45
  os.environ["AWS_SESSION_TOKEN"] = credentials["SessionToken"]
45
46
  os.environ["AWS_DEFAULT_REGION"] = extract_region(repo_url)
46
47
 
47
- return await codecommit_ls_remote(repo_url=repo_url, branch=branch)
48
+ return await codecommit_ls_remote(
49
+ repo_url=repo_url, branch=branch, follow_redirects=follow_redirects
50
+ )
48
51
 
49
52
  except ClientError as exc:
50
53
  LOGGER.exception(
@@ -81,14 +84,14 @@ async def assume_role_and_execute_git(
81
84
 
82
85
 
83
86
  async def _execute_git_command(
84
- url: str, branch: str
87
+ url: str, branch: str, follow_redirects: bool = True
85
88
  ) -> tuple[bytes, bytes, int | None]:
86
89
  proc = await asyncio.create_subprocess_exec(
87
90
  "git",
88
91
  "-c",
89
92
  "http.sslVerify=false",
90
93
  "-c",
91
- "http.followRedirects=true",
94
+ f"http.followRedirects={follow_redirects}",
92
95
  "ls-remote",
93
96
  "--",
94
97
  url,
@@ -104,10 +107,11 @@ async def _execute_git_command(
104
107
  async def codecommit_ls_remote(
105
108
  repo_url: str,
106
109
  branch: str = "HEAD",
110
+ follow_redirects: bool = True,
107
111
  ) -> str | None:
108
112
  try:
109
113
  stdout, _stderr, return_code = await _execute_git_command(
110
- repo_url, branch
114
+ repo_url, branch, follow_redirects
111
115
  )
112
116
  if _stderr and return_code != 0:
113
117
  LOGGER.error(
@@ -136,10 +140,12 @@ async def call_codecommit_ls_remote(
136
140
  arn: str,
137
141
  branch: str,
138
142
  org_external_id: str,
143
+ follow_redirects: bool = True,
139
144
  ) -> str | None:
140
145
  return await assume_role_and_execute_git(
141
146
  repo_url=repo_url,
142
147
  arn=arn,
143
148
  branch=branch,
144
149
  org_external_id=org_external_id,
150
+ follow_redirects=follow_redirects,
145
151
  )
@@ -12,14 +12,18 @@ LOGGER = logging.getLogger(__name__)
12
12
 
13
13
 
14
14
  async def _execute_git_command(
15
- url: str, branch: str, is_pat: bool, token: str | None = None
15
+ url: str,
16
+ branch: str,
17
+ is_pat: bool,
18
+ token: str | None = None,
19
+ follow_redirects: bool = True,
16
20
  ) -> tuple[bytes, bytes, int | None]:
17
21
  proc = await asyncio.create_subprocess_exec(
18
22
  "git",
19
23
  "-c",
20
24
  "http.sslVerify=false",
21
25
  "-c",
22
- "http.followRedirects=true",
26
+ f"http.followRedirects={follow_redirects}",
23
27
  *(
24
28
  [
25
29
  "-c",
@@ -50,6 +54,7 @@ async def https_ls_remote(
50
54
  branch: str = "HEAD",
51
55
  provider: str | None = None,
52
56
  is_pat: bool = False,
57
+ follow_redirects: bool = True,
53
58
  ) -> str | None:
54
59
  url = format_url(
55
60
  repo_url=repo_url,
@@ -62,7 +67,7 @@ async def https_ls_remote(
62
67
 
63
68
  try:
64
69
  stdout, _stderr, return_code = await _execute_git_command(
65
- url, branch, is_pat, token
70
+ url, branch, is_pat, token, follow_redirects
66
71
  )
67
72
  if _stderr and return_code != 0:
68
73
  LOGGER.error(
@@ -96,6 +101,7 @@ async def call_https_ls_remote(
96
101
  branch: str,
97
102
  provider: str | None,
98
103
  is_pat: bool,
104
+ follow_redirects: bool = True,
99
105
  ) -> str | None:
100
106
  if user is not None and password is not None:
101
107
  return await https_ls_remote(
@@ -103,6 +109,7 @@ async def call_https_ls_remote(
103
109
  user=user,
104
110
  password=password,
105
111
  branch=branch,
112
+ follow_redirects=follow_redirects,
106
113
  )
107
114
  if token is not None:
108
115
  return await https_ls_remote(
@@ -111,10 +118,12 @@ async def call_https_ls_remote(
111
118
  branch=branch,
112
119
  provider=provider or "",
113
120
  is_pat=is_pat,
121
+ follow_redirects=follow_redirects,
114
122
  )
115
123
  if repo_url.startswith("http"):
116
124
  return await https_ls_remote(
117
125
  repo_url=repo_url,
118
126
  branch=branch,
127
+ follow_redirects=follow_redirects,
119
128
  )
120
129
  raise InvalidParameter()
@@ -0,0 +1,199 @@
1
+ import base64
2
+ import logging
3
+ from contextlib import suppress
4
+
5
+ import aiohttp
6
+ from urllib3.exceptions import LocationParseError
7
+ from urllib3.util import Url, parse_url
8
+
9
+ from ..http.client import request
10
+ from ..http.validations import HTTPValidationError
11
+ from .classes import InvalidParameter
12
+ from .codecommit_utils import call_codecommit_ls_remote
13
+ from .https_utils import call_https_ls_remote
14
+ from .ssh_utils import call_ssh_ls_remote
15
+
16
+ LOGGER = logging.getLogger(__name__)
17
+
18
+
19
+ def _format_redirected_url(
20
+ original_url: Url,
21
+ redirect_url: Url,
22
+ ) -> str:
23
+ return (
24
+ redirect_url._replace(
25
+ query=None,
26
+ path=(redirect_url.path or "").removesuffix("info/refs"),
27
+ ).url
28
+ if original_url.host == redirect_url.host
29
+ else original_url.url
30
+ )
31
+
32
+
33
+ async def get_redirected_url(
34
+ url: str,
35
+ user: str | None,
36
+ password: str | None,
37
+ token: str | None,
38
+ is_pat: bool,
39
+ ) -> str:
40
+ if user is not None and password is not None:
41
+ with suppress(LocationParseError):
42
+ return await _get_redirected_url(
43
+ parse_url(url)._replace(auth=None).url,
44
+ authorization="Basic "
45
+ + base64.b64encode(f"{user}:{password}".encode()).decode(),
46
+ )
47
+ return await _get_redirected_url(
48
+ url,
49
+ authorization="Basic "
50
+ + base64.b64encode(f"{user}:{password}".encode()).decode(),
51
+ )
52
+ if token is not None and is_pat:
53
+ return await _get_redirected_url(
54
+ url,
55
+ authorization=(
56
+ "Basic "
57
+ + base64.b64encode(f":{token}".encode()).decode()
58
+ ),
59
+ )
60
+ if token is not None and not is_pat:
61
+ return await _get_redirected_url(url, authorization=f"Bearer {token}")
62
+ if url.startswith("http"):
63
+ return await _get_redirected_url(url)
64
+ raise InvalidParameter()
65
+
66
+
67
+ async def _get_redirected_url(
68
+ url: str,
69
+ authorization: str | None = None,
70
+ ) -> str:
71
+ try:
72
+ return await _get_url(url, authorization=authorization)
73
+ except (
74
+ TimeoutError,
75
+ ValueError,
76
+ aiohttp.ClientError,
77
+ HTTPValidationError,
78
+ ) as exc:
79
+ LOGGER.warning(
80
+ "Failed to get redirected-url",
81
+ extra={"extra": {"url": url, "exc": exc}},
82
+ )
83
+ return url
84
+
85
+
86
+ async def _get_url(
87
+ original_url: str,
88
+ *,
89
+ redirect_url: str = "",
90
+ max_retries: int = 5,
91
+ authorization: str | None = None,
92
+ ) -> str:
93
+ try:
94
+ original = parse_url(original_url.removesuffix("/"))
95
+ url = (
96
+ parse_url(redirect_url.removesuffix("/"))
97
+ if redirect_url else original
98
+ )
99
+ except LocationParseError as exc:
100
+ raise HTTPValidationError(f"Invalid URL {redirect_url}") from exc
101
+
102
+ if max_retries < 1:
103
+ return _format_redirected_url(original, url)
104
+
105
+ # https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
106
+ url = (
107
+ url._replace(path=(url.path or "") + "/info/refs")
108
+ if not (url.path or "").endswith("info/refs")
109
+ else url
110
+ )
111
+ url = url._replace(query="service=git-upload-pack")
112
+
113
+ result = await request(
114
+ url.url,
115
+ method="GET",
116
+ headers={
117
+ "Host": url.host or "",
118
+ "Accept": "*/*",
119
+ "Accept-Encoding": "deflate, gzip",
120
+ "Pragma": "no-cache",
121
+ "Accept-Language": "*",
122
+ **({"Authorization": authorization} if authorization else {}),
123
+ },
124
+ )
125
+ if result.status == 200:
126
+ return _format_redirected_url(original, url)
127
+ if (
128
+ result.status > 300
129
+ and result.status < 400
130
+ and "Location" in result.headers
131
+ ):
132
+ with suppress(LocationParseError):
133
+ _url = parse_url(result.headers["Location"])
134
+ return await _get_url(
135
+ original_url,
136
+ redirect_url=result.headers["Location"],
137
+ max_retries=max_retries - 1,
138
+ authorization=authorization if _url.host == url.host else None,
139
+ )
140
+
141
+ return await _get_url(
142
+ original_url,
143
+ redirect_url=result.headers["Location"],
144
+ max_retries=max_retries - 1,
145
+ )
146
+
147
+ return _format_redirected_url(original, url)
148
+
149
+
150
+ async def ls_remote(
151
+ repo_url: str,
152
+ repo_branch: str,
153
+ *,
154
+ credential_key: str | None = None,
155
+ user: str | None = None,
156
+ password: str | None = None,
157
+ token: str | None = None,
158
+ provider: str | None = None,
159
+ is_pat: bool = False,
160
+ arn: str | None = None,
161
+ org_external_id: str | None = None,
162
+ follow_redirects: bool = True,
163
+ ) -> str | None:
164
+ last_commit: str | None = None
165
+ if credential_key is not None:
166
+ last_commit = await call_ssh_ls_remote(
167
+ repo_url,
168
+ credential_key,
169
+ repo_branch,
170
+ )
171
+ elif arn is not None and org_external_id is not None:
172
+ last_commit = await call_codecommit_ls_remote(
173
+ repo_url,
174
+ arn,
175
+ repo_branch,
176
+ org_external_id=org_external_id,
177
+ follow_redirects=follow_redirects,
178
+ )
179
+ else:
180
+ if not follow_redirects:
181
+ repo_url = await get_redirected_url(
182
+ repo_url,
183
+ user=user,
184
+ password=password,
185
+ token=token,
186
+ is_pat=is_pat,
187
+ )
188
+ last_commit = await call_https_ls_remote(
189
+ repo_url=repo_url,
190
+ user=user,
191
+ password=password,
192
+ token=token,
193
+ branch=repo_branch,
194
+ provider=provider,
195
+ is_pat=is_pat,
196
+ follow_redirects=follow_redirects,
197
+ )
198
+
199
+ return last_commit
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fluidattacks-core"
3
- version = "2.2.2"
3
+ version = "2.2.4"
4
4
  description = "Fluid Attacks Core Library"
5
5
  authors = ["Development <development@fluidattacks.com>"]
6
6
  license = "MPL-2.0"
@@ -17,7 +17,7 @@ classifiers = [
17
17
  [tool.poetry.dependencies]
18
18
  python = "^3.11"
19
19
  aiofiles = ">=23.2.1"
20
- aiohttp = ">=3.9.2"
20
+ aiohttp = ">=3.11.4"
21
21
  boto3 = ">=1.34"
22
22
  certifi = ">=2024.8.30"
23
23
  GitPython = ">=3.1.41"
@@ -1,51 +0,0 @@
1
- from .codecommit_utils import (
2
- call_codecommit_ls_remote,
3
- )
4
- from .https_utils import (
5
- call_https_ls_remote,
6
- )
7
- from .ssh_utils import (
8
- call_ssh_ls_remote,
9
- )
10
- import logging
11
-
12
- LOGGER = logging.getLogger(__name__)
13
-
14
-
15
- async def ls_remote(
16
- repo_url: str,
17
- repo_branch: str,
18
- *,
19
- credential_key: str | None = None,
20
- user: str | None = None,
21
- password: str | None = None,
22
- token: str | None = None,
23
- provider: str | None = None,
24
- is_pat: bool = False,
25
- arn: str | None = None,
26
- org_external_id: str | None = None,
27
- ) -> str | None:
28
- last_commit: str | None = None
29
- if credential_key is not None:
30
- last_commit = await call_ssh_ls_remote(
31
- repo_url, credential_key, repo_branch
32
- )
33
- elif arn is not None and org_external_id is not None:
34
- last_commit = await call_codecommit_ls_remote(
35
- repo_url,
36
- arn,
37
- repo_branch,
38
- org_external_id=org_external_id,
39
- )
40
- else:
41
- last_commit = await call_https_ls_remote(
42
- repo_url=repo_url,
43
- user=user,
44
- password=password,
45
- token=token,
46
- branch=repo_branch,
47
- provider=provider,
48
- is_pat=is_pat,
49
- )
50
-
51
- return last_commit