Skip to content

Commit ac5e1e7

Browse files
committed
pangea-sdk: add AuthN groups
1 parent 4bb4b47 commit ac5e1e7

File tree

6 files changed

+447
-62
lines changed

6 files changed

+447
-62
lines changed

.vscode/extensions.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

.vscode/settings.json

Lines changed: 0 additions & 17 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- AuthZ: `expires_at` to tuples.
13+
- AuthN: groups.
1314

1415
## 6.0.0 - 2025-04-21
1516

16-
### Added
17+
### Added
1718

1819
- Redact: `fpe_context` on `StructuredResult`.
1920
- AI Guard: detector overrides.
@@ -84,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8485
- `Audit.fix_consistency_proofs` is now a private method.
8586
- `pangea.deep_verify` error message to `warning` when `not_persisted` event.
8687

87-
### Fixed
88+
### Fixed
8889

8990
- `pangea.audit_dump` only dump before events if the leaf_index is not None.
9091

@@ -131,7 +132,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
131132

132133
### Changed
133134

134-
- `attributes` field in `/check` endpoint. Now it's a `Dict[str, Any]`
135+
- `attributes` field in `/check` endpoint. Now it's a `Dict[str, Any]`
135136

136137
### Fixed
137138

@@ -238,7 +239,7 @@ Note that Sanitize and Secure Share did not make it into this release.
238239

239240
## [3.7.0] - 2024-02-26
240241

241-
### Added
242+
### Added
242243

243244
- Vault service. Post quantum signing algorithms support
244245

packages/pangea-sdk/pangea/asyncio/services/authn.py

Lines changed: 171 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
# Author: Pangea Cyber Corporation
33
from __future__ import annotations
44

5-
from typing import Dict, List, Optional, Union
5+
from typing import Dict, List, Literal, Optional, Union
66

77
import pangea.services.authn.models as m
88
from pangea.asyncio.services.base import ServiceBaseAsync
99
from pangea.config import PangeaConfig
1010
from pangea.response import PangeaResponse, PangeaResponseResult
1111

12-
SERVICE_NAME = "authn"
12+
__all__ = ["AuthNAsync"]
13+
14+
15+
_SERVICE_NAME = "authn"
1316

1417

