Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add creating folders feature in terraform provider #165

Merged
merged 2 commits into from
Nov 29, 2024
Merged
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
16 changes: 16 additions & 0 deletions TestClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,22 @@ 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 File secret: %v", createdSecret.Title))

folderDetails := entities.FolderDetails{
Name: "FOLDER_" + uuid.New().String(),
Description: "My Folder Secret Description",
}

// creating a folder secret in folder1 folder.
createdFolder, err := secretObj.CreateFolderFlow("folder1", folderDetails)

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 Folder: %v", createdFolder.Name))

// signing out
_ = authenticate.SignOut()

Expand Down
2 changes: 1 addition & 1 deletion api/authentication/authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func TestSignAppinWithApiKey(t *testing.T) {

var authenticate, _ = AuthenticateUsingApiKey(*httpClientObj, backoffDefinition, "https://fake.api.com:443/BeyondTrust/api/public/v3/", zapLogger, 300, "fake_api_key_")
testConfig := UserTestConfig{
name: "TestSignAppin",
name: "TestSignAppinWithApiKey",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`))
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions api/entities/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,18 @@ type UrlDetails struct {
CredentialId uuid.UUID `json:",omitempty" validate:"omitempty,uuid"`
Url string `json:",omitempty" validate:"required,max=2048,url"`
}

type CreateFolderResponse struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ParentId uuid.UUID `json:"parentId"`
UserGroupId int `json:"userGroupId"`
}

type FolderDetails struct {
Name string `json:",omitempty" validate:"required"`
Description string `json:",omitempty" validate:"omitempty,max=256"`
ParentId uuid.UUID `json:",omitempty" validate:"required"`
UserGroupId int `json:",omitempty" validate:"omitempty"`
}
2 changes: 1 addition & 1 deletion api/managed_account/managed_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func TestManageAccountFlowNotFound(t *testing.T) {

var authenticate, _ = authentication.Authenticate(*httpClientObj, backoffDefinition, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300)
testConfig := ManagedAccountTestConfigStringResponse{
name: "TestManageAccountFlowFailedManagedAccounts",
name: "TestManageAccountFlowNotFound",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down
99 changes: 99 additions & 0 deletions api/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/BeyondTrust/go-client-library-passwordsafe/api/entities"
"github.com/BeyondTrust/go-client-library-passwordsafe/api/logging"
"github.com/BeyondTrust/go-client-library-passwordsafe/api/utils"
"github.com/google/uuid"

backoff "github.com/cenkalti/backoff/v4"
)
Expand Down Expand Up @@ -370,3 +371,101 @@ func (secretObj *SecretObj) SecretGetFolders(endpointPath string) ([]entities.Fo
return foldersObj, nil

}

// CreateFolderFlow is responsible for creating folder in Password Safe.
func (secretObj *SecretObj) CreateFolderFlow(folderTarget string, folderDetails entities.FolderDetails) (entities.CreateFolderResponse, error) {

var folder *entities.FolderResponse
var createFolderesponse entities.CreateFolderResponse

folders, err := secretObj.SecretGetFolders("secrets-safe/folders/")

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)

if err != nil {
return createFolderesponse, err
}

if err != nil {
return entities.CreateFolderResponse{}, err
}

createFolderesponse, err = secretObj.SecretCreateFolder(folderDetails)

if err != nil {
return createFolderesponse, err
}

return createFolderesponse, nil
}

// SecretCreateFolder calls Secret Safe API Requests enpoint to create folders in Password Safe.
func (secretObj *SecretObj) SecretCreateFolder(folderDetails entities.FolderDetails) (entities.CreateFolderResponse, error) {

folderCredentialDetailsJson, err := json.Marshal(folderDetails)

if err != nil {
return entities.CreateFolderResponse{}, err
}

payload := string(folderCredentialDetailsJson)
b := bytes.NewBufferString(payload)

var createSecretResponse entities.CreateFolderResponse

SecretCreateSecreUrl := secretObj.authenticationObj.ApiUrl.JoinPath("secrets-safe/folders/").String()
messageLog := fmt.Sprintf("%v %v", "POST", SecretCreateSecreUrl)
secretObj.log.Debug(messageLog)

var body io.ReadCloser
var technicalError error
var businessError error

technicalError = backoff.Retry(func() error {
body, _, technicalError, businessError = secretObj.authenticationObj.HttpClient.CallSecretSafeAPI(SecretCreateSecreUrl, "POST", *b, "SecretCreateSecret", "", "", "application/json")
return technicalError
}, secretObj.authenticationObj.ExponentialBackOff)

if technicalError != nil {
return entities.CreateFolderResponse{}, technicalError
}

if businessError != nil {
return entities.CreateFolderResponse{}, businessError
}

defer body.Close()
bodyBytes, err := io.ReadAll(body)

if err != nil {
return entities.CreateFolderResponse{}, err
}

err = json.Unmarshal([]byte(bodyBytes), &createSecretResponse)

if err != nil {
secretObj.log.Error(err.Error())
return entities.CreateFolderResponse{}, err
}

return createSecretResponse, nil

}
126 changes: 119 additions & 7 deletions api/secrets/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func TestSecretFlow_SecretNotFound(t *testing.T) {

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: "TestSecretFlow",
name: "TestSecretFlow_SecretNotFound",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -668,7 +668,7 @@ func TestSecretCreateTextSecretFlow(t *testing.T) {

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: "TestSecretCreateSecretFlowFolderNotFound",
name: "TestSecretCreateTextSecretFlow",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -740,7 +740,7 @@ func TestSecretCreateCredentialSecretFlow(t *testing.T) {

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: "TestSecretCreateSecretFlowFolderNotFound",
name: "TestSecretCreateCredentialSecretFlow",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -812,7 +812,7 @@ func TestSecretCreateFileSecretFlow(t *testing.T) {

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: "TestSecretCreateSecretFlowFolderNotFound",
name: "TestSecretCreateFileSecretFlow",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -884,7 +884,7 @@ func TestSecretCreateFileSecretFlowError(t *testing.T) {

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: "TestSecretCreateSecretFlowFolderNotFound",
name: "TestSecretCreateFileSecretFlowError",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -954,7 +954,7 @@ func TestSecretCreateBadInput(t *testing.T) {

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: "TestSecretCreateSecretFlowFolderNotFound",
name: "TestSecretCreateBadInput",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -1078,7 +1078,7 @@ func TestSecretCreateSecretFlowEmptyFolderList(t *testing.T) {

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: "TestSecretCreateSecretFlowFolderNotFound",
name: "TestSecretCreateSecretFlowEmptyFolderList",
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mocking Response according to the endpoint path
switch r.URL.Path {
Expand Down Expand Up @@ -1123,3 +1123,115 @@ func TestSecretCreateSecretFlowEmptyFolderList(t *testing.T) {
}

}

func TestSecretFolderFlow(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: "TestSecretFolderFlow",
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/folders/" && r.Method == "GET" {
_, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}, {"Id": "a4af73dc-4e89-41ec-eb9a-08dcf22d3aba","Name": "folder2"}]`))
if err != nil {
t.Error("Test case Failed")
}
}
if r.URL.Path == "/secrets-safe/folders/" && r.Method == "POST" {
_, err := w.Write([]byte(`{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "Folder Title", "Description": "Folder 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",
}

response, err := secretObj.CreateFolderFlow("folder1", folderDetails)

if response.Name != "Folder Title" {
t.Errorf("Test case Failed %v, %v", response.Name, "Folder Title")
}

if response.Description != "Folder Description" {
t.Errorf("Test case Failed %v, %v", response.Description, "Folder Description")
}

if err != nil {
t.Errorf("Test case Failed: %v", err)
}
}

func TestSecretFolderFlowBadParentFolder(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: "TestSecretFolderFlowBadParentFolder",
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/folders/" && r.Method == "GET" {
_, err := w.Write([]byte(`[{"Id": "cb871861-8b40-4556-820c-1ca6d522adfa","Name": "folder1"}, {"Id": "a4af73dc-4e89-41ec-eb9a-08dcf22d3aba","Name": "folder2"}]`))
if err != nil {
t.Error("Test case Failed")
}
} else if r.URL.Path == "/secrets-safe/folders/" && r.Method == "POST" {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte(`{"error": "InvalidFolderName"}`))
if err != nil {
t.Error("Test case Failed")
}
} else {
http.NotFound(w, r)
}

})),
}

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",
}

_, err := secretObj.CreateFolderFlow("folder1", folderDetails)

expetedErrorMessage := `error - status code: 400 - {"error": "InvalidFolderName"}`

if err.Error() != expetedErrorMessage {
t.Errorf("Test case Failed %v, %v", err.Error(), expetedErrorMessage)
}
if err == nil {
t.Errorf("Test case Failed: %v", err)
}
}
12 changes: 12 additions & 0 deletions api/utils/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,15 @@ func formatErrorMessage(err validator.FieldError) string {
return fmt.Sprintf("Error en el campo '%s': %s.", err.Field(), err.Tag())
}
}

// ValidateCreateFolderInput responsible for validating folder input.
func ValidateCreateFolderInput(folderDetails entities.FolderDetails) (entities.FolderDetails, error) {
validate := validator.New()
err := validate.Struct(folderDetails)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
return folderDetails, errors.New(formatErrorMessage(err))
}
}
return folderDetails, nil
}
Loading