Skip to content

Commit c625ee3

Browse files
committed
Support Descoper methods
1 parent 366b4b4 commit c625ee3

File tree

6 files changed

+748
-0
lines changed

6 files changed

+748
-0
lines changed

descope/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
)
2020
from descope.management.common import (
2121
AssociatedTenant,
22+
DescoperAttributes,
23+
DescoperCreate,
24+
DescoperProjectRole,
25+
DescoperRBAC,
26+
DescoperRole,
27+
DescoperTagRole,
2228
SAMLIDPAttributeMappingInfo,
2329
SAMLIDPGroupsMappingInfo,
2430
SAMLIDPRoleGroupMappingInfo,

descope/http_client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ def post(
113113
self._raise_from_response(response)
114114
return response
115115

116+
def put(
117+
self,
118+
uri: str,
119+
*,
120+
body: Optional[Union[dict, list[dict], list[str]]] = None,
121+
params=None,
122+
pswd: Optional[str] = None,
123+
) -> requests.Response:
124+
response = requests.put(
125+
f"{self.base_url}{uri}",
126+
headers=self._get_default_headers(pswd),
127+
json=body,
128+
allow_redirects=False,
129+
verify=self.secure,
130+
params=params,
131+
timeout=self.timeout_seconds,
132+
)
133+
self._raise_from_response(response)
134+
return response
135+
116136
def patch(
117137
self,
118138
uri: str,

descope/management/common.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ class MgmtV1:
258258
project_import = "/v1/mgmt/project/import"
259259
project_list_projects = "/v1/mgmt/projects/list"
260260

261+
# Descoper
262+
descoper_create_path = "/v1/mgmt/descoper"
263+
descoper_update_path = "/v1/mgmt/descoper"
264+
descoper_load_path = "/v1/mgmt/descoper"
265+
descoper_delete_path = "/v1/mgmt/descoper"
266+
descoper_list_path = "/v1/mgmt/descoper/list"
267+
261268

262269
class MgmtSignUpOptions:
263270
def __init__(
@@ -468,3 +475,128 @@ def sort_to_dict(sort: List[Sort]) -> list:
468475
}
469476
)
470477
return sort_list
478+
479+
480+
class DescoperRole(Enum):
481+
"""Represents a Descoper role."""
482+
483+
ADMIN = "admin"
484+
DEVELOPER = "developer"
485+
SUPPORT = "support"
486+
AUDITOR = "auditor"
487+
488+
489+
class DescoperAttributes:
490+
"""
491+
Represents Descoper attributes, such as name and email/phone.
492+
"""
493+
494+
def __init__(
495+
self,
496+
display_name: Optional[str] = None,
497+
email: Optional[str] = None,
498+
phone: Optional[str] = None,
499+
):
500+
self.display_name = display_name
501+
self.email = email
502+
self.phone = phone
503+
504+
def to_dict(self) -> dict:
505+
return {
506+
"displayName": self.display_name,
507+
"email": self.email,
508+
"phone": self.phone,
509+
}
510+
511+
512+
class DescoperTagRole:
513+
"""
514+
Represents a Descoper tags to role mapping.
515+
"""
516+
517+
def __init__(
518+
self,
519+
tags: Optional[List[str]] = None,
520+
role: Optional[DescoperRole] = None,
521+
):
522+
self.tags = tags if tags is not None else []
523+
self.role = role
524+
525+
def to_dict(self) -> dict:
526+
return {
527+
"tags": self.tags,
528+
"role": self.role.value if self.role else None,
529+
}
530+
531+
532+
class DescoperProjectRole:
533+
"""
534+
Represents a Descoper projects to role mapping.
535+
"""
536+
537+
def __init__(
538+
self,
539+
project_ids: Optional[List[str]] = None,
540+
role: Optional[DescoperRole] = None,
541+
):
542+
self.project_ids = project_ids if project_ids is not None else []
543+
self.role = role
544+
545+
def to_dict(self) -> dict:
546+
return {
547+
"projectIds": self.project_ids,
548+
"role": self.role.value if self.role else None,
549+
}
550+
551+
552+
class DescoperRBAC:
553+
"""
554+
Represents Descoper RBAC configuration.
555+
"""
556+
557+
def __init__(
558+
self,
559+
is_company_admin: bool = False,
560+
tags: Optional[List[DescoperTagRole]] = None,
561+
projects: Optional[List[DescoperProjectRole]] = None,
562+
):
563+
self.is_company_admin = is_company_admin
564+
self.tags = tags if tags is not None else []
565+
self.projects = projects if projects is not None else []
566+
567+
def to_dict(self) -> dict:
568+
return {
569+
"isCompanyAdmin": self.is_company_admin,
570+
"tags": [t.to_dict() for t in self.tags],
571+
"projects": [p.to_dict() for p in self.projects],
572+
}
573+
574+
575+
class DescoperCreate:
576+
"""
577+
Represents a Descoper to be created.
578+
"""
579+
580+
def __init__(
581+
self,
582+
login_id: str,
583+
attributes: Optional[DescoperAttributes] = None,
584+
send_invite: bool = False,
585+
rbac: Optional[DescoperRBAC] = None,
586+
):
587+
self.login_id = login_id
588+
self.attributes = attributes
589+
self.send_invite = send_invite
590+
self.rbac = rbac
591+
592+
def to_dict(self) -> dict:
593+
return {
594+
"loginId": self.login_id,
595+
"attributes": self.attributes.to_dict() if self.attributes else None,
596+
"sendInvite": self.send_invite,
597+
"rbac": self.rbac.to_dict() if self.rbac else None,
598+
}
599+
600+
601+
def descopers_to_dict(descopers: List[DescoperCreate]) -> list:
602+
return [d.to_dict() for d in descopers]

descope/management/descoper.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from typing import List, Optional
2+
3+
from descope._http_base import HTTPBase
4+
from descope.management.common import (
5+
DescoperAttributes,
6+
DescoperCreate,
7+
DescoperRBAC,
8+
MgmtV1,
9+
descopers_to_dict,
10+
)
11+
12+
13+
class Descoper(HTTPBase):
14+
def create(
15+
self,
16+
descopers: List[DescoperCreate],
17+
) -> dict:
18+
"""
19+
Create new Descopers.
20+
21+
Args:
22+
descopers (List[DescoperCreate]): List of Descopers to create.
23+
Note that tags are referred to by name, without the company ID prefix.
24+
25+
Return value (dict):
26+
Return dict in the format
27+
{
28+
"descopers": [...],
29+
"total": <int>
30+
}
31+
32+
Raise:
33+
AuthException: raised if create operation fails
34+
"""
35+
if not descopers:
36+
raise ValueError("descopers list cannot be empty")
37+
38+
response = self._http.put(
39+
MgmtV1.descoper_create_path,
40+
body={"descopers": descopers_to_dict(descopers)},
41+
)
42+
return response.json()
43+
44+
def update(
45+
self,
46+
id: str,
47+
attributes: Optional[DescoperAttributes] = None,
48+
rbac: Optional[DescoperRBAC] = None,
49+
) -> dict:
50+
"""
51+
Update an existing Descoper's RBAC and/or Attributes.
52+
53+
IMPORTANT: All parameter *fields*, if set, will override whatever values are currently set
54+
in the existing Descoper. Use carefully.
55+
56+
Args:
57+
id (str): The id of the Descoper to update.
58+
attributes (DescoperAttributes): Optional attributes to update.
59+
rbac (DescoperRBAC): Optional RBAC configuration to update.
60+
61+
Return value (dict):
62+
Return dict in the format
63+
{"descoper": {...}}
64+
Containing the updated Descoper information.
65+
66+
Raise:
67+
AuthException: raised if update operation fails
68+
"""
69+
if not id:
70+
raise ValueError("id cannot be empty")
71+
72+
body = {"id": id}
73+
if attributes is not None:
74+
body["attributes"] = attributes.to_dict()
75+
if rbac is not None:
76+
body["rbac"] = rbac.to_dict()
77+
78+
response = self._http.patch(
79+
MgmtV1.descoper_update_path,
80+
body=body,
81+
)
82+
return response.json()
83+
84+
def load(
85+
self,
86+
id: str,
87+
) -> dict:
88+
"""
89+
Load an existing Descoper by ID.
90+
91+
Args:
92+
id (str): The id of the Descoper to load.
93+
94+
Return value (dict):
95+
Return dict in the format
96+
{"descoper": {...}}
97+
Containing the loaded Descoper information.
98+
99+
Raise:
100+
AuthException: raised if load operation fails
101+
"""
102+
if not id:
103+
raise ValueError("id cannot be empty")
104+
105+
response = self._http.get(
106+
uri=MgmtV1.descoper_load_path,
107+
params={"id": id},
108+
)
109+
return response.json()
110+
111+
def delete(
112+
self,
113+
id: str,
114+
):
115+
"""
116+
Delete an existing Descoper. IMPORTANT: This action is irreversible. Use carefully.
117+
118+
Args:
119+
id (str): The id of the Descoper to delete.
120+
121+
Raise:
122+
AuthException: raised if delete operation fails
123+
"""
124+
if not id:
125+
raise ValueError("id cannot be empty")
126+
127+
self._http.delete(
128+
uri=MgmtV1.descoper_delete_path,
129+
params={"id": id},
130+
)
131+
132+
def list(
133+
self,
134+
) -> dict:
135+
"""
136+
List all Descopers.
137+
138+
Return value (dict):
139+
Return dict in the format
140+
{
141+
"descopers": [...],
142+
"total": <int>
143+
}
144+
Containing all Descopers and the total count.
145+
146+
Raise:
147+
AuthException: raised if list operation fails
148+
"""
149+
response = self._http.post(
150+
MgmtV1.descoper_list_path,
151+
body={},
152+
)
153+
return response.json()

descope/mgmt.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from descope.management.access_key import AccessKey
77
from descope.management.audit import Audit
88
from descope.management.authz import Authz
9+
from descope.management.descoper import Descoper
910
from descope.management.fga import FGA
1011
from descope.management.flow import Flow
1112
from descope.management.group import Group
@@ -40,6 +41,7 @@ def __init__(
4041
self._access_key = AccessKey(http_client)
4142
self._audit = Audit(http_client)
4243
self._authz = Authz(http_client)
44+
self._descoper = Descoper(http_client)
4345
self._fga = FGA(http_client, fga_cache_url=fga_cache_url)
4446
self._flow = Flow(http_client)
4547
self._group = Group(http_client)
@@ -141,3 +143,8 @@ def outbound_application(self):
141143
def outbound_application_by_token(self):
142144
# No management key check for outbound_app_token (as authentication for those methods is done by inbound app token)
143145
return self._outbound_application_by_token
146+
147+
@property
148+
def descoper(self):
149+
self._ensure_management_key("descoper")
150+
return self._descoper

0 commit comments

Comments
 (0)