Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
51 changes: 51 additions & 0 deletions descope/management/_lists_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

from typing import Any, List, Optional


class ListsBase:
@staticmethod
def _compose_create_body(name: str, description: Optional[str], list_type: str, data: Any) -> dict:
body = {"name": name, "type": list_type}
if description is not None:
body["description"] = description
if data is not None:
body["data"] = data
return body

@staticmethod
def _compose_update_body(id: str, name: str, description: Optional[str], list_type: str, data: Any) -> dict:
body = {"id": id, "name": name, "type": list_type}
if description is not None:
body["description"] = description
if data is not None:
body["data"] = data
return body

@staticmethod
def _compose_delete_body(id: str) -> dict:
return {"id": id}

@staticmethod
def _compose_import_body(lists: List[dict]) -> dict:
return {"lists": lists}

@staticmethod
def _compose_ip_body(id: str, ips: List[str]) -> dict:
return {"id": id, "ips": ips}

@staticmethod
def _compose_check_ip_body(id: str, ip: str) -> dict:
return {"id": id, "ip": ip}

@staticmethod
def _compose_text_body(id: str, texts: List[str]) -> dict:
return {"id": id, "texts": texts}

@staticmethod
def _compose_check_text_body(id: str, text: str) -> dict:
return {"id": id, "text": text}

@staticmethod
def _compose_clear_body(id: str) -> dict:
return {"id": id}
35 changes: 35 additions & 0 deletions descope/management/_sso_application_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,38 @@ def _compose_create_update_saml_body(
"defaultSignatureAlgorithm": default_signature_algorithm,
}
return body

@staticmethod
def _compose_create_update_wsfed_body(
name: str,
login_page_url: str,
realm: str,
reply_url: str,
id: Optional[str] = None,
description: Optional[str] = None,
logo: Optional[str] = None,
enabled: Optional[bool] = True,
reply_allowed_callbacks: Optional[List[str]] = None,
attribute_mapping: Optional[List[SAMLIDPAttributeMappingInfo]] = None,
groups_mapping: Optional[List[SAMLIDPGroupsMappingInfo]] = None,
force_authentication: Optional[bool] = False,
logout_redirect_url: Optional[str] = None,
error_redirect_url: Optional[str] = None,
) -> dict:
body: dict[str, Any] = {
"id": id,
"name": name,
"description": description,
"enabled": enabled,
"logo": logo,
"loginPageUrl": login_page_url,
"realm": realm,
"replyUrl": reply_url,
"replyAllowedCallbacks": reply_allowed_callbacks if reply_allowed_callbacks else [],
"attributeMapping": saml_idp_attribute_mapping_info_to_dict(attribute_mapping),
"groupsMapping": saml_idp_groups_mapping_info_to_dict(groups_mapping),
"forceAuthentication": force_authentication,
"logoutRedirectUrl": logout_redirect_url,
"errorRedirectUrl": error_redirect_url,
}
return body
43 changes: 43 additions & 0 deletions descope/management/_third_party_application_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any, Dict, List, Optional


# This is not part of the public API but a code helper
def compose_create_update_body(
name: str,
login_page_url: str,
id: Optional[str] = None,
description: Optional[str] = None,
logo: Optional[str] = None,
approved_callback_urls: Optional[List[str]] = None,
permissions_scopes: Optional[List[Dict[str, Any]]] = None,
attributes_scopes: Optional[List[Dict[str, Any]]] = None,
jwt_bearer_settings: Optional[Dict[str, Any]] = None,
custom_attributes: Optional[Dict[str, Any]] = None,
force_pkce: Optional[bool] = None,
default_audience: Optional[str] = None,
) -> dict:
body: Dict[str, Any] = {
"name": name,
"loginPageUrl": login_page_url,
}
if id is not None:
body["id"] = id
if description is not None:
body["description"] = description
if logo is not None:
body["logo"] = logo
if approved_callback_urls is not None:
body["approvedCallbackUrls"] = approved_callback_urls
if permissions_scopes is not None:
body["permissionsScopes"] = permissions_scopes

Check warning on line 32 in descope/management/_third_party_application_base.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
if attributes_scopes is not None:
body["attributesScopes"] = attributes_scopes

Check warning on line 34 in descope/management/_third_party_application_base.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
if jwt_bearer_settings is not None:
body["jwtBearerSettings"] = jwt_bearer_settings

Check warning on line 36 in descope/management/_third_party_application_base.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
if custom_attributes is not None:
body["customAttributes"] = custom_attributes

Check warning on line 38 in descope/management/_third_party_application_base.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
if force_pkce is not None:
body["forcePkce"] = force_pkce
if default_audience is not None:
body["defaultAudience"] = default_audience
return body
83 changes: 83 additions & 0 deletions descope/management/access_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,86 @@ def delete(
MgmtV1.access_key_delete_path,
body={"id": id},
)

def rotate(
self,
id: str,
) -> dict:
"""
Rotate an existing access key. Regenerates the secret while preserving the same access key ID,
name, roles, tenants, expiry, and metadata.

Args:
id (str): The id of the access key to be rotated.

Return value (dict):
Return dict in the format
{
"key": {},
"cleartext": {}
}
The cleartext is the new secret and is only visible once. Save it immediately.
The previous secret stops working as soon as this call returns.

Raise:
AuthException: raised if rotation operation fails
"""
response = self._http.post(
MgmtV1.access_key_rotate_path,
body={"id": id},
)
return response.json()

def activate_batch(
self,
ids: List[str],
):
"""
Activate multiple existing access keys in a single request.

Args:
ids (List[str]): The list of access key IDs to be activated.

Raise:
AuthException: raised if batch activation operation fails
"""
self._http.post(
MgmtV1.access_key_activate_batch_path,
body={"ids": ids},
)

