From f878a97c1c9658c492d273b7c0221713fd84fab0 Mon Sep 17 00:00:00 2001 From: Dion Gionet Mallet Date: Thu, 30 May 2024 14:16:46 -0400 Subject: [PATCH 1/2] feat: Add support for RequestOptions in Request method --- dvls.go | 18 ++++++-- entries.go | 44 +++++++++++++++++++ entry_user_credentials.go | 34 ++------------ ..._test.go => entry_user_credentials_test.go | 3 +- 4 files changed, 63 insertions(+), 36 deletions(-) rename entries_test.go => entry_user_credentials_test.go (99%) diff --git a/dvls.go b/dvls.go index 135a529..718543b 100644 --- a/dvls.go +++ b/dvls.go @@ -20,12 +20,16 @@ type RequestError struct { Err error } +type RequestOptions struct { + ContentType string +} + func (e RequestError) Error() string { return fmt.Sprintf("error while submitting request on url %s. error: %s", e.Url, e.Err.Error()) } // Request returns a Response that contains the HTTP response body in bytes, the result code and result message. -func (c *Client) Request(url string, reqMethod string, reqBody io.Reader) (Response, error) { +func (c *Client) Request(url string, reqMethod string, reqBody io.Reader, options ...RequestOptions) (Response, error) { islogged, err := c.isLogged() if err != nil { return Response{}, &RequestError{Err: fmt.Errorf("failed to fetch login status. error: %w", err), Url: url} @@ -37,20 +41,26 @@ func (c *Client) Request(url string, reqMethod string, reqBody io.Reader) (Respo } } - resp, err := c.rawRequest(url, reqMethod, reqBody) + resp, err := c.rawRequest(url, reqMethod, reqBody, options...) if err != nil { return Response{}, err } return resp, nil } -func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader) (Response, error) { +func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader, options ...RequestOptions) (Response, error) { + contentType := "application/json" + + if len(options) > 0 { + contentType = options[0].ContentType + } + req, err := http.NewRequest(reqMethod, url, reqBody) if err != nil { return Response{}, &RequestError{Err: fmt.Errorf("failed to make request. error: %w", err), Url: url} } - req.Header.Add("Content-Type", "application/json") + req.Header.Add("Content-Type", contentType) req.Header.Add("tokenId", c.credential.token) resp, err := c.client.Do(req) diff --git a/entries.go b/entries.go index c9cbcbc..9825e4c 100644 --- a/entries.go +++ b/entries.go @@ -1,5 +1,49 @@ package dvls +import ( + "strconv" + "strings" +) + +const ( + entryEndpoint string = "/api/connections/partial" + entryConnectionsEndpoint string = "/api/connections" +) + type Entries struct { UserCredential *EntryUserCredentialService + Certificate *EntryCertificateService +} + +func keywordsToSlice(kw string) []string { + var spacedTag bool + tags := strings.FieldsFunc(string(kw), func(r rune) bool { + if r == '"' { + spacedTag = !spacedTag + } + return !spacedTag && r == ' ' + }) + for i, v := range tags { + unquotedTag, err := strconv.Unquote(v) + if err != nil { + continue + } + + tags[i] = unquotedTag + } + + return tags +} + +func sliceToKeywords(kw []string) string { + keywords := []string(kw) + for i, v := range keywords { + if strings.Contains(v, " ") { + kw[i] = "\"" + v + "\"" + } + } + + kString := strings.Join(keywords, " ") + + return kString } diff --git a/entry_user_credentials.go b/entry_user_credentials.go index cef755d..a2b2af7 100644 --- a/entry_user_credentials.go +++ b/entry_user_credentials.go @@ -6,8 +6,6 @@ import ( "fmt" "net/http" "net/url" - "strconv" - "strings" ) type EntryUserCredentialService service @@ -56,14 +54,8 @@ func (e EntryUserCredential) MarshalJSON() ([]byte, error) { Keywords string `json:"keywords"` }{} - for i, v := range e.Tags { - if strings.Contains(v, " ") { - e.Tags[i] = "\"" + v + "\"" - } - } - raw.Id = e.ID - raw.Keywords = strings.Join(e.Tags, " ") + raw.Keywords = sliceToKeywords(e.Tags) raw.Description = e.Description raw.RepositoryId = e.VaultId raw.Group = e.EntryFolderPath @@ -116,23 +108,7 @@ func (e *EntryUserCredential) UnmarshalJSON(d []byte) error { e.EntryFolderPath = raw.Data.Group e.VaultId = raw.Data.RepositoryId - var spacedTag bool - tags := strings.FieldsFunc(raw.Data.Keywords, func(r rune) bool { - if r == '"' { - spacedTag = !spacedTag - } - return !spacedTag && r == ' ' - }) - for i, v := range tags { - unquotedTag, err := strconv.Unquote(v) - if err != nil { - continue - } - - tags[i] = unquotedTag - } - - e.Tags = tags + e.Tags = keywordsToSlice(raw.Data.Keywords) return nil } @@ -207,10 +183,6 @@ func (s *EntryUserAuthDetails) UnmarshalJSON(d []byte) error { return nil } -const ( - entryEndpoint string = "/api/connections/partial" -) - // GetUserAuthDetails returns entry with the entry.Credentials.Password field. func (c *EntryUserCredentialService) GetUserAuthDetails(entry EntryUserCredential) (EntryUserCredential, error) { var secret EntryUserAuthDetails @@ -294,7 +266,7 @@ func (c *EntryUserCredentialService) New(entry EntryUserCredential) (EntryUserCr return entry, nil } -// Update updates an EntryUserCredential based on entry. Will replace all other fields wether included or not. +// Update updates an EntryUserCredential based on entry. Will replace all other fields whether included or not. func (c *EntryUserCredentialService) Update(entry EntryUserCredential) (EntryUserCredential, error) { if entry.ConnectionType != ServerConnectionCredential || entry.ConnectionSubType != ServerConnectionSubTypeDefault { return EntryUserCredential{}, fmt.Errorf("unsupported entry type (%s %s). Only %s %s is supported", entry.ConnectionType, entry.ConnectionSubType, ServerConnectionCredential, ServerConnectionSubTypeDefault) diff --git a/entries_test.go b/entry_user_credentials_test.go similarity index 99% rename from entries_test.go rename to entry_user_credentials_test.go index d26ad3b..da46f66 100644 --- a/entries_test.go +++ b/entry_user_credentials_test.go @@ -27,8 +27,9 @@ func Test_Entries(t *testing.T) { testEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails(testEntryUsername, testEntryPassword) t.Run("GetEntry", test_GetEntry) - t.Run("GetEntryCredentialsPassword", test_GetEntryCredentialsPassword) t.Run("NewEntry", test_NewEntry) + t.Run("GetEntryCredentialsPassword", test_GetEntryCredentialsPassword) + t.Run("UpdateEntry", test_UpdateEntry) t.Run("DeleteEntry", test_DeleteEntry) } From b7595b289068fabb30e563bafe53d221eb7f90fa Mon Sep 17 00:00:00 2001 From: Dion Gionet Mallet Date: Thu, 30 May 2024 14:18:34 -0400 Subject: [PATCH 2/2] feat: Added certificate entries --- .github/workflows/test.yml | 11 +- VERSION | 2 +- attachments.go | 89 ++++++++++ authentication.go | 1 + dvls.go | 11 +- dvls_test.go | 2 - dvlstypes.go | 3 +- entry_certificate.go | 313 +++++++++++++++++++++++++++++++++ entry_certificate_test.go | 188 ++++++++++++++++++++ entry_user_credentials_test.go | 63 +++---- 10 files changed, 645 insertions(+), 38 deletions(-) create mode 100644 attachments.go create mode 100644 entry_certificate.go create mode 100644 entry_certificate_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec90864..1126bf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,6 +48,13 @@ jobs: content: ${{ secrets.DEVOLUTIONS_HQ_ISSUING_CA_PEM }} path: ${{ runner.temp }}/certificate.pem + - name: Download test certificate + uses: ./.github/workflows/create-file-from-secret + with: + content: ${{ secrets.TEST_CERTIFICATE }} + path: ${{ runner.temp }}/test.p12 + format: base64 + - name: Update CA store run: | sudo cp ${{ runner.temp }}/certificate.pem /usr/local/share/ca-certificates/certificate.crt @@ -66,7 +73,9 @@ jobs: TEST_USER: ${{ secrets.TEST_USER }} TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }} TEST_INSTANCE: ${{ secrets.TEST_INSTANCE }} - TEST_ENTRY_ID: ${{ secrets.TEST_ENTRY_ID }} + TEST_USER_ENTRY_ID: ${{ secrets.TEST_USER_ENTRY_ID }} + TEST_CERTIFICATE_ENTRY_ID: ${{ secrets.TEST_CERTIFICATE_ENTRY_ID }} + TEST_CERTIFICATE_FILE_PATH: '${{ runner.temp }}/test.p12' TEST_VAULT_ID: ${{ secrets.TEST_VAULT_ID }} with: github_token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }} diff --git a/VERSION b/VERSION index a918a2a..faef31a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.7.0 diff --git a/attachments.go b/attachments.go new file mode 100644 index 0000000..ec09025 --- /dev/null +++ b/attachments.go @@ -0,0 +1,89 @@ +package dvls + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +type EntryAttachment struct { + ID string `json:"id,omitempty"` + IDString string `json:"idString"` + EntryID string `json:"connectionID"` + EntryIDString string `json:"connectionIDString"` + Description string `json:"description"` + FileName string `json:"filename"` + IsPrivate bool `json:"isPrivate"` + Size int `json:"size"` + Title string `json:"title"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (e *EntryAttachment) UnmarshalJSON(d []byte) error { + type rawEntryAttachment EntryAttachment + raw := struct { + Data rawEntryAttachment `json:"data"` + }{} + + err := json.Unmarshal(d, &raw) + if err != nil { + return err + } + + *e = EntryAttachment(raw.Data) + + return nil +} + +const attachmentEndpoint = "/api/attachment" + +func (c *Client) newAttachmentRequest(attachment EntryAttachment) (string, error) { + reqUrl, err := url.JoinPath(c.baseUri, attachmentEndpoint, "save?=&private=false&useSensitiveMode=true") + if err != nil { + return "", fmt.Errorf("failed to build attachment url. error: %w", err) + } + + reqUrl, err = url.QueryUnescape(reqUrl) + if err != nil { + return "", fmt.Errorf("failed to unescape query url. error: %w", err) + } + + entryJson, err := json.Marshal(attachment) + if err != nil { + return "", fmt.Errorf("failed to marshal body. error: %w", err) + } + + resp, err := c.Request(reqUrl, http.MethodPost, bytes.NewBuffer(entryJson)) + if err != nil { + return "", fmt.Errorf("error while submitting entry attachment request. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return "", err + } + + err = json.Unmarshal(resp.Response, &attachment) + if err != nil { + return "", fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + return attachment.ID, nil +} + +func (c *Client) uploadAttachment(fileBytes []byte, attachmentId string) error { + reqUrl, err := url.JoinPath(c.baseUri, attachmentEndpoint, attachmentId, "document") + if err != nil { + return fmt.Errorf("failed to build attachment url. error: %w", err) + } + + contentType := http.DetectContentType(fileBytes) + + resp, err := c.Request(reqUrl, http.MethodPost, bytes.NewBuffer(fileBytes), RequestOptions{ContentType: contentType}) + if err != nil { + return fmt.Errorf("error while uploading entry attachment. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return err + } + + return nil +} diff --git a/authentication.go b/authentication.go index 0ca5f1a..2187a97 100644 --- a/authentication.go +++ b/authentication.go @@ -114,6 +114,7 @@ func NewClient(username string, password string, baseUri string) (Client, error) client.Entries = &Entries{ UserCredential: (*EntryUserCredentialService)(&client.common), + Certificate: (*EntryCertificateService)(&client.common), } client.Vaults = (*Vaults)(&client.common) diff --git a/dvls.go b/dvls.go index 718543b..418f5ce 100644 --- a/dvls.go +++ b/dvls.go @@ -22,6 +22,7 @@ type RequestError struct { type RequestOptions struct { ContentType string + RawBody bool } func (e RequestError) Error() string { @@ -50,9 +51,11 @@ func (c *Client) Request(url string, reqMethod string, reqBody io.Reader, option func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader, options ...RequestOptions) (Response, error) { contentType := "application/json" + var rawBody bool if len(options) > 0 { contentType = options[0].ContentType + rawBody = options[0].RawBody } req, err := http.NewRequest(reqMethod, url, reqBody) @@ -77,9 +80,11 @@ func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader, opt } defer resp.Body.Close() - err = json.Unmarshal(response.Response, &response) - if err != nil { - return response, &RequestError{Err: fmt.Errorf("failed to unmarshal response body. error: %w", err), Url: url} + if !rawBody { + err = json.Unmarshal(response.Response, &response) + if err != nil { + return response, &RequestError{Err: fmt.Errorf("failed to unmarshal response body. error: %w", err), Url: url} + } } return response, nil diff --git a/dvls_test.go b/dvls_test.go index 9785b48..3f5611d 100644 --- a/dvls_test.go +++ b/dvls_test.go @@ -8,12 +8,10 @@ import ( var ( testClient Client - testEntryId string testVaultId string ) func TestMain(m *testing.M) { - testEntryId = os.Getenv("TEST_ENTRY_ID") testVaultId = os.Getenv("TEST_VAULT_ID") err := setupTestClient() diff --git a/dvlstypes.go b/dvlstypes.go index 81f8957..2752a38 100644 --- a/dvlstypes.go +++ b/dvlstypes.go @@ -216,7 +216,8 @@ const ( type ServerConnectionSubType string const ( - ServerConnectionSubTypeDefault ServerConnectionSubType = "Default" + ServerConnectionSubTypeDefault ServerConnectionSubType = "Default" + ServerConnectionSubTypeCertificate ServerConnectionSubType = "Certificate" ) type VaultVisibility int diff --git a/entry_certificate.go b/entry_certificate.go new file mode 100644 index 0000000..3dfb898 --- /dev/null +++ b/entry_certificate.go @@ -0,0 +1,313 @@ +package dvls + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" +) + +type EntryCertificateService service + +// EntryCertificate represents a certificate entry. +type EntryCertificate struct { + ID string + VaultId string + Name string + Description string + EntryFolderPath string + Tags []string + Expiration time.Time + Password string + UseDefaultCredentials bool + + // Can either be a URL or a file name. + CertificateIdentifier string + + data entryCertificateData +} + +type rawEntryCertificate struct { + ID string `json:"id,omitempty"` + VaultId string `json:"repositoryId"` + Name string `json:"name"` + Description string `json:"description"` + EntryFolderPath string `json:"group"` + ModifiedDate *ServerTime `json:"modifiedDate,omitempty"` + Tags string `json:"keywords,omitempty"` + Expiration time.Time `json:"expiration,omitempty"` + + ConnectionType ServerConnectionType `json:"connectionType"` // 45 - document + ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` // "Certificate" + + Data entryCertificateData `json:"data"` +} + +type entryCertificateData struct { + Mode int `json:"dataMode"` // 3 - URL, 2 - File + FileSize int `json:"documentSize"` // 0 on mode 3 + FileName string `json:"fileName"` + Type any `json:"type"` // "Certificate" + UseDefaultCredentials bool `json:"useWebDefaultCredentials"` + Password struct { + HasSensitiveData bool `json:"hasSensitiveData"` + SensitiveData string `json:"sensitiveData"` + } `json:"password"` +} + +// MarshalJSON implements the json.Marshaler interface. +func (e EntryCertificate) MarshalJSON() ([]byte, error) { + raw := rawEntryCertificate{ + ID: e.ID, + VaultId: e.VaultId, + Name: e.Name, + Description: e.Description, + EntryFolderPath: e.EntryFolderPath, + Tags: sliceToKeywords(e.Tags), + Expiration: e.Expiration, + Data: entryCertificateData{ + Mode: e.data.Mode, + FileName: e.CertificateIdentifier, + Type: "Certificate", + UseDefaultCredentials: e.UseDefaultCredentials, + FileSize: e.data.FileSize, + Password: struct { + HasSensitiveData bool `json:"hasSensitiveData"` + SensitiveData string `json:"sensitiveData"` + }{ + HasSensitiveData: true, + SensitiveData: e.Password, + }, + }, + } + + raw.ConnectionType = ServerConnectionDocument + raw.ConnectionSubType = ServerConnectionSubTypeCertificate + + entryJson, err := json.Marshal(raw) + if err != nil { + return nil, err + } + + return entryJson, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (e *EntryCertificate) UnmarshalJSON(d []byte) error { + rawString := struct { + Data string + }{} + err := json.Unmarshal(d, &rawString) + if err != nil && !strings.Contains(err.Error(), "cannot unmarshal object into Go struct") { + return err + } + + raw := struct { + Data rawEntryCertificate + }{} + + if rawString.Data != "" { + err = json.Unmarshal([]byte(rawString.Data), &raw.Data) + if err != nil { + return err + } + } else { + err = json.Unmarshal(d, &raw) + if err != nil { + return err + } + } + + e.ID = raw.Data.ID + e.VaultId = raw.Data.VaultId + e.Name = raw.Data.Name + e.Description = raw.Data.Description + e.EntryFolderPath = raw.Data.EntryFolderPath + e.Tags = keywordsToSlice(raw.Data.Tags) + e.Expiration = raw.Data.Expiration + + e.data.Mode = raw.Data.Data.Mode + e.CertificateIdentifier = raw.Data.Data.FileName + e.UseDefaultCredentials = raw.Data.Data.UseDefaultCredentials + e.Password = raw.Data.Data.Password.SensitiveData + e.data.FileSize = raw.Data.Data.FileSize + + return nil +} + +// Get returns a single Certificate specified by entryId. +func (c *EntryCertificateService) Get(entryId string) (EntryCertificate, error) { + var entry EntryCertificate + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, entryId) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodGet, nil) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while fetching entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryCertificate{}, err + } + + err = json.Unmarshal(resp.Response, &entry) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + return entry, nil +} + +// GetFileContent returns the content of the file specified by entryId. +func (c *EntryCertificateService) GetFileContent(entryId string) ([]byte, error) { + reqUrl, err := url.JoinPath(c.client.baseUri, entryConnectionsEndpoint, entryId, "document") + if err != nil { + return nil, fmt.Errorf("failed to build entry url. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodGet, nil, RequestOptions{RawBody: true}) + if err != nil { + return nil, fmt.Errorf("error while fetching entry content. error: %w", err) + } + + return resp.Response, nil +} + +// GetPassword returns the password of the entry specified by entry. +func (c *EntryCertificateService) GetPassword(entry EntryCertificate) (EntryCertificate, error) { + var entryPassword EntryCertificate + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, entry.ID, "/sensitive-data") + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodPost, nil) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while fetching sensitive data. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryCertificate{}, err + } + + err = json.Unmarshal(resp.Response, &entryPassword) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + entry.Password = entryPassword.Password + + return entry, nil +} + +// NewURL creates a new EntryCertificate based on entry. Will use the url as the file content. +func (c *EntryCertificateService) NewURL(entry EntryCertificate) (EntryCertificate, error) { + return c.new(entry, nil) +} + +// NewFile creates a new EntryCertificate based on entry. Will upload the file content to the DVLS server. +func (c *EntryCertificateService) NewFile(entry EntryCertificate, content []byte) (EntryCertificate, error) { + return c.new(entry, content) +} + +func (c *EntryCertificateService) new(entry EntryCertificate, content []byte) (EntryCertificate, error) { + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, "save") + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + entry.data.Mode = 3 + + if content != nil { + entry.data.Mode = 2 + entry.data.FileSize = len(content) + } + + entryJson, err := json.Marshal(entry) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to marshal body. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodPost, bytes.NewBuffer(entryJson)) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while creating entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryCertificate{}, err + } + + err = json.Unmarshal(resp.Response, &entry) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + if content != nil { + attachment := EntryAttachment{ + EntryID: entry.ID, + FileName: entry.CertificateIdentifier, + Size: len(content), + IsPrivate: true, + } + + entryAttachment, err := c.client.newAttachmentRequest(attachment) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while creating entry attachment. error: %w", err) + } + + err = c.client.uploadAttachment(content, entryAttachment) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while uploading attachment. error: %w", err) + } + } + + return entry, nil +} + +// Update updates an EntryCertificate based on entry. Will replace all other fields whether included or not. +func (c *EntryCertificateService) Update(entry EntryCertificate) (EntryCertificate, error) { + _, err := c.Get(entry.ID) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while fetching entry. error: %w", err) + } + + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, "save") + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + entryJson, err := json.Marshal(entry) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to marshal body. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodPut, bytes.NewBuffer(entryJson)) + if err != nil { + return EntryCertificate{}, fmt.Errorf("error while creating entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryCertificate{}, err + } + + err = json.Unmarshal(resp.Response, &entry) + if err != nil { + return EntryCertificate{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + return entry, nil +} + +// Delete deletes an EntryCertificate based on entryId. +func (c *EntryCertificateService) Delete(entryId string) error { + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, entryId) + if err != nil { + return fmt.Errorf("failed to delete entry url. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodDelete, nil) + if err != nil { + return fmt.Errorf("error while deleting entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return err + } + + return nil +} diff --git a/entry_certificate_test.go b/entry_certificate_test.go new file mode 100644 index 0000000..f63a7dd --- /dev/null +++ b/entry_certificate_test.go @@ -0,0 +1,188 @@ +package dvls + +import ( + "io" + "os" + "reflect" + "testing" + "time" +) + +var ( + testCertificateFilePath string + testCertificateEntryId string + testNewCertificateEntryFile EntryCertificate + testNewCertificateEntryURL EntryCertificate + testCertificateEntry EntryCertificate = EntryCertificate{ + VaultId: testVaultId, + Name: "TestK8sCertificate", + Password: testEntryPassword, + Tags: []string{"test", "k8s"}, + CertificateIdentifier: "test", + } +) + +func Test_EntryCertificate(t *testing.T) { + testCertificateFilePath = os.Getenv("TEST_CERTIFICATE_FILE_PATH") + testCertificateEntryId = os.Getenv("TEST_CERTIFICATE_ENTRY_ID") + testCertificateEntry.ID = testCertificateEntryId + testCertificateEntry.VaultId = testVaultId + location, err := time.LoadLocation("America/Montreal") + if err != nil { + t.Fatal(err) + } + expiration := time.Date(2099, 1, 1, 0, 0, 0, 0, location) + testCertificateEntry.Expiration = expiration + + t.Run("GetEntry", test_GetCertificateEntry) + t.Run("NewCertificateFile", test_NewCertificateEntryFile) + t.Run("NewCertificateURL", test_NewCertificateEntryURL) + t.Run("UpdateEntry", test_UpdateCertificateEntry) + t.Run("DeleteEntry", test_DeleteCertificateEntry) +} + +func test_GetCertificateEntry(t *testing.T) { + testGetEntry := testCertificateEntry + + entry, err := testClient.Entries.Certificate.Get(testGetEntry.ID) + if err != nil { + t.Fatal(err) + } + + entry, err = testClient.Entries.Certificate.GetPassword(entry) + if err != nil { + t.Fatal(err) + } + + entry.data = testGetEntry.data + + if !entry.Expiration.Equal(testGetEntry.Expiration) { + t.Fatalf("fetched entry expiration did not match test entry. Expected %v, got %v", testGetEntry.Expiration, entry.Expiration) + } + + entry.Expiration = testGetEntry.Expiration + + if !reflect.DeepEqual(entry, testGetEntry) { + t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testGetEntry, entry) + } +} + +func test_NewCertificateEntryFile(t *testing.T) { + entry := testCertificateEntry + entry.ID = "" + file, err := os.Open(testCertificateFilePath) + if err != nil { + t.Fatal(err) + } + + fileBytes, err := io.ReadAll(file) + if err != nil { + t.Fatal("failed read file. error: %w", err) + } + + stat, err := file.Stat() + if err != nil { + t.Fatal("failed read file. error: %w", err) + } + + entry.CertificateIdentifier = stat.Name() + entry.UseDefaultCredentials = true + + newEntry, err := testClient.Entries.Certificate.NewFile(entry, fileBytes) + if err != nil { + t.Fatal(err) + } + + returnedFileBytes, err := testClient.Entries.Certificate.GetFileContent(newEntry.ID) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(fileBytes, returnedFileBytes) { + t.Fatalf("fetched file content did not match test file content. Expected %#v, got %#v", fileBytes, returnedFileBytes) + } + + entry.ID = newEntry.ID + entry.data = newEntry.data + newEntry, err = testClient.Entries.Certificate.GetPassword(newEntry) + if err != nil { + t.Fatal(err) + } + + testNewCertificateEntryFile = newEntry + + if !entry.Expiration.Equal(newEntry.Expiration) { + t.Fatalf("fetched entry expiration did not match test entry. Expected %v, got %v", entry.Expiration, newEntry.Expiration) + } + + entry.Expiration = newEntry.Expiration + + if !reflect.DeepEqual(entry, newEntry) { + t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", entry, newEntry) + } +} + +func test_NewCertificateEntryURL(t *testing.T) { + entry := testCertificateEntry + entry.ID = "" + entry.CertificateIdentifier = "https://devolutions.net/" + + newEntry, err := testClient.Entries.Certificate.NewURL(entry) + if err != nil { + t.Fatal(err) + } + + entry.ID = newEntry.ID + entry.data = newEntry.data + newEntry, err = testClient.Entries.Certificate.GetPassword(newEntry) + if err != nil { + t.Fatal(err) + } + + testNewCertificateEntryURL = newEntry + + if !entry.Expiration.Equal(newEntry.Expiration) { + t.Fatalf("fetched entry expiration did not match test entry. Expected %v, got %v", entry.Expiration, newEntry.Expiration) + } + + entry.Expiration = newEntry.Expiration + + if !reflect.DeepEqual(entry, newEntry) { + t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", entry, newEntry) + } +} + +func test_UpdateCertificateEntry(t *testing.T) { + testUpdatedEntry := testNewCertificateEntryURL + testUpdatedEntry.Name = "TestK8sUpdatedEntry" + + entry, err := testClient.Entries.Certificate.Update(testUpdatedEntry) + if err != nil { + t.Fatal(err) + } + + entry, err = testClient.Entries.Certificate.GetPassword(entry) + if err != nil { + t.Fatal(err) + } + + entry.data = testUpdatedEntry.data + + if !reflect.DeepEqual(entry, testUpdatedEntry) { + t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testUpdatedEntry, entry) + } + + testNewCertificateEntryURL = entry +} + +func test_DeleteCertificateEntry(t *testing.T) { + err := testClient.Entries.Certificate.Delete(testNewCertificateEntryURL.ID) + if err != nil { + t.Fatal(err) + } + + err = testClient.Entries.Certificate.Delete(testNewCertificateEntryFile.ID) + if err != nil { + t.Fatal(err) + } +} diff --git a/entry_user_credentials_test.go b/entry_user_credentials_test.go index da46f66..49db644 100644 --- a/entry_user_credentials_test.go +++ b/entry_user_credentials_test.go @@ -1,13 +1,15 @@ package dvls import ( + "os" "reflect" "testing" ) var ( - testNewEntry EntryUserCredential - testEntry EntryUserCredential = EntryUserCredential{ + testUserEntryId string + testNewUserEntry EntryUserCredential + testUserEntry EntryUserCredential = EntryUserCredential{ Description: "Test description", EntryName: "TestK8sSecret", ConnectionType: ServerConnectionCredential, @@ -21,24 +23,25 @@ const ( testEntryPassword string = "TestK8sPassword" ) -func Test_Entries(t *testing.T) { - testEntry.ID = testEntryId - testEntry.VaultId = testVaultId - testEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails(testEntryUsername, testEntryPassword) +func Test_EntryUserCredentials(t *testing.T) { + testUserEntryId = os.Getenv("TEST_USER_ENTRY_ID") + testUserEntry.ID = testUserEntryId + testUserEntry.VaultId = testVaultId + testUserEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails(testEntryUsername, testEntryPassword) - t.Run("GetEntry", test_GetEntry) - t.Run("NewEntry", test_NewEntry) + t.Run("GetEntry", test_GetUserEntry) + t.Run("NewEntry", test_NewUserEntry) t.Run("GetEntryCredentialsPassword", test_GetEntryCredentialsPassword) - t.Run("UpdateEntry", test_UpdateEntry) - t.Run("DeleteEntry", test_DeleteEntry) + t.Run("UpdateEntry", test_UpdateUserEntry) + t.Run("DeleteEntry", test_DeleteUserEntry) } -func test_GetEntry(t *testing.T) { - testGetEntry := testEntry +func test_GetUserEntry(t *testing.T) { + testGetEntry := testUserEntry testGetEntry.Credentials = EntryUserAuthDetails{ - Username: testEntry.Credentials.Username, + Username: testUserEntry.Credentials.Username, } entry, err := testClient.Entries.UserCredential.Get(testGetEntry.ID) if err != nil { @@ -54,8 +57,8 @@ func test_GetEntry(t *testing.T) { } func test_GetEntryCredentialsPassword(t *testing.T) { - testSecret := testEntry.Credentials - secret, err := testClient.Entries.UserCredential.GetUserAuthDetails(testEntry) + testSecret := testUserEntry.Credentials + secret, err := testClient.Entries.UserCredential.GetUserAuthDetails(testUserEntry) if err != nil { t.Fatal(err) } @@ -65,29 +68,29 @@ func test_GetEntryCredentialsPassword(t *testing.T) { } } -func test_NewEntry(t *testing.T) { - testNewEntry = testEntry +func test_NewUserEntry(t *testing.T) { + testNewUserEntry = testUserEntry - testNewEntry.EntryName = "TestK8sNewEntry" + testNewUserEntry.EntryName = "TestK8sNewEntry" - entry, err := testClient.Entries.UserCredential.New(testNewEntry) + entry, err := testClient.Entries.UserCredential.New(testNewUserEntry) if err != nil { t.Fatal(err) } - testNewEntry.ID = entry.ID - testNewEntry.ModifiedDate = entry.ModifiedDate - testNewEntry.Tags = entry.Tags + testNewUserEntry.ID = entry.ID + testNewUserEntry.ModifiedDate = entry.ModifiedDate + testNewUserEntry.Tags = entry.Tags - if !reflect.DeepEqual(entry, testNewEntry) { - t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testNewEntry, entry) + if !reflect.DeepEqual(entry, testNewUserEntry) { + t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testNewUserEntry, entry) } - testNewEntry = entry + testNewUserEntry = entry } -func test_UpdateEntry(t *testing.T) { - testUpdatedEntry := testNewEntry +func test_UpdateUserEntry(t *testing.T) { + testUpdatedEntry := testNewUserEntry testUpdatedEntry.EntryName = "TestK8sUpdatedEntry" testUpdatedEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails("TestK8sUpdatedUser", "TestK8sUpdatedPassword") @@ -103,11 +106,11 @@ func test_UpdateEntry(t *testing.T) { t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testUpdatedEntry, entry) } - testNewEntry = entry + testNewUserEntry = entry } -func test_DeleteEntry(t *testing.T) { - err := testClient.Entries.UserCredential.Delete(testNewEntry.ID) +func test_DeleteUserEntry(t *testing.T) { + err := testClient.Entries.UserCredential.Delete(testNewUserEntry.ID) if err != nil { t.Fatal(err) }