Skip to content

Commit

Permalink
expose clone project (#308)
Browse files Browse the repository at this point in the history
  • Loading branch information
asafshen authored Nov 9, 2023
1 parent 88b8156 commit a1c2f46
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ These sections show how to use the SDK to perform permission and user management
9. [Manage JWTs](#manage-jwts)
10. [Embedded links](#embedded-links)
11. [Search Audit](#search-audit)
12. [Manaage Project](#manage-project)

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 @@ -844,6 +845,20 @@ audits = descope_client.mgmt.audit.search(
audits = descope_client.mgmt.audit.search(actions=["LoginSucceed"])
```

### Manage Project

You can change the project name, as well as to clone the current project to a new one.

```python

# Change the project name
descope.client.mgmt.project.change_name("new-project-name")

# Clone the current project, including its settings and configurations.
# Users, tenants and access keys are not cloned.
clone_resp = descope.client.mgmt.project.clone("new-project-name")
```

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

To ease your e2e tests, we exposed dedicated management methods,
Expand Down
4 changes: 4 additions & 0 deletions descope/management/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class MgmtV1:
# Audit
audit_search = "/v1/mgmt/audit/search"

# Project
project_update_name = "/v1/mgmt/project/update/name"
project_clone = "/v1/mgmt/project/clone"


class AssociatedTenant:
"""
Expand Down
55 changes: 55 additions & 0 deletions descope/management/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import Optional

from descope._auth_base import AuthBase
from descope.management.common import MgmtV1


class Project(AuthBase):
def update_name(
self,
name: str,
):
"""
Update the current project name.
Args:
name (str): The new name for the project.
Raise:
AuthException: raised if operation fails
"""
self._auth.do_post(
MgmtV1.project_update_name,
{
"name": name,
},
pswd=self._auth.management_key,
)

def clone(
self,
name: str,
tag: Optional[str] = None,
):
"""
Clone the current project, including its settings and configurations.
Users, tenants and access keys are not cloned.
Args:
name (str): The new name for the project.
tag(str): Optional tag for the project. Currently, only the "production" tag is supported.
Return value (dict):
Return dict Containing the new project details (name, id, tag, and settings).
Raise:
AuthException: raised if clone operation fails
"""
response = self._auth.do_post(
MgmtV1.project_clone,
{
"name": name,
"tag": tag,
},
pswd=self._auth.management_key,
)
return response.json()
6 changes: 6 additions & 0 deletions descope/mgmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from descope.management.group import Group # noqa: F401
from descope.management.jwt import JWT # noqa: F401
from descope.management.permission import Permission # noqa: F401
from descope.management.project import Project # noqa: F401
from descope.management.role import Role # noqa: F401
from descope.management.sso_settings import SSOSettings # noqa: F401
from descope.management.tenant import Tenant # noqa: F401
Expand All @@ -26,6 +27,7 @@ def __init__(self, auth: Auth):
self._group = Group(auth)
self._flow = Flow(auth)
self._audit = Audit(auth)
self._project = Project(auth)

@property
def tenant(self):
Expand Down Expand Up @@ -66,3 +68,7 @@ def flow(self):
@property
def audit(self):
return self._audit

@property
def project(self):
return self._project
112 changes: 112 additions & 0 deletions tests/management/test_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import json
from unittest import mock
from unittest.mock import patch

from descope import AuthException, DescopeClient
from descope.common import DEFAULT_TIMEOUT_SECONDS
from descope.management.common import MgmtV1

from .. import common


class TestProject(common.DescopeTest):
def setUp(self) -> None:
super().setUp()
self.dummy_project_id = "dummy"
self.dummy_management_key = "key"
self.public_key_dict = {
"alg": "ES384",
"crv": "P-384",
"kid": "P2CtzUhdqpIF2ys9gg7ms06UvtC4",
"kty": "EC",
"use": "sig",
"x": "pX1l7nT2turcK5_Cdzos8SKIhpLh1Wy9jmKAVyMFiOCURoj-WQX1J0OUQqMsQO0s",
"y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0",
}

def test_update_name(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
)

# Test failed flows
with patch("requests.post") as mock_post:
mock_post.return_value.ok = False
self.assertRaises(
AuthException,
client.mgmt.project.update_name,
"name",
)

# Test success flow
with patch("requests.post") as mock_post:
mock_post.return_value.ok = True
self.assertIsNone(client.mgmt.project.update_name("new-name"))
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.project_update_name}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
},
params=None,
json={
"name": "new-name",
},
allow_redirects=False,
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_clone(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
)

# Test failed flows
with patch("requests.post") as mock_post:
mock_post.return_value.ok = False
self.assertRaises(
AuthException,
client.mgmt.project.clone,
"new-name",
"production",
)

# Test success flow
with patch("requests.post") as mock_post:
network_resp = mock.Mock()
network_resp.ok = True
network_resp.json.return_value = json.loads(
"""
{
"id":"dummy"
}
"""
)
mock_post.return_value = network_resp
resp = client.mgmt.project.clone(
"new-name",
"production",
)
self.assertEqual(resp["id"], "dummy")
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.project_clone}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
},
params=None,
json={
"name": "new-name",
"tag": "production",
},
allow_redirects=False,
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

0 comments on commit a1c2f46

Please sign in to comment.