Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ These sections show how to use the SDK to perform permission and user management
14. [Manage Project](#manage-project)
15. [Manage SSO Applications](#manage-sso-applications)
16. [Manage Outbound Applications](#manage-outbound-applications)
17. [Manage Descopers](#manage-descopers)

If you wish to run any of our code samples and play with them, check out our [Code Examples](#code-examples) section.

Expand Down Expand Up @@ -1512,6 +1513,74 @@ latest_tenant_token = descope_client.mgmt.outbound_application_by_token.fetch_te
)
```

### Manage Descopers

You can create, update, delete, load or list Descopers (users who have access to the Descope console):

```python
from descope import (
DescoperAttributes,
DescoperCreate,
DescoperProjectRole,
DescoperRBAC,
DescoperRole,
)

# Create a new Descoper
resp = descope_client.mgmt.descoper.create(
descopers=[
DescoperCreate(
login_id="user@example.com",
attributes=DescoperAttributes(
display_name="John Doe",
email="user@example.com",
phone="+1234567890",
),
send_invite=True, # Send an invitation email
rbac=DescoperRBAC(
is_company_admin=False,
projects=[
DescoperProjectRole(
project_ids=["project-id-1"],
role=DescoperRole.ADMIN,
)
],
),
)
]
)
descopers = resp["descopers"]
total = resp["total"]

# Load a Descoper by ID
resp = descope_client.mgmt.descoper.load("descoper-id")
descoper = resp["descoper"]

# Update a Descoper's attributes and/or RBAC
# Note: All fields that are set will override existing values
resp = descope_client.mgmt.descoper.update(
id="descoper-id",
attributes=DescoperAttributes(
display_name="Updated Name",
),
rbac=DescoperRBAC(
is_company_admin=True,
),
)
updated_descoper = resp["descoper"]

# List all Descopers
resp = descope_client.mgmt.descoper.list()
descopers = resp["descopers"]
total = resp["total"]
for descoper in descopers:
# Do something

# Delete a Descoper
# Descoper deletion cannot be undone. Use carefully.
descope_client.mgmt.descoper.delete("descoper-id")
```

### Utils for your end to end (e2e) tests and integration tests

To ease your e2e tests, we exposed dedicated management methods,
Expand Down
6 changes: 6 additions & 0 deletions descope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
)
from descope.management.common import (
AssociatedTenant,
DescoperAttributes,
DescoperCreate,
DescoperProjectRole,
DescoperRBAC,
DescoperRole,
DescoperTagRole,
SAMLIDPAttributeMappingInfo,
SAMLIDPGroupsMappingInfo,
SAMLIDPRoleGroupMappingInfo,
Expand Down
20 changes: 20 additions & 0 deletions descope/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ def post(
self._raise_from_response(response)
return response

def put(
self,
uri: str,
*,
body: Optional[Union[dict, list[dict], list[str]]] = None,
params=None,
pswd: Optional[str] = None,
) -> requests.Response:
response = requests.put(
f"{self.base_url}{uri}",
headers=self._get_default_headers(pswd),
json=body,
allow_redirects=False,
verify=self.secure,
params=params,
timeout=self.timeout_seconds,
)
self._raise_from_response(response)
return response

def patch(
self,
uri: str,
Expand Down
132 changes: 132 additions & 0 deletions descope/management/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@
project_import = "/v1/mgmt/project/import"
project_list_projects = "/v1/mgmt/projects/list"

# Descoper
descoper_create_path = "/v1/mgmt/descoper"
descoper_update_path = "/v1/mgmt/descoper"
descoper_load_path = "/v1/mgmt/descoper"
descoper_delete_path = "/v1/mgmt/descoper"
descoper_list_path = "/v1/mgmt/descoper/list"


class MgmtSignUpOptions:
def __init__(
Expand Down Expand Up @@ -468,3 +475,128 @@
}
)
return sort_list


class DescoperRole(Enum):
"""Represents a Descoper role."""

ADMIN = "admin"
DEVELOPER = "developer"
SUPPORT = "support"
AUDITOR = "auditor"


class DescoperAttributes:
"""
Represents Descoper attributes, such as name and email/phone.
"""

def __init__(
self,
display_name: Optional[str] = None,
email: Optional[str] = None,
phone: Optional[str] = None,
):
self.display_name = display_name
self.email = email
self.phone = phone

def to_dict(self) -> dict:
return {
"displayName": self.display_name,
"email": self.email,
"phone": self.phone,
}


class DescoperTagRole:
"""
Represents a Descoper tags to role mapping.
"""

def __init__(
self,
tags: Optional[List[str]] = None,
role: Optional[DescoperRole] = None,
):
self.tags = tags if tags is not None else []

Check warning on line 522 in descope/management/common.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
self.role = role

Check warning on line 523 in descope/management/common.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage

def to_dict(self) -> dict:
return {

Check warning on line 526 in descope/management/common.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
"tags": self.tags,
"role": self.role.value if self.role else None,
}


class DescoperProjectRole:
"""
Represents a Descoper projects to role mapping.
"""

def __init__(
self,
project_ids: Optional[List[str]] = None,
role: Optional[DescoperRole] = None,
):
self.project_ids = project_ids if project_ids is not None else []
self.role = role

def to_dict(self) -> dict:
return {
"projectIds": self.project_ids,
"role": self.role.value if self.role else None,
}


class DescoperRBAC:
"""
Represents Descoper RBAC configuration.
"""

def __init__(
self,
is_company_admin: bool = False,
tags: Optional[List[DescoperTagRole]] = None,
projects: Optional[List[DescoperProjectRole]] = None,
):
self.is_company_admin = is_company_admin
self.tags = tags if tags is not None else []
self.projects = projects if projects is not None else []

def to_dict(self) -> dict:
return {
"isCompanyAdmin": self.is_company_admin,
"tags": [t.to_dict() for t in self.tags],
"projects": [p.to_dict() for p in self.projects],
}


class DescoperCreate:
"""
Represents a Descoper to be created.
"""

def __init__(
self,
login_id: str,
attributes: Optional[DescoperAttributes] = None,
send_invite: bool = False,
rbac: Optional[DescoperRBAC] = None,
):
self.login_id = login_id
self.attributes = attributes
self.send_invite = send_invite
self.rbac = rbac

def to_dict(self) -> dict:
return {
"loginId": self.login_id,
"attributes": self.attributes.to_dict() if self.attributes else None,
"sendInvite": self.send_invite,
"rbac": self.rbac.to_dict() if self.rbac else None,
}


def descopers_to_dict(descopers: List[DescoperCreate]) -> list:
return [d.to_dict() for d in descopers]
Loading
Loading