Update packages and add vendor directory

This commit is contained in:
Eddy Filip
2022-09-13 16:24:52 +03:00
parent 23b66f73af
commit 1d75f78891
3906 changed files with 1365387 additions and 465 deletions

21
vendor/github.com/1Password/connect-sdk-go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 1Password
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,860 @@
package connect
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"regexp"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
jaegerClientConfig "github.com/uber/jaeger-client-go/config"
"github.com/uber/jaeger-client-go/zipkin"
"github.com/1Password/connect-sdk-go/onepassword"
)
const (
defaultUserAgent = "connect-sdk-go/%s"
)
var (
vaultUUIDError = fmt.Errorf("malformed vault uuid provided")
itemUUIDError = fmt.Errorf("malformed item uuid provided")
fileUUIDError = fmt.Errorf("malformed file uuid provided")
)
// Client Represents an available 1Password Connect API to connect to
type Client interface {
GetVaults() ([]onepassword.Vault, error)
GetVault(uuid string) (*onepassword.Vault, error)
GetVaultByUUID(uuid string) (*onepassword.Vault, error)
GetVaultByTitle(title string) (*onepassword.Vault, error)
GetVaultsByTitle(uuid string) ([]onepassword.Vault, error)
GetItems(vaultQuery string) ([]onepassword.Item, error)
GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error)
GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error)
GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error)
GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error)
CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
DeleteItem(item *onepassword.Item, vaultQuery string) error
DeleteItemByID(itemUUID string, vaultQuery string) error
DeleteItemByTitle(title string, vaultQuery string) error
GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error)
GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error)
GetFileContent(file *onepassword.File) ([]byte, error)
DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error)
LoadStructFromItemByUUID(config interface{}, itemUUID string, vaultQuery string) error
LoadStructFromItemByTitle(config interface{}, itemTitle string, vaultQuery string) error
LoadStructFromItem(config interface{}, itemQuery string, vaultQuery string) error
LoadStruct(config interface{}) error
}
type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}
const (
envHostVariable = "OP_CONNECT_HOST"
envTokenVariable = "OP_CONNECT_TOKEN"
)
// NewClientFromEnvironment Returns a Secret Service client assuming that your
// jwt is set in the OP_TOKEN environment variable
func NewClientFromEnvironment() (Client, error) {
host, found := os.LookupEnv(envHostVariable)
if !found {
return nil, fmt.Errorf("There is no hostname available in the %q variable", envHostVariable)
}
token, found := os.LookupEnv(envTokenVariable)
if !found {
return nil, fmt.Errorf("There is no token available in the %q variable", envTokenVariable)
}
return NewClient(host, token), nil
}
// NewClient Returns a Secret Service client for a given url and jwt
func NewClient(url string, token string) Client {
return NewClientWithUserAgent(url, token, fmt.Sprintf(defaultUserAgent, SDKVersion))
}
// NewClientWithUserAgent Returns a Secret Service client for a given url and jwt and identifies with userAgent
func NewClientWithUserAgent(url string, token string, userAgent string) Client {
if !opentracing.IsGlobalTracerRegistered() {
cfg := jaegerClientConfig.Configuration{}
zipkinPropagator := zipkin.NewZipkinB3HTTPHeaderPropagator()
cfg.InitGlobalTracer(
userAgent,
jaegerClientConfig.Injector(opentracing.HTTPHeaders, zipkinPropagator),
jaegerClientConfig.Extractor(opentracing.HTTPHeaders, zipkinPropagator),
jaegerClientConfig.ZipkinSharedRPCSpan(true),
)
}
return &restClient{
URL: url,
Token: token,
userAgent: userAgent,
tracer: opentracing.GlobalTracer(),
client: http.DefaultClient,
}
}
type restClient struct {
URL string
Token string
userAgent string
tracer opentracing.Tracer
client httpClient
}
// GetVaults Get a list of all available vaults
func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
span := rs.tracer.StartSpan("GetVaults")
defer span.Finish()
vaultURL := fmt.Sprintf("/v1/vaults")
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var vaults []onepassword.Vault
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
return nil, err
}
return vaults, nil
}
// GetVault Get a vault based on its name or ID
func (rs *restClient) GetVault(vaultQuery string) (*onepassword.Vault, error) {
span := rs.tracer.StartSpan("GetVault")
defer span.Finish()
if vaultQuery == "" {
return nil, fmt.Errorf("Please provide either the vault name or its ID.")
}
if !isValidUUID(vaultQuery) {
return rs.GetVaultByTitle(vaultQuery)
}
return rs.GetVaultByUUID(vaultQuery)
}
func (rs *restClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) {
if !isValidUUID(uuid) {
return nil, vaultUUIDError
}
span := rs.tracer.StartSpan("GetVaultByUUID")
defer span.Finish()
vaultURL := fmt.Sprintf("/v1/vaults/%s", uuid)
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var vault onepassword.Vault
if err := parseResponse(response, http.StatusOK, &vault); err != nil {
return nil, err
}
return &vault, nil
}
func (rs *restClient) GetVaultByTitle(vaultName string) (*onepassword.Vault, error) {
span := rs.tracer.StartSpan("GetVaultByTitle")
defer span.Finish()
vaults, err := rs.GetVaultsByTitle(vaultName)
if err != nil {
return nil, err
}
if len(vaults) != 1 {
return nil, fmt.Errorf("Found %d vaults with title %q", len(vaults), vaultName)
}
return &vaults[0], nil
}
func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
span := rs.tracer.StartSpan("GetVaultsByTitle")
defer span.Finish()
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
itemURL := fmt.Sprintf("/v1/vaults?filter=%s", filter)
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var vaults []onepassword.Vault
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
return nil, err
}
return vaults, nil
}
func (rs *restClient) getVaultUUID(vaultQuery string) (string, error) {
if vaultQuery == "" {
return "", fmt.Errorf("Please provide either the vault name or its ID.")
}
if isValidUUID(vaultQuery) {
return vaultQuery, nil
}
vault, err := rs.GetVaultByTitle(vaultQuery)
if err != nil {
return "", err
}
return vault.ID, nil
}
// GetItem Get a specific Item from the 1Password Connect API by either title or UUID
func (rs *restClient) GetItem(itemQuery string, vaultQuery string) (*onepassword.Item, error) {
span := rs.tracer.StartSpan("GetItem")
defer span.Finish()
if itemQuery == "" {
return nil, fmt.Errorf("Please provide either the item name or its ID.")
}
if !isValidUUID(itemQuery) {
return rs.GetItemByTitle(itemQuery, vaultQuery)
}
return rs.GetItemByUUID(itemQuery, vaultQuery)
}
// GetItemByUUID Get a specific Item from the 1Password Connect API by its UUID
func (rs *restClient) GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error) {
if !isValidUUID(uuid) {
return nil, itemUUIDError
}
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("GetItemByUUID")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", vaultUUID, uuid)
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var item onepassword.Item
if err := parseResponse(response, http.StatusOK, &item); err != nil {
return nil, err
}
return &item, nil
}
func (rs *restClient) GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error) {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("GetItemByTitle")
defer span.Finish()
items, err := rs.GetItemsByTitle(title, vaultUUID)
if err != nil {
return nil, err
}
if len(items) != 1 {
return nil, fmt.Errorf("Found %d item(s) in vault %q with title %q", len(items), vaultUUID, title)
}
return &items[0], nil
}
func (rs *restClient) GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error) {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("GetItemsByTitle")
defer span.Finish()
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
itemURL := fmt.Sprintf("/v1/vaults/%s/items?filter=%s", vaultUUID, filter)
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var itemSummaries []onepassword.Item
if err := parseResponse(response, http.StatusOK, &itemSummaries); err != nil {
return nil, err
}
items := make([]onepassword.Item, len(itemSummaries))
for i, itemSummary := range itemSummaries {
tempItem, err := rs.GetItem(itemSummary.ID, itemSummary.Vault.ID)
if err != nil {
return nil, err
}
items[i] = *tempItem
}
return items, nil
}
func (rs *restClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("GetItems")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items", vaultUUID)
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var items []onepassword.Item
if err := parseResponse(response, http.StatusOK, &items); err != nil {
return nil, err
}
return items, nil
}
func (rs *restClient) getItemUUID(itemQuery, vaultQuery string) (string, error) {
if itemQuery == "" {
return "", fmt.Errorf("Please provide either the item name or its ID.")
}
if isValidUUID(itemQuery) {
return itemQuery, nil
}
item, err := rs.GetItemByTitle(itemQuery, vaultQuery)
if err != nil {
return "", err
}
return item.ID, nil
}
// CreateItem Create a new item in a specified vault
func (rs *restClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("CreateItem")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items", vaultUUID)
itemBody, err := json.Marshal(item)
if err != nil {
return nil, err
}
request, err := rs.buildRequest(http.MethodPost, itemURL, bytes.NewBuffer(itemBody), span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var newItem onepassword.Item
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
return nil, err
}
return &newItem, nil
}
// UpdateItem Update a new item in a specified vault
func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
span := rs.tracer.StartSpan("UpdateItem")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", item.Vault.ID, item.ID)
itemBody, err := json.Marshal(item)
if err != nil {
return nil, err
}
request, err := rs.buildRequest(http.MethodPut, itemURL, bytes.NewBuffer(itemBody), span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
var newItem onepassword.Item
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
return nil, err
}
return &newItem, nil
}
// DeleteItem Delete a new item in a specified vault
func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error {
span := rs.tracer.StartSpan("DeleteItem")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", item.Vault.ID, item.ID)
request, err := rs.buildRequest(http.MethodDelete, itemURL, http.NoBody, span)
if err != nil {
return err
}
response, err := rs.client.Do(request)
if err != nil {
return err
}
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
return err
}
return nil
}
// DeleteItemByID Delete a new item in a specified vault, specifying the item's uuid
func (rs *restClient) DeleteItemByID(itemUUID string, vaultQuery string) error {
if !isValidUUID(itemUUID) {
return itemUUIDError
}
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return err
}
span := rs.tracer.StartSpan("DeleteItemByID")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", vaultUUID, itemUUID)
request, err := rs.buildRequest(http.MethodDelete, itemURL, http.NoBody, span)
if err != nil {
return err
}
response, err := rs.client.Do(request)
if err != nil {
return err
}
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
return err
}
return nil
}
// DeleteItemByTitle Delete a new item in a specified vault, specifying the item's title
func (rs *restClient) DeleteItemByTitle(title string, vaultQuery string) error {
span := rs.tracer.StartSpan("DeleteItemByTitle")
defer span.Finish()
item, err := rs.GetItemByTitle(title, vaultQuery)
if err != nil {
return err
}
return rs.DeleteItem(item, item.Vault.ID)
}
func (rs *restClient) GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error) {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
itemUUID, err := rs.getItemUUID(itemQuery, vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("GetFiles")
defer span.Finish()
jsonURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files", vaultUUID, itemUUID)
request, err := rs.buildRequest(http.MethodGet, jsonURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
return nil, err
}
var files []onepassword.File
if err := parseResponse(response, http.StatusOK, &files); err != nil {
return nil, err
}
return files, nil
}
// GetFile Get a specific File in a specified item.
// This does not include the file contents. Call GetFileContent() to load the file's content.
func (rs *restClient) GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error) {
if !isValidUUID(uuid) {
return nil, fileUUIDError
}
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return nil, err
}
itemUUID, err := rs.getItemUUID(itemQuery, vaultQuery)
if err != nil {
return nil, err
}
span := rs.tracer.StartSpan("GetFile")
defer span.Finish()
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s", vaultUUID, itemUUID, uuid)
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
return nil, err
}
var file onepassword.File
if err := parseResponse(response, http.StatusOK, &file); err != nil {
return nil, err
}
return &file, nil
}
// GetFileContent retrieves the file's content.
// If the file's content have previously been fetched, those contents are returned without making another request.
func (rs *restClient) GetFileContent(file *onepassword.File) ([]byte, error) {
if content, err := file.Content(); err == nil {
return content, nil
}
response, err := rs.retrieveDocumentContent(file)
if err != nil {
return nil, err
}
content, err := readResponseBody(response, http.StatusOK)
if err != nil {
return nil, err
}
file.SetContent(content)
return content, nil
}
func (rs *restClient) DownloadFile(file *onepassword.File, targetDirectory string, overwriteIfExists bool) (string, error) {
response, err := rs.retrieveDocumentContent(file)
if err != nil {
return "", err
}
path := filepath.Join(targetDirectory, filepath.Base(file.Name))
var osFile *os.File
if overwriteIfExists {
osFile, err = createFile(path)
if err != nil {
return "", err
}
} else {
_, err = os.Stat(path)
if os.IsNotExist(err) {
osFile, err = createFile(path)
if err != nil {
return "", err
}
} else {
return "", fmt.Errorf("a file already exists under the %s path. In order to overwrite it, set `overwriteIfExists` to true", path)
}
}
defer osFile.Close()
if _, err = io.Copy(osFile, response.Body); err != nil {
return "", err
}
return path, nil
}
func (rs *restClient) retrieveDocumentContent(file *onepassword.File) (*http.Response, error) {
span := rs.tracer.StartSpan("GetFileContent")
defer span.Finish()
request, err := rs.buildRequest(http.MethodGet, file.ContentPath, http.NoBody, span)
if err != nil {
return nil, err
}
response, err := rs.client.Do(request)
if err != nil {
return nil, err
}
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
return nil, err
}
return response, nil
}
func createFile(path string) (*os.File, error) {
osFile, err := os.Create(path)
if err != nil {
return nil, err
}
err = os.Chmod(path, 0600)
if err != nil {
return nil, err
}
return osFile, nil
}
func (rs *restClient) buildRequest(method string, path string, body io.Reader, span opentracing.Span) (*http.Request, error) {
url := fmt.Sprintf("%s%s", rs.URL, path)
request, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rs.Token))
request.Header.Set("User-Agent", rs.userAgent)
ext.SpanKindRPCClient.Set(span)
ext.HTTPUrl.Set(span, path)
ext.HTTPMethod.Set(span, method)
rs.tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))
return request, nil
}
func loadToStruct(item *parsedItem, config reflect.Value) error {
t := config.Type()
for i := 0; i < t.NumField(); i++ {
value := config.Field(i)
field := t.Field(i)
if !value.CanSet() {
return fmt.Errorf("cannot load config into private fields")
}
item.fields = append(item.fields, &field)
item.values = append(item.values, &value)
}
return nil
}
// LoadStructFromItem Load configuration values based on struct tag from one 1P item.
// It accepts as parameters item title/UUID and vault title/UUID.
func (rs *restClient) LoadStructFromItem(i interface{}, itemQuery string, vaultQuery string) error {
if itemQuery == "" {
return fmt.Errorf("Please provide either the item name or its ID.")
}
if isValidUUID(itemQuery) {
return rs.LoadStructFromItemByUUID(i, itemQuery, vaultQuery)
}
return rs.LoadStructFromItemByTitle(i, itemQuery, vaultQuery)
}
// LoadStructFromItemByUUID Load configuration values based on struct tag from one 1P item.
func (rs *restClient) LoadStructFromItemByUUID(i interface{}, itemUUID string, vaultQuery string) error {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return err
}
if !isValidUUID(itemUUID) {
return itemUUIDError
}
config, err := checkStruct(i)
if err != nil {
return err
}
item := parsedItem{}
item.itemUUID = itemUUID
item.vaultUUID = vaultUUID
if err := loadToStruct(&item, config); err != nil {
return err
}
if err := setValuesForTag(rs, &item, false); err != nil {
return err
}
return nil
}
// LoadStructFromItemByTitle Load configuration values based on struct tag from one 1P item
func (rs *restClient) LoadStructFromItemByTitle(i interface{}, itemTitle string, vaultQuery string) error {
vaultUUID, err := rs.getVaultUUID(vaultQuery)
if err != nil {
return err
}
config, err := checkStruct(i)
if err != nil {
return err
}
item := parsedItem{}
item.itemTitle = itemTitle
item.vaultUUID = vaultUUID
if err := loadToStruct(&item, config); err != nil {
return err
}
if err := setValuesForTag(rs, &item, true); err != nil {
return err
}
return nil
}
// LoadStruct Load configuration values based on struct tag
func (rs *restClient) LoadStruct(i interface{}) error {
config, err := checkStruct(i)
if err != nil {
return err
}
t := config.Type()
// Multiple fields may be from a single item so we will collect them
items := map[string]parsedItem{}
// Fetch the Vault from the environment
vaultUUID, envVarFound := os.LookupEnv(envVaultVar)
for i := 0; i < t.NumField(); i++ {
value := config.Field(i)
field := t.Field(i)
tag := field.Tag.Get(itemTag)
if tag == "" {
continue
}
if !value.CanSet() {
return fmt.Errorf("Cannot load config into private fields")
}
itemVault, err := vaultUUIDForField(&field, vaultUUID, envVarFound)
if err != nil {
return err
}
if !isValidUUID(itemVault) {
return vaultUUIDError
}
key := fmt.Sprintf("%s/%s", itemVault, tag)
parsed := items[key]
parsed.vaultUUID = itemVault
parsed.itemTitle = tag
parsed.fields = append(parsed.fields, &field)
parsed.values = append(parsed.values, &value)
items[key] = parsed
}
for _, item := range items {
if err := setValuesForTag(rs, &item, true); err != nil {
return err
}
}
return nil
}
func parseResponse(resp *http.Response, expectedStatusCode int, result interface{}) error {
body, err := readResponseBody(resp, expectedStatusCode)
if err != nil {
return err
}
if result != nil {
if err := json.Unmarshal(body, result); err != nil {
return fmt.Errorf("decoding response: %s", err)
}
}
return nil
}
func readResponseBody(resp *http.Response, expectedStatusCode int) ([]byte, error) {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != expectedStatusCode {
var errResp *onepassword.Error
if err := json.Unmarshal(body, &errResp); err != nil {
return nil, fmt.Errorf("decoding error response: %s", err)
}
return nil, errResp
}
return body, nil
}
func isValidUUID(u string) bool {
r := regexp.MustCompile("^[a-z0-9]{26}$")
return r.MatchString(u)
}

