Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Added certificate entries #18

Merged
merged 2 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.0
0.7.0
89 changes: 89 additions & 0 deletions attachments.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
29 changes: 22 additions & 7 deletions dvls.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ type RequestError struct {
Err error
}

type RequestOptions struct {
ContentType string
RawBody bool
}

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}
Expand All @@ -37,20 +42,28 @@ 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"
var rawBody bool

if len(options) > 0 {
contentType = options[0].ContentType
rawBody = options[0].RawBody
}

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)
Expand All @@ -67,9 +80,11 @@ func (c *Client) rawRequest(url string, reqMethod string, reqBody io.Reader) (Re
}
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
Expand Down
2 changes: 0 additions & 2 deletions dvls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion dvlstypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ const (
type ServerConnectionSubType string

const (
ServerConnectionSubTypeDefault ServerConnectionSubType = "Default"
ServerConnectionSubTypeDefault ServerConnectionSubType = "Default"
ServerConnectionSubTypeCertificate ServerConnectionSubType = "Certificate"
)

type VaultVisibility int
Expand Down
44 changes: 44 additions & 0 deletions entries.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading