provablyfine-client 0.3.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.
- provablyfine_client-0.3.0/.gitignore +15 -0
- provablyfine_client-0.3.0/LICENSE +20 -0
- provablyfine_client-0.3.0/PKG-INFO +25 -0
- provablyfine_client-0.3.0/README.md +11 -0
- provablyfine_client-0.3.0/pyproject.toml +43 -0
- provablyfine_client-0.3.0/src/provablyfine_client/__init__.py +28 -0
- provablyfine_client-0.3.0/src/provablyfine_client/_base64url.py +9 -0
- provablyfine_client-0.3.0/src/provablyfine_client/account_client.py +31 -0
- provablyfine_client-0.3.0/src/provablyfine_client/aio.py +324 -0
- provablyfine_client-0.3.0/src/provablyfine_client/directory.py +88 -0
- provablyfine_client-0.3.0/src/provablyfine_client/exceptions.py +11 -0
- provablyfine_client-0.3.0/src/provablyfine_client/http_session.py +105 -0
- provablyfine_client-0.3.0/src/provablyfine_client/http_signatures.py +82 -0
- provablyfine_client-0.3.0/src/provablyfine_client/invitation_client.py +32 -0
- provablyfine_client-0.3.0/src/provablyfine_client/public_client.py +61 -0
- provablyfine_client-0.3.0/src/provablyfine_client/py.typed +0 -0
- provablyfine_client-0.3.0/src/provablyfine_client/schemas.py +628 -0
- provablyfine_client-0.3.0/src/provablyfine_client/session_client.py +619 -0
- provablyfine_client-0.3.0/src/provablyfine_client/signer.py +43 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Mathieu Lacage
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
6
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
|
7
|
+
without restriction, including without limitation the rights to use, copy, modify,
|
|
8
|
+
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to the following
|
|
10
|
+
conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all copies
|
|
13
|
+
or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
16
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
17
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
18
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
19
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
20
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: provablyfine-client
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: HTTP client library for the ProvablyFine API
|
|
5
|
+
Project-URL: Documentation, https://docs.provablyfine.net
|
|
6
|
+
Project-URL: Repository, https://github.com/provablyfine/pf.git
|
|
7
|
+
Project-URL: Issues, https://github.com/provablyfine/pf/issues
|
|
8
|
+
Author-email: Mathieu Lacage <mathieu.lacage@cutebugs.net>
|
|
9
|
+
Maintainer-email: Mathieu Lacage <mathieu.lacage@cutebugs.net>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: SSH,access-control,bastion
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: Pydantic
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: http-sfv
|
|
24
|
+
Requires-Dist: pydantic
|
|
25
|
+
Requires-Dist: requests>=2.32.5
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Provably Fine, the HTTP client library
|
|
2
|
+
|
|
3
|
+
This is an HTTP client library for Provably Fine, the SSH
|
|
4
|
+
centralized access control package.
|
|
5
|
+
|
|
6
|
+
This library will be installed as a dependency when you install
|
|
7
|
+
[ProvablyFine](https://github.com/provablyfine/pf)
|
|
8
|
+
|
|
9
|
+
## License
|
|
10
|
+
|
|
11
|
+
This software is released under the [MIT license](./LICENSE.md).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build.targets.wheel]
|
|
6
|
+
packages = ["src/provablyfine_client"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "provablyfine-client"
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Mathieu Lacage", email = "mathieu.lacage@cutebugs.net"},
|
|
13
|
+
]
|
|
14
|
+
maintainers = [
|
|
15
|
+
{name = "Mathieu Lacage", email = "mathieu.lacage@cutebugs.net"},
|
|
16
|
+
]
|
|
17
|
+
description = "HTTP client library for the ProvablyFine API"
|
|
18
|
+
version = "0.3.0"
|
|
19
|
+
license = "MIT"
|
|
20
|
+
license-file = "LICENSE"
|
|
21
|
+
keywords = ["SSH", "access-control", "bastion"]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Framework :: Pydantic",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: GNU Affero General Public License v3",
|
|
27
|
+
"Natural Language :: English",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
requires-python = ">=3.12"
|
|
34
|
+
dependencies = [
|
|
35
|
+
"http-sfv",
|
|
36
|
+
"pydantic",
|
|
37
|
+
"requests>=2.32.5",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Documentation = "https://docs.provablyfine.net"
|
|
42
|
+
Repository = "https://github.com/provablyfine/pf.git"
|
|
43
|
+
Issues = "https://github.com/provablyfine/pf/issues"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from . import exceptions, schemas
|
|
2
|
+
from .account_client import AccountClient
|
|
3
|
+
from .aio import AsyncAccountClient, AsyncInvitationClient, AsyncPublicClient, AsyncSessionClient
|
|
4
|
+
from .directory import Directory
|
|
5
|
+
from .http_session import HttpSession
|
|
6
|
+
from .http_signatures import Auth
|
|
7
|
+
from .invitation_client import InvitationClient
|
|
8
|
+
from .public_client import PublicClient
|
|
9
|
+
from .session_client import SessionClient
|
|
10
|
+
from .signer import HmacSigner, Signer
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AccountClient",
|
|
14
|
+
"AsyncAccountClient",
|
|
15
|
+
"AsyncInvitationClient",
|
|
16
|
+
"AsyncPublicClient",
|
|
17
|
+
"AsyncSessionClient",
|
|
18
|
+
"Auth",
|
|
19
|
+
"Directory",
|
|
20
|
+
"HmacSigner",
|
|
21
|
+
"HttpSession",
|
|
22
|
+
"InvitationClient",
|
|
23
|
+
"PublicClient",
|
|
24
|
+
"SessionClient",
|
|
25
|
+
"Signer",
|
|
26
|
+
"exceptions",
|
|
27
|
+
"schemas",
|
|
28
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from . import directory, exceptions, http_session, http_signatures, signer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AccountClient:
|
|
9
|
+
"""API methods that require account + session authentication."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
session: http_session.HttpSession,
|
|
14
|
+
_directory: directory.Directory,
|
|
15
|
+
account_signer: signer.Signer,
|
|
16
|
+
session_signer: signer.Signer,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._session = session
|
|
19
|
+
self._directory = _directory
|
|
20
|
+
self._account_signer = account_signer
|
|
21
|
+
self._session_signer = session_signer
|
|
22
|
+
|
|
23
|
+
def login_http_sig(self, session_public_key: dict[str, typing.Any]) -> None:
|
|
24
|
+
auth = http_signatures.Auth([self._account_signer, self._session_signer])
|
|
25
|
+
response = self._session.post(
|
|
26
|
+
self._directory.login,
|
|
27
|
+
auth=auth,
|
|
28
|
+
json={"session_public_key": session_public_key},
|
|
29
|
+
)
|
|
30
|
+
if response.status_code != 204:
|
|
31
|
+
raise exceptions.UI(f"Unable to login successfully: {response.text}")
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from . import account_client, invitation_client, public_client, schemas, session_client, signer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AsyncPublicClient:
|
|
10
|
+
def __init__(self, inner: public_client.PublicClient) -> None:
|
|
11
|
+
self._inner = inner
|
|
12
|
+
self._lock = asyncio.Lock()
|
|
13
|
+
|
|
14
|
+
async def _run(self, fn: typing.Callable[[], typing.Any]) -> typing.Any:
|
|
15
|
+
async with self._lock:
|
|
16
|
+
return await asyncio.to_thread(fn)
|
|
17
|
+
|
|
18
|
+
async def get_user_trusted_keys_public(self) -> str:
|
|
19
|
+
return await self._run(self._inner.get_user_trusted_keys_public)
|
|
20
|
+
|
|
21
|
+
async def get_public_auth(self, auth_name: str) -> schemas.AuthPublic:
|
|
22
|
+
return await self._run(lambda: self._inner.get_public_auth(auth_name))
|
|
23
|
+
|
|
24
|
+
async def list_public_auths(self) -> list[schemas.AuthPublicSummary]:
|
|
25
|
+
return await self._run(self._inner.list_public_auths)
|
|
26
|
+
|
|
27
|
+
async def initialize(self, account_signer: signer.Signer, account_public_key: dict[str, typing.Any]) -> None:
|
|
28
|
+
return await self._run(lambda: self._inner.initialize(account_signer, account_public_key))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AsyncSessionClient:
|
|
32
|
+
def __init__(self, inner: session_client.SessionClient) -> None:
|
|
33
|
+
self._inner = inner
|
|
34
|
+
self._lock = asyncio.Lock()
|
|
35
|
+
|
|
36
|
+
async def _run(self, fn: typing.Callable[[], typing.Any]) -> typing.Any:
|
|
37
|
+
async with self._lock:
|
|
38
|
+
return await asyncio.to_thread(fn)
|
|
39
|
+
|
|
40
|
+
async def list_ssh_hosts(self) -> schemas.SshHostsResponse:
|
|
41
|
+
return await self._run(self._inner.list_ssh_hosts)
|
|
42
|
+
|
|
43
|
+
async def get_host_trusted_keys(self) -> str:
|
|
44
|
+
return await self._run(self._inner.get_host_trusted_keys)
|
|
45
|
+
|
|
46
|
+
async def get_user_certificate(
|
|
47
|
+
self,
|
|
48
|
+
hostname: str,
|
|
49
|
+
username: str,
|
|
50
|
+
action: str,
|
|
51
|
+
public_key: dict[str, typing.Any],
|
|
52
|
+
command: str | None = None,
|
|
53
|
+
) -> schemas.SshUserCertificateResponse:
|
|
54
|
+
return await self._run(
|
|
55
|
+
lambda: self._inner.get_user_certificate(hostname, username, action, public_key, command)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def sign_host_certificates(
|
|
59
|
+
self, public_keys: list[dict[str, typing.Any]]
|
|
60
|
+
) -> schemas.SshHostCertificateResponse:
|
|
61
|
+
return await self._run(lambda: self._inner.sign_host_certificates(public_keys))
|
|
62
|
+
|
|
63
|
+
async def list_self_bastions(self) -> schemas.IdentitySelfBastionListResponse:
|
|
64
|
+
return await self._run(self._inner.list_self_bastions)
|
|
65
|
+
|
|
66
|
+
async def get_self_token(self, service: str) -> schemas.IdentitySelfTokenResponse:
|
|
67
|
+
return await self._run(lambda: self._inner.get_self_token(service))
|
|
68
|
+
|
|
69
|
+
async def list_tags(
|
|
70
|
+
self,
|
|
71
|
+
id: int | None = None,
|
|
72
|
+
name: str | None = None,
|
|
73
|
+
value: str | None = None,
|
|
74
|
+
) -> schemas.TagsResponse:
|
|
75
|
+
return await self._run(lambda: self._inner.list_tags(id, name, value))
|
|
76
|
+
|
|
77
|
+
async def create_tag(self, name: str, value: str) -> schemas.Tag:
|
|
78
|
+
return await self._run(lambda: self._inner.create_tag(name, value))
|
|
79
|
+
|
|
80
|
+
async def delete_tag(self, id: int) -> None:
|
|
81
|
+
return await self._run(lambda: self._inner.delete_tag(id))
|
|
82
|
+
|
|
83
|
+
async def list_tenants(self, id: int | None = None) -> schemas.TenantsResponse:
|
|
84
|
+
return await self._run(lambda: self._inner.list_tenants(id))
|
|
85
|
+
|
|
86
|
+
async def get_tenant(self, id: int) -> schemas.Tenant:
|
|
87
|
+
return await self._run(lambda: self._inner.get_tenant(id))
|
|
88
|
+
|
|
89
|
+
async def create_tenant(self, name: str, display_name: str) -> schemas.Tenant:
|
|
90
|
+
return await self._run(lambda: self._inner.create_tenant(name, display_name))
|
|
91
|
+
|
|
92
|
+
async def update_tenant(
|
|
93
|
+
self,
|
|
94
|
+
id: int,
|
|
95
|
+
display_name: str | None = None,
|
|
96
|
+
is_enabled: bool | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
return await self._run(lambda: self._inner.update_tenant(id, display_name, is_enabled))
|
|
99
|
+
|
|
100
|
+
async def delete_tenant(self, id: int) -> None:
|
|
101
|
+
return await self._run(lambda: self._inner.delete_tenant(id))
|
|
102
|
+
|
|
103
|
+
async def list_auths(self) -> schemas.AuthListResponse:
|
|
104
|
+
return await self._run(self._inner.list_auths)
|
|
105
|
+
|
|
106
|
+
async def get_auth(self, id: int) -> schemas.Auth:
|
|
107
|
+
return await self._run(lambda: self._inner.get_auth(id))
|
|
108
|
+
|
|
109
|
+
async def create_auth_http_sig(self, name: str, description: str, tags: list[dict[str, str]]) -> schemas.Auth:
|
|
110
|
+
return await self._run(lambda: self._inner.create_auth_http_sig(name, description, tags))
|
|
111
|
+
|
|
112
|
+
async def create_auth_oidc(
|
|
113
|
+
self,
|
|
114
|
+
name: str,
|
|
115
|
+
description: str,
|
|
116
|
+
tags: list[dict[str, str]],
|
|
117
|
+
issuer: str,
|
|
118
|
+
client_id: str,
|
|
119
|
+
client_secret: str | None,
|
|
120
|
+
) -> schemas.Auth:
|
|
121
|
+
return await self._run(
|
|
122
|
+
lambda: self._inner.create_auth_oidc(name, description, tags, issuer, client_id, client_secret)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def create_auth_oauth2_github(
|
|
126
|
+
self,
|
|
127
|
+
name: str,
|
|
128
|
+
description: str,
|
|
129
|
+
tags: list[dict[str, str]],
|
|
130
|
+
client_id: str,
|
|
131
|
+
client_secret: str,
|
|
132
|
+
) -> schemas.Auth:
|
|
133
|
+
return await self._run(
|
|
134
|
+
lambda: self._inner.create_auth_oauth2_github(name, description, tags, client_id, client_secret)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def update_auth(
|
|
138
|
+
self,
|
|
139
|
+
id: int,
|
|
140
|
+
name: str | None = None,
|
|
141
|
+
description: str | None = None,
|
|
142
|
+
is_enabled: bool | None = None,
|
|
143
|
+
tags: list[schemas.TagNameValue] | None = None,
|
|
144
|
+
) -> None:
|
|
145
|
+
return await self._run(lambda: self._inner.update_auth(id, name, description, is_enabled, tags))
|
|
146
|
+
|
|
147
|
+
async def delete_auth(self, id: int) -> None:
|
|
148
|
+
return await self._run(lambda: self._inner.delete_auth(id))
|
|
149
|
+
|
|
150
|
+
async def list_bastions(self, id: int | None = None) -> schemas.BastionListResponse:
|
|
151
|
+
return await self._run(lambda: self._inner.list_bastions(id))
|
|
152
|
+
|
|
153
|
+
async def get_bastion(self, id: int) -> schemas.Bastion:
|
|
154
|
+
return await self._run(lambda: self._inner.get_bastion(id))
|
|
155
|
+
|
|
156
|
+
async def create_bastion(
|
|
157
|
+
self,
|
|
158
|
+
url: str,
|
|
159
|
+
ssh_proxy_jump: str | None,
|
|
160
|
+
tag_id_list: list[int],
|
|
161
|
+
tag_name_value_list: list[dict[str, str]],
|
|
162
|
+
) -> schemas.Bastion:
|
|
163
|
+
return await self._run(
|
|
164
|
+
lambda: self._inner.create_bastion(url, ssh_proxy_jump, tag_id_list, tag_name_value_list)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
async def update_bastion(
|
|
168
|
+
self,
|
|
169
|
+
id: int,
|
|
170
|
+
url: str | None = None,
|
|
171
|
+
ssh_proxy_jump: str | None = None,
|
|
172
|
+
tag_id_list: list[int] | None = None,
|
|
173
|
+
tag_name_value_list: list[schemas.TagNameValue] | None = None,
|
|
174
|
+
) -> None:
|
|
175
|
+
return await self._run(
|
|
176
|
+
lambda: self._inner.update_bastion(id, url, ssh_proxy_jump, tag_id_list, tag_name_value_list)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def delete_bastion(self, id: int) -> None:
|
|
180
|
+
return await self._run(lambda: self._inner.delete_bastion(id))
|
|
181
|
+
|
|
182
|
+
async def list_roles(self, id: int | None = None, name: str | None = None) -> schemas.RolesResponse:
|
|
183
|
+
return await self._run(lambda: self._inner.list_roles(id, name))
|
|
184
|
+
|
|
185
|
+
async def get_role(self, id: int) -> schemas.Role:
|
|
186
|
+
return await self._run(lambda: self._inner.get_role(id))
|
|
187
|
+
|
|
188
|
+
async def create_role(self, name: str, description: str) -> schemas.Role:
|
|
189
|
+
return await self._run(lambda: self._inner.create_role(name, description))
|
|
190
|
+
|
|
191
|
+
async def update_role(
|
|
192
|
+
self,
|
|
193
|
+
id: int,
|
|
194
|
+
name: str | None = None,
|
|
195
|
+
description: str | None = None,
|
|
196
|
+
grant_list: list[schemas.Grant] | None = None,
|
|
197
|
+
member_list: list[schemas.RoleMemberRef] | None = None,
|
|
198
|
+
) -> None:
|
|
199
|
+
return await self._run(lambda: self._inner.update_role(id, name, description, grant_list, member_list))
|
|
200
|
+
|
|
201
|
+
async def delete_role(self, id: int) -> None:
|
|
202
|
+
return await self._run(lambda: self._inner.delete_role(id))
|
|
203
|
+
|
|
204
|
+
async def list_boundaries(self, id: int | None = None, name: str | None = None) -> schemas.BoundariesResponse:
|
|
205
|
+
return await self._run(lambda: self._inner.list_boundaries(id, name))
|
|
206
|
+
|
|
207
|
+
async def get_boundary(self, id: int) -> schemas.Boundary:
|
|
208
|
+
return await self._run(lambda: self._inner.get_boundary(id))
|
|
209
|
+
|
|
210
|
+
async def create_boundary(self, name: str, description: str) -> schemas.Boundary:
|
|
211
|
+
return await self._run(lambda: self._inner.create_boundary(name, description))
|
|
212
|
+
|
|
213
|
+
async def update_boundary(
|
|
214
|
+
self,
|
|
215
|
+
id: int,
|
|
216
|
+
name: str | None = None,
|
|
217
|
+
description: str | None = None,
|
|
218
|
+
ceiling_list: list[schemas.Grant] | None = None,
|
|
219
|
+
denied_list: list[schemas.Grant] | None = None,
|
|
220
|
+
) -> None:
|
|
221
|
+
return await self._run(lambda: self._inner.update_boundary(id, name, description, ceiling_list, denied_list))
|
|
222
|
+
|
|
223
|
+
async def delete_boundary(self, id: int) -> None:
|
|
224
|
+
return await self._run(lambda: self._inner.delete_boundary(id))
|
|
225
|
+
|
|
226
|
+
async def list_identities(
|
|
227
|
+
self,
|
|
228
|
+
id: int | None = None,
|
|
229
|
+
name: str | None = None,
|
|
230
|
+
tag_id: list[str] | None = None,
|
|
231
|
+
tag_name: list[str] | None = None,
|
|
232
|
+
boundary_id: list[str] | None = None,
|
|
233
|
+
boundary_name: list[str] | None = None,
|
|
234
|
+
) -> schemas.IdentitiesResponse:
|
|
235
|
+
return await self._run(
|
|
236
|
+
lambda: self._inner.list_identities(id, name, tag_id, tag_name, boundary_id, boundary_name)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
async def get_identity(self, id: int) -> schemas.Identity:
|
|
240
|
+
return await self._run(lambda: self._inner.get_identity(id))
|
|
241
|
+
|
|
242
|
+
async def create_identity(
|
|
243
|
+
self,
|
|
244
|
+
name: str | None,
|
|
245
|
+
boundary_id_list: list[int],
|
|
246
|
+
boundary_name_list: list[str],
|
|
247
|
+
tag_id_list: list[int],
|
|
248
|
+
tag_name_value_list: list[dict[str, str]],
|
|
249
|
+
) -> schemas.Identity:
|
|
250
|
+
return await self._run(
|
|
251
|
+
lambda: self._inner.create_identity(
|
|
252
|
+
name, boundary_id_list, boundary_name_list, tag_id_list, tag_name_value_list
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
async def invite_identity(self, id: int, delivery: str) -> str | None:
|
|
257
|
+
return await self._run(lambda: self._inner.invite_identity(id, delivery))
|
|
258
|
+
|
|
259
|
+
async def delete_identity(self, id: int) -> None:
|
|
260
|
+
return await self._run(lambda: self._inner.delete_identity(id))
|
|
261
|
+
|
|
262
|
+
async def update_identity(
|
|
263
|
+
self,
|
|
264
|
+
id: int,
|
|
265
|
+
name: str | None = None,
|
|
266
|
+
tags: list[schemas.IdentityTagOp] | None = None,
|
|
267
|
+
) -> None:
|
|
268
|
+
return await self._run(lambda: self._inner.update_identity(id, name, tags))
|
|
269
|
+
|
|
270
|
+
async def login_oidc(
|
|
271
|
+
self,
|
|
272
|
+
auth_name: str,
|
|
273
|
+
id_token: str,
|
|
274
|
+
session_public_key: dict[str, typing.Any],
|
|
275
|
+
) -> None:
|
|
276
|
+
return await self._run(lambda: self._inner.login_oidc(auth_name, id_token, session_public_key))
|
|
277
|
+
|
|
278
|
+
async def login_oauth2_start(
|
|
279
|
+
self,
|
|
280
|
+
auth_name: str,
|
|
281
|
+
session_public_key: dict[str, typing.Any],
|
|
282
|
+
client_redirect_uri: str,
|
|
283
|
+
) -> str:
|
|
284
|
+
return await self._run(
|
|
285
|
+
lambda: self._inner.login_oauth2_start(auth_name, session_public_key, client_redirect_uri)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
async def list_audit_log(
|
|
289
|
+
self,
|
|
290
|
+
level: int | None = None,
|
|
291
|
+
object_type: str | None = None,
|
|
292
|
+
by_identity_id: str | None = None,
|
|
293
|
+
start_time: int | None = None,
|
|
294
|
+
end_time: int | None = None,
|
|
295
|
+
) -> schemas.AuditLogListResponse:
|
|
296
|
+
return await self._run(
|
|
297
|
+
lambda: self._inner.list_audit_log(level, object_type, by_identity_id, start_time, end_time)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class AsyncAccountClient:
|
|
302
|
+
def __init__(self, inner: account_client.AccountClient) -> None:
|
|
303
|
+
self._inner = inner
|
|
304
|
+
self._lock = asyncio.Lock()
|
|
305
|
+
|
|
306
|
+
async def _run(self, fn: typing.Callable[[], typing.Any]) -> typing.Any:
|
|
307
|
+
async with self._lock:
|
|
308
|
+
return await asyncio.to_thread(fn)
|
|
309
|
+
|
|
310
|
+
async def login_http_sig(self, session_public_key: dict[str, typing.Any]) -> None:
|
|
311
|
+
return await self._run(lambda: self._inner.login_http_sig(session_public_key))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class AsyncInvitationClient:
|
|
315
|
+
def __init__(self, inner: invitation_client.InvitationClient) -> None:
|
|
316
|
+
self._inner = inner
|
|
317
|
+
self._lock = asyncio.Lock()
|
|
318
|
+
|
|
319
|
+
async def _run(self, fn: typing.Callable[[], typing.Any]) -> typing.Any:
|
|
320
|
+
async with self._lock:
|
|
321
|
+
return await asyncio.to_thread(fn)
|
|
322
|
+
|
|
323
|
+
async def connect(self, account_public_key: dict[str, typing.Any]) -> None:
|
|
324
|
+
return await self._run(lambda: self._inner.connect(account_public_key))
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from . import exceptions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Directory:
|
|
9
|
+
"""Fetches and caches the server's endpoint URL map from a well-known URL."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, url: str, timeout: float = 5.0) -> None:
|
|
12
|
+
self._url = url
|
|
13
|
+
self._timeout = timeout
|
|
14
|
+
self._data: dict[str, str] | None = None
|
|
15
|
+
|
|
16
|
+
def _load(self) -> dict[str, str]:
|
|
17
|
+
if self._data is None:
|
|
18
|
+
try:
|
|
19
|
+
response = requests.get(self._url, timeout=self._timeout)
|
|
20
|
+
except requests.exceptions.ConnectionError:
|
|
21
|
+
raise exceptions.UI("Unable to connect to server")
|
|
22
|
+
except requests.exceptions.ReadTimeout:
|
|
23
|
+
raise exceptions.UI("Request timed out")
|
|
24
|
+
if response.status_code != 200:
|
|
25
|
+
raise exceptions.UI("Unable to read directory from server")
|
|
26
|
+
self._data = response.json()
|
|
27
|
+
assert self._data is not None
|
|
28
|
+
return self._data
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def accept_invitation(self) -> str:
|
|
32
|
+
return self._load()["accept_invitation"]
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def audit_log(self) -> str:
|
|
36
|
+
return self._load()["audit_log"]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def auth(self) -> str:
|
|
40
|
+
return self._load()["auth"]
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def bastion(self) -> str:
|
|
44
|
+
return self._load()["bastion"]
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def boundary(self) -> str:
|
|
48
|
+
return self._load()["boundary"]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def identity(self) -> str:
|
|
52
|
+
return self._load()["identity"]
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def initialize(self) -> str:
|
|
56
|
+
return self._load()["initialize"]
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def login(self) -> str:
|
|
60
|
+
return self._load()["login"]
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def login_oauth2_start(self) -> str:
|
|
64
|
+
return self._load()["login_oauth2_start"]
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def login_oidc(self) -> str:
|
|
68
|
+
return self._load()["login_oidc"]
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def public_auth(self) -> str:
|
|
72
|
+
return self._load()["public_auth"]
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def role(self) -> str:
|
|
76
|
+
return self._load()["role"]
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def ssh(self) -> str:
|
|
80
|
+
return self._load()["ssh"]
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def tag(self) -> str:
|
|
84
|
+
return self._load()["tag"]
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def tenant(self) -> str:
|
|
88
|
+
return self._load()["tenant"]
|