Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.13.0
0.14.0
5 changes: 4 additions & 1 deletion authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ func NewClient(appKey string, appSecret string, baseUri string) (Client, error)
}

func (c *Client) login() error {
loginBody := fmt.Sprintf("AppKey=%s&AppSecret=%s", c.credential.appKey, c.credential.appSecret)
form := url.Values{}
form.Set("AppKey", c.credential.appKey)
form.Set("AppSecret", c.credential.appSecret)
loginBody := form.Encode()

reqUrl, err := url.JoinPath(c.baseUri, loginEndpoint)
if err != nil {
Expand Down
27 changes: 23 additions & 4 deletions dvls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dvls

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -16,8 +17,10 @@ type Response struct {
}

type RequestError struct {
Url string
Err error
Url string
StatusCode int
Body []byte
Err error
}

const defaultContentType string = "application/json"
Expand All @@ -28,9 +31,23 @@ type RequestOptions struct {
}

func (e RequestError) Error() string {
if e.StatusCode != 0 {
return fmt.Sprintf("error while submitting request on url %s (status %d). error: %s", e.Url, e.StatusCode, e.Err.Error())
}

return fmt.Sprintf("error while submitting request on url %s. error: %s", e.Url, e.Err.Error())
}

// IsNotFound reports whether the error is a DVLS RequestError with an HTTP 404 status code.
func IsNotFound(err error) bool {
var reqErr *RequestError
if errors.As(err, &reqErr) {
return reqErr.StatusCode == http.StatusNotFound
}

return false
}

// 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, options ...RequestOptions) (Response, error) {
islogged, err := c.isLogged()
Expand Down Expand Up @@ -74,17 +91,19 @@ func (c *Client) rawRequest(url string, reqMethod string, contentType string, re
if err != nil {
return Response{}, &RequestError{Err: fmt.Errorf("error while submitting request. error: %w", err), Url: url}
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return Response{}, &RequestError{Err: fmt.Errorf("unexpected status code %d", resp.StatusCode), Url: url}
body, _ := io.ReadAll(resp.Body)

return Response{}, &RequestError{Err: fmt.Errorf("unexpected status code %d", resp.StatusCode), Url: url, StatusCode: resp.StatusCode, Body: body}
}

var response Response
response.Response, err = io.ReadAll(resp.Body)
if err != nil {
return Response{}, &RequestError{Err: fmt.Errorf("failed to read response body. error: %w", err), Url: url}
}
defer resp.Body.Close()

if !opts.RawBody && len(response.Response) > 0 {
err = json.Unmarshal(response.Response, &response)
Expand Down
119 changes: 119 additions & 0 deletions entry_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,125 @@ func (e *Entry) GetCredentialPrivateKeyData() (*EntryCredentialPrivateKeyData, b
return data, ok
}

// ToCredentialMap flattens a credential entry into a map of fields keyed by a stable name.
// It always includes "entry-id" and "entry-name" and then subtype-specific keys.
func (e *Entry) ToCredentialMap() (map[string]string, error) {
if e.GetType() != EntryCredentialType {
return nil, fmt.Errorf("unsupported entry type (%s). Only %s is supported", e.GetType(), EntryCredentialType)
}

secretMap := map[string]string{
"entry-id": e.Id,
"entry-name": e.Name,
}

switch e.SubType {
case EntryCredentialSubTypeDefault:
if data, ok := e.GetCredentialDefaultData(); ok {
if data.Username != "" {
secretMap["username"] = data.Username
}
if data.Password != "" {
secretMap["password"] = data.Password
}
if data.Domain != "" {
secretMap["domain"] = data.Domain
}
}

case EntryCredentialSubTypeAccessCode:
if data, ok := e.GetCredentialAccessCodeData(); ok {
if data.Password != "" {
secretMap["password"] = data.Password
}
}

case EntryCredentialSubTypeApiKey:
if data, ok := e.GetCredentialApiKeyData(); ok {
if data.ApiId != "" {
secretMap["api-id"] = data.ApiId
}
if data.ApiKey != "" {
secretMap["api-key"] = data.ApiKey
}
if data.TenantId != "" {
secretMap["tenant-id"] = data.TenantId
}
}

case EntryCredentialSubTypeAzureServicePrincipal:
if data, ok := e.GetCredentialAzureServicePrincipalData(); ok {
if data.ClientId != "" {
secretMap["client-id"] = data.ClientId
}
if data.ClientSecret != "" {
secretMap["client-secret"] = data.ClientSecret
}
if data.TenantId != "" {
secretMap["tenant-id"] = data.TenantId
}
}

case EntryCredentialSubTypeConnectionString:
if data, ok := e.GetCredentialConnectionStringData(); ok {
if data.ConnectionString != "" {
secretMap["connection-string"] = data.ConnectionString
}
}

case EntryCredentialSubTypePrivateKey:
if data, ok := e.GetCredentialPrivateKeyData(); ok {
if data.Username != "" {
secretMap["username"] = data.Username
}
if data.Password != "" {
secretMap["password"] = data.Password
}
if data.PrivateKey != "" {
secretMap["private-key"] = data.PrivateKey
}
if data.PublicKey != "" {
secretMap["public-key"] = data.PublicKey
}
if data.Passphrase != "" {
secretMap["passphrase"] = data.Passphrase
}
}

default:
return nil, fmt.Errorf("unsupported credential subtype (%s)", e.SubType)
}

return secretMap, nil
}

// SetCredentialSecret mutates the Entry data to update the secret value for supported subtypes.
// It preserves existing fields and only updates the password/secret field.
func (e *Entry) SetCredentialSecret(secret string) error {
if e.GetType() != EntryCredentialType {
return fmt.Errorf("unsupported entry type (%s). Only %s is supported", e.GetType(), EntryCredentialType)
}

switch e.SubType {
case EntryCredentialSubTypeDefault:
if data, ok := e.GetCredentialDefaultData(); ok {
data.Password = secret
} else {
e.Data = &EntryCredentialDefaultData{Password: secret}
}
case EntryCredentialSubTypeAccessCode:
if data, ok := e.GetCredentialAccessCodeData(); ok {
data.Password = secret
} else {
e.Data = &EntryCredentialAccessCodeData{Password: secret}
}
default:
return fmt.Errorf("cannot set secret for credential subtype (%s)", e.SubType)
}

return nil
}

// validateEntry checks if an Entry has the required fields and valid type/subtype.
func (c *EntryCredentialService) validateEntry(entry *Entry) error {
if entry.VaultId == "" {
Expand Down
19 changes: 13 additions & 6 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,29 @@ func (z *ServerTime) UnmarshalJSON(d []byte) error {
return nil
}

dateParsed, err := time.Parse(serverTimeLayout, s)
if err != nil {
return err
for _, layout := range serverTimeLayouts {
if dateParsed, err := time.Parse(layout, s); err == nil {
z.Time = dateParsed
return nil
}
}

z.Time = dateParsed
return nil
return fmt.Errorf("cannot parse server time %q", s)
}

const (
serverPublicInfoEndpoint string = "api/public-instance-information"
serverPrivateInfoEndpoint string = "api/private-instance-information"
serverTimezonesEndpoint string = "/api/configuration/timezones"
serverTimeLayout string = "2006-01-02T15:04:05"
)

var serverTimeLayouts = []string{
time.RFC3339Nano,
"2006-01-02T15:04:05.9999999Z07:00",
"2006-01-02T15:04:05.9999999Z",
"2006-01-02T15:04:05",
}

// GetPublicServerInfo returns Server that contains public information on the DVLS instance.
func (c *Client) GetPublicServerInfo() (Server, error) {
var server Server
Expand Down
Loading