From 1d0247522f22c7bb8fbbdf0d1b01c93308379b1a Mon Sep 17 00:00:00 2001 From: Asaf Shen Date: Tue, 31 Oct 2023 13:37:17 +0200 Subject: [PATCH 1/2] expose clone project --- descope/api/client.go | 16 +++++++- descope/internal/mgmt/project.go | 34 ++++++++++++++++ descope/internal/mgmt/project_test.go | 45 ++++++++++++++++++++++ descope/sdk/mgmt.go | 8 ++++ descope/tests/mocks/mgmt/managementmock.go | 22 +++++++++++ descope/types.go | 23 +++++++++++ 6 files changed, 146 insertions(+), 2 deletions(-) diff --git a/descope/api/client.go b/descope/api/client.go index b7c6b78f..2a297c5e 100644 --- a/descope/api/client.go +++ b/descope/api/client.go @@ -132,6 +132,8 @@ var ( themeImport: "mgmt/theme/import", projectExport: "mgmt/project/export", projectImport: "mgmt/project/import", + projectUpdateName: "mgmt/project/update/name", + projectClone: "mgmt/project/clone", auditSearch: "mgmt/audit/search", }, logout: "auth/logout", @@ -264,8 +266,10 @@ type mgmtEndpoints struct { themeExport string themeImport string - projectExport string - projectImport string + projectExport string + projectImport string + projectUpdateName string + projectClone string auditSearch string } @@ -649,6 +653,14 @@ func (e *endpoints) ManagementProjectImport() string { return path.Join(e.version, e.mgmt.projectImport) } +func (e *endpoints) ManagementProjectUpdateName() string { + return path.Join(e.version, e.mgmt.projectUpdateName) +} + +func (e *endpoints) ManagementProjectClone() string { + return path.Join(e.version, e.mgmt.projectClone) +} + func (e *endpoints) ManagementAuditSearch() string { return path.Join(e.version, e.mgmt.auditSearch) } diff --git a/descope/internal/mgmt/project.go b/descope/internal/mgmt/project.go index 0f106b51..a91d219f 100644 --- a/descope/internal/mgmt/project.go +++ b/descope/internal/mgmt/project.go @@ -1,6 +1,7 @@ package mgmt import ( + "github.com/descope/go-sdk/descope" "github.com/descope/go-sdk/descope/api" "github.com/descope/go-sdk/descope/internal/utils" ) @@ -13,6 +14,15 @@ type project struct { managementBase } +type updateProjectBody struct { + Name string `json:"name"` +} + +type cloneProjectBody struct { + Name string `json:"name"` + Tag string `json:"tag"` +} + func (p *project) ExportRaw() (map[string]any, error) { body := map[string]any{} res, err := p.client.DoPostRequest(api.Routes.ManagementProjectExport(), body, nil, p.conf.ManagementKey) @@ -31,3 +41,27 @@ func (p *project) ImportRaw(files map[string]any) error { _, err := p.client.DoPostRequest(api.Routes.ManagementProjectImport(), body, nil, p.conf.ManagementKey) return err } + +func (p *project) UpdateName(name string) error { + body := updateProjectBody{Name: name} + _, err := p.client.DoPostRequest(api.Routes.ManagementProjectUpdateName(), body, nil, p.conf.ManagementKey) + return err +} + +func (p *project) Clone(name string, tag descope.ProjectTag) (*descope.NewProjectResponse, error) { + body := cloneProjectBody{Name: name, Tag: string(tag)} + res, err := p.client.DoPostRequest(api.Routes.ManagementProjectClone(), body, nil, p.conf.ManagementKey) + if err != nil { + return nil, err + } + return unmarshalNewProjectResponseResponse(res) +} + +func unmarshalNewProjectResponseResponse(res *api.HTTPResponse) (*descope.NewProjectResponse, error) { + var newProjectRes *descope.NewProjectResponse + err := utils.Unmarshal([]byte(res.BodyStr), &newProjectRes) + if err != nil { + return nil, err + } + return newProjectRes, err +} diff --git a/descope/internal/mgmt/project_test.go b/descope/internal/mgmt/project_test.go index d77f6fde..8b090005 100644 --- a/descope/internal/mgmt/project_test.go +++ b/descope/internal/mgmt/project_test.go @@ -32,3 +32,48 @@ func TestProjectImportRaw(t *testing.T) { err := mgmt.Project().ImportRaw(map[string]any{"foo": "bar"}) require.NoError(t, err) } + +func TestProjectUpdateNameSuccess(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + req := map[string]any{} + require.NoError(t, helpers.ReadBody(r, &req)) + name, ok := req["name"].(string) + require.True(t, ok) + require.Equal(t, "foo", name) + })) + err := mgmt.Project().UpdateName("foo") + require.NoError(t, err) +} + +func TestProjectUpdateNameError(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoBadRequest(nil)) + err := mgmt.Project().UpdateName("foo") + require.Error(t, err) +} + +func TestProjectCloneSuccess(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoOkWithBody(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + req := map[string]any{} + require.NoError(t, helpers.ReadBody(r, &req)) + name, ok := req["name"].(string) + require.True(t, ok) + require.Equal(t, "foo", name) + tag, ok := req["tag"].(string) + require.True(t, ok) + require.Equal(t, "production", tag) + }, map[string]any{"projectId": "id1", "projectName": "foo"})) + res, err := mgmt.Project().Clone("foo", "production") + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, "foo", res.ProjectName) + require.Equal(t, "id1", res.ProjectID) + +} + +func TestProjectCloneError(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoBadRequest(nil)) + _, err := mgmt.Project().Clone("foo", "") + require.Error(t, err) +} diff --git a/descope/sdk/mgmt.go b/descope/sdk/mgmt.go index b4cde5e8..fd7a9372 100644 --- a/descope/sdk/mgmt.go +++ b/descope/sdk/mgmt.go @@ -436,6 +436,14 @@ type Project interface { // This API is meant to be used via the 'environment' command line tool that can be // found in the '/tools' directory. ImportRaw(files map[string]any) error + + // Update the current project name. + UpdateName(name string) error + + // Clone the current project, including its settings and configurations. + // Users, tenants and access keys are not cloned. + // Returns The new project details (name, id, tag, and settings). + Clone(name string, tag descope.ProjectTag) (*descope.NewProjectResponse, error) } // Provides search project audit trail diff --git a/descope/tests/mocks/mgmt/managementmock.go b/descope/tests/mocks/mgmt/managementmock.go index fd30f2b3..41ad1583 100644 --- a/descope/tests/mocks/mgmt/managementmock.go +++ b/descope/tests/mocks/mgmt/managementmock.go @@ -816,6 +816,13 @@ type MockProject struct { ImportRawAssert func(files map[string]any) ImportRawError error + + UpdateNameAssert func(name string) + UpdateNameError error + + CloneAssert func(name string, tag descope.ProjectTag) + CloneResponse *descope.NewProjectResponse + CloneError error } func (m *MockProject) ExportRaw() (map[string]any, error) { @@ -829,6 +836,21 @@ func (m *MockProject) ImportRaw(files map[string]any) error { return m.ExportRawError } +func (m *MockProject) UpdateName(name string) error { + if m.UpdateNameAssert != nil { + m.UpdateNameAssert(name) + } + + return m.UpdateNameError +} + +func (m *MockProject) Clone(name string, tag descope.ProjectTag) (*descope.NewProjectResponse, error) { + if m.CloneAssert != nil { + m.CloneAssert(name, tag) + } + return m.CloneResponse, m.CloneError +} + // Mock Audit type MockAudit struct { SearchAssert func(*descope.AuditSearchOptions) diff --git a/descope/types.go b/descope/types.go index cf34b788..d4bdb890 100644 --- a/descope/types.go +++ b/descope/types.go @@ -503,12 +503,32 @@ type AuditSearchOptions struct { Text string `json:"text"` // Free text search across all fields } +type NewProjectResponse struct { + ProjectID string `json:"projectId"` + ProjectName string `json:"projectName"` + ProjectSettingsWeb map[string]any `json:"projectSettingsWeb"` + AuthMethodsMagicLink map[string]any `json:"authMethodsMagicLink"` + AuthMethodsOTP map[string]any `json:"authMethodsOTP"` + AuthMethodsSAML map[string]any `json:"authMethodsSAML"` + AuthMethodsOAuth map[string]any `json:"authMethodsOAuth"` + AuthMethodsWebAuthn map[string]any `json:"authMethodsWebAuthn"` + AuthMethodsTOTP map[string]any `json:"authMethodsTOTP"` + MessagingProvidersWeb map[string]any `json:"messagingProvidersWeb"` + AuthMethodsEnchantedLink map[string]any `json:"authMethodsEnchantedLink"` + AuthMethodsPassword map[string]any `json:"authMethodsPassword"` + AuthMethodsOIDCIDP map[string]any `json:"authMethodsOIDCIDP"` + AuthMethodsEmbeddedLink map[string]any `json:"authMethodsEmbeddedLink"` + Tag string `json:"tag"` +} + type DeliveryMethod string type OAuthProvider string type ContextKey string +type ProjectTag string + const ( MethodWhatsApp DeliveryMethod = "whatsapp" MethodSMS DeliveryMethod = "sms" @@ -521,6 +541,9 @@ const ( OAuthGitlab OAuthProvider = "gitlab" OAuthApple OAuthProvider = "apple" + ProjectTagNone ProjectTag = "" + ProjectTagProduction ProjectTag = "production" + SessionCookieName = "DS" RefreshCookieName = "DSR" From c33e72f2a1448afc8a32fbc3d4fb05d402e362a9 Mon Sep 17 00:00:00 2001 From: Asaf Shen Date: Sun, 5 Nov 2023 12:29:41 +0200 Subject: [PATCH 2/2] readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index eba1dde6..1731b611 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ These sections show how to use the SDK to perform API management functions. Befo 10. [Search Audit](#search-audit) 11. [Embedded Links](#embedded-links) 12. [Manage ReBAC Authz](#manage-rebac-authz) +13. [Manage 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. @@ -1096,6 +1097,22 @@ relations, err := descopeClient.Management.Authz().HasRelations([]*descope.Authz }) ``` +### Manage Project + +You can update project name, as well as to clone the current project to a new one: + +```go + +// Update project name +descopeClient.Management.Project().UpdateName("new-project-name") + +// Clone the current project to a new one +res, err := descopeClient.Management.Project().Clone("new-project-name", "") +if err == nil { + fmt.Println(cloneRes) +} +``` + ## Code Examples You can find various usage examples in the [examples folder](https://github.com/descope/go-sdk/blob/main/examples).