def deactivate_batch(
self,
ids: List[str],
):
"""
Deactivate multiple existing access keys in a single request.

Args:
ids (List[str]): The list of access key IDs to be deactivated.

Raise:
AuthException: raised if batch deactivation operation fails
"""
self._http.post(
MgmtV1.access_key_deactivate_batch_path,
body={"ids": ids},
)

def delete_batch(
self,
ids: List[str],
):
"""
Delete multiple existing access keys in a single request. IMPORTANT: This action is irreversible. Use carefully.

Args:
ids (List[str]): The list of access key IDs to be deleted.

Raise:
AuthException: raised if batch deletion operation fails
"""
self._http.post(
MgmtV1.access_key_delete_batch_path,
body={"ids": ids},
)
83 changes: 83 additions & 0 deletions descope/management/access_key_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,86 @@ async def delete(
MgmtV1.access_key_delete_path,
body={"id": id},
)

async def rotate(
self,
id: str,
) -> dict:
"""
Rotate an existing access key. Regenerates the secret while preserving the same access key ID,
name, roles, tenants, expiry, and metadata.

Args:
id (str): The id of the access key to be rotated.

Return value (dict):
Return dict in the format
{
"key": {},
"cleartext": {}
}
The cleartext is the new secret and is only visible once. Save it immediately.
The previous secret stops working as soon as this call returns.

Raise:
AuthException: raised if rotation operation fails
"""
response = await self._http.post(
MgmtV1.access_key_rotate_path,
body={"id": id},
)
return response.json()

async def activate_batch(
self,
ids: List[str],
):
"""
Activate multiple existing access keys in a single request.

Args:
ids (List[str]): The list of access key IDs to be activated.

Raise:
AuthException: raised if batch activation operation fails
"""
await self._http.post(
MgmtV1.access_key_activate_batch_path,
body={"ids": ids},
)

async def deactivate_batch(
self,
ids: List[str],
):
"""
Deactivate multiple existing access keys in a single request.

Args:
ids (List[str]): The list of access key IDs to be deactivated.

Raise:
AuthException: raised if batch deactivation operation fails
"""
await self._http.post(
MgmtV1.access_key_deactivate_batch_path,
body={"ids": ids},
)

async def delete_batch(
self,
ids: List[str],
):
"""
Delete multiple existing access keys in a single request. IMPORTANT: This action is irreversible. Use carefully.

Args:
ids (List[str]): The list of access key IDs to be deleted.

Raise:
AuthException: raised if batch deletion operation fails
"""
await self._http.post(
MgmtV1.access_key_delete_batch_path,
body={"ids": ids},
)
81 changes: 81 additions & 0 deletions descope/management/analytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from __future__ import annotations

from datetime import datetime
from typing import List, Optional

from descope._http_base import HTTPBase
from descope.management.common import MgmtV1


class Analytics(HTTPBase):
def search(
self,
actions: Optional[List[str]] = None,
excluded_actions: Optional[List[str]] = None,
from_ts: Optional[datetime] = None,
to_ts: Optional[datetime] = None,
devices: Optional[List[str]] = None,
methods: Optional[List[str]] = None,
geos: Optional[List[str]] = None,
tenants: Optional[List[str]] = None,
group_by_action: bool = False,
group_by_device: bool = False,
group_by_method: bool = False,
group_by_geo: bool = False,
group_by_tenant: bool = False,
group_by_referrer: bool = False,
group_by_created: Optional[str] = None,
) -> dict:
"""
Search analytics records according to given filters.

Args:
actions (List[str]): Optional list of actions to filter by.
excluded_actions (List[str]): Optional list of actions to exclude.
from_ts (datetime): Optional retrieve analytics newer than given time. Limited to no older than 12 months.
to_ts (datetime): Optional retrieve records older than given time.
devices (List[str]): Optional list of devices to filter by. Current devices supported are "Bot"/"Mobile"/"Desktop"/"Tablet"/"Unknown".
methods (List[str]): Optional list of methods to filter by. Current auth methods are "otp"/"totp"/"magiclink"/"oauth"/"saml"/"password".
geos (List[str]): Optional list of geos to filter by. Geo is currently country code like "US", "IL", etc.
tenants (List[str]): Optional list of tenants to filter by.
group_by_action (bool): Should we group summarized results by action.
group_by_device (bool): Should we group summarized results by device.
group_by_method (bool): Should we group summarized results by method.
group_by_geo (bool): Should we group summarized results by geo.
group_by_tenant (bool): Should we group summarized results by tenant.
group_by_referrer (bool): Should we group summarized results by referrer.
group_by_created (str): Optional how should we group the dates. Possible values are "h" for hour, "d" for day, "w" for week, "m" for month and "q" for quarter.

Return value (dict):
Return dict in the format {"analytics": [...]}
"analytics" contains a list of analytic records matching the filters.

Raise:
AuthException: raised if search operation fails
"""
body = {
"actions": actions or [],
"excludedActions": excluded_actions or [],
"devices": devices or [],
"methods": methods or [],
"geos": geos or [],
"tenants": tenants or [],
"groupByAction": group_by_action,
"groupByDevice": group_by_device,
"groupByMethod": group_by_method,
"groupByGeo": group_by_geo,
"groupByTenant": group_by_tenant,
"groupByReferrer": group_by_referrer,
}
if from_ts is not None:
body["from"] = int(from_ts.timestamp() * 1000)
if to_ts is not None:
body["to"] = int(to_ts.timestamp() * 1000)
if group_by_created is not None:
body["groupByCreated"] = group_by_created

response = self._http.post(
MgmtV1.analytics_search_path,
body=body,
)
return response.json()
Loading
Loading