fluidattacks_core_auth 12.0.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.
@@ -0,0 +1 @@
1
+ use_flake ".#python311.devShell"
@@ -0,0 +1,7 @@
1
+ dist-*/
2
+ /.cargo-cache
3
+ target/
4
+ .direnv/
5
+ .venv/
6
+ .vscode/settings.json
7
+ .zed/settings.json
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.5
2
+ Name: fluidattacks_core_auth
3
+ Version: 12.0.0
4
+ Summary: Fluid Attacks Core Auth Library
5
+ Author-email: Development <development@fluidattacks.com>
6
+ License: MPL-2.0
7
+ Classifier: Development Status :: 1 - Planning
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Topic :: Software Development :: Libraries
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: fluidattacks-core-rustport>=12.0.0
@@ -0,0 +1,19 @@
1
+ { src, workspace_root }:
2
+ {
3
+ inherit src workspace_root;
4
+ root_path = "develops/fluidattacks-core/python/auth";
5
+ module_name = "fluidattacks_core";
6
+ pypi_token = {
7
+ method = "oidc";
8
+ };
9
+ override = {
10
+ checks = {
11
+ basedpyright = false;
12
+ };
13
+ configs = {
14
+ import_linter = ../import-linter.cfg;
15
+ mypy = ../mypy.ini;
16
+ ruff = ../ruff.toml;
17
+ };
18
+ };
19
+ }
@@ -0,0 +1,13 @@
1
+ path_filter: src:
2
+ path_filter {
3
+ root = src;
4
+ include = [
5
+ "fluidattacks_core"
6
+ "tests"
7
+ "import-linter.cfg"
8
+ "mypy.ini"
9
+ "pyproject.toml"
10
+ "ruff.toml"
11
+ "uv.lock"
12
+ ];
13
+ }
@@ -0,0 +1,303 @@
1
+ {
2
+ "nodes": {
3
+ "flake-parts": {
4
+ "inputs": {
5
+ "nixpkgs-lib": "nixpkgs-lib"
6
+ },
7
+ "locked": {
8
+ "lastModified": 1768135262,
9
+ "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
10
+ "owner": "hercules-ci",
11
+ "repo": "flake-parts",
12
+ "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
13
+ "type": "github"
14
+ },
15
+ "original": {
16
+ "owner": "hercules-ci",
17
+ "repo": "flake-parts",
18
+ "type": "github"
19
+ }
20
+ },
21
+ "nix_filter": {
22
+ "locked": {
23
+ "lastModified": 1757882181,
24
+ "narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
25
+ "owner": "numtide",
26
+ "repo": "nix-filter",
27
+ "rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
28
+ "type": "github"
29
+ },
30
+ "original": {
31
+ "owner": "numtide",
32
+ "repo": "nix-filter",
33
+ "type": "github"
34
+ }
35
+ },
36
+ "nixpkgs": {
37
+ "locked": {
38
+ "lastModified": 1758690382,
39
+ "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
40
+ "owner": "nixos",
41
+ "repo": "nixpkgs",
42
+ "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
43
+ "type": "github"
44
+ },
45
+ "original": {
46
+ "owner": "nixos",
47
+ "ref": "nixos-unstable",
48
+ "repo": "nixpkgs",
49
+ "type": "github"
50
+ }
51
+ },
52
+ "nixpkgs-lib": {
53
+ "locked": {
54
+ "lastModified": 1765674936,
55
+ "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
56
+ "owner": "nix-community",
57
+ "repo": "nixpkgs.lib",
58
+ "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
59
+ "type": "github"
60
+ },
61
+ "original": {
62
+ "owner": "nix-community",
63
+ "repo": "nixpkgs.lib",
64
+ "type": "github"
65
+ }
66
+ },
67
+ "nixpkgs_2": {
68
+ "locked": {
69
+ "lastModified": 1758690382,
70
+ "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
71
+ "owner": "nixos",
72
+ "repo": "nixpkgs",
73
+ "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
74
+ "type": "github"
75
+ },
76
+ "original": {
77
+ "owner": "nixos",
78
+ "ref": "nixos-unstable",
79
+ "repo": "nixpkgs",
80
+ "type": "github"
81
+ }
82
+ },
83
+ "nixpkgs_3": {
84
+ "locked": {
85
+ "lastModified": 1761373498,
86
+ "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=",
87
+ "owner": "nixos",
88
+ "repo": "nixpkgs",
89
+ "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce",
90
+ "type": "github"
91
+ },
92
+ "original": {
93
+ "owner": "nixos",
94
+ "ref": "nixos-unstable",
95
+ "repo": "nixpkgs",
96
+ "type": "github"
97
+ }
98
+ },
99
+ "nixpkgs_flake": {
100
+ "locked": {
101
+ "lastModified": 1778253243,
102
+ "narHash": "sha256-KkH2QvU6iRLDTGGEwH5jnMqHWj4zMTuHxi1S1zdr01w=",
103
+ "owner": "nixos",
104
+ "repo": "nixpkgs",
105
+ "rev": "4fcda7103530b23c5ca44286344912082aad4220",
106
+ "type": "github"
107
+ },
108
+ "original": {
109
+ "owner": "nixos",
110
+ "repo": "nixpkgs",
111
+ "type": "github"
112
+ }
113
+ },
114
+ "pyproject-build-systems": {
115
+ "inputs": {
116
+ "nixpkgs": "nixpkgs",
117
+ "pyproject-nix": "pyproject-nix",
118
+ "uv2nix": "uv2nix"
119
+ },
120
+ "locked": {
121
+ "lastModified": 1761781027,
122
+ "narHash": "sha256-YDvxPAm2WnxrznRqWwHLjryBGG5Ey1ATEJXrON+TWt8=",
123
+ "owner": "pyproject-nix",
124
+ "repo": "build-system-pkgs",
125
+ "rev": "795a980d25301e5133eca37adae37283ec3c8e66",
126
+ "type": "github"
127
+ },
128
+ "original": {
129
+ "owner": "pyproject-nix",
130
+ "repo": "build-system-pkgs",
131
+ "type": "github"
132
+ }
133
+ },
134
+ "pyproject-nix": {
135
+ "inputs": {
136
+ "nixpkgs": [
137
+ "python_builder",
138
+ "pyproject-build-systems",
139
+ "nixpkgs"
140
+ ]
141
+ },
142
+ "locked": {
143
+ "lastModified": 1758265079,
144
+ "narHash": "sha256-amLaLNwKSZPShQHzfgmc/9o76dU8xzN0743dWgvYlr8=",
145
+ "owner": "nix-community",
146
+ "repo": "pyproject.nix",
147
+ "rev": "02e9418fd4af638447dca4b17b1280da95527fc9",
148
+ "type": "github"
149
+ },
150
+ "original": {
151
+ "owner": "nix-community",
152
+ "repo": "pyproject.nix",
153
+ "type": "github"
154
+ }
155
+ },
156
+ "pyproject-nix_2": {
157
+ "inputs": {
158
+ "nixpkgs": "nixpkgs_2"
159
+ },
160
+ "locked": {
161
+ "lastModified": 1762427963,
162
+ "narHash": "sha256-CkPlAbIQ87wmjy5qHibfzk4DmMGBNqFer+lLfXjpP5M=",
163
+ "owner": "pyproject-nix",
164
+ "repo": "pyproject.nix",
165
+ "rev": "4540ea004e04fcd12dd2738d51383d10f956f7b9",
166
+ "type": "github"
167
+ },
168
+ "original": {
169
+ "owner": "pyproject-nix",
170
+ "repo": "pyproject.nix",
171
+ "type": "github"
172
+ }
173
+ },
174
+ "pyproject-nix_3": {
175
+ "inputs": {
176
+ "nixpkgs": [
177
+ "python_builder",
178
+ "uv2nix",
179
+ "nixpkgs"
180
+ ]
181
+ },
182
+ "locked": {
183
+ "lastModified": 1760402624,
184
+ "narHash": "sha256-jF6UKLs2uGc2rtved8Vrt58oTWjTQoAssuYs/0578Z4=",
185
+ "owner": "pyproject-nix",
186
+ "repo": "pyproject.nix",
187
+ "rev": "84c4ea102127c77058ea1ed7be7300261fafc7d2",
188
+ "type": "github"
189
+ },
190
+ "original": {
191
+ "owner": "pyproject-nix",
192
+ "repo": "pyproject.nix",
193
+ "type": "github"
194
+ }
195
+ },
196
+ "python_builder": {
197
+ "inputs": {
198
+ "nix_filter": "nix_filter",
199
+ "nixpkgs_flake": "nixpkgs_flake",
200
+ "pyproject-build-systems": "pyproject-build-systems",
201
+ "pyproject-nix": "pyproject-nix_2",
202
+ "shell-helpers": "shell-helpers",
203
+ "uv2nix": "uv2nix_2"
204
+ },
205
+ "locked": {
206
+ "dir": "common/utils/python-builder",
207
+ "lastModified": 1787250154,
208
+ "narHash": "sha256-K5gcXYdMptekSODkJpTjMez8RtNAT09qM+UhvHK2Jb4=",
209
+ "ref": "refs/heads/trunk",
210
+ "rev": "44642cdb6027b0fd00646123248a84858c753eeb",
211
+ "shallow": true,
212
+ "type": "git",
213
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
214
+ },
215
+ "original": {
216
+ "dir": "common/utils/python-builder",
217
+ "rev": "44642cdb6027b0fd00646123248a84858c753eeb",
218
+ "shallow": true,
219
+ "type": "git",
220
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
221
+ }
222
+ },
223
+ "root": {
224
+ "inputs": {
225
+ "python_builder": "python_builder"
226
+ }
227
+ },
228
+ "shell-helpers": {
229
+ "inputs": {
230
+ "flake-parts": "flake-parts",
231
+ "nixpkgs": [
232
+ "python_builder",
233
+ "nixpkgs_flake"
234
+ ]
235
+ },
236
+ "locked": {
237
+ "dir": "common/utils/shell-helpers",
238
+ "lastModified": 1787145059,
239
+ "narHash": "sha256-i5i+qLR9JhcNasrQvzG4S/0AotpZAqTHXa4/68MmvwA=",
240
+ "ref": "refs/heads/trunk",
241
+ "rev": "e0d58302690af6f38b7544265e59c625b63df76f",
242
+ "shallow": true,
243
+ "type": "git",
244
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
245
+ },
246
+ "original": {
247
+ "dir": "common/utils/shell-helpers",
248
+ "rev": "e0d58302690af6f38b7544265e59c625b63df76f",
249
+ "shallow": true,
250
+ "type": "git",
251
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
252
+ }
253
+ },
254
+ "uv2nix": {
255
+ "inputs": {
256
+ "nixpkgs": [
257
+ "python_builder",
258
+ "pyproject-build-systems",
259
+ "nixpkgs"
260
+ ],
261
+ "pyproject-nix": [
262
+ "python_builder",
263
+ "pyproject-build-systems",
264
+ "pyproject-nix"
265
+ ]
266
+ },
267
+ "locked": {
268
+ "lastModified": 1758933732,
269
+ "narHash": "sha256-HAmm1GBS1myZCFuog0DC2ZLaynvZtiUI2Crmo+cdQI0=",
270
+ "owner": "pyproject-nix",
271
+ "repo": "uv2nix",
272
+ "rev": "273ce18f913d8559e0d04f820d724308966d7c4d",
273
+ "type": "github"
274
+ },
275
+ "original": {
276
+ "owner": "pyproject-nix",
277
+ "repo": "uv2nix",
278
+ "type": "github"
279
+ }
280
+ },
281
+ "uv2nix_2": {
282
+ "inputs": {
283
+ "nixpkgs": "nixpkgs_3",
284
+ "pyproject-nix": "pyproject-nix_3"
285
+ },
286
+ "locked": {
287
+ "lastModified": 1761872265,
288
+ "narHash": "sha256-i25GRgp2vUOebY70L3NTAgkd+Pr1hnn5xM3qHxH0ONU=",
289
+ "owner": "pyproject-nix",
290
+ "repo": "uv2nix",
291
+ "rev": "74dfb62871be152ad3673b143b0cc56105a4f3c5",
292
+ "type": "github"
293
+ },
294
+ "original": {
295
+ "owner": "pyproject-nix",
296
+ "repo": "uv2nix",
297
+ "type": "github"
298
+ }
299
+ }
300
+ },
301
+ "root": "root",
302
+ "version": 7
303
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ description = "Fluid Attacks Core auth package.";
3
+
4
+ inputs = {
5
+ python_builder = {
6
+ url = "git+ssh://git@gitlab.com/fluidattacks/universe?shallow=1&rev=44642cdb6027b0fd00646123248a84858c753eeb&dir=common/utils/python-builder";
7
+ };
8
+ };
9
+
10
+ outputs =
11
+ { self, ... }@inputs:
12
+ let
13
+ build_args =
14
+ {
15
+ system,
16
+ python_version,
17
+ nixpkgs,
18
+ builders,
19
+ scripts,
20
+ }:
21
+ import ./build {
22
+ src = import ./build/filter.nix nixpkgs.nix-filter self;
23
+ workspace_root = ./.;
24
+ };
25
+ in
26
+ {
27
+ packages = inputs.python_builder.outputs.build build_args;
28
+ };
29
+ }
@@ -0,0 +1,14 @@
1
+ """Credential handling shared by every CLI that talks to the platform.
2
+
3
+ The decisions live in the shared client: which credential is in force, and what can be
4
+ done when the platform rejects one. What lives here is the one thing that cannot, because
5
+ it depends on the caller's own GraphQL client: recognising the rejection in a response.
6
+ """
7
+
8
+ from .recovery import (
9
+ is_login_required,
10
+ )
11
+
12
+ __all__ = [
13
+ "is_login_required",
14
+ ]
@@ -0,0 +1,29 @@
1
+ """Recognise a rejected credential in a GraphQL response."""
2
+
3
+ import json
4
+ from types import ModuleType
5
+
6
+
7
+ def _rustport() -> ModuleType:
8
+ # Imported on use, not at module load: the extension is built by maturin, so a
9
+ # source checkout can import this module without it.
10
+ from fluidattacks_core import rustport # noqa: PLC0415
11
+
12
+ return rustport
13
+
14
+
15
+ def is_login_required(errors: object) -> bool:
16
+ """Whether a GraphQL error payload says the credential was rejected.
17
+
18
+ Pass the `errors` list the platform returned, whatever client produced it. The
19
+ contract lives in the shared client, so nothing here walks the payload or pins the
20
+ code: this only re-serialises what a Python client already parsed. On a match,
21
+ `rustport.recover` decides what can be done about it.
22
+ """
23
+ if errors is None:
24
+ return False
25
+ try:
26
+ body = json.dumps({"errors": errors})
27
+ except (TypeError, ValueError):
28
+ return False
29
+ return bool(_rustport().says_login_required(body))
@@ -0,0 +1,21 @@
1
+ [importlinter]
2
+ root_package = fluidattacks_core
3
+ include_external_packages = True
4
+
5
+ [importlinter:contract:portion]
6
+ name = Single Portion
7
+ type = layers
8
+ containers =
9
+ fluidattacks_core
10
+ exhaustive = true
11
+ layers =
12
+ auth
13
+
14
+ [importlinter:contract:dag]
15
+ name = Direct Acyclic Graph
16
+ type = layers
17
+ containers =
18
+ fluidattacks_core.auth
19
+ exhaustive = true
20
+ layers =
21
+ recovery
@@ -0,0 +1,32 @@
1
+ [mypy]
2
+ disallow_any_decorated = True
3
+ disallow_any_expr = False
4
+ disallow_any_explicit = False
5
+ disallow_any_generics = True
6
+ disallow_any_unimported = True
7
+ disallow_incomplete_defs = True
8
+ disallow_subclassing_any = True
9
+ disallow_untyped_calls = True
10
+ disallow_untyped_decorators = True
11
+ disallow_untyped_defs = True
12
+
13
+ check_untyped_defs = True
14
+ no_implicit_optional = True
15
+ strict = True
16
+ strict_equality = True
17
+ strict_optional = True
18
+
19
+ warn_redundant_casts = True
20
+ warn_unused_ignores = True
21
+ warn_no_return = True
22
+ warn_return_any = False
23
+ warn_unreachable = True
24
+
25
+ explicit_package_bases = True
26
+
27
+ ignore_errors = False
28
+ ignore_missing_imports = False
29
+ allow_untyped_globals = False
30
+ allow_redefinition = False
31
+ local_partial_types = False
32
+ implicit_reexport = False
@@ -0,0 +1,40 @@
1
+ [project]
2
+ name = "fluidattacks_core_auth"
3
+ version = "12.0.0"
4
+ description = "Fluid Attacks Core Auth Library"
5
+ authors = [{ name = "Development", email = "development@fluidattacks.com" }]
6
+ license = { text = "MPL-2.0" }
7
+ classifiers = [
8
+ "Development Status :: 1 - Planning",
9
+ "Intended Audience :: Developers",
10
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Topic :: Software Development :: Libraries",
14
+ ]
15
+ requires-python = ">=3.11"
16
+ dependencies = ["fluidattacks-core-rustport>=12.0.0"]
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "deptry>=0.23.1",
21
+ "import-linter==2.11",
22
+ "mypy==1.19.1",
23
+ "pytest-cov>=7.0.0",
24
+ "pytest==9.0.3",
25
+ "ruff==0.15.0",
26
+ ]
27
+
28
+ [tool.deptry]
29
+ known_first_party = ["fluidattacks_core"]
30
+
31
+ [tool.uv]
32
+ exclude-newer = "7 days"
33
+ exclude-newer-package = { fluidattacks-core-rustport = "2099-01-01T00:00:00Z" }
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["fluidattacks_core"]
@@ -0,0 +1,45 @@
1
+ cache-dir = ".ruff_cache"
2
+ line-length = 100
3
+ indent-width = 4
4
+ target-version = "py311"
5
+ include = ["fluidattacks_core/**/*.py", "tests/**/*.py"]
6
+
7
+ [lint]
8
+ preview = true
9
+ explicit-preview-rules = true
10
+ select = ["ALL"]
11
+ extend-select = ["PLR1702", "PLR0916"]
12
+ ignore = [
13
+ # General omissions. Do not add to this list.
14
+ "COM812",
15
+ "D100",
16
+ "D101",
17
+ "D102",
18
+ "D103",
19
+ "D104",
20
+ "D107",
21
+ "D203",
22
+ "D213",
23
+ "PLR2004",
24
+ ]
25
+ fixable = ["ALL"]
26
+ unfixable = []
27
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
28
+
29
+ [lint.per-file-ignores]
30
+ "tests/*.py" = ["S101"]
31
+
32
+ [format]
33
+ quote-style = "double"
34
+ indent-style = "space"
35
+ skip-magic-trailing-comma = false
36
+ line-ending = "lf"
37
+
38
+ [lint.mccabe]
39
+ max-complexity = 8
40
+
41
+ [lint.pylint]
42
+ max-branches = 8
43
+ max-nested-blocks = 4
44
+ max-statements = 25
45
+ max-bool-expr = 5
File without changes
@@ -0,0 +1,55 @@
1
+ import json
2
+ from types import SimpleNamespace
3
+ from typing import Any
4
+
5
+ import pytest
6
+
7
+ from fluidattacks_core.auth import recovery
8
+
9
+ _CODE = "LOGIN_REQUIRED"
10
+ _REJECTED: list[dict[str, Any]] = [{"message": "Login required", "extensions": {"code": _CODE}}]
11
+
12
+
13
+ @pytest.fixture(autouse=True)
14
+ def _stub(monkeypatch: pytest.MonkeyPatch) -> None:
15
+ # Stands in for the shared client, which owns the contract: this module only hands it
16
+ # a body to look at.
17
+ def says_login_required(body: str) -> bool:
18
+ return _CODE in body
19
+
20
+ fake = SimpleNamespace(says_login_required=says_login_required)
21
+ monkeypatch.setattr(recovery, "_rustport", lambda: fake)
22
+
23
+
24
+ def test_recognises_a_rejected_credential() -> None:
25
+ assert recovery.is_login_required(_REJECTED)
26
+
27
+
28
+ def test_ignores_any_other_error() -> None:
29
+ # Only the platform's own code counts, so an unrelated failure is not mistaken for an
30
+ # auth problem and retried pointlessly.
31
+ assert not recovery.is_login_required([{"message": "boom"}])
32
+ assert not recovery.is_login_required([{"extensions": {"code": "OTHER"}}])
33
+
34
+
35
+ def test_tolerates_any_payload_shape() -> None:
36
+ # Whatever client produced it, this must not raise on the way to a verdict.
37
+ assert not recovery.is_login_required(None)
38
+ assert not recovery.is_login_required([{"extensions": "not a dict"}])
39
+ # Something a json encoder cannot take is not a rejection either.
40
+ assert not recovery.is_login_required([{"extensions": {"code": object()}}])
41
+
42
+
43
+ def test_passes_the_payload_to_the_shared_client(monkeypatch: pytest.MonkeyPatch) -> None:
44
+ # The contract is checked there, not here, so what matters is that it gets the errors.
45
+ seen: list[str] = []
46
+
47
+ def says_login_required(body: str) -> bool:
48
+ seen.append(body)
49
+ return False
50
+
51
+ monkeypatch.setattr(
52
+ recovery, "_rustport", lambda: SimpleNamespace(says_login_required=says_login_required)
53
+ )
54
+ recovery.is_login_required(_REJECTED)
55
+ assert json.loads(seen[0]) == {"errors": _REJECTED}