diff --git a/.gitignore b/.gitignore index e43b0f9..94f1119 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.vscode diff --git a/VERSION b/VERSION index 78bc1ab..d9df1bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.10.0 +0.11.0 diff --git a/authentication.go b/authentication.go index 1caf98c..c4c3ccf 100644 --- a/authentication.go +++ b/authentication.go @@ -14,7 +14,6 @@ type Client struct { client *http.Client baseUri string credential credentials - ClientUser User common service @@ -27,89 +26,37 @@ type service struct { } type credentials struct { - username string - password string - token string + appKey string + appSecret string + token string } type loginResponse struct { - Data struct { - Message string - Result ServerLoginResult - TokenId string - } -} - -type loginReqBody struct { - Username string `json:"userName"` - LoginParameters loginParameters `json:"LoginParameters"` -} - -type loginParameters struct { - Password string `json:"Password"` - Client string `json:"Client"` - Version string `json:"Version,omitempty"` - LocalMachineName string `json:"LocalMachineName,omitempty"` - LocalUserName string `json:"LocalUserName,omitempty"` -} - -// User represents a DVLS user. -type User struct { - ID string - Username string - UserType UserAuthenticationType -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (u *User) UnmarshalJSON(d []byte) error { - raw := struct { - Data struct { - TokenId string - UserEntity struct { - Id string - Display string - UserSecurity struct { - AuthenticationType UserAuthenticationType - } - } - } - Result ServerLoginResult - Message string - }{} - err := json.Unmarshal(d, &raw) - if err != nil { - return err - } - - u.ID = raw.Data.UserEntity.Id - u.Username = raw.Data.UserEntity.Display - u.UserType = raw.Data.UserEntity.UserSecurity.AuthenticationType - - return nil + TokenId string } const ( - loginEndpoint string = "/api/login/partial" + loginEndpoint string = "/api/v1/login" isLoggedEndpoint string = "/api/is-logged" ) +const loginContentType = "application/x-www-form-urlencoded" + // NewClient returns a new Client configured with the specified credentials and // base URI. baseUri should be the full URI to your DVLS instance (ex.: https://dvls.your-dvls-instance.com) -func NewClient(username string, password string, baseUri string) (Client, error) { - credential := credentials{username: username, password: password} +func NewClient(appKey string, appSecret string, baseUri string) (Client, error) { + credential := credentials{appKey: appKey, appSecret: appSecret} client := Client{ client: &http.Client{}, baseUri: baseUri, credential: credential, } - user, err := client.login() + err := client.login() if err != nil { return Client{}, fmt.Errorf("login failed \"%w\"", err) } - client.ClientUser = user - client.common.client = &client client.Entries = &Entries{ @@ -123,70 +70,17 @@ func NewClient(username string, password string, baseUri string) (Client, error) return client, nil } -func (c *Client) login() (User, error) { - loginBody := loginReqBody{ - Username: c.credential.username, - LoginParameters: loginParameters{ - Password: c.credential.password, - Client: "Cli", - }, - } - loginJson, err := json.Marshal(loginBody) - if err != nil { - return User{}, fmt.Errorf("failed to marshal login body. error: %w", err) - } - - reqUrl, err := url.JoinPath(c.baseUri, loginEndpoint) - if err != nil { - return User{}, fmt.Errorf("failed to build login url. error: %w", err) - } - - resp, err := c.rawRequest(reqUrl, http.MethodPost, bytes.NewBuffer(loginJson)) - if err != nil { - return User{}, fmt.Errorf("error while submitting refreshtoken request. error: %w", err) - } - - var loginResponse loginResponse - err = json.Unmarshal(resp.Response, &loginResponse) - if err != nil { - 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) - } - - var user User - err = json.Unmarshal(resp.Response, &user) - if err != nil { - return User{}, fmt.Errorf("failed to unmarshal user body. error: %w", err) - } - - c.credential.token = loginResponse.Data.TokenId - - return user, nil -} - -func (c *Client) refreshToken() error { - loginBody := loginReqBody{ - Username: c.credential.username, - LoginParameters: loginParameters{ - Password: c.credential.password, - Client: "Cli", - }, - } - loginJson, err := json.Marshal(loginBody) - if err != nil { - return fmt.Errorf("failed to marshal login body. error: %w", err) - } +func (c *Client) login() error { + loginBody := fmt.Sprintf("AppKey=%s&AppSecret=%s", c.credential.appKey, c.credential.appSecret) reqUrl, err := url.JoinPath(c.baseUri, loginEndpoint) if err != nil { return fmt.Errorf("failed to build login url. error: %w", err) } - resp, err := c.rawRequest(reqUrl, http.MethodPost, bytes.NewBuffer(loginJson)) + resp, err := c.rawRequest(reqUrl, http.MethodPost, loginContentType, bytes.NewBufferString(loginBody)) if err != nil { - return fmt.Errorf("error while submitting refreshtoken request. error: %w", err) + return fmt.Errorf("error while submitting login request. error: %w", err) } var loginResponse loginResponse @@ -194,11 +88,8 @@ func (c *Client) refreshToken() error { if err != nil { 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) - } - c.credential.token = loginResponse.Data.TokenId + c.credential.token = loginResponse.TokenId return nil } @@ -209,7 +100,7 @@ func (c *Client) isLogged() (bool, error) { return false, fmt.Errorf("failed to build isLogged url. error: %w", err) } - resp, err := c.rawRequest(reqUrl, http.MethodGet, nil) + resp, err := c.rawRequest(reqUrl, http.MethodGet, defaultContentType, nil) if err != nil && !strings.Contains(err.Error(), "json: cannot unmarshal bool into Go value") { return false, fmt.Errorf("error while submitting isLogged request. error: %w", err) } diff --git a/dvls.go b/dvls.go index 418f5ce..67d3ed5 100644 --- a/dvls.go +++ b/dvls.go @@ -20,6 +20,8 @@ type RequestError struct { Err error } +const defaultContentType string = "application/json" + type RequestOptions struct { ContentType string RawBody bool @@ -36,26 +38,28 @@ func (c *Client) Request(url string, reqMethod string, reqBody io.Reader, option return Response{}, &RequestError{Err: fmt.Errorf("failed to fetch login status. error: %w", err), Url: url} } if !islogged { - err := c.refreshToken() + err := c.login() if err != nil { return Response{}, &RequestError{Err: fmt.Errorf("failed to refresh login token. error: %w", err), Url: url} } } - resp, err := c.rawRequest(url, reqMethod, reqBody, options...) + var opts RequestOptions + if len(options) > 0 { + opts = options[0] + } + + resp, err := c.rawRequest(url, reqMethod, defaultContentType, reqBody, opts) if err != nil { return Response{}, err } return resp, nil } -func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader, options ...RequestOptions) (Response, error) { - contentType := "application/json" - var rawBody bool - +func (c *Client) rawRequest(url string, reqMethod string, contentType string, reqBody io.Reader, options ...RequestOptions) (Response, error) { + var opts RequestOptions if len(options) > 0 { - contentType = options[0].ContentType - rawBody = options[0].RawBody + opts = options[0] } req, err := http.NewRequest(reqMethod, url, reqBody) @@ -69,7 +73,9 @@ func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader, opt resp, err := c.client.Do(req) if err != nil { return Response{}, &RequestError{Err: fmt.Errorf("error while submitting request. error: %w", err), Url: url} - } else if resp.StatusCode != http.StatusOK { + } + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return Response{}, &RequestError{Err: fmt.Errorf("unexpected status code %d", resp.StatusCode), Url: url} } @@ -80,7 +86,7 @@ func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader, opt } defer resp.Body.Close() - if !rawBody { + if !opts.RawBody && len(response.Response) > 0 { 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} diff --git a/entry_certificate_test.go b/entry_certificate_test.go index f63a7dd..775429c 100644 --- a/entry_certificate_test.go +++ b/entry_certificate_test.go @@ -34,9 +34,9 @@ func Test_EntryCertificate(t *testing.T) { 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("GetEntry", test_GetCertificateEntry) t.Run("UpdateEntry", test_UpdateCertificateEntry) t.Run("DeleteEntry", test_DeleteCertificateEntry) } @@ -150,6 +150,7 @@ func test_NewCertificateEntryURL(t *testing.T) { if !reflect.DeepEqual(entry, newEntry) { t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", entry, newEntry) } + testCertificateEntry = entry } func test_UpdateCertificateEntry(t *testing.T) { diff --git a/entry_user_credentials.go b/entry_user_credentials.go index a2b2af7..3cf0dea 100644 --- a/entry_user_credentials.go +++ b/entry_user_credentials.go @@ -6,213 +6,74 @@ import ( "fmt" "net/http" "net/url" + "strings" ) type EntryUserCredentialService service +const ( + entryPublicEndpoint string = "/api/v1/vault/{vaultId}/entry/{id}" + EntryTypeCredential string = "Credential" + EntrySubTypeDefault string = "Default" +) + // 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"` + ID string `json:"id,omitempty"` + VaultId string `json:"vaultId"` + EntryName string `json:"name"` + Description string `json:"description"` + Path string `json:"path"` + ModifiedOn *ServerTime `json:"modifiedOn,omitempty"` + ModifiedBy string `json:"modifiedBy,omitempty"` + CreatedOn *ServerTime `json:"createdOn,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + Type string `json:"type"` + SubType string `json:"subType"` + Tags []string `json:"tags,omitempty"` + + Credentials EntryCredentials `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"` - }{} - - raw.Id = e.ID - raw.Keywords = sliceToKeywords(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 +// EntryCredentials represents an EntryUserCredential Credentials fields. +type EntryCredentials struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` } -// 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 - - e.Tags = keywordsToSlice(raw.Data.Keywords) - - return nil +func entryReplacer(vaultId string, entryId string) string { + replacer := strings.NewReplacer("{vaultId}", vaultId, "{id}", entryId) + return replacer.Replace(entryPublicEndpoint) } -// 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 +// validateEntry checks if an EntryUserCredential has the required fields and valid type/subtype. +func (c *EntryUserCredentialService) validateEntry(entry *EntryUserCredential) error { + if entry.VaultId == "" { + return fmt.Errorf("entry must have a VaultId") } - 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 entry.Type != EntryTypeCredential { + return fmt.Errorf("unsupported entry type (%s). Only %s is supported", entry.Type, EntryTypeCredential) } - 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 + if entry.SubType == "" { + entry.SubType = EntrySubTypeDefault + } else if entry.SubType != EntrySubTypeDefault { + return fmt.Errorf("unsupported entry subtype (%s). Only %s is supported", entry.SubType, EntrySubTypeDefault) } return nil } -// 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) +// Get returns a single EntryUserCredential specified by entryId. +func (c *EntryUserCredentialService) Get(vaultId string, entryId string) (EntryUserCredential, error) { + if entryId == "" || vaultId == "" { + return EntryUserCredential{}, fmt.Errorf("both entry ID and vault ID are required for deletion") } - - 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) + entryUri := entryReplacer(vaultId, entryId) + + reqUrl, err := url.JoinPath(c.client.baseUri, entryUri) if err != nil { return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) } @@ -220,8 +81,6 @@ func (c *EntryUserCredentialService) Get(entryId string) (EntryUserCredential, e 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) @@ -229,19 +88,25 @@ func (c *EntryUserCredentialService) Get(entryId string) (EntryUserCredential, e return EntryUserCredential{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) } + entry.VaultId = vaultId + if entry.SubType == "" { + entry.SubType = EntrySubTypeDefault + } + 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) + if err := c.validateEntry(&entry); err != nil { + return EntryUserCredential{}, err } entry.ID = "" - entry.ModifiedDate = nil - reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, "save") + baseEntryEndpoint := strings.Replace(entryPublicEndpoint, "/{id}", "", 1) + entryUri := strings.Replace(baseEntryEndpoint, "{vaultId}", entry.VaultId, 1) + reqUrl, err := url.JoinPath(c.client.baseUri, entryUri) if err != nil { return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) } @@ -254,31 +119,36 @@ func (c *EntryUserCredentialService) New(entry EntryUserCredential) (EntryUserCr 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 whether included or not. +// Update updates an EntryUserCredential based on entry. 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) + if err := c.validateEntry(&entry); err != nil { + return EntryUserCredential{}, err } - _, err := c.Get(entry.ID) + + if entry.ID == "" { + return EntryUserCredential{}, fmt.Errorf("entry ID is required for updates") + } + + originalEntry, err := c.Get(entry.VaultId, entry.ID) if err != nil { - return EntryUserCredential{}, fmt.Errorf("error while fetching entry. error: %w", err) + return EntryUserCredential{}, fmt.Errorf("failed to fetch original entry. error: %w", err) } - entry.ModifiedDate = nil + if originalEntry.SubType != entry.SubType { + return EntryUserCredential{}, fmt.Errorf("entry subType cannot be changed") + } + + entryUri := entryReplacer(entry.VaultId, entry.ID) - reqUrl, err := url.JoinPath(c.client.baseUri, entryEndpoint, "save") + reqUrl, err := url.JoinPath(c.client.baseUri, entryUri) if err != nil { return EntryUserCredential{}, fmt.Errorf("failed to build entry url. error: %w", err) } @@ -288,43 +158,35 @@ func (c *EntryUserCredentialService) Update(entry EntryUserCredential) (EntryUse return EntryUserCredential{}, fmt.Errorf("failed to marshal body. error: %w", err) } - resp, err := c.client.Request(reqUrl, http.MethodPut, bytes.NewBuffer(entryJson)) + _, 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 + return EntryUserCredential{}, fmt.Errorf("error while updating entry. error: %w", err) } - err = json.Unmarshal(resp.Response, &entry) + entry, err = c.Get(entry.VaultId, entry.ID) if err != nil { - return EntryUserCredential{}, fmt.Errorf("failed to unmarshal response body. error: %w", err) + return EntryUserCredential{}, fmt.Errorf("update succeeded but failed to fetch updated entry: %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) +// Delete deletes an entry based on entry. +func (c *EntryUserCredentialService) Delete(entry EntryUserCredential) error { + if entry.ID == "" || entry.VaultId == "" { + return fmt.Errorf("both entry ID and vault ID are required") + } + + entryUri := entryReplacer(entry.VaultId, entry.ID) + reqUrl, err := url.JoinPath(c.client.baseUri, entryUri) if err != nil { - return fmt.Errorf("failed to delete entry url. error: %w", err) + return fmt.Errorf("failed to build delete entry url. error: %w", err) } - resp, err := c.client.Request(reqUrl, http.MethodDelete, nil) + _, 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/entry_user_credentials_test.go b/entry_user_credentials_test.go index 49db644..840162d 100644 --- a/entry_user_credentials_test.go +++ b/entry_user_credentials_test.go @@ -1,117 +1,104 @@ package dvls import ( - "os" "reflect" "testing" ) var ( - testUserEntryId string - testNewUserEntry EntryUserCredential - testUserEntry EntryUserCredential = EntryUserCredential{ - Description: "Test description", - EntryName: "TestK8sSecret", - ConnectionType: ServerConnectionCredential, - ConnectionSubType: ServerConnectionSubTypeDefault, - Tags: []string{"Test tag 1", "Test tag 2", "testtag"}, + testUserEntry = EntryUserCredential{ + ID: "", + VaultId: testVaultId, + EntryName: "TestGoDvlsSecret", + Description: "Test description", + Type: "Credential", + SubType: "Default", + Tags: []string{"testtag"}, + Credentials: EntryCredentials{ + Username: testEntryUsername, + Password: testEntryPassword, + }, } ) const ( - testEntryUsername string = "TestK8s" - testEntryPassword string = "TestK8sPassword" + testEntryUsername string = "Test" + testEntryPassword string = "TestPassword" ) 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_GetUserEntry) t.Run("NewEntry", test_NewUserEntry) - t.Run("GetEntryCredentialsPassword", test_GetEntryCredentialsPassword) - + t.Run("GetEntry", test_GetUserEntry) t.Run("UpdateEntry", test_UpdateUserEntry) t.Run("DeleteEntry", test_DeleteUserEntry) } -func test_GetUserEntry(t *testing.T) { - testGetEntry := testUserEntry - - testGetEntry.Credentials = EntryUserAuthDetails{ - Username: testUserEntry.Credentials.Username, - } - 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) { - t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testGetEntry, entry) - } +// Allowing accurate comparison by ignoring fields that differ due to server assignment. +func NormalizeEntry(source, target *EntryUserCredential) { + target.ID = source.ID + target.ModifiedOn = source.ModifiedOn + target.ModifiedBy = source.ModifiedBy + target.CreatedOn = source.CreatedOn + target.CreatedBy = source.CreatedBy } -func test_GetEntryCredentialsPassword(t *testing.T) { - testSecret := testUserEntry.Credentials - secret, err := testClient.Entries.UserCredential.GetUserAuthDetails(testUserEntry) +func test_NewUserEntry(t *testing.T) { + testUserEntry.VaultId = testVaultId + newEntry, err := testClient.Entries.UserCredential.New(testUserEntry) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to create new entry: %v", err) } - if !reflect.DeepEqual(testSecret, secret.Credentials) { - t.Fatalf("fetched secret did not match test secret. Expected %#v, got %#v", testSecret, secret.Credentials) + NormalizeEntry(&newEntry, &testUserEntry) + + if !reflect.DeepEqual(&newEntry, &testUserEntry) { + t.Fatalf("Entries differ.\nGot: %+v\nExpected: %+v", &newEntry, &testUserEntry) } } -func test_NewUserEntry(t *testing.T) { - testNewUserEntry = testUserEntry - - testNewUserEntry.EntryName = "TestK8sNewEntry" - - entry, err := testClient.Entries.UserCredential.New(testNewUserEntry) +func test_GetUserEntry(t *testing.T) { + entry, err := testClient.Entries.UserCredential.Get(testVaultId, testUserEntry.ID) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to get entry: %v", err) } - testNewUserEntry.ID = entry.ID - testNewUserEntry.ModifiedDate = entry.ModifiedDate - testNewUserEntry.Tags = entry.Tags + NormalizeEntry(&entry, &testUserEntry) - if !reflect.DeepEqual(entry, testNewUserEntry) { - t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testNewUserEntry, entry) + if !reflect.DeepEqual(&entry, &testUserEntry) { + t.Fatalf("Entries differ.\nGot: %+v\nExpected: %+v", &entry, &testUserEntry) } - - testNewUserEntry = entry } func test_UpdateUserEntry(t *testing.T) { - testUpdatedEntry := testNewUserEntry - testUpdatedEntry.EntryName = "TestK8sUpdatedEntry" - testUpdatedEntry.Credentials = testClient.Entries.UserCredential.NewUserAuthDetails("TestK8sUpdatedUser", "TestK8sUpdatedPassword") + testUpdatedEntry := testUserEntry + testUpdatedEntry.EntryName = "TestGoDvlsSecretUpdated" + testUpdatedEntry.Description = "Test description updated" + testUpdatedEntry.Credentials = EntryCredentials{ + Username: "TestK8sUpdatedUser", + Password: "TestK8sUpdatedPassword", + } - entry, err := testClient.Entries.UserCredential.Update(testUpdatedEntry) + updatedEntry, err := testClient.Entries.UserCredential.Update(testUpdatedEntry) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to update entry: %v", err) } + NormalizeEntry(&updatedEntry, &testUpdatedEntry) - testUpdatedEntry.ModifiedDate = entry.ModifiedDate - testUpdatedEntry.Tags = entry.Tags - - if !reflect.DeepEqual(entry, testUpdatedEntry) { - t.Fatalf("fetched entry did not match test entry. Expected %#v, got %#v", testUpdatedEntry, entry) + if !reflect.DeepEqual(&updatedEntry, &testUpdatedEntry) { + t.Fatalf("Entries differ.\nGot: %+v\nExpected: %+v", &updatedEntry, &testUpdatedEntry) } - - testNewUserEntry = entry + testUserEntry = updatedEntry } func test_DeleteUserEntry(t *testing.T) { - err := testClient.Entries.UserCredential.Delete(testNewUserEntry.ID) + err := testClient.Entries.UserCredential.Delete(testUserEntry) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to delete entry: %v", err) + } + + // Verify it's gone by trying to retrieve it + _, err = testClient.Entries.UserCredential.Get(testVaultId, testUserEntry.ID) + if err == nil { + t.Fatalf("Entry still exists after deletion: %s", testUserEntry.ID) } }