From 9b5a8707126e5ebe6ad4a41664f760de0d4a41c5 Mon Sep 17 00:00:00 2001 From: btfhernandez <133419363+btfhernandez@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:02:38 -0500 Subject: [PATCH] feat: add creating safes feature in terraform provider (#175) * feat: add creating safes feature in terraform provider * feat: fix PR comments --- TestClient.go | 17 ++++ api/authentication/authentication_test.go | 12 +-- api/entities/entities.go | 3 +- api/managed_account/managed_account_test.go | 24 ++--- api/secrets/secrets.go | 60 +++++++---- api/secrets/secrets_test.go | 104 ++++++++++++++++++-- 6 files changed, 173 insertions(+), 47 deletions(-) diff --git a/TestClient.go b/TestClient.go index a733829..df29163 100644 --- a/TestClient.go +++ b/TestClient.go @@ -275,6 +275,23 @@ func main() { // WARNING: Do not log secrets in production code, the following log statement logs test secrets for testing purposes: zapLogger.Debug(fmt.Sprintf("Created Folder: %v", createdFolder.Name)) + safeDetails := entities.FolderDetails{ + Name: "SAFE_" + uuid.New().String(), + Description: "My new Safe", + FolderType: "SAFE", + } + + // creating a safe. + createdSafe, err := secretObj.CreateFolderFlow("", safeDetails) + + if err != nil { + zapLogger.Error(err.Error()) + return + } + + // WARNING: Do not log secrets in production code, the following log statement logs test secrets for testing purposes: + zapLogger.Debug(fmt.Sprintf("Created Safe: %v", createdSafe.Name)) + // signing out _ = authenticate.SignOut() diff --git a/api/authentication/authentication_test.go b/api/authentication/authentication_test.go index 0cd2e42..60f58fc 100644 --- a/api/authentication/authentication_test.go +++ b/api/authentication/authentication_test.go @@ -83,14 +83,14 @@ func TestSignAppin(t *testing.T) { testConfig := UserTestConfig{ name: "TestSignAppin", server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } })), response: &entities.SignApinResponse{ UserId: 1, - EmailAddress: "Felipe", + EmailAddress: "test@beyondtrust.com", }, } @@ -120,14 +120,14 @@ func TestSignAppinWithApiKey(t *testing.T) { testConfig := UserTestConfig{ name: "TestSignAppinWithApiKey", server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } })), response: &entities.SignApinResponse{ UserId: 1, - EmailAddress: "Felipe", + EmailAddress: "test@beyondtrust.com", }, } @@ -209,7 +209,7 @@ func TestGetPasswordSafeAuthentication(t *testing.T) { } case "/Auth/SignAppIn": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") @@ -221,7 +221,7 @@ func TestGetPasswordSafeAuthentication(t *testing.T) { })), response: &entities.SignApinResponse{ UserId: 1, - EmailAddress: "Felipe", + EmailAddress: "test@beyondtrust.com", }, } apiUrl, _ := url.Parse(testConfig.server.URL + "/") diff --git a/api/entities/entities.go b/api/entities/entities.go index 6e8ac66..75ffeab 100644 --- a/api/entities/entities.go +++ b/api/entities/entities.go @@ -160,6 +160,7 @@ type CreateFolderResponse struct { type FolderDetails struct { Name string `json:",omitempty" validate:"required"` Description string `json:",omitempty" validate:"omitempty,max=256"` - ParentId uuid.UUID `json:",omitempty" validate:"required"` + ParentId uuid.UUID `json:",omitempty" validate:"required_if=FolderType FOLDER"` UserGroupId int `json:",omitempty" validate:"omitempty"` + FolderType string `json:",omitempty" validate:"required"` } diff --git a/api/managed_account/managed_account_test.go b/api/managed_account/managed_account_test.go index 6cfdf1d..878c355 100644 --- a/api/managed_account/managed_account_test.go +++ b/api/managed_account/managed_account_test.go @@ -212,7 +212,7 @@ func TestManageAccountFlow(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -289,7 +289,7 @@ func TestManageAccountFlowNotFound(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -366,7 +366,7 @@ func TestSecretGetSecret(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -441,7 +441,7 @@ func TestSecretGetSecrets(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -517,7 +517,7 @@ func TestManagedAccountFlowTechnicalErrorCreatingRequest(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -592,7 +592,7 @@ func TestManagedAccountFlowBusinesslErrorCreatingRequest(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -667,7 +667,7 @@ func TestManagedAccountFlowTechnicalErrorCredentialByRequestId(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -742,7 +742,7 @@ func TestManagedAccountFlowBusinessErrorCredentialByRequestId(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -817,7 +817,7 @@ func TestManagedAccountFlowBusinessErrorAccountRequestCheckIn(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -892,7 +892,7 @@ func TestManagedAccountFlowTechnicalErrorAccountRequestCheckIn(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -967,7 +967,7 @@ func TestManagedAccountFlowGetAccountTechnicalError(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -1023,7 +1023,7 @@ func TestManageAccountFlowGetAccountBadResponse(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } diff --git a/api/secrets/secrets.go b/api/secrets/secrets.go index 9a46ff9..61b85bf 100644 --- a/api/secrets/secrets.go +++ b/api/secrets/secrets.go @@ -376,31 +376,45 @@ func (secretObj *SecretObj) SecretGetFolders(endpointPath string) ([]entities.Fo } -// CreateFolderFlow is responsible for creating folder in Password Safe. +// CreateFolderFlow is responsible for creating folders/safes in Password Safe. func (secretObj *SecretObj) CreateFolderFlow(folderTarget string, folderDetails entities.FolderDetails) (entities.CreateFolderResponse, error) { var folder *entities.FolderResponse var createFolderesponse entities.CreateFolderResponse + var err error - folders, err := secretObj.SecretGetFolders("secrets-safe/folders/") - - if err != nil { - return createFolderesponse, err + if folderDetails.FolderType == "" { + folderDetails.FolderType = "FOLDER" } - for _, v := range folders { - if v.Name == strings.TrimSpace(folderTarget) { - folder = &v - break + secretObj.log.Debug(fmt.Sprintf("Folder Type: %v", folderDetails.FolderType)) + + // if it is folder + if folderDetails.FolderType == "FOLDER" { + if folderTarget == "" { + return createFolderesponse, fmt.Errorf("parent folder name must not be empty") } - } - if folder == nil { - return createFolderesponse, fmt.Errorf("folder %v was not found in folder list", folderTarget) - } + folders, err := secretObj.SecretGetFolders("secrets-safe/folders/") - folderId, _ := uuid.Parse(folder.Id) - folderDetails.ParentId = folderId + if err != nil { + return createFolderesponse, err + } + + for _, v := range folders { + if v.Name == strings.TrimSpace(folderTarget) { + folder = &v + break + } + } + + if folder == nil { + return createFolderesponse, fmt.Errorf("folder %v was not found in folder list", folderTarget) + } + + folderId, _ := uuid.Parse(folder.Id) + folderDetails.ParentId = folderId + } folderDetails, err = utils.ValidateCreateFolderInput(folderDetails) @@ -421,9 +435,17 @@ func (secretObj *SecretObj) CreateFolderFlow(folderTarget string, folderDetails return createFolderesponse, nil } -// SecretCreateFolder calls Secret Safe API Requests enpoint to create folders in Password Safe. +// SecretCreateFolder calls Secret Safe API Requests enpoint to create folders/safes in Password Safe. func (secretObj *SecretObj) SecretCreateFolder(folderDetails entities.FolderDetails) (entities.CreateFolderResponse, error) { + path := "secrets-safe/folders/" + function := "SecretCreateSecret" + + if folderDetails.FolderType == "SAFE" { + path = "secrets-safe/safes/" + function = "SecretCreateSafes" + } + folderCredentialDetailsJson, err := json.Marshal(folderDetails) if err != nil { @@ -435,8 +457,8 @@ func (secretObj *SecretObj) SecretCreateFolder(folderDetails entities.FolderDeta var createSecretResponse entities.CreateFolderResponse - SecretCreateSecreUrl := secretObj.authenticationObj.ApiUrl.JoinPath("secrets-safe/folders/").String() - messageLog := fmt.Sprintf("%v %v", "POST", SecretCreateSecreUrl) + SecretCreateSecretUrl := secretObj.authenticationObj.ApiUrl.JoinPath(path).String() + messageLog := fmt.Sprintf("%v %v", "POST", SecretCreateSecretUrl) secretObj.log.Debug(messageLog) var body io.ReadCloser @@ -444,7 +466,7 @@ func (secretObj *SecretObj) SecretCreateFolder(folderDetails entities.FolderDeta var businessError error technicalError = backoff.Retry(func() error { - body, _, technicalError, businessError = secretObj.authenticationObj.HttpClient.CallSecretSafeAPI(SecretCreateSecreUrl, "POST", *b, "SecretCreateSecret", "", "", "application/json") + body, _, technicalError, businessError = secretObj.authenticationObj.HttpClient.CallSecretSafeAPI(SecretCreateSecretUrl, "POST", *b, function, "", "", "application/json") return technicalError }, secretObj.authenticationObj.ExponentialBackOff) diff --git a/api/secrets/secrets_test.go b/api/secrets/secrets_test.go index bbbe3d9..1abd495 100644 --- a/api/secrets/secrets_test.go +++ b/api/secrets/secrets_test.go @@ -129,7 +129,7 @@ func TestSecretFlow(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -193,7 +193,7 @@ func TestSecretFlow_SecretNotFound(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -326,7 +326,7 @@ func TestSecretFlowTechnicalErrorFile(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -387,7 +387,7 @@ func TestSecretFlowBusinessErrorFile(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -448,7 +448,7 @@ func TestSecretFlowLongSecret(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -510,7 +510,7 @@ func TestSecretFlowTechnicalErrorCredential(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -565,7 +565,7 @@ func TestSecretFlowBusinessErrorCredential(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -620,7 +620,7 @@ func TestSecretFlowBadBody(t *testing.T) { switch r.URL.Path { case "/Auth/SignAppin": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -632,7 +632,7 @@ func TestSecretFlowBadBody(t *testing.T) { } case "/secrets-safe/secrets": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) + _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) if err != nil { t.Error("Test case Failed") } @@ -1163,6 +1163,7 @@ func TestSecretFolderFlow(t *testing.T) { folderDetails := entities.FolderDetails{ Name: "FOLDER_" + uuid.New().String(), Description: "My Folder Description", + FolderType: "FOLDER", } response, err := secretObj.CreateFolderFlow("folder1", folderDetails) @@ -1222,6 +1223,7 @@ func TestSecretFolderFlowBadParentFolder(t *testing.T) { folderDetails := entities.FolderDetails{ Name: "FOLDER_" + uuid.New().String(), Description: "My Folder Description", + FolderType: "FOLDER", } _, err := secretObj.CreateFolderFlow("folder1", folderDetails) @@ -1235,3 +1237,87 @@ func TestSecretFolderFlowBadParentFolder(t *testing.T) { t.Errorf("Test case Failed: %v", err) } } + +func TestSecretFolderFlowEmptyParentFolder(t *testing.T) { + logger, _ := zap.NewDevelopment() + + // create a zap logger wrapper + zapLogger := logging.NewZapLogger(logger) + + httpClientObj, _ := utils.GetHttpClient(5, false, "", "", zapLogger) + + backoffDefinition := backoff.NewExponentialBackOff() + backoffDefinition.MaxElapsedTime = time.Second + + var authenticate, _ = authentication.Authenticate(*httpClientObj, backoffDefinition, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300) + + secretObj, _ := NewSecretObj(*authenticate, zapLogger, 4000) + + folderDetails := entities.FolderDetails{ + Name: "FOLDER_" + uuid.New().String(), + Description: "My Folder Description", + FolderType: "FOLDER", + } + + _, err := secretObj.CreateFolderFlow("", folderDetails) + + expetedErrorMessage := "parent folder name must not be empty" + + if err.Error() != expetedErrorMessage { + t.Errorf("Test case Failed %v, %v", err.Error(), expetedErrorMessage) + } + if err == nil { + t.Errorf("Test case Failed: %v", err) + } +} + +func TestSecretSafeFlow(t *testing.T) { + logger, _ := zap.NewDevelopment() + + // create a zap logger wrapper + zapLogger := logging.NewZapLogger(logger) + + httpClientObj, _ := utils.GetHttpClient(5, false, "", "", zapLogger) + + backoffDefinition := backoff.NewExponentialBackOff() + backoffDefinition.MaxElapsedTime = time.Second + + var authenticate, _ = authentication.Authenticate(*httpClientObj, backoffDefinition, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300) + testConfig := SecretTestConfigStringResponse{ + name: "TestSecretSafeFlow", + server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // Mocking Response according to the endpoint path + if r.URL.Path == "/secrets-safe/safes/" && r.Method == "POST" { + _, err := w.Write([]byte(`{"Id": "5b6fc3fb-fa78-48f9-9796-08dd18b16b5b","Name": "Safe Title", "Description": "Safe Description"}`)) + if err != nil { + t.Error("Test case Failed") + } + } + })), + } + + apiUrl, _ := url.Parse(testConfig.server.URL + "/") + authenticate.ApiUrl = *apiUrl + secretObj, _ := NewSecretObj(*authenticate, zapLogger, 4000) + + folderDetails := entities.FolderDetails{ + Name: "FOLDER_" + uuid.New().String(), + Description: "My Folder Description", + FolderType: "SAFE", + } + + response, err := secretObj.CreateFolderFlow("", folderDetails) + + if response.Name != "Safe Title" { + t.Errorf("Test case Failed %v, %v", response.Name, "Safe Title") + } + + if response.Description != "Safe Description" { + t.Errorf("Test case Failed %v, %v", response.Description, "Safe Description") + } + + if err != nil { + t.Errorf("Test case Failed: %v", err) + } +}