View File

@@ -0,0 +1,209 @@
package connect
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/1Password/connect-sdk-go/onepassword"
)
const (
vaultTag = "opvault"
itemTag = "opitem"
sectionTag = "opsection"
fieldTag = "opfield"
urlTag = "opurl"
envVaultVar = "OP_VAULT"
)
type parsedItem struct {
vaultUUID string
itemUUID string
itemTitle string
fields []*reflect.StructField
values []*reflect.Value
}
func checkStruct(i interface{}) (reflect.Value, error) {
configP := reflect.ValueOf(i)
if configP.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("you must pass a pointer to Config struct")
}
config := configP.Elem()
if config.Kind() != reflect.Struct {
return reflect.Value{}, fmt.Errorf("config values can only be loaded into a struct")
}
return config, nil
}
func vaultUUIDForField(field *reflect.StructField, vaultUUID string, envVaultFound bool) (string, error) {
// Check to see if a specific vault has been specified on the field
// If the env vault id has not been found and item doesn't have a vault
// return an error
if vaultUUIDTag := field.Tag.Get(vaultTag); vaultUUIDTag == "" {
if !envVaultFound {
return "", fmt.Errorf("There is no vault for %q field", field.Name)
}
} else {
return vaultUUIDTag, nil
}
return vaultUUID, nil
}
func setValuesForTag(client Client, parsedItem *parsedItem, byTitle bool) error {
var item *onepassword.Item
var err error
if byTitle {
item, err = client.GetItemByTitle(parsedItem.itemTitle, parsedItem.vaultUUID)
} else {
item, err = client.GetItem(parsedItem.itemUUID, parsedItem.vaultUUID)
}
if err != nil {
return err
}
for i, field := range parsedItem.fields {
value := parsedItem.values[i]
if field.Type == reflect.TypeOf(onepassword.ItemURL{}) {
url := &onepassword.ItemURL{
Primary: urlPrimaryForName(field.Tag.Get(urlTag), item.URLs),
Label: urlLabelForName(field.Tag.Get(urlTag), item.URLs),
URL: urlURLForName(field.Tag.Get(urlTag), item.URLs),
}
value.Set(reflect.ValueOf(*url))
continue
}
path := fmt.Sprintf("%s.%s", field.Tag.Get(sectionTag), field.Tag.Get(fieldTag))
if path == "." {
if field.Type == reflect.TypeOf(onepassword.Item{}) {
value.Set(reflect.ValueOf(*item))
continue
}
return fmt.Errorf("There is no %q specified for %q", fieldTag, field.Name)
}
if strings.HasSuffix(path, ".") {
if field.Type == reflect.TypeOf(onepassword.ItemSection{}) {
section := &onepassword.ItemSection{
ID: sectionIDForName(field.Tag.Get(sectionTag), item.Sections),
Label: sectionLabelForName(field.Tag.Get(sectionTag), item.Sections),
}
value.Set(reflect.ValueOf(*section))
continue
}
}
sectionID := sectionIDForName(field.Tag.Get(sectionTag), item.Sections)
for _, f := range item.Fields {
fieldSectionID := ""
if f.Section != nil {
fieldSectionID = f.Section.ID
}
if fieldSectionID == sectionID && f.Label == field.Tag.Get(fieldTag) {
if err := setValue(value, f.Value); err != nil {
return err
}
break
}
}
}
return nil
}
func setValue(value *reflect.Value, toSet string) error {
switch value.Kind() {
case reflect.String:
value.SetString(toSet)
case reflect.Int:
v, err := strconv.Atoi(toSet)
if err != nil {
return err
}
value.SetInt(int64(v))
default:
return fmt.Errorf("Unsupported type %q. Only string, int64, and onepassword.Item are supported", value.Kind())
}
return nil
}
func sectionIDForName(name string, sections []*onepassword.ItemSection) string {
if sections == nil {
return ""
}
for _, s := range sections {
if name == strings.ToLower(s.Label) {
return s.ID
}
}
return ""
}
func sectionLabelForName(name string, sections []*onepassword.ItemSection) string {
if sections == nil {
return ""
}
for _, s := range sections {
if name == strings.ToLower(s.Label) {
return s.Label
}
}
return ""
}
func urlPrimaryForName(name string, itemURLs []onepassword.ItemURL) bool {
if itemURLs == nil {
return false
}
for _, url := range itemURLs {
if url.Label == strings.ToLower(name) {
return url.Primary
}
}
return false
}
func urlLabelForName(name string, itemURLs []onepassword.ItemURL) string {
if itemURLs == nil {
return ""
}
for _, url := range itemURLs {
if url.Label == strings.ToLower(name) {
return url.Label
}
}
return ""
}
func urlURLForName(name string, itemURLs []onepassword.ItemURL) string {
if itemURLs == nil {
return ""
}
for _, url := range itemURLs {
if url.Label == strings.ToLower(name) {
return url.URL
}
}
return ""
}

