From 3168fa41064b9e1344cc2041a613f719a967634d Mon Sep 17 00:00:00 2001 From: Dion Gionet Mallet Date: Thu, 23 May 2024 17:03:59 -0400 Subject: [PATCH] chore!: added different client services to improve usage --- authentication.go | 27 ++- dvls.go | 2 +- entries.go | 355 +------------------------------------ entries_test.go | 26 +-- entry_user_credentials.go | 358 ++++++++++++++++++++++++++++++++++++++ server.go | 6 +- vaults.go | 46 ++--- vaults_test.go | 14 +- 8 files changed, 432 insertions(+), 402 deletions(-) create mode 100644 entry_user_credentials.go diff --git a/authentication.go b/authentication.go index 9d1868d..0ca5f1a 100644 --- a/authentication.go +++ b/authentication.go @@ -14,8 +14,16 @@ type Client struct { client *http.Client baseUri string credential credentials - ClientUser User + + common service + + Entries *Entries + Vaults *Vaults +} + +type service struct { + client *Client } type credentials struct { @@ -102,6 +110,13 @@ func NewClient(username string, password string, baseUri string) (Client, error) client.ClientUser = user + client.common.client = &client + + client.Entries = &Entries{ + UserCredential: (*EntryUserCredentialService)(&client.common), + } + client.Vaults = (*Vaults)(&client.common) + return client, nil } @@ -115,7 +130,7 @@ func (c *Client) login() (User, error) { } loginJson, err := json.Marshal(loginBody) if err != nil { - return User{}, fmt.Errorf("failed to marshall login body. error: %w", err) + return User{}, fmt.Errorf("failed to marshal login body. error: %w", err) } reqUrl, err := url.JoinPath(c.baseUri, loginEndpoint) @@ -131,7 +146,7 @@ func (c *Client) login() (User, error) { var loginResponse loginResponse err = json.Unmarshal(resp.Response, &loginResponse) if err != nil { - return User{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) + return User{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) } if loginResponse.Data.Result != ServerLoginSuccess { return User{}, fmt.Errorf("failed to refresh token (%s) : %s", loginResponse.Data.Result, loginResponse.Data.Message) @@ -140,7 +155,7 @@ func (c *Client) login() (User, error) { var user User err = json.Unmarshal(resp.Response, &user) if err != nil { - return User{}, fmt.Errorf("failed to unmarshall user body. error: %w", err) + return User{}, fmt.Errorf("failed to unmarshal user body. error: %w", err) } c.credential.token = loginResponse.Data.TokenId @@ -158,7 +173,7 @@ func (c *Client) refreshToken() error { } loginJson, err := json.Marshal(loginBody) if err != nil { - return fmt.Errorf("failed to marshall login body. error: %w", err) + return fmt.Errorf("failed to marshal login body. error: %w", err) } reqUrl, err := url.JoinPath(c.baseUri, loginEndpoint) @@ -174,7 +189,7 @@ func (c *Client) refreshToken() error { var loginResponse loginResponse err = json.Unmarshal(resp.Response, &loginResponse) if err != nil { - return fmt.Errorf("failed to unmarshall response body. error: %w", err) + return fmt.Errorf("failed to unmarshal response body. error: %w", err) } if loginResponse.Data.Result != ServerLoginSuccess { return fmt.Errorf("failed to refresh token (%s) : %s", loginResponse.Data.Result, loginResponse.Data.Message) diff --git a/dvls.go b/dvls.go index 2483271..135a529 100644 --- a/dvls.go +++ b/dvls.go @@ -69,7 +69,7 @@ func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader) (Re err = json.Unmarshal(response.Response, &response) if err != nil { - return response, &RequestError{Err: fmt.Errorf("failed to unmarshall response body. error: %w", err), Url: url} + return response, &RequestError{Err: fmt.Errorf("failed to unmarshal response body. error: %w", err), Url: url} } return response, nil diff --git a/entries.go b/entries.go index 05aa9e4..c9cbcbc 100644 --- a/entries.go +++ b/entries.go @@ -1,356 +1,5 @@ package dvls -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" -) - -// Entry represents a DVLS entry/connection. -type Entry struct { - ID string `json:"id,omitempty"` - VaultId string `json:"repositoryId"` - EntryName string `json:"name"` - Description string `json:"description"` - EntryFolderPath string `json:"group"` - ModifiedDate *ServerTime `json:"modifiedDate,omitempty"` - ConnectionType ServerConnectionType `json:"connectionType"` - ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` - Tags []string `json:"keywords,omitempty"` - - Credentials EntryCredentials `json:"data,omitempty"` -} - -// MarshalJSON implements the json.Marshaler interface. -func (e Entry) MarshalJSON() ([]byte, error) { - raw := struct { - Id string `json:"id,omitempty"` - RepositoryId string `json:"repositoryId"` - Name string `json:"name"` - Description string `json:"description"` - Events struct { - OpenCommentPrompt bool `json:"openCommentPrompt"` - CredentialViewedPrompt bool `json:"credentialViewedPrompt"` - TicketNumberIsRequiredOnCredentialViewed bool `json:"ticketNumberIsRequiredOnCredentialViewed"` - TicketNumberIsRequiredOnClose bool `json:"ticketNumberIsRequiredOnClose"` - CredentialViewedCommentIsRequired bool `json:"credentialViewedCommentIsRequired"` - TicketNumberIsRequiredOnOpen bool `json:"ticketNumberIsRequiredOnOpen"` - CloseCommentIsRequired bool `json:"closeCommentIsRequired"` - OpenCommentPromptOnBrowserExtensionLink bool `json:"openCommentPromptOnBrowserExtensionLink"` - CloseCommentPrompt bool `json:"closeCommentPrompt"` - OpenCommentIsRequired bool `json:"openCommentIsRequired"` - WarnIfAlreadyOpened bool `json:"warnIfAlreadyOpened"` - } `json:"events"` - Data string `json:"data"` - Expiration string `json:"expiration"` - CheckOutMode int `json:"checkOutMode"` - Group string `json:"group"` - ConnectionType ServerConnectionType `json:"connectionType"` - ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` - 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.Description = e.Description - raw.RepositoryId = e.VaultId - raw.Group = e.EntryFolderPath - raw.ConnectionSubType = e.ConnectionSubType - raw.ConnectionType = e.ConnectionType - raw.Name = e.EntryName - sensitiveJson, err := json.Marshal(e.Credentials) - if err != nil { - return nil, fmt.Errorf("failed to marshall sensitive data. error: %w", err) - } - - raw.Data = string(sensitiveJson) - - entryJson, err := json.Marshal(raw) - if err != nil { - return nil, err - } - - return entryJson, nil -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (e *Entry) UnmarshalJSON(d []byte) error { - raw := struct { - Data struct { - ID string - Description string - Name string - Group string - Username string - ModifiedDate *ServerTime - Keywords string - RepositoryId string - ConnectionType ServerConnectionType - ConnectionSubType ServerConnectionSubType - } - }{} - err := json.Unmarshal(d, &raw) - if err != nil { - return err - } - - e.ID = raw.Data.ID - e.EntryName = raw.Data.Name - e.ConnectionType = raw.Data.ConnectionType - e.ConnectionSubType = raw.Data.ConnectionSubType - e.ModifiedDate = raw.Data.ModifiedDate - e.Credentials.Username = raw.Data.Username - e.Description = raw.Data.Description - 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 - - return nil -} - -// EntryCredentials represents an Entry Credentials fields. -type EntryCredentials struct { - Username string - Password *string -} - -// MarshalJSON implements the json.Marshaler interface. -func (s EntryCredentials) MarshalJSON() ([]byte, error) { - raw := struct { - AllowClipboard bool `json:"allowClipboard"` - CredentialConnectionId string `json:"credentialConnectionId"` - PamCredentialId string `json:"pamCredentialId"` - PamCredentialName string `json:"pamCredentialName"` - CredentialMode int `json:"credentialMode"` - Credentials *string `json:"credentials"` - Domain string `json:"domain"` - MnemonicPassword string `json:"mnemonicPassword"` - PasswordItem struct { - HasSensitiveData bool `json:"hasSensitiveData"` - SensitiveData string `json:"sensitiveData"` - } `json:"passwordItem"` - PromptForPassword bool `json:"promptForPassword"` - UserName string `json:"userName"` - }{} - - if s.Password != nil { - raw.PasswordItem.HasSensitiveData = true - raw.PasswordItem.SensitiveData = *s.Password - } - raw.UserName = s.Username - - secretJson, err := json.Marshal(raw) - if err != nil { - return nil, err - } - - return secretJson, nil -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (s *EntryCredentials) UnmarshalJSON(d []byte) error { - raw := struct { - Data string - }{} - err := json.Unmarshal(d, &raw) - if err != nil { - return err - } - - if raw.Data != "" { - newRaw := struct { - Data struct { - Credentials struct { - Username string - Password string - } - } - }{} - err = json.Unmarshal([]byte(raw.Data), &newRaw) - if err != nil { - return err - } - - s.Username = newRaw.Data.Credentials.Username - s.Password = &newRaw.Data.Credentials.Password - } - - return nil -} - -const ( - entryEndpoint string = "/api/connections/partial" -) - -// GetEntryCredentialsPassword returns entry with the entry.Credentials.Password field. -func (c *Client) GetEntryCredentialsPassword(entry Entry) (Entry, error) { - var secret EntryCredentials - reqUrl, err := url.JoinPath(c.baseUri, entryEndpoint, entry.ID, "/sensitive-data") - if err != nil { - return Entry{}, fmt.Errorf("failed to build entry url. error: %w", err) - } - - resp, err := c.Request(reqUrl, http.MethodPost, nil) - if err != nil { - return Entry{}, fmt.Errorf("error while fetching sensitive data. error: %w", err) - } else if err = resp.CheckRespSaveResult(); err != nil { - return Entry{}, err - } - - err = json.Unmarshal(resp.Response, &secret) - if err != nil { - return Entry{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) - } - - entry.Credentials = secret - - return entry, nil -} - -// GetEntry returns a single Entry specified by entryId. Call GetEntryCredentialsPassword with -// the returned Entry to fetch the password. -func (c *Client) GetEntry(entryId string) (Entry, error) { - var entry Entry - reqUrl, err := url.JoinPath(c.baseUri, entryEndpoint, entryId) - if err != nil { - return Entry{}, fmt.Errorf("failed to build entry url. error: %w", err) - } - - resp, err := c.Request(reqUrl, http.MethodGet, nil) - if err != nil { - return Entry{}, fmt.Errorf("error while fetching entry. error: %w", err) - } else if err = resp.CheckRespSaveResult(); err != nil { - return Entry{}, err - } - - err = json.Unmarshal(resp.Response, &entry) - if err != nil { - return Entry{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) - } - - return entry, nil -} - -// NewEntry creates a new Entry based on entry. -func (c *Client) NewEntry(entry Entry) (Entry, error) { - if entry.ConnectionType != ServerConnectionCredential || entry.ConnectionSubType != ServerConnectionSubTypeDefault { - return Entry{}, fmt.Errorf("unsupported entry type (%s %s). Only %s %s is supported", entry.ConnectionType, entry.ConnectionSubType, ServerConnectionCredential, ServerConnectionSubTypeDefault) - } - - entry.ID = "" - entry.ModifiedDate = nil - - reqUrl, err := url.JoinPath(c.baseUri, entryEndpoint, "save") - if err != nil { - return Entry{}, fmt.Errorf("failed to build entry url. error: %w", err) - } - - entryJson, err := json.Marshal(entry) - if err != nil { - return Entry{}, fmt.Errorf("failed to marshall body. error: %w", err) - } - - resp, err := c.Request(reqUrl, http.MethodPost, bytes.NewBuffer(entryJson)) - if err != nil { - return Entry{}, fmt.Errorf("error while creating entry. error: %w", err) - } else if err = resp.CheckRespSaveResult(); err != nil { - return Entry{}, err - } - - err = json.Unmarshal(resp.Response, &entry) - if err != nil { - return Entry{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) - } - - return entry, nil -} - -// UpdateEntry updates an Entry based on entry. Will replace all other fields wether included or not. -func (c *Client) UpdateEntry(entry Entry) (Entry, error) { - if entry.ConnectionType != ServerConnectionCredential || entry.ConnectionSubType != ServerConnectionSubTypeDefault { - return Entry{}, fmt.Errorf("unsupported entry type (%s %s). Only %s %s is supported", entry.ConnectionType, entry.ConnectionSubType, ServerConnectionCredential, ServerConnectionSubTypeDefault) - } - _, err := c.GetEntry(entry.ID) - if err != nil { - return Entry{}, fmt.Errorf("error while fetching entry. error: %w", err) - } - - entry.ModifiedDate = nil - - reqUrl, err := url.JoinPath(c.baseUri, entryEndpoint, "save") - if err != nil { - return Entry{}, fmt.Errorf("failed to build entry url. error: %w", err) - } - - entryJson, err := json.Marshal(entry) - if err != nil { - return Entry{}, fmt.Errorf("failed to marshall body. error: %w", err) - } - - resp, err := c.Request(reqUrl, http.MethodPut, bytes.NewBuffer(entryJson)) - if err != nil { - return Entry{}, fmt.Errorf("error while creating entry. error: %w", err) - } else if err = resp.CheckRespSaveResult(); err != nil { - return Entry{}, err - } - - err = json.Unmarshal(resp.Response, &entry) - if err != nil { - return Entry{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) - } - - return entry, nil -} - -// DeleteEntry deletes an entry based on entryId. -func (c *Client) DeleteEntry(entryId string) error { - reqUrl, err := url.JoinPath(c.baseUri, entryEndpoint, entryId) - if err != nil { - return fmt.Errorf("failed to delete entry url. error: %w", err) - } - - resp, err := c.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 -} - -// NewEntryCredentials returns an EntryCredentials with an initialised EntryCredentials.Password. -func NewEntryCredentials(username string, password string) EntryCredentials { - creds := EntryCredentials{ - Username: username, - Password: &password, - } - return creds +type Entries struct { + UserCredential *EntryUserCredentialService } diff --git a/entries_test.go b/entries_test.go index 9797403..d26ad3b 100644 --- a/entries_test.go +++ b/entries_test.go @@ -6,20 +6,25 @@ import ( ) var ( - testNewEntry Entry - testEntry Entry = Entry{ + testNewEntry EntryUserCredential + testEntry EntryUserCredential = EntryUserCredential{ Description: "Test description", EntryName: "TestK8sSecret", ConnectionType: ServerConnectionCredential, ConnectionSubType: ServerConnectionSubTypeDefault, Tags: []string{"Test tag 1", "Test tag 2", "testtag"}, - Credentials: NewEntryCredentials("TestK8s", "TestK8sPassword"), } ) +const ( + testEntryUsername string = "TestK8s" + testEntryPassword string = "TestK8sPassword" +) + func Test_Entries(t *testing.T) { testEntry.ID = testEntryId testEntry.VaultId = testVaultId + testEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails(testEntryUsername, testEntryPassword) t.Run("GetEntry", test_GetEntry) t.Run("GetEntryCredentialsPassword", test_GetEntryCredentialsPassword) @@ -31,14 +36,15 @@ func Test_Entries(t *testing.T) { func test_GetEntry(t *testing.T) { testGetEntry := testEntry - testGetEntry.Credentials = EntryCredentials{ + testGetEntry.Credentials = EntryUserAuthDetails{ Username: testEntry.Credentials.Username, } - entry, err := testClient.GetEntry(testGetEntry.ID) + entry, err := testClient.Entries.UserCredential.Get(testGetEntry.ID) if err != nil { t.Fatal(err) } + testClient.Entries.UserCredential.Get(testGetEntry.ID) testGetEntry.ModifiedDate = entry.ModifiedDate if !reflect.DeepEqual(entry, testGetEntry) { @@ -48,7 +54,7 @@ func test_GetEntry(t *testing.T) { func test_GetEntryCredentialsPassword(t *testing.T) { testSecret := testEntry.Credentials - secret, err := testClient.GetEntryCredentialsPassword(testEntry) + secret, err := testClient.Entries.UserCredential.GetUserAuthDetails(testEntry) if err != nil { t.Fatal(err) } @@ -63,7 +69,7 @@ func test_NewEntry(t *testing.T) { testNewEntry.EntryName = "TestK8sNewEntry" - entry, err := testClient.NewEntry(testNewEntry) + entry, err := testClient.Entries.UserCredential.New(testNewEntry) if err != nil { t.Fatal(err) } @@ -82,9 +88,9 @@ func test_NewEntry(t *testing.T) { func test_UpdateEntry(t *testing.T) { testUpdatedEntry := testNewEntry testUpdatedEntry.EntryName = "TestK8sUpdatedEntry" - testUpdatedEntry.Credentials = NewEntryCredentials("TestK8sUpdatedUser", "TestK8sUpdatedPassword") + testUpdatedEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails("TestK8sUpdatedUser", "TestK8sUpdatedPassword") - entry, err := testClient.UpdateEntry(testUpdatedEntry) + entry, err := testClient.Entries.UserCredential.Update(testUpdatedEntry) if err != nil { t.Fatal(err) } @@ -100,7 +106,7 @@ func test_UpdateEntry(t *testing.T) { } func test_DeleteEntry(t *testing.T) { - err := testClient.DeleteEntry(testNewEntry.ID) + err := testClient.Entries.UserCredential.Delete(testNewEntry.ID) if err != nil { t.Fatal(err) } diff --git a/entry_user_credentials.go b/entry_user_credentials.go new file mode 100644 index 0000000..cef755d --- /dev/null +++ b/entry_user_credentials.go @@ -0,0 +1,358 @@ +package dvls + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +type EntryUserCredentialService service + +// EntryUserCredential represents a DVLS entry/connection. +type EntryUserCredential struct { + ID string `json:"id,omitempty"` + VaultId string `json:"repositoryId"` + EntryName string `json:"name"` + Description string `json:"description"` + EntryFolderPath string `json:"group"` + ModifiedDate *ServerTime `json:"modifiedDate,omitempty"` + ConnectionType ServerConnectionType `json:"connectionType"` + ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` + Tags []string `json:"keywords,omitempty"` + + Credentials EntryUserAuthDetails `json:"data,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +func (e EntryUserCredential) MarshalJSON() ([]byte, error) { + raw := struct { + Id string `json:"id,omitempty"` + RepositoryId string `json:"repositoryId"` + Name string `json:"name"` + Description string `json:"description"` + Events struct { + OpenCommentPrompt bool `json:"openCommentPrompt"` + CredentialViewedPrompt bool `json:"credentialViewedPrompt"` + TicketNumberIsRequiredOnCredentialViewed bool `json:"ticketNumberIsRequiredOnCredentialViewed"` + TicketNumberIsRequiredOnClose bool `json:"ticketNumberIsRequiredOnClose"` + CredentialViewedCommentIsRequired bool `json:"credentialViewedCommentIsRequired"` + TicketNumberIsRequiredOnOpen bool `json:"ticketNumberIsRequiredOnOpen"` + CloseCommentIsRequired bool `json:"closeCommentIsRequired"` + OpenCommentPromptOnBrowserExtensionLink bool `json:"openCommentPromptOnBrowserExtensionLink"` + CloseCommentPrompt bool `json:"closeCommentPrompt"` + OpenCommentIsRequired bool `json:"openCommentIsRequired"` + WarnIfAlreadyOpened bool `json:"warnIfAlreadyOpened"` + } `json:"events"` + Data string `json:"data"` + Expiration string `json:"expiration"` + CheckOutMode int `json:"checkOutMode"` + Group string `json:"group"` + ConnectionType ServerConnectionType `json:"connectionType"` + ConnectionSubType ServerConnectionSubType `json:"connectionSubType"` + 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.Description = e.Description + raw.RepositoryId = e.VaultId + raw.Group = e.EntryFolderPath + raw.ConnectionSubType = e.ConnectionSubType + raw.ConnectionType = e.ConnectionType + raw.Name = e.EntryName + sensitiveJson, err := json.Marshal(e.Credentials) + if err != nil { + return nil, fmt.Errorf("failed to marshal sensitive data. error: %w", err) + } + + raw.Data = string(sensitiveJson) + + entryJson, err := json.Marshal(raw) + if err != nil { + return nil, err + } + + return entryJson, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (e *EntryUserCredential) UnmarshalJSON(d []byte) error { + raw := struct { + Data struct { + ID string + Description string + Name string + Group string + Username string + ModifiedDate *ServerTime + Keywords string + RepositoryId string + ConnectionType ServerConnectionType + ConnectionSubType ServerConnectionSubType + } + }{} + err := json.Unmarshal(d, &raw) + if err != nil { + return err + } + + e.ID = raw.Data.ID + e.EntryName = raw.Data.Name + e.ConnectionType = raw.Data.ConnectionType + e.ConnectionSubType = raw.Data.ConnectionSubType + e.ModifiedDate = raw.Data.ModifiedDate + e.Credentials.Username = raw.Data.Username + e.Description = raw.Data.Description + 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 + + return nil +} + +// EntryUserAuthDetails represents an Entry User Authentication Details fields. +type EntryUserAuthDetails struct { + Username string + Password *string +} + +// MarshalJSON implements the json.Marshaler interface. +func (s EntryUserAuthDetails) MarshalJSON() ([]byte, error) { + raw := struct { + AllowClipboard bool `json:"allowClipboard"` + CredentialConnectionId string `json:"credentialConnectionId"` + PamCredentialId string `json:"pamCredentialId"` + PamCredentialName string `json:"pamCredentialName"` + CredentialMode int `json:"credentialMode"` + Credentials *string `json:"credentials"` + Domain string `json:"domain"` + MnemonicPassword string `json:"mnemonicPassword"` + PasswordItem struct { + HasSensitiveData bool `json:"hasSensitiveData"` + SensitiveData string `json:"sensitiveData"` + } `json:"passwordItem"` + PromptForPassword bool `json:"promptForPassword"` + UserName string `json:"userName"` + }{} + + if s.Password != nil { + raw.PasswordItem.HasSensitiveData = true + raw.PasswordItem.SensitiveData = *s.Password + } + raw.UserName = s.Username + + secretJson, err := json.Marshal(raw) + if err != nil { + return nil, err + } + + return secretJson, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *EntryUserAuthDetails) UnmarshalJSON(d []byte) error { + raw := struct { + Data string + }{} + err := json.Unmarshal(d, &raw) + if err != nil { + return err + } + + if raw.Data != "" { + newRaw := struct { + Data struct { + Credentials struct { + Username string + Password string + } + } + }{} + err = json.Unmarshal([]byte(raw.Data), &newRaw) + if err != nil { + return err + } + + s.Username = newRaw.Data.Credentials.Username + s.Password = &newRaw.Data.Credentials.Password + } + + 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 + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, entry.ID, "/sensitive-data") + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodPost, nil) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("error while fetching sensitive data. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryUserCredential{}, err + } + + err = json.Unmarshal(resp.Response, &secret) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + entry.Credentials = secret + + return entry, nil +} + +// Get returns a single Entry specified by entryId. Call GetEntryCredentialsPassword with +// the returned Entry to fetch the password. +func (c *EntryUserCredentialService) Get(entryId string) (EntryUserCredential, error) { + var entry EntryUserCredential + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, entryId) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodGet, nil) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("error while fetching entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryUserCredential{}, err + } + + err = json.Unmarshal(resp.Response, &entry) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + return entry, nil +} + +// New creates a new EntryUserCredential based on entry. +func (c *EntryUserCredentialService) New(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) + } + + entry.ID = "" + entry.ModifiedDate = nil + + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, "save") + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + entryJson, err := json.Marshal(entry) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to marshal body. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodPost, bytes.NewBuffer(entryJson)) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("error while creating entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryUserCredential{}, err + } + + err = json.Unmarshal(resp.Response, &entry) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + return entry, nil +} + +// Update updates an EntryUserCredential based on entry. Will replace all other fields wether 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) + } + _, err := c.Get(entry.ID) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("error while fetching entry. error: %w", err) + } + + entry.ModifiedDate = nil + + reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, "save") + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) + } + + entryJson, err := json.Marshal(entry) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to marshal body. error: %w", err) + } + + resp, err := c.client.Request(reqUrl, http.MethodPut, bytes.NewBuffer(entryJson)) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("error while creating entry. error: %w", err) + } else if err = resp.CheckRespSaveResult(); err != nil { + return EntryUserCredential{}, err + } + + err = json.Unmarshal(resp.Response, &entry) + if err != nil { + return EntryUserCredential{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + } + + return entry, nil +} + +// Delete deletes an EntryUserCredential based on entryId. +func (c *EntryUserCredentialService) 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 +} + +// NewEntryUserAuthDetails returns an EntryUserAuthDetails with an initialised EntryUserAuthDetails.Password. +func (c *EntryUserCredentialService) NewUserAuthDetails(username string, password string) EntryUserAuthDetails { + creds := EntryUserAuthDetails{ + Username: username, + Password: &password, + } + return creds +} diff --git a/server.go b/server.go index 9723aad..7477233 100644 --- a/server.go +++ b/server.go @@ -120,7 +120,7 @@ func (c *Client) GetPublicServerInfo() (Server, error) { err = json.Unmarshal(resp.Response, &server) if err != nil { - return Server{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) + return Server{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) } return server, nil @@ -143,7 +143,7 @@ func (c *Client) GetPrivateServerInfo() (Server, error) { err = json.Unmarshal(resp.Response, &server) if err != nil { - return Server{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) + return Server{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) } return server, nil @@ -170,7 +170,7 @@ func (c *Client) GetServerTimezones() ([]Timezone, error) { }{} err = json.Unmarshal(resp.Response, &raw) if err != nil { - return nil, fmt.Errorf("failed to unmarshall response body. error: %w", err) + return nil, fmt.Errorf("failed to unmarshal response body. error: %w", err) } timezones = raw.Data diff --git a/vaults.go b/vaults.go index 8f7d2ae..21c9b48 100644 --- a/vaults.go +++ b/vaults.go @@ -8,6 +8,8 @@ import ( "net/url" ) +type Vaults service + // Vault represents a DVLS vault. Contains relevant vault information. type Vault struct { ID string @@ -120,15 +122,15 @@ const ( vaultEndpoint string = "/api/security/repositories" ) -// GetVault returns a single Vault based on vaultId. -func (c *Client) GetVault(vaultId string) (Vault, error) { +// Get returns a single Vault based on vaultId. +func (c *Vaults) Get(vaultId string) (Vault, error) { var vault Vault - reqUrl, err := url.JoinPath(c.baseUri, vaultEndpoint, vaultId) + reqUrl, err := url.JoinPath(c.client.baseUri, vaultEndpoint, vaultId) if err != nil { return Vault{}, fmt.Errorf("failed to build vault url. error: %w", err) } - resp, err := c.Request(reqUrl, http.MethodGet, nil) + resp, err := c.client.Request(reqUrl, http.MethodGet, nil) if err != nil { return Vault{}, fmt.Errorf("error while fetching vault. error: %w", err) } else if err = resp.CheckRespSaveResult(); err != nil { @@ -137,15 +139,15 @@ func (c *Client) GetVault(vaultId string) (Vault, error) { err = json.Unmarshal(resp.Response, &vault) if err != nil { - return Vault{}, fmt.Errorf("failed to unmarshall response body. error: %w", err) + return Vault{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) } return vault, nil } -// NewVault creates a new Vault based on vault. -func (c *Client) NewVault(vault Vault, options *VaultOptions) error { - reqUrl, err := url.JoinPath(c.baseUri, vaultEndpoint) +// New creates a new Vault based on vault. +func (c *Vaults) New(vault Vault, options *VaultOptions) error { + reqUrl, err := url.JoinPath(c.client.baseUri, vaultEndpoint) if err != nil { return fmt.Errorf("failed to build vault url. error: %w", err) } @@ -159,10 +161,10 @@ func (c *Client) NewVault(vault Vault, options *VaultOptions) error { vaultJson, err := json.Marshal(vault) if err != nil { - return fmt.Errorf("failed to marshall body. error: %w", err) + return fmt.Errorf("failed to marshal body. error: %w", err) } - resp, err := c.Request(reqUrl, http.MethodPut, bytes.NewBuffer(vaultJson)) + resp, err := c.client.Request(reqUrl, http.MethodPut, bytes.NewBuffer(vaultJson)) if err != nil { return fmt.Errorf("error while creating vault. error: %w", err) } else if err = resp.CheckRespSaveResult(); err != nil { @@ -172,14 +174,14 @@ func (c *Client) NewVault(vault Vault, options *VaultOptions) error { return nil } -// UpdateVault updates a Vault based on vault. -func (c *Client) UpdateVault(vault Vault, options *VaultOptions) error { - _, err := c.GetVault(vault.ID) +// Update updates a Vault based on vault. +func (c *Vaults) Update(vault Vault, options *VaultOptions) error { + _, err := c.client.Vaults.Get(vault.ID) if err != nil { return fmt.Errorf("error while fetching vault. error: %w", err) } - err = c.NewVault(vault, options) + err = c.client.Vaults.New(vault, options) if err != nil { return fmt.Errorf("error while updating vault. error: %w", err) } @@ -187,14 +189,14 @@ func (c *Client) UpdateVault(vault Vault, options *VaultOptions) error { return nil } -// DeleteVault deletes a Vault based on vaultId. -func (c *Client) DeleteVault(vaultId string) error { - reqUrl, err := url.JoinPath(c.baseUri, vaultEndpoint, vaultId) +// Delete deletes a Vault based on vaultId. +func (c *Vaults) Delete(vaultId string) error { + reqUrl, err := url.JoinPath(c.client.baseUri, vaultEndpoint, vaultId) if err != nil { return fmt.Errorf("failed to delete vault url. error: %w", err) } - resp, err := c.Request(reqUrl, http.MethodDelete, nil) + resp, err := c.client.Request(reqUrl, http.MethodDelete, nil) if err != nil { return fmt.Errorf("error while deleting vault. error: %w", err) } else if err = resp.CheckRespSaveResult(); err != nil { @@ -204,14 +206,14 @@ func (c *Client) DeleteVault(vaultId string) error { return nil } -// ValidateVaultPassword validates a Vault password based on vaultId and password. -func (c *Client) ValidateVaultPassword(vaultId string, password string) (bool, error) { - reqUrl, err := url.JoinPath(c.baseUri, vaultEndpoint, vaultId, "login") +// ValidatePassword validates a Vault password based on vaultId and password. +func (c *Vaults) ValidatePassword(vaultId string, password string) (bool, error) { + reqUrl, err := url.JoinPath(c.client.baseUri, vaultEndpoint, vaultId, "login") if err != nil { return false, fmt.Errorf("failed to build vault url. error: %w", err) } - resp, err := c.Request(reqUrl, http.MethodPost, bytes.NewBufferString(fmt.Sprintf("\"%s\"", password))) + resp, err := c.client.Request(reqUrl, http.MethodPost, bytes.NewBufferString(fmt.Sprintf("\"%s\"", password))) if err != nil { return false, fmt.Errorf("error while fetching vault. error: %w", err) } else if resp.Result == uint8(SaveResultAccessDenied) { diff --git a/vaults_test.go b/vaults_test.go index 370ec52..30efa70 100644 --- a/vaults_test.go +++ b/vaults_test.go @@ -29,7 +29,7 @@ func Test_Vaults(t *testing.T) { } func test_GetVault(t *testing.T) { - vault, err := testClient.GetVault(testVaultId) + vault, err := testClient.Vaults.Get(testVaultId) if err != nil { t.Fatal(err) } @@ -43,12 +43,12 @@ func test_GetVault(t *testing.T) { } func test_NewVault(t *testing.T) { - err := testClient.NewVault(testNewVault, nil) + err := testClient.Vaults.New(testNewVault, nil) if err != nil { t.Fatal(err) } - vault, err := testClient.GetVault(testNewVault.ID) + vault, err := testClient.Vaults.Get(testNewVault.ID) if err != nil { t.Fatal(err) } @@ -66,12 +66,12 @@ func test_UpdateVault(t *testing.T) { testNewVault.Description = "Test updated" options := VaultOptions{Password: &testNewVaultPassword} - err := testClient.UpdateVault(testNewVault, &options) + err := testClient.Vaults.Update(testNewVault, &options) if err != nil { t.Fatal(err) } - valid, err := testClient.ValidateVaultPassword(testNewVault.ID, testNewVaultPassword) + valid, err := testClient.Vaults.ValidatePassword(testNewVault.ID, testNewVaultPassword) if err != nil { t.Fatal(err) } @@ -80,7 +80,7 @@ func test_UpdateVault(t *testing.T) { t.Fatal("vault password validation failed, expected ", testNewVaultPassword) } - vault, err := testClient.GetVault(testNewVault.ID) + vault, err := testClient.Vaults.Get(testNewVault.ID) if err != nil { t.Fatal(err) } @@ -94,7 +94,7 @@ func test_UpdateVault(t *testing.T) { } func test_DeleteVault(t *testing.T) { - err := testClient.DeleteVault(testNewVault.ID) + err := testClient.Vaults.Delete(testNewVault.ID) if err != nil { t.Fatal(err) }