1518
class AuthNAsync(ServiceBaseAsync):
@@ -35,7 +38,7 @@ class AuthNAsync(ServiceBaseAsync):
3538
authn = AuthNAsync(token=PANGEA_TOKEN, config=authn_config)
3639
"""
3740

38-
service_name = SERVICE_NAME
41+
service_name = _SERVICE_NAME
3942

4043
def __init__(
4144
self,
@@ -65,7 +68,7 @@ def __init__(
6568
self.agreements = AuthNAsync.AgreementsAsync(token, config, logger_name=logger_name)
6669

6770
class SessionAsync(ServiceBaseAsync):
68-
service_name = SERVICE_NAME
71+
service_name = _SERVICE_NAME
6972

7073
def __init__(
7174
self,
@@ -162,7 +165,7 @@ async def logout(self, user_id: str) -> PangeaResponse[m.SessionLogoutResult]:
162165
)
163166

164167
class ClientAsync(ServiceBaseAsync):
165-
service_name = SERVICE_NAME
168+
service_name = _SERVICE_NAME
166169

167170
def __init__(
168171
self,
@@ -222,7 +225,7 @@ async def jwks(
222225
return await self.request.post("v2/client/jwks", m.ClientJWKSResult, {})
223226

224227
class SessionAsync(ServiceBaseAsync):
225-
service_name = SERVICE_NAME
228+
service_name = _SERVICE_NAME
226229

227230
def __init__(
228231
self,
@@ -359,7 +362,7 @@ async def refresh(
359362
)
360363

361364
class PasswordAsync(ServiceBaseAsync):
362-
service_name = SERVICE_NAME
365+
service_name = _SERVICE_NAME
363366

364367
def __init__(
365368
self,
@@ -419,7 +422,7 @@ async def expire(self, user_id: str) -> PangeaResponse[PangeaResponseResult]:
419422
return await self.request.post("v2/user/password/expire", PangeaResponseResult, {"id": user_id})
420423

421424
class TokenAsync(ServiceBaseAsync):
422-
service_name = SERVICE_NAME
425+
service_name = _SERVICE_NAME
423426

424427
def __init__(
425428
self,
@@ -456,7 +459,7 @@ async def check(self, token: str) -> PangeaResponse[m.ClientTokenCheckResult]:
456459
)
457460

458461
class UserAsync(ServiceBaseAsync):
459-
service_name = SERVICE_NAME
462+
service_name = _SERVICE_NAME
460463

461464
def __init__(
462465
self,
@@ -667,7 +670,7 @@ async def list(
667670
return await self.request.post("v2/user/list", m.UserListResult, data=input.model_dump(exclude_none=True))
668671

669672
class InvitesAsync(ServiceBaseAsync):
670-
service_name = SERVICE_NAME
673+
service_name = _SERVICE_NAME
671674

672675
def __init__(
673676
self,
@@ -739,7 +742,7 @@ async def delete(self, id: str) -> PangeaResponse[m.UserInviteDeleteResult]:
739742
)
740743

741744
class AuthenticatorsAsync(ServiceBaseAsync):
742-
service_name = SERVICE_NAME
745+
service_name = _SERVICE_NAME
743746

744747
def __init__(
745748
self,
@@ -821,7 +824,7 @@ async def list(
821824
)
822825

823826
class ProfileAsync(ServiceBaseAsync):
824-
service_name = SERVICE_NAME
827+
service_name = _SERVICE_NAME
825828

826829
def __init__(
827830
self,
@@ -905,8 +908,57 @@ async def update(
905908
"v2/user/profile/update", m.UserProfileUpdateResult, data=input.model_dump(exclude_none=True)
906909
)
907910

911+
class GroupAsync(ServiceBaseAsync):
912+
service_name = _SERVICE_NAME
913+
914+
def __init__(
915+
self,
916+
token: str,
917+
config: PangeaConfig | None = None,
918+
logger_name: str = "pangea",
919+
) -> None:
920+
super().__init__(token, config, logger_name=logger_name)
921+
922+
async def assign(self, user_id: str, group_ids: list[str]) -> PangeaResponse[PangeaResponseResult]:
923+
"""
924+
Assign groups to a user
925+
926+
Add a list of groups to a specified user
927+
928+
OperationId: authn_post_v2_user_group_assign
929+
"""
930+
return await self.request.post(
931+
"v2/user/group/assign",
932+
data={"id": user_id, "group_ids": group_ids},
933+
result_class=m.PangeaResponseResult,
934+
)
935+
936+
async def remove(self, user_id: str, group_id: str) -> PangeaResponse[PangeaResponseResult]:
937+
"""
938+
Remove a group assigned to a user
939+
940+
Remove a group assigned to a user
941+
942+
OperationId: authn_post_v2_user_group_remove
943+
"""
944+
return await self.request.post(
945+
"v2/user/group/remove",
946+
data={"id": user_id, "group_id": group_id},
947+
result_class=m.PangeaResponseResult,
948+
)
949+
950+
async def list(self, user_id: str) -> PangeaResponse[m.GroupList]:
951+
"""
952+
List of groups assigned to a user
953+
954+
Return a list of ids for groups assigned to a user
955+
956+
OperationId: authn_post_v2_user_group_list
957+
"""
958+
return await self.request.post("v2/user/group/list", data={"id": user_id}, result_class=m.GroupList)
959+
908960
class FlowAsync(ServiceBaseAsync):
909-
service_name = SERVICE_NAME
961+
service_name = _SERVICE_NAME
910962

911963
def __init__(
912964
self,
@@ -1052,7 +1104,7 @@ async def update(
10521104
)
10531105

10541106
class AgreementsAsync(ServiceBaseAsync):
1055-
service_name = SERVICE_NAME
1107+
service_name = _SERVICE_NAME
10561108

10571109
def __init__(
10581110
self,
@@ -1201,3 +1253,108 @@ async def update(
12011253
return await self.request.post(
12021254
"v2/agreements/update", m.AgreementUpdateResult, data=input.model_dump(exclude_none=True)
12031255
)
1256+
1257+
class GroupAsync(ServiceBaseAsync):
1258+
service_name = _SERVICE_NAME
1259+
1260+
def __init__(
1261+
self,
1262+
token: str,
1263+
config: PangeaConfig | None = None,
1264+
logger_name: str = "pangea",
1265+
) -> None:
1266+
super().__init__(token, config, logger_name=logger_name)
1267+
1268+
async def create(
1269+
self, name: str, type: str, *, description: str | None = None, attributes: dict[str, str] | None = None
1270+
) -> PangeaResponse[m.GroupInfo]:
1271+
"""
1272+
Create a new group
1273+
1274+
Create a new group
1275+
1276+
OperationId: authn_post_v2_group_create
1277+
"""
1278+
return await self.request.post(
1279+
"v2/group/create",
1280+
data={"name": name, "type": type, "description": description, "attributes": attributes},
1281+
result_class=m.GroupInfo,
1282+
)
1283+
1284+
async def delete(self, id: str) -> PangeaResponse[PangeaResponseResult]:
1285+
"""
1286+
Delete a group
1287+
1288+
Delete a group
1289+
1290+
OperationId: authn_post_v2_group_delete
1291+
"""
1292+
return await self.request.post("v2/group/delete", data={"id": id}, result_class=PangeaResponseResult)
1293+
1294+
async def get(self, id: str) -> PangeaResponse[m.GroupInfo]:
1295+
"""
1296+
Get group information
1297+
1298+
Look up a group by ID and return its information.
1299+
1300+
OperationId: authn_post_v2_group_get
1301+
"""
1302+
return await self.request.post("v2/group/get", data={"id": id}, result_class=m.GroupInfo)
1303+
1304+
async def list(
1305+
self,
1306+
*,
1307+
filter: m.GroupsFilter | None = None,
1308+
last: str | None = None,
1309+
order: Literal["asc", "desc"] | None = None,
1310+
order_by: Literal["id", "created_at", "updated_at", "name", "type"] | None = None,
1311+
size: int | None = None,
1312+
) -> PangeaResponse[m.GroupList]:
1313+
"""
1314+
List groups
1315+
1316+
Look up groups by name, type, or attributes.
1317+
"""
1318+
return await self.request.post(
1319+
"v2/group/list",
1320+
data={"filter": filter, "last": last, "order": order, "order_by": order_by, "size": size},
1321+
result_class=m.GroupList,
1322+
)
1323+
1324+
async def list_users(
1325+
self, id: str, *, last: str | None = None, size: int | None = None
1326+
) -> PangeaResponse[m.GroupUserList]:
1327+
"""
1328+
List of users assigned to a group
1329+
1330+
Return a list of ids for users assigned to a group
1331+
1332+
OperationId: authn_post_v2_group_user_list
1333+
"""
1334+
return await self.request.post(
1335+
"v2/group/user/list",
1336+
data={"id": id, "last": last, "size": size},
1337+
result_class=m.GroupUserList,
1338+
)
1339+
1340+
async def update(
1341+
self,
1342+
id: str,
1343+
*,
1344+
name: str | None = None,
1345+
description: str | None = None,
1346+
type: str | None = None,
1347+
attributes: dict[str, str] | None = None,
1348+
) -> PangeaResponse[m.GroupInfo]:
1349+
"""
1350+
Update group information
1351+
1352+
Update group information
1353+
1354+
OperationId: authn_post_v2_group_update
1355+
"""
1356+
return await self.request.post(
1357+
"v2/group/update",
1358+
data={"id": id, "name": name, "description": description, "type": type, "attributes": attributes},
1359+
result_class=m.GroupInfo,
1360+
)

0 commit comments

Comments
 (0)