View File

@@ -0,0 +1,104 @@
package connect
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
)
// SDKVersion is the latest Semantic Version of the library
// Do not rename this variable without changing the regex in the Makefile
const SDKVersion = "1.5.0"
const VersionHeaderKey = "1Password-Connect-Version"
// expectMinimumConnectVersion returns an error if the provided minimum version for Connect is lower than the version
// reported in the response from Connect.
func expectMinimumConnectVersion(resp *http.Response, minimumVersion version) error {
serverVersion, err := getServerVersion(resp)
if err != nil {
// Return gracefully if server version cannot be determined reliably
return nil
}
if !serverVersion.IsGreaterOrEqualThan(minimumVersion) {
return fmt.Errorf("need at least version %s of Connect for this function, detected version %s. Please update your Connect server", minimumVersion, serverVersion)
}
return nil
}
func getServerVersion(resp *http.Response) (serverVersion, error) {
versionHeader := resp.Header.Get(VersionHeaderKey)
if versionHeader == "" {
// The last version without the version header was v1.2.0
return serverVersion{
version: version{1, 2, 0},
orEarlier: true,
}, nil
}
return parseServerVersion(versionHeader)
}
type version struct {
major int
minor int
patch int
}
// serverVersion describes the version reported by the server.
type serverVersion struct {
version
// orEarlier is true if the version is derived from the lack of a version header from the server.
orEarlier bool
}
func (v version) String() string {
return fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch)
}
func (v serverVersion) String() string {
if v.orEarlier {
return v.version.String() + " (or earlier)"
}
return v.version.String()
}
// IsGreaterOrEqualThan returns true if the lefthand-side version is equal to or or a higher version than the provided
// minimum according to the semantic versioning rules.
func (v version) IsGreaterOrEqualThan(min version) bool {
if v.major != min.major {
// Different major version
return v.major > min.major
}
if v.minor != min.minor {
// Same major, but different minor version
return v.minor > min.minor
}
// Same major and minor version
return v.patch >= min.patch
}
func parseServerVersion(v string) (serverVersion, error) {
spl := strings.Split(v, ".")
if len(spl) != 3 {
return serverVersion{}, errors.New("wrong length")
}
var res [3]int
for i := range res {
tmp, err := strconv.Atoi(spl[i])
if err != nil {
return serverVersion{}, err
}
res[i] = tmp
}
return serverVersion{
version: version{
major: res[0],
minor: res[1],
patch: res[2],
},
}, nil
}

View File

@@ -0,0 +1,21 @@
package onepassword
import "fmt"
// Error is an error returned by the Connect API.
type Error struct {
StatusCode int `json:"status"`
Message string `json:"message"`
}
func (e *Error) Error() string {
return fmt.Sprintf("status %d: %s", e.StatusCode, e.Message)
}
func (e *Error) Is(target error) bool {
t, ok := target.(*Error)
if !ok {
return false
}
return t.Message == e.Message && t.StatusCode == e.StatusCode
}

View File

@@ -0,0 +1,49 @@
package onepassword
import (
"encoding/json"
"errors"
)
type File struct {
ID string `json:"id"`
Name string `json:"name"`
Section *ItemSection `json:"section,omitempty"`
Size int `json:"size"`
ContentPath string `json:"content_path"`
content []byte
}
func (f *File) UnmarshalJSON(data []byte) error {
var jsonFile struct {
ID string `json:"id"`
Name string `json:"name"`
Section *ItemSection `json:"section,omitempty"`
Size int `json:"size"`
ContentPath string `json:"content_path"`
Content []byte `json:"content,omitempty"`
}
if err := json.Unmarshal(data, &jsonFile); err != nil {
return err
}
f.ID = jsonFile.ID
f.Name = jsonFile.Name
f.Section = jsonFile.Section
f.Size = jsonFile.Size
f.ContentPath = jsonFile.ContentPath
f.content = jsonFile.Content
return nil
}
// Content returns the content of the file if they have been loaded and returns an error if they have not been loaded.
// Use `client.GetFileContent(file *File)` instead to make sure the content is fetched automatically if not present.
func (f *File) Content() ([]byte, error) {
if f.content == nil {
return nil, errors.New("file content not loaded")
}
return f.content, nil
}
func (f *File) SetContent(content []byte) {
f.content = content
}

