caido-sdk-client 0.1.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.
- caido_sdk_client-0.1.0/PKG-INFO +27 -0
- caido_sdk_client-0.1.0/README.md +15 -0
- caido_sdk_client-0.1.0/pyproject.toml +26 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/__init__.py +191 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/__init__.py +27 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/cache/__init__.py +6 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/cache/file.py +71 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/cache/types.py +19 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/manager.py +196 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/types.py +61 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/auth/utils.py +31 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/client.py +198 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/__init__.py +25 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/blob.py +19 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/connection.py +27 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/environment.py +27 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/filter.py +17 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/finding.py +25 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/hosted_file.py +22 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/instance_settings.py +58 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/project.py +25 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/request.py +56 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/convert/scope.py +18 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/__init__.py +71 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/all_errors.py +39 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/auth.py +15 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/authorization.py +40 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/base.py +25 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/cloud.py +24 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/form.py +23 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/from_error.py +82 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/graphql.py +20 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/health.py +15 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/misc.py +23 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/plugin.py +112 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/project.py +30 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/rest.py +23 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/sdk.py +26 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/tasks.py +15 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/version.py +14 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/errors/workflow.py +24 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/__generated__/schema.py +2688 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/__init__.py +18 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/client.py +199 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/connectionInfo.graphql +7 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/environment.graphql +110 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/errors.graphql +79 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/filter.graphql +70 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/finding.graphql +91 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/hostedFile.graphql +37 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/instanceSettings.graphql +55 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/plugin.graphql +39 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/project.graphql +103 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/range.graphql +4 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/replay.graphql +276 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/request.graphql +81 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/scope.graphql +59 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/task.graphql +50 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/viewer.graphql +30 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/documents/workflow.graphql +84 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/graphql/utils.py +72 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/logger.py +31 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/rest/__init__.py +7 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/rest/client.py +149 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/__init__.py +33 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/environment.py +267 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/filter.py +109 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/finding.py +139 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/hosted_file.py +79 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/instance.py +13 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/instance_settings.py +112 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/plugin.py +208 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/project.py +108 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/request.py +194 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/scope.py +101 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/sdks/user.py +85 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/__init__.py +127 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/connection.py +75 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/environment.py +55 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/filter.py +45 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/finding.py +43 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/hosted_file.py +45 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/instance_settings.py +113 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/plugin.py +70 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/project.py +33 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/request.py +69 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/scope.py +46 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/strings.py +57 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/types/user.py +58 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/utils/__init__.py +7 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/utils/errors.py +14 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/utils/list.py +101 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/utils/misc.py +10 -0
- caido_sdk_client-0.1.0/src/caido_sdk_client/utils/pydantic.py +6 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: caido-sdk-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client SDK for interacting with a Caido instance
|
|
5
|
+
Author: Caido Labs Inc.
|
|
6
|
+
Author-email: Caido Labs Inc. <dev@caido.io>
|
|
7
|
+
Requires-Dist: caido-server-auth>=0.1.2
|
|
8
|
+
Requires-Dist: gql[aiohttp,websockets]>=3.5.0
|
|
9
|
+
Requires-Dist: pydantic>=2.11.0
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
<div align="center">
|
|
14
|
+
<img width="1000" alt="image" src="https://github.com/caido-community/.github/blob/main/content/banner.png?raw=true">
|
|
15
|
+
|
|
16
|
+
<br />
|
|
17
|
+
<br />
|
|
18
|
+
<a href="https://github.com/caido-community" target="_blank">Github</a>
|
|
19
|
+
<span> • </span>
|
|
20
|
+
<a href="https://developer.caido.io/" target="_blank">Documentation</a>
|
|
21
|
+
<span> • </span>
|
|
22
|
+
<a href="https://links.caido.io/www-discord" target="_blank">Discord</a>
|
|
23
|
+
<br />
|
|
24
|
+
<hr />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
## 👋 Client SDK
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img width="1000" alt="image" src="https://github.com/caido-community/.github/blob/main/content/banner.png?raw=true">
|
|
3
|
+
|
|
4
|
+
<br />
|
|
5
|
+
<br />
|
|
6
|
+
<a href="https://github.com/caido-community" target="_blank">Github</a>
|
|
7
|
+
<span> • </span>
|
|
8
|
+
<a href="https://developer.caido.io/" target="_blank">Documentation</a>
|
|
9
|
+
<span> • </span>
|
|
10
|
+
<a href="https://links.caido.io/www-discord" target="_blank">Discord</a>
|
|
11
|
+
<br />
|
|
12
|
+
<hr />
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## 👋 Client SDK
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "caido-sdk-client"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Client SDK for interacting with a Caido instance"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Caido Labs Inc.", email = "dev@caido.io" }]
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"caido-server-auth>=0.1.2",
|
|
10
|
+
"gql[aiohttp,websockets]>=3.5.0",
|
|
11
|
+
"pydantic>=2.11.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[dependency-groups]
|
|
15
|
+
dev = ["pytest>=8.0.0", "pytest-asyncio>=0.24.0"]
|
|
16
|
+
|
|
17
|
+
[tool.pytest.ini_options]
|
|
18
|
+
asyncio_mode = "auto"
|
|
19
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
20
|
+
testpaths = ["tests"]
|
|
21
|
+
# Run tests one at a time (no parallel workers); integration tests share one Caido instance
|
|
22
|
+
addopts = ["-p", "no:xdist"]
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["uv_build>=0.9.8,<0.10.0"]
|
|
26
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Public package exports."""
|
|
2
|
+
|
|
3
|
+
from caido_sdk_client.auth import (
|
|
4
|
+
AuthCacheFile,
|
|
5
|
+
AuthManager,
|
|
6
|
+
AuthOptions,
|
|
7
|
+
BrowserAuthOptions,
|
|
8
|
+
CachedToken,
|
|
9
|
+
FileTokenCache,
|
|
10
|
+
PATAuthOptions,
|
|
11
|
+
TokenAuthOptions,
|
|
12
|
+
TokenCache,
|
|
13
|
+
TokenPair,
|
|
14
|
+
)
|
|
15
|
+
from caido_sdk_client.client import Client, ConnectOptions, Health, ReadyOptions
|
|
16
|
+
from caido_sdk_client.errors import (
|
|
17
|
+
BaseError,
|
|
18
|
+
InstanceNotReadyError,
|
|
19
|
+
NoViewerInResponseError,
|
|
20
|
+
UnsupportedViewerTypeError,
|
|
21
|
+
from_error,
|
|
22
|
+
)
|
|
23
|
+
from caido_sdk_client.graphql import GraphQLClient
|
|
24
|
+
from caido_sdk_client.logger import ConsoleLogger, Logger
|
|
25
|
+
from caido_sdk_client.rest import RestClient
|
|
26
|
+
from caido_sdk_client.sdks import (
|
|
27
|
+
EnvironmentInstance,
|
|
28
|
+
EnvironmentSDK,
|
|
29
|
+
FilterSDK,
|
|
30
|
+
FindingSDK,
|
|
31
|
+
FindingsListBuilder,
|
|
32
|
+
HostedFileSDK,
|
|
33
|
+
InstanceSDK,
|
|
34
|
+
PluginPackage,
|
|
35
|
+
PluginSDK,
|
|
36
|
+
RequestSDK,
|
|
37
|
+
RequestsListBuilder,
|
|
38
|
+
ScopeSDK,
|
|
39
|
+
UserSDK,
|
|
40
|
+
)
|
|
41
|
+
from caido_sdk_client.types import (
|
|
42
|
+
AISettings,
|
|
43
|
+
AnalyticsSettings,
|
|
44
|
+
AnthropicAISetting,
|
|
45
|
+
CloudUser,
|
|
46
|
+
CreateEnvironmentOptions,
|
|
47
|
+
CreateFilterPresetOptions,
|
|
48
|
+
CreateFindingOptions,
|
|
49
|
+
CreateScopeOptions,
|
|
50
|
+
Cursor,
|
|
51
|
+
CursorLike,
|
|
52
|
+
Environment,
|
|
53
|
+
EnvironmentVariable,
|
|
54
|
+
EnvironmentVariableKind,
|
|
55
|
+
FilterPreset,
|
|
56
|
+
Finding,
|
|
57
|
+
GoogleAISetting,
|
|
58
|
+
GuestUser,
|
|
59
|
+
HostedFile,
|
|
60
|
+
Httpql,
|
|
61
|
+
HttpqlLike,
|
|
62
|
+
Id,
|
|
63
|
+
IdLike,
|
|
64
|
+
InstallPluginPackageOptions,
|
|
65
|
+
InstallPluginPackageSourceFile,
|
|
66
|
+
InstallPluginPackageSourceManifest,
|
|
67
|
+
InstanceSettings,
|
|
68
|
+
OnboardingSettings,
|
|
69
|
+
OpenAIAISetting,
|
|
70
|
+
OpenRouterAISetting,
|
|
71
|
+
Plugin,
|
|
72
|
+
PluginBackend,
|
|
73
|
+
PluginFrontend,
|
|
74
|
+
PluginWorkflow,
|
|
75
|
+
Request,
|
|
76
|
+
RequestGetOptions,
|
|
77
|
+
RequestMetadata,
|
|
78
|
+
RequestResponseOpt,
|
|
79
|
+
Response,
|
|
80
|
+
Scope,
|
|
81
|
+
ScriptUser,
|
|
82
|
+
SetAISettingsInput,
|
|
83
|
+
SetAnalyticsSettingsInput,
|
|
84
|
+
SetInstanceSettingsInput,
|
|
85
|
+
SetOnboardingSettingsInput,
|
|
86
|
+
SubscriptionEntitlement,
|
|
87
|
+
SubscriptionPlan,
|
|
88
|
+
UpdateEnvironmentOptions,
|
|
89
|
+
UpdateFilterPresetOptions,
|
|
90
|
+
UpdateFindingOptions,
|
|
91
|
+
UpdateScopeOptions,
|
|
92
|
+
UploadHostedFileOptions,
|
|
93
|
+
User,
|
|
94
|
+
UserIdentity,
|
|
95
|
+
UserProfile,
|
|
96
|
+
UserSubscription,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
__all__ = [
|
|
100
|
+
"AISettings",
|
|
101
|
+
"AnalyticsSettings",
|
|
102
|
+
"AnthropicAISetting",
|
|
103
|
+
"AuthCacheFile",
|
|
104
|
+
"AuthManager",
|
|
105
|
+
"AuthOptions",
|
|
106
|
+
"BaseError",
|
|
107
|
+
"BrowserAuthOptions",
|
|
108
|
+
"CachedToken",
|
|
109
|
+
"Client",
|
|
110
|
+
"CloudUser",
|
|
111
|
+
"ConnectOptions",
|
|
112
|
+
"ConsoleLogger",
|
|
113
|
+
"CreateEnvironmentOptions",
|
|
114
|
+
"CreateFilterPresetOptions",
|
|
115
|
+
"CreateFindingOptions",
|
|
116
|
+
"CreateScopeOptions",
|
|
117
|
+
"Cursor",
|
|
118
|
+
"CursorLike",
|
|
119
|
+
"Environment",
|
|
120
|
+
"EnvironmentInstance",
|
|
121
|
+
"EnvironmentSDK",
|
|
122
|
+
"EnvironmentVariable",
|
|
123
|
+
"EnvironmentVariableKind",
|
|
124
|
+
"FileTokenCache",
|
|
125
|
+
"FilterPreset",
|
|
126
|
+
"FilterSDK",
|
|
127
|
+
"Finding",
|
|
128
|
+
"HostedFile",
|
|
129
|
+
"HostedFileSDK",
|
|
130
|
+
"FindingSDK",
|
|
131
|
+
"FindingsListBuilder",
|
|
132
|
+
"GoogleAISetting",
|
|
133
|
+
"GraphQLClient",
|
|
134
|
+
"GuestUser",
|
|
135
|
+
"Health",
|
|
136
|
+
"InstallPluginPackageOptions",
|
|
137
|
+
"InstallPluginPackageSourceFile",
|
|
138
|
+
"InstallPluginPackageSourceManifest",
|
|
139
|
+
"Httpql",
|
|
140
|
+
"HttpqlLike",
|
|
141
|
+
"Id",
|
|
142
|
+
"IdLike",
|
|
143
|
+
"InstanceNotReadyError",
|
|
144
|
+
"InstanceSDK",
|
|
145
|
+
"InstanceSettings",
|
|
146
|
+
"Plugin",
|
|
147
|
+
"PluginBackend",
|
|
148
|
+
"PluginFrontend",
|
|
149
|
+
"PluginPackage",
|
|
150
|
+
"PluginSDK",
|
|
151
|
+
"PluginWorkflow",
|
|
152
|
+
"Logger",
|
|
153
|
+
"NoViewerInResponseError",
|
|
154
|
+
"OnboardingSettings",
|
|
155
|
+
"OpenAIAISetting",
|
|
156
|
+
"OpenRouterAISetting",
|
|
157
|
+
"PATAuthOptions",
|
|
158
|
+
"ReadyOptions",
|
|
159
|
+
"Request",
|
|
160
|
+
"RequestGetOptions",
|
|
161
|
+
"RequestMetadata",
|
|
162
|
+
"RequestResponseOpt",
|
|
163
|
+
"RequestSDK",
|
|
164
|
+
"RequestsListBuilder",
|
|
165
|
+
"Response",
|
|
166
|
+
"RestClient",
|
|
167
|
+
"Scope",
|
|
168
|
+
"ScopeSDK",
|
|
169
|
+
"ScriptUser",
|
|
170
|
+
"SetAISettingsInput",
|
|
171
|
+
"SetAnalyticsSettingsInput",
|
|
172
|
+
"SetInstanceSettingsInput",
|
|
173
|
+
"SetOnboardingSettingsInput",
|
|
174
|
+
"SubscriptionEntitlement",
|
|
175
|
+
"SubscriptionPlan",
|
|
176
|
+
"TokenAuthOptions",
|
|
177
|
+
"TokenCache",
|
|
178
|
+
"TokenPair",
|
|
179
|
+
"UnsupportedViewerTypeError",
|
|
180
|
+
"UpdateEnvironmentOptions",
|
|
181
|
+
"UploadHostedFileOptions",
|
|
182
|
+
"UpdateFilterPresetOptions",
|
|
183
|
+
"UpdateFindingOptions",
|
|
184
|
+
"UpdateScopeOptions",
|
|
185
|
+
"User",
|
|
186
|
+
"UserIdentity",
|
|
187
|
+
"UserProfile",
|
|
188
|
+
"UserSDK",
|
|
189
|
+
"UserSubscription",
|
|
190
|
+
"from_error",
|
|
191
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Authentication module exports."""
|
|
2
|
+
|
|
3
|
+
from caido_sdk_client.auth.cache import CachedToken, FileTokenCache, TokenCache
|
|
4
|
+
from caido_sdk_client.auth.manager import AuthManager
|
|
5
|
+
from caido_sdk_client.auth.types import (
|
|
6
|
+
AuthCacheFile,
|
|
7
|
+
AuthCacheOptions,
|
|
8
|
+
AuthOptions,
|
|
9
|
+
BrowserAuthOptions,
|
|
10
|
+
PATAuthOptions,
|
|
11
|
+
TokenAuthOptions,
|
|
12
|
+
TokenPair,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AuthCacheFile",
|
|
17
|
+
"AuthCacheOptions",
|
|
18
|
+
"AuthManager",
|
|
19
|
+
"AuthOptions",
|
|
20
|
+
"BrowserAuthOptions",
|
|
21
|
+
"CachedToken",
|
|
22
|
+
"FileTokenCache",
|
|
23
|
+
"PATAuthOptions",
|
|
24
|
+
"TokenAuthOptions",
|
|
25
|
+
"TokenCache",
|
|
26
|
+
"TokenPair",
|
|
27
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""File-based token cache."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from caido_sdk_client.auth.cache.types import CachedToken
|
|
10
|
+
from caido_sdk_client.logger import Logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FileTokenCache:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
path: str | None = None,
|
|
18
|
+
logger: Logger | None = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
self._path = path or ".caido-token.json"
|
|
21
|
+
self._logger = logger
|
|
22
|
+
|
|
23
|
+
async def load(self) -> CachedToken | None:
|
|
24
|
+
try:
|
|
25
|
+
resolved = self._resolve_path()
|
|
26
|
+
with open(resolved) as f:
|
|
27
|
+
parsed = json.load(f)
|
|
28
|
+
|
|
29
|
+
access_token = parsed.get("access_token")
|
|
30
|
+
if access_token is None:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
return CachedToken(
|
|
34
|
+
access_token=access_token,
|
|
35
|
+
refresh_token=parsed.get("refresh_token"),
|
|
36
|
+
expires_at=parsed.get("expires_at"),
|
|
37
|
+
)
|
|
38
|
+
except Exception:
|
|
39
|
+
if self._logger is not None:
|
|
40
|
+
self._logger.debug("Failed to load cached token from file")
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
async def save(self, token: CachedToken) -> None:
|
|
44
|
+
try:
|
|
45
|
+
resolved = self._resolve_path()
|
|
46
|
+
Path(resolved).parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
data = {
|
|
49
|
+
"access_token": token.access_token,
|
|
50
|
+
"refresh_token": token.refresh_token,
|
|
51
|
+
"expires_at": token.expires_at,
|
|
52
|
+
}
|
|
53
|
+
fd = os.open(resolved, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
54
|
+
with os.fdopen(fd, "w") as f:
|
|
55
|
+
json.dump(data, f, indent=2)
|
|
56
|
+
except Exception:
|
|
57
|
+
if self._logger is not None:
|
|
58
|
+
self._logger.warn("Failed to save token cache to file")
|
|
59
|
+
|
|
60
|
+
async def clear(self) -> None:
|
|
61
|
+
try:
|
|
62
|
+
resolved = self._resolve_path()
|
|
63
|
+
os.unlink(resolved)
|
|
64
|
+
except Exception:
|
|
65
|
+
if self._logger is not None:
|
|
66
|
+
self._logger.warn("Failed to clear token cache file")
|
|
67
|
+
|
|
68
|
+
def _resolve_path(self) -> str:
|
|
69
|
+
if os.path.isabs(self._path):
|
|
70
|
+
return self._path
|
|
71
|
+
return os.path.join(os.getcwd(), self._path)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Cache types for persisting authentication tokens."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class CachedToken:
|
|
11
|
+
access_token: str
|
|
12
|
+
refresh_token: str | None = None
|
|
13
|
+
expires_at: str | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TokenCache(Protocol):
|
|
17
|
+
async def load(self) -> CachedToken | None: ...
|
|
18
|
+
async def save(self, token: CachedToken) -> None: ...
|
|
19
|
+
async def clear(self) -> None: ...
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Authentication manager."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from caido_server_auth import (
|
|
11
|
+
AuthClient,
|
|
12
|
+
AuthClientOptions,
|
|
13
|
+
BrowserApprover,
|
|
14
|
+
PATApprover,
|
|
15
|
+
PATApproverOptions,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from caido_sdk_client.auth.cache.types import CachedToken, TokenCache
|
|
19
|
+
from caido_sdk_client.auth.types import (
|
|
20
|
+
AuthOptions,
|
|
21
|
+
BrowserAuthOptions,
|
|
22
|
+
TokenAuthOptions,
|
|
23
|
+
)
|
|
24
|
+
from caido_sdk_client.auth.utils import is_pat_auth, resolve_cache
|
|
25
|
+
from caido_sdk_client.errors.auth import TokenRefreshError
|
|
26
|
+
from caido_sdk_client.logger import Logger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(slots=True)
|
|
30
|
+
class _TokenState:
|
|
31
|
+
access_token: str
|
|
32
|
+
refresh_token: str | None = None
|
|
33
|
+
expires_at: datetime | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuthManager:
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
instance_url: str,
|
|
40
|
+
logger: Logger,
|
|
41
|
+
auth_options: AuthOptions | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self._instance_url = instance_url
|
|
44
|
+
self._logger = logger
|
|
45
|
+
self._auth_options = auth_options
|
|
46
|
+
self._cache: TokenCache | None = (
|
|
47
|
+
resolve_cache(auth_options.cache, logger)
|
|
48
|
+
if auth_options is not None and auth_options.cache is not None
|
|
49
|
+
else None
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self._token_state: _TokenState | None = None
|
|
53
|
+
self._auth_client: Any = None
|
|
54
|
+
self._on_token_refresh_callbacks: set[Callable[[], None]] = set()
|
|
55
|
+
|
|
56
|
+
def get_access_token(self) -> str | None:
|
|
57
|
+
if self._token_state is None:
|
|
58
|
+
return None
|
|
59
|
+
return self._token_state.access_token
|
|
60
|
+
|
|
61
|
+
def can_refresh(self) -> bool:
|
|
62
|
+
return (
|
|
63
|
+
self._token_state is not None
|
|
64
|
+
and self._token_state.refresh_token is not None
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def on_token_refresh(self, callback: Callable[[], None]) -> Callable[[], None]:
|
|
68
|
+
"""Subscribe to token refresh events.
|
|
69
|
+
|
|
70
|
+
Returns an unsubscribe function.
|
|
71
|
+
"""
|
|
72
|
+
self._on_token_refresh_callbacks.add(callback)
|
|
73
|
+
|
|
74
|
+
def unsubscribe() -> None:
|
|
75
|
+
self._on_token_refresh_callbacks.discard(callback)
|
|
76
|
+
|
|
77
|
+
return unsubscribe
|
|
78
|
+
|
|
79
|
+
def _notify_token_refresh(self) -> None:
|
|
80
|
+
for callback in self._on_token_refresh_callbacks:
|
|
81
|
+
try:
|
|
82
|
+
callback()
|
|
83
|
+
except Exception:
|
|
84
|
+
self._logger.warn("Error in token refresh callback")
|
|
85
|
+
|
|
86
|
+
async def authenticate(self) -> None:
|
|
87
|
+
auth = self._auth_options
|
|
88
|
+
|
|
89
|
+
cache = self._cache
|
|
90
|
+
if cache is not None:
|
|
91
|
+
self._logger.debug("Attempting to load cached token")
|
|
92
|
+
cached = await cache.load()
|
|
93
|
+
if cached is not None:
|
|
94
|
+
self._logger.info("Loaded token from cache")
|
|
95
|
+
self._token_state = _from_cached_token(cached)
|
|
96
|
+
self._notify_token_refresh()
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if auth is not None and isinstance(auth, TokenAuthOptions):
|
|
100
|
+
self._logger.debug("Using provided token")
|
|
101
|
+
if isinstance(auth.token, str):
|
|
102
|
+
self._token_state = _TokenState(access_token=auth.token)
|
|
103
|
+
else:
|
|
104
|
+
self._token_state = _TokenState(
|
|
105
|
+
access_token=auth.token.access_token,
|
|
106
|
+
refresh_token=auth.token.refresh_token,
|
|
107
|
+
)
|
|
108
|
+
self._notify_token_refresh()
|
|
109
|
+
await self._maybe_cache_token()
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
self._logger.info("Starting authentication flow")
|
|
113
|
+
auth_client = self._get_or_create_auth_client()
|
|
114
|
+
token = await auth_client.start_authentication_flow()
|
|
115
|
+
self._apply_auth_token(token)
|
|
116
|
+
self._logger.info("Authentication flow completed")
|
|
117
|
+
await self._maybe_cache_token()
|
|
118
|
+
|
|
119
|
+
async def refresh(self) -> None:
|
|
120
|
+
refresh_token = self._token_state.refresh_token if self._token_state else None
|
|
121
|
+
if refresh_token is None:
|
|
122
|
+
raise TokenRefreshError()
|
|
123
|
+
|
|
124
|
+
self._logger.debug("Refreshing access token")
|
|
125
|
+
auth_client = self._get_or_create_auth_client()
|
|
126
|
+
token = await auth_client.refresh_token(refresh_token)
|
|
127
|
+
self._apply_auth_token(token)
|
|
128
|
+
self._logger.info("Access token refreshed")
|
|
129
|
+
await self._maybe_cache_token()
|
|
130
|
+
self._notify_token_refresh()
|
|
131
|
+
|
|
132
|
+
def _get_or_create_auth_client(self) -> Any:
|
|
133
|
+
if self._auth_client is None:
|
|
134
|
+
approver = self._create_approver()
|
|
135
|
+
self._auth_client = AuthClient(
|
|
136
|
+
AuthClientOptions(
|
|
137
|
+
instance_url=self._instance_url,
|
|
138
|
+
approver=approver,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return self._auth_client
|
|
142
|
+
|
|
143
|
+
def _create_approver(self) -> Any:
|
|
144
|
+
auth = self._auth_options
|
|
145
|
+
|
|
146
|
+
if auth is not None and is_pat_auth(auth):
|
|
147
|
+
return PATApprover(PATApproverOptions(pat=auth.pat)) # type: ignore[union-attr]
|
|
148
|
+
|
|
149
|
+
logger = self._logger
|
|
150
|
+
browser_auth = auth if isinstance(auth, BrowserAuthOptions) else None
|
|
151
|
+
on_request = (
|
|
152
|
+
browser_auth.on_request
|
|
153
|
+
if browser_auth is not None and browser_auth.on_request is not None
|
|
154
|
+
else lambda request: logger.info(
|
|
155
|
+
f"Please visit the following URL to authenticate: {request.verification_url}"
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
return BrowserApprover(on_request)
|
|
159
|
+
|
|
160
|
+
def _apply_auth_token(self, token: Any) -> None:
|
|
161
|
+
self._token_state = _TokenState(
|
|
162
|
+
access_token=token.access_token,
|
|
163
|
+
refresh_token=token.refresh_token,
|
|
164
|
+
expires_at=token.expires_at,
|
|
165
|
+
)
|
|
166
|
+
self._notify_token_refresh()
|
|
167
|
+
|
|
168
|
+
async def _maybe_cache_token(self) -> None:
|
|
169
|
+
cache = self._cache
|
|
170
|
+
token_state = self._token_state
|
|
171
|
+
if cache is None or token_state is None:
|
|
172
|
+
return
|
|
173
|
+
self._logger.debug("Saving token to cache")
|
|
174
|
+
await cache.save(_to_cached_token(token_state))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _to_cached_token(state: _TokenState) -> CachedToken:
|
|
178
|
+
return CachedToken(
|
|
179
|
+
access_token=state.access_token,
|
|
180
|
+
refresh_token=state.refresh_token,
|
|
181
|
+
expires_at=state.expires_at.isoformat()
|
|
182
|
+
if state.expires_at is not None
|
|
183
|
+
else None,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _from_cached_token(cached: CachedToken) -> _TokenState:
|
|
188
|
+
return _TokenState(
|
|
189
|
+
access_token=cached.access_token,
|
|
190
|
+
refresh_token=cached.refresh_token,
|
|
191
|
+
expires_at=(
|
|
192
|
+
datetime.fromisoformat(cached.expires_at)
|
|
193
|
+
if cached.expires_at is not None
|
|
194
|
+
else None
|
|
195
|
+
),
|
|
196
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Authentication option types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Union
|
|
8
|
+
|
|
9
|
+
from caido_sdk_client.auth.cache.types import TokenCache
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class AuthCacheFile:
|
|
14
|
+
"""File-based cache configuration. Path is relative to cwd or absolute."""
|
|
15
|
+
|
|
16
|
+
file: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class AuthCacheLocalStorage:
|
|
21
|
+
"""LocalStorage-based cache configuration (browser only)."""
|
|
22
|
+
|
|
23
|
+
localstorage: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
AuthCacheOptions = Union[AuthCacheFile, AuthCacheLocalStorage, TokenCache]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class TokenPair:
|
|
31
|
+
"""A pair of access and refresh tokens."""
|
|
32
|
+
|
|
33
|
+
access_token: str
|
|
34
|
+
refresh_token: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class PATAuthOptions:
|
|
39
|
+
"""Authenticate using a Personal Access Token (PAT)."""
|
|
40
|
+
|
|
41
|
+
pat: str
|
|
42
|
+
cache: AuthCacheOptions | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True)
|
|
46
|
+
class TokenAuthOptions:
|
|
47
|
+
"""Authenticate with a direct access token or token pair."""
|
|
48
|
+
|
|
49
|
+
token: str | TokenPair
|
|
50
|
+
cache: AuthCacheOptions | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class BrowserAuthOptions:
|
|
55
|
+
"""Authenticate via browser-based device code flow."""
|
|
56
|
+
|
|
57
|
+
on_request: Callable[[Any], None] | None = None
|
|
58
|
+
cache: AuthCacheOptions | None = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
AuthOptions = Union[PATAuthOptions, TokenAuthOptions, BrowserAuthOptions]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Authentication utility functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from caido_sdk_client.auth.cache.file import FileTokenCache
|
|
6
|
+
from caido_sdk_client.auth.cache.types import TokenCache
|
|
7
|
+
from caido_sdk_client.auth.types import (
|
|
8
|
+
AuthCacheFile,
|
|
9
|
+
AuthCacheLocalStorage,
|
|
10
|
+
AuthCacheOptions,
|
|
11
|
+
AuthOptions,
|
|
12
|
+
PATAuthOptions,
|
|
13
|
+
TokenAuthOptions,
|
|
14
|
+
)
|
|
15
|
+
from caido_sdk_client.logger import Logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_pat_auth(auth: AuthOptions) -> bool:
|
|
19
|
+
return isinstance(auth, PATAuthOptions)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_token_auth(auth: AuthOptions) -> bool:
|
|
23
|
+
return isinstance(auth, TokenAuthOptions)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def resolve_cache(options: AuthCacheOptions, logger: Logger) -> TokenCache:
|
|
27
|
+
if isinstance(options, AuthCacheFile):
|
|
28
|
+
return FileTokenCache(path=options.file, logger=logger)
|
|
29
|
+
if isinstance(options, AuthCacheLocalStorage):
|
|
30
|
+
raise NotImplementedError("LocalStorage cache is not supported in Python")
|
|
31
|
+
return options
|