View File

@@ -0,0 +1,167 @@
package onepassword
import (
"encoding/json"
"strings"
"time"
)
// ItemCategory Represents the template of the Item
type ItemCategory string
const (
Login ItemCategory = "LOGIN"
Password ItemCategory = "PASSWORD"
ApiCredential ItemCategory = "API_CREDENTIAL"
Server ItemCategory = "SERVER"
Database ItemCategory = "DATABASE"
CreditCard ItemCategory = "CREDIT_CARD"
Membership ItemCategory = "MEMBERSHIP"
Passport ItemCategory = "PASSPORT"
SoftwareLicense ItemCategory = "SOFTWARE_LICENSE"
OutdoorLicense ItemCategory = "OUTDOOR_LICENSE"
SecureNote ItemCategory = "SECURE_NOTE"
WirelessRouter ItemCategory = "WIRELESS_ROUTER"
BankAccount ItemCategory = "BANK_ACCOUNT"
DriverLicense ItemCategory = "DRIVER_LICENSE"
Identity ItemCategory = "IDENTITY"
RewardProgram ItemCategory = "REWARD_PROGRAM"
Document ItemCategory = "DOCUMENT"
EmailAccount ItemCategory = "EMAIL_ACCOUNT"
SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER"
MedicalRecord ItemCategory = "MEDICAL_RECORD"
SSHKey ItemCategory = "SSH_KEY"
Custom ItemCategory = "CUSTOM"
)
// UnmarshalJSON Unmarshall Item Category enum strings to Go string enums
func (ic *ItemCategory) UnmarshalJSON(b []byte) error {
var s string
json.Unmarshal(b, &s)
category := ItemCategory(s)
switch category {
case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense,
OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram,
Document, EmailAccount, SocialSecurityNumber, ApiCredential, MedicalRecord, SSHKey:
*ic = category
default:
*ic = Custom
}
return nil
}
// Item represents an item returned to the consumer
type Item struct {
ID string `json:"id"`
Title string `json:"title"`
URLs []ItemURL `json:"urls,omitempty"`
Favorite bool `json:"favorite,omitempty"`
Tags []string `json:"tags,omitempty"`
Version int `json:"version,omitempty"`
Vault ItemVault `json:"vault"`
Category ItemCategory `json:"category,omitempty"` // TODO: switch this to `category`
Sections []*ItemSection `json:"sections,omitempty"`
Fields []*ItemField `json:"fields,omitempty"`
Files []*File `json:"files,omitempty"`
LastEditedBy string `json:"lastEditedBy,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
// Deprecated: Connect does not return trashed items.
Trashed bool `json:"trashed,omitempty"`
}
// ItemVault represents the Vault the Item is found in
type ItemVault struct {
ID string `json:"id"`
}
// ItemURL is a simplified item URL
type ItemURL struct {
Primary bool `json:"primary,omitempty"`
Label string `json:"label,omitempty"`
URL string `json:"href"`
}
// ItemSection Representation of a Section on an item
type ItemSection struct {
ID string `json:"id,omitempty"`
Label string `json:"label,omitempty"`
}
// GeneratorRecipe Representation of a "recipe" used to generate a field
type GeneratorRecipe struct {
Length int `json:"length,omitempty"`
CharacterSets []string `json:"characterSets,omitempty"`
ExcludeCharacters string `json:"excludeCharacters,omitempty"`
}
// ItemField Representation of a single field on an Item
type ItemField struct {
ID string `json:"id"`
Section *ItemSection `json:"section,omitempty"`
Type string `json:"type"`
Purpose string `json:"purpose,omitempty"`
Label string `json:"label,omitempty"`
Value string `json:"value,omitempty"`
Generate bool `json:"generate,omitempty"`
Recipe *GeneratorRecipe `json:"recipe,omitempty"`
Entropy float64 `json:"entropy,omitempty"`
TOTP string `json:"totp,omitempty"`
}
// GetValue Retrieve the value of a field on the item by its label. To specify a
// field from a specific section pass in <section label>.<field label>. If
// no field matching the selector is found return "".
func (i *Item) GetValue(field string) string {
if i == nil || len(i.Fields) == 0 {
return ""
}
sectionFilter := false
sectionLabel := ""
fieldLabel := field
if strings.Contains(field, ".") {
parts := strings.Split(field, ".")
// Test to make sure the . isn't the last character
if len(parts) == 2 {
sectionFilter = true
sectionLabel = parts[0]
fieldLabel = parts[1]
}
}
for _, f := range i.Fields {
if sectionFilter {
if f.Section != nil {
if sectionLabel != i.SectionLabelForID(f.Section.ID) {
continue
}
}
}
if fieldLabel == f.Label {
return f.Value
}
}
return ""
}
func (i *Item) SectionLabelForID(id string) string {
if i != nil || len(i.Sections) > 0 {
for _, s := range i.Sections {
if s.ID == id {
return s.Label
}
}
}
return ""
}

View File

@@ -0,0 +1,46 @@
package onepassword
import (
"encoding/json"
"time"
)
// Vault represents a 1password Vault
type Vault struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AttrVersion int `json:"attributeVersion,omitempty"`
ContentVersoin int `json:"contentVersion,omitempty"`
Items int `json:"items,omitempty"`
Type VaultType `json:"type,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
}
// VaultType Representation of what the Vault Type is
type VaultType string
const (
PersonalVault VaultType = "PERSONAL"
EveryoneVault VaultType = "EVERYONE"
TransferVault VaultType = "TRANSFER"
UserCreatedVault VaultType = "USER_CREATED"
UnknownVault VaultType = "UNKNOWN"
)
// UnmarshalJSON Unmarshall Vault Type enum strings to Go string enums
func (vt *VaultType) UnmarshalJSON(b []byte) error {
var s string
json.Unmarshal(b, &s)
vaultType := VaultType(s)
switch vaultType {
case PersonalVault, EveryoneVault, TransferVault, UserCreatedVault:
*vt = vaultType
default:
*vt = UnknownVault
}
return nil
}