Move code from Internal Git

This commit is contained in:
Samuel Lorch 2021-08-30 13:52:31 +02:00
parent 4457df3e5f
commit ff29c83d56
26 changed files with 2008 additions and 0 deletions

57
api.go Normal file
View file

@ -0,0 +1,57 @@
package passbolt
import (
"context"
"encoding/json"
"fmt"
"net/http"
)
// APIResponse is the Struct representation of a Json Response
type APIResponse struct {
Header APIHeader `json:"header"`
Body json.RawMessage `json:"body"`
}
// APIHeader is the Struct representation of the Header of a APIResponse
type APIHeader struct {
ID string `json:"id"`
Status string `json:"status"`
Servertime int `json:"servertime"`
Action string `json:"action"`
Message string `json:"message"`
URL string `json:"url"`
Code int `json:"code"`
}
// DoCustomRequest Executes a Custom Request and returns a APIResponse
func (c *Client) DoCustomRequest(ctx context.Context, method, path, version string, body interface{}, opts interface{}) (*APIResponse, error) {
_, response, err := c.DoCustomRequestAndReturnRawResponse(ctx, method, path, version, body, opts)
return response, err
}
func (c *Client) DoCustomRequestAndReturnRawResponse(ctx context.Context, method, path, version string, body interface{}, opts interface{}) (*http.Response, *APIResponse, error) {
u, err := addOptions(path, version, opts)
if err != nil {
return nil, nil, fmt.Errorf("Adding Request Options: %w", err)
}
req, err := c.newRequest(method, u, body)
if err != nil {
return nil, nil, fmt.Errorf("Creating New Request: %w", err)
}
var res APIResponse
r, err := c.do(ctx, req, &res)
if err != nil {
return r, &res, fmt.Errorf("Doing Request: %w", err)
}
if res.Header.Status == "success" {
return r, &res, nil
} else if res.Header.Status == "error" {
return r, &res, fmt.Errorf("%w: Message: %v, Body: %v", ErrAPIResponseErrorStatusCode, res.Header.Message, string(res.Body))
} else {
return r, &res, fmt.Errorf("%w: Message: %v, Body: %v", ErrAPIResponseUnknownStatusCode, res.Header.Message, string(res.Body))
}
}

206
auth.go Normal file
View file

@ -0,0 +1,206 @@
package passbolt
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/gopenpgp/v2/helper"
)
// PublicKeyReponse the Body of a Public Key Api Request
type PublicKeyReponse struct {
Fingerprint string `json:"fingerprint"`
Keydata string `json:"keydata"`
}
// Login is used for login
type Login struct {
Auth *GPGAuth `json:"gpg_auth"`
}
// GPGAuth is used for login
type GPGAuth struct {
KeyID string `json:"keyid"`
Token string `json:"user_token_result,omitempty"`
}
// TODO add Server Verification Function
// GetPublicKey gets the Public Key and Fingerprint of the Passbolt instance
func (c *Client) GetPublicKey(ctx context.Context) (string, string, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "auth/verify.json", "v2", nil, nil)
if err != nil {
return "", "", fmt.Errorf("Doing Request: %w", err)
}
var body PublicKeyReponse
err = json.Unmarshal(msg.Body, &body)
if err != nil {
return "", "", fmt.Errorf("Parsing JSON: %w", err)
}
// TODO check if that Fingerpirnt is actually from the Publickey
return body.Keydata, body.Fingerprint, nil
}
// CheckSession Check to see if you have a Valid Session
func (c *Client) CheckSession(ctx context.Context) bool {
_, err := c.DoCustomRequest(ctx, "GET", "auth/is-authenticated.json", "v2", nil, nil)
return err == nil
}
// Login gets a Session and CSRF Token from Passbolt and Stores them in the Clients Cookie Jar
func (c *Client) Login(ctx context.Context) error {
privateKeyObj, err := crypto.NewKeyFromArmored(c.userPrivateKey)
if err != nil {
return fmt.Errorf("Parsing User Private Key: %w", err)
}
data := Login{&GPGAuth{KeyID: privateKeyObj.GetFingerprint()}}
res, _, err := c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/login.json", "v2", data, nil)
if err != nil && !strings.Contains(err.Error(), "Error API JSON Response Status: Message: The authentication failed.") {
return fmt.Errorf("Doing Stage 1 Request: %w", err)
}
encAuthToken := res.Header.Get("X-GPGAuth-User-Auth-Token")
if encAuthToken == "" {
return fmt.Errorf("Got Empty X-GPGAuth-User-Auth-Token Header")
}
c.log("Got Encrypted Auth Token: %v", encAuthToken)
encAuthToken, err = url.QueryUnescape(encAuthToken)
if err != nil {
return fmt.Errorf("Unescaping User Auth Token: %w", err)
}
encAuthToken = strings.ReplaceAll(encAuthToken, "\\ ", " ")
authToken, err := helper.DecryptMessageArmored(c.userPrivateKey, c.userPassword, encAuthToken)
if err != nil {
return fmt.Errorf("Decrypting User Auth Token: %w", err)
}
c.log("Decrypted Auth Token: %v", authToken)
err = checkAuthTokenFormat(authToken)
if err != nil {
return fmt.Errorf("Checking Auth Token Format: %w", err)
}
data.Auth.Token = string(authToken)
res, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/login.json", "v2", data, nil)
if err != nil {
return fmt.Errorf("Doing Stage 2 Request: %w", err)
}
c.log("Got Cookies: %+v", res.Cookies())
for _, cookie := range res.Cookies() {
if cookie.Name == "passbolt_session" {
c.sessionToken = *cookie
// Session Cookie in older Passbolt Versions
} else if cookie.Name == "CAKEPHP" {
c.sessionToken = *cookie
}
}
if c.sessionToken.Name == "" {
return fmt.Errorf("Cannot Find Session Cookie!")
}
// Do Mfa Here if ever
// You have to get a make GET Request to get the CSRF Token which is Required for Write Operations
msg, apiMsg, err := c.DoCustomRequestAndReturnRawResponse(ctx, "GET", "/users/me.json", "v2", nil, nil)
if err != nil {
c.log("is MFA Enabled? That is not yet Supported!")
return fmt.Errorf("Getting CSRF Token: %w", err)
}
for _, cookie := range msg.Cookies() {
if cookie.Name == "csrfToken" {
c.csrfToken = *cookie
}
}
if c.csrfToken.Name == "" {
return fmt.Errorf("Cannot Find csrfToken Cookie!")
}
// Get Users Own Public Key from Server
var user User
err = json.Unmarshal(apiMsg.Body, &user)
if err != nil {
return fmt.Errorf("Parsing User 'Me' JSON from API Request: %w", err)
}
// Validate that this Publickey that the Server gave us actually Matches our Privatekey
randomString, err := randStringBytesRmndr(50)
if err != nil {
return fmt.Errorf("Generating Random String as PublicKey Validation Message: %w", err)
}
armor, err := helper.EncryptMessageArmored(user.GPGKey.ArmoredKey, randomString)
if err != nil {
return fmt.Errorf("Encryping PublicKey Validation Message: %w", err)
}
decrypted, err := helper.DecryptMessageArmored(c.userPrivateKey, c.userPassword, armor)
if err != nil {
return fmt.Errorf("Decrypting PublicKey Validation Message (you might be getting Hacked): %w", err)
}
if decrypted != randomString {
return fmt.Errorf("Decrypted PublicKey Validation Message does not Match Original (you might be getting Hacked): %w", err)
}
// Insert PublicKey into Client after checking it to Prevent ignored errors leading to proceding with a potentially Malicious PublicKey
c.userPublicKey = user.GPGKey.ArmoredKey
c.userID = user.ID
return nil
}
// Logout closes the current Session on the Passbolt server
func (c *Client) Logout(ctx context.Context) error {
_, err := c.DoCustomRequest(ctx, "GET", "/auth/logout.json", "v2", nil, nil)
if err != nil {
return fmt.Errorf("Doing Logout Request: %w", err)
}
c.sessionToken = http.Cookie{}
c.csrfToken = http.Cookie{}
return nil
}
func (c *Client) GetUserID() string {
return c.userID
}
func checkAuthTokenFormat(authToken string) error {
splitAuthToken := strings.Split(authToken, "|")
if len(splitAuthToken) != 4 {
return fmt.Errorf("Auth Token Has Wrong amount of Fields")
}
if splitAuthToken[0] != splitAuthToken[3] {
return fmt.Errorf("Auth Token Version Fields Don't match")
}
if !strings.HasPrefix(splitAuthToken[0], "gpgauth") {
return fmt.Errorf("Auth Token Version does not start with 'gpgauth'")
}
length, err := strconv.Atoi(splitAuthToken[1])
if err != nil {
return fmt.Errorf("Cannot Convert Auth Token Length Field to int: %w", err)
}
if len(splitAuthToken[2]) != length {
return fmt.Errorf("Auth Token Data Length does not Match Length Field")
}
return nil
}

152
client.go Normal file
View file

@ -0,0 +1,152 @@
package passbolt
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/google/go-querystring/query"
)
// Client is a Client struct for the Passbolt api
type Client struct {
baseURL *url.URL
userAgent string
httpClient *http.Client
sessionToken http.Cookie
csrfToken http.Cookie
// for some reason []byte is used for Passwords in gopenpgp instead of string like they do for keys...
userPassword []byte
userPrivateKey string
userPublicKey string
userID string
// Enable Debug Logging
Debug bool
}
// NewClient Returns a new Passbolt Client
func NewClient(BaseURL *url.URL, httpClient *http.Client, UserAgent, UserPrivateKey, UserPassword string) (*Client, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
if UserAgent == "" {
UserAgent = "goPassboltClient/1.0"
}
// Verify that the Given Privatekey and Password are valid and work Together
privateKeyObj, err := crypto.NewKeyFromArmored(UserPrivateKey)
if err != nil {
return nil, fmt.Errorf("Unable to Create Key From UserPrivateKey string: %w", err)
}
unlockedKeyObj, err := privateKeyObj.Unlock([]byte(UserPassword))
if err != nil {
return nil, fmt.Errorf("Unable to Unlock UserPrivateKey using UserPassword: %w", err)
}
privateKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
if err != nil {
return nil, fmt.Errorf("Unable to Create a new Key Ring using the unlocked UserPrivateKey: %w", err)
}
// Cleanup Secrets
privateKeyRing.ClearPrivateParams()
// Create Client Object
c := &Client{
httpClient: httpClient,
baseURL: BaseURL,
userAgent: UserAgent,
userPassword: []byte(UserPassword),
userPrivateKey: UserPrivateKey,
}
return c, err
}
func (c *Client) newRequest(method, path string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(path)
if err != nil {
return nil, fmt.Errorf("Parsing URL: %w", err)
}
u := c.baseURL.ResolveReference(rel)
var buf io.ReadWriter
if body != nil {
buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, fmt.Errorf("JSON Encoding Request: %w", err)
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, fmt.Errorf("Creating HTTP Request: %w", err)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", c.userAgent)
req.Header.Set("X-CSRF-Token", c.csrfToken.Value)
req.AddCookie(&c.sessionToken)
req.AddCookie(&c.csrfToken)
return req, nil
}
func (c *Client) do(ctx context.Context, req *http.Request, v *APIResponse) (*http.Response, error) {
req = req.WithContext(ctx)
resp, err := c.httpClient.Do(req)
if err != nil {
select {
case <-ctx.Done():
return nil, fmt.Errorf("Request Context: %w", ctx.Err())
default:
return nil, fmt.Errorf("Request: %w", err)
}
}
defer func() {
resp.Body.Close()
}()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return resp, fmt.Errorf("Error Reading Resopnse Body: %w", err)
}
err = json.Unmarshal(bodyBytes, v)
if err != nil {
return resp, fmt.Errorf("Unable to Parse JSON API Response with HTTP Status Code %v: %w", resp.StatusCode, err)
}
return resp, nil
}
func (c *Client) log(msg string, args ...interface{}) {
if !c.Debug {
return
}
fmt.Printf("[go-passbolt] "+msg+"\n", args...)
}
func addOptions(s, version string, opt interface{}) (string, error) {
u, err := url.Parse(s)
if err != nil {
return s, fmt.Errorf("Parsing URL: %w", err)
}
vs, err := query.Values(opt)
if err != nil {
return s, fmt.Errorf("Getting URL Query Values: %w", err)
}
if version != "" {
vs.Add("api-version", version)
}
u.RawQuery = vs.Encode()
return u.String(), nil
}

80
comments.go Normal file
View file

@ -0,0 +1,80 @@
package passbolt
import (
"context"
"encoding/json"
)
// Comment is a Comment
type Comment struct {
ID string `json:"id,omitempty"`
ParentID string `json:"parent_id,omitempty"`
ForeignKey string `json:"foreign_key,omitempty"`
Content string `json:"content,omitempty"`
ForeignModel string `json:"foreign_model,omitempty"`
Created *Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UserID string `json:"user_id,omitempty"`
Description string `json:"description,omitempty"`
Modified *Time `json:"modified,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"`
Children []Comment `json:"children,omitempty"`
}
// GetCommentsOptions are all available query parameters
type GetCommentsOptions struct {
ContainCreator bool `url:"contain[creator],omitempty"`
ContainModifier bool `url:"contain[modifier],omitempty"`
}
// GetComments gets all Passbolt Comments an The Specified Resource
func (c *Client) GetComments(ctx context.Context, resourceID string, opts *GetCommentsOptions) ([]Comment, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/comments/resource/"+resourceID+".json", "v2", nil, opts)
if err != nil {
return nil, err
}
var comments []Comment
err = json.Unmarshal(msg.Body, &comments)
if err != nil {
return nil, err
}
return comments, nil
}
// CreateComment Creates a new Passbolt Comment
func (c *Client) CreateComment(ctx context.Context, resourceID string, comment Comment) (*Comment, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/comments/resource/"+resourceID+".json", "v2", comment, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &comment)
if err != nil {
return nil, err
}
return &comment, nil
}
// UpdateComment Updates a existing Passbolt Comment
func (c *Client) UpdateComment(ctx context.Context, commentID string, comment Comment) (*Comment, error) {
msg, err := c.DoCustomRequest(ctx, "PUT", "/comments/"+commentID+".json", "v2", comment, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &comment)
if err != nil {
return nil, err
}
return &comment, nil
}
// DeleteComment Deletes a Passbolt Comment
func (c *Client) DeleteComment(ctx context.Context, commentID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/comments/"+commentID+".json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}

20
encryption.go Normal file
View file

@ -0,0 +1,20 @@
package passbolt
import "github.com/ProtonMail/gopenpgp/v2/helper"
// EncryptMessage encrypts a message using the users public key and then signes the message using the users private key
func (c *Client) EncryptMessage(message string) (string, error) {
return helper.EncryptSignMessageArmored(c.userPublicKey, c.userPrivateKey, c.userPassword, message)
}
// EncryptMessageWithPublicKey encrypts a message using the provided public key and then signes the message using the users private key
func (c *Client) EncryptMessageWithPublicKey(publickey, message string) (string, error) {
return helper.EncryptSignMessageArmored(publickey, c.userPrivateKey, c.userPassword, message)
}
// DecryptMessage decrypts a message using the users Private Key and Validates its Signature using the users public key
func (c *Client) DecryptMessage(message string) (string, error) {
// We cant Verify the signature as we don't store other users public keys locally and don't know which user did encrypt it
//return helper.DecryptVerifyMessageArmored(c.userPublicKey, c.userPrivateKey, c.userPassword, message)
return helper.DecryptMessageArmored(c.userPrivateKey, c.userPassword, message)
}

8
errors.go Normal file
View file

@ -0,0 +1,8 @@
package passbolt
import "errors"
var (
ErrAPIResponseErrorStatusCode = errors.New("Error API JSON Response Status")
ErrAPIResponseUnknownStatusCode = errors.New("Unknown API JSON Response Status")
)

39
favorites.go Normal file
View file

@ -0,0 +1,39 @@
package passbolt
import (
"context"
"encoding/json"
)
// Favorite is a Favorite
type Favorite struct {
ID string `json:"id,omitempty"`
Created *Time `json:"created,omitempty"`
ForeignKey string `json:"foreign_key,omitempty"`
ForeignModel string `json:"foreign_model,omitempty"`
Modified *Time `json:"modified,omitempty"`
}
// CreateFavorite Creates a new Passbolt Favorite for the given Resource ID
func (c *Client) CreateFavorite(ctx context.Context, resourceID string) (*Favorite, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/favorites/resource/"+resourceID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var favorite Favorite
err = json.Unmarshal(msg.Body, &favorite)
if err != nil {
return nil, err
}
return &favorite, nil
}
// DeleteFavorite Deletes a Passbolt Favorite
func (c *Client) DeleteFavorite(ctx context.Context, favoriteID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/favorites/"+favoriteID+".json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}

117
folders.go Normal file
View file

@ -0,0 +1,117 @@
package passbolt
import (
"context"
"encoding/json"
)
// Folder is a Folder
type Folder struct {
ID string `json:"id,omitempty"`
Created *Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
Modified *Time `json:"modified,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"`
Name string `json:"name,omitempty"`
Permissions []Permission `json:"permissions,omitempty"`
FolderParentID string `json:"folder_parent_id,omitempty"`
Personal bool `json:"personal,omitempty"`
ChildrenResources []Resource `json:"children_resources,omitempty"`
ChildrenFolders []Folder `json:"children_folders,omitempty"`
}
// GetFolderOptions are all available query parameters
type GetFolderOptions struct {
ContainChildrenResources bool `url:"contain[children_resources],omitempty"`
ContainChildrenFolders bool `url:"contain[children_folders],omitempty"`
ContainCreator bool `url:"contain[creator],omitempty"`
ContainCreatorProfile bool `url:"contain[creator.profile],omitempty"`
ContainModifier bool `url:"contain[modifier],omitempty"`
ContainModiferProfile bool `url:"contain[modifier.profile],omitempty"`
ContainPermission bool `url:"contain[permission],omitempty"`
ContainPermissions bool `url:"contain[permissions],omitempty"`
ContainPermissionUserProfile bool `url:"contain[permissions.user.profile],omitempty"`
ContainPermissionGroup bool `url:"contain[permissions.group],omitempty"`
FilterHasID string `url:"filter[has-id][],omitempty"`
FilterHasParent string `url:"filter[has-parent][],omitempty"`
FilterSearch string `url:"filter[search],omitempty"`
}
// GetFolders gets all Folders from the Passboltserver
func (c *Client) GetFolders(ctx context.Context, opts *GetFolderOptions) ([]Folder, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/folders.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var body []Folder
err = json.Unmarshal(msg.Body, &body)
if err != nil {
return nil, err
}
return body, nil
}
// CreateFolder Creates a new Passbolt Folder
func (c *Client) CreateFolder(ctx context.Context, folder Folder) (*Folder, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/folders.json", "v2", folder, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &folder)
if err != nil {
return nil, err
}
return &folder, nil
}
// GetFolder gets a Passbolt Folder
func (c *Client) GetFolder(ctx context.Context, folderID string) (*Folder, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/folders/"+folderID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var folder Folder
err = json.Unmarshal(msg.Body, &folder)
if err != nil {
return nil, err
}
return &folder, nil
}
// UpdateFolder Updates a existing Passbolt Folder
func (c *Client) UpdateFolder(ctx context.Context, folderID string, folder Folder) (*Folder, error) {
msg, err := c.DoCustomRequest(ctx, "PUT", "/folders/"+folderID+".json", "v2", folder, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &folder)
if err != nil {
return nil, err
}
return &folder, nil
}
// DeleteFolder Deletes a Passbolt Folder
func (c *Client) DeleteFolder(ctx context.Context, folderID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/folders/"+folderID+".json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}
// MoveFolder Moves a Passbolt Folder
func (c *Client) MoveFolder(ctx context.Context, folderID, folderParentID string) error {
_, err := c.DoCustomRequest(ctx, "PUT", "/move/folder/"+folderID+".json", "v2", Folder{
FolderParentID: folderParentID,
}, nil)
if err != nil {
return err
}
return nil
}

13
go.mod Normal file
View file

@ -0,0 +1,13 @@
module github.com/speatzle/go-passbolt
go 1.16
require (
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect
github.com/ProtonMail/gopenpgp/v2 v2.2.2
github.com/google/go-querystring v1.1.0
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/text v0.3.7 // indirect
)

71
go.sum Normal file
View file

@ -0,0 +1,71 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

57
gpgkey.go Normal file
View file

@ -0,0 +1,57 @@
package passbolt
import (
"context"
"encoding/json"
)
// GPGKey is a GPGKey
type GPGKey struct {
ID string `json:"id,omitempty"`
ArmoredKey string `json:"armored_key,omitempty"`
Created *Time `json:"created,omitempty"`
KeyCreated *Time `json:"key_created,omitempty"`
Bits int `json:"bits,omitempty"`
Deleted bool `json:"deleted,omitempty"`
Modified *Time `json:"modified,omitempty"`
KeyID string `json:"key_id,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Type string `json:"type,omitempty"`
Expires *Time `json:"expires,omitempty"`
}
// GetGPGKeysOptions are all available query parameters
type GetGPGKeysOptions struct {
// This is a Unix TimeStamp
FilterModifiedAfter int `url:"filter[modified-after],omitempty"`
}
// GetGPGKeys gets all Passbolt GPGKeys
func (c *Client) GetGPGKeys(ctx context.Context, opts *GetGPGKeysOptions) ([]GPGKey, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/gpgkeys.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var gpgkeys []GPGKey
err = json.Unmarshal(msg.Body, &gpgkeys)
if err != nil {
return nil, err
}
return gpgkeys, nil
}
// GetGPGKey gets a Passbolt GPGKey
func (c *Client) GetGPGKey(ctx context.Context, gpgkeyID string) (*GPGKey, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/gpgkeys/"+gpgkeyID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var gpgkey GPGKey
err = json.Unmarshal(msg.Body, &gpgkey)
if err != nil {
return nil, err
}
return &gpgkey, nil
}

97
groups.go Normal file
View file

@ -0,0 +1,97 @@
package passbolt
import (
"context"
"encoding/json"
)
//Group is a Group
type Group struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Created *Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
Deleted bool `json:"deleted,omitempty"`
Modified *Time `json:"modified,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"`
GroupUsers []User `json:"groups_users,omitempty"`
}
// GetGroupsOptions are all available query parameters
type GetGroupsOptions struct {
FilterHasUsers []string `url:"filter[has_users],omitempty"`
FilterHasManagers []string `url:"filter[has-managers],omitempty"`
ContainModifier bool `url:"contain[modifier],omitempty"`
ContainModifierProfile bool `url:"contain[modifier.profile],omitempty"`
ContainUser bool `url:"contain[user],omitempty"`
ContainGroupUser bool `url:"contain[group_user],omitempty"`
ContainMyGroupUser bool `url:"contain[my_group_user],omitempty"`
}
// GetGroups gets all Passbolt Groups
func (c *Client) GetGroups(ctx context.Context, opts *GetGroupsOptions) ([]Group, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/groups.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var groups []Group
err = json.Unmarshal(msg.Body, &groups)
if err != nil {
return nil, err
}
return groups, nil
}
// CreateGroup Creates a new Passbolt Group
func (c *Client) CreateGroup(ctx context.Context, group Group) (*Group, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/groups.json", "v2", group, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &group)
if err != nil {
return nil, err
}
return &group, nil
}
// GetGroup gets a Passbolt Group
func (c *Client) GetGroup(ctx context.Context, groupID string) (*Group, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/groups/"+groupID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var group Group
err = json.Unmarshal(msg.Body, &group)
if err != nil {
return nil, err
}
return &group, nil
}
// UpdateGroup Updates a existing Passbolt Group
func (c *Client) UpdateGroup(ctx context.Context, groupID string, group Group) (*Group, error) {
msg, err := c.DoCustomRequest(ctx, "PUT", "/groups/"+groupID+".json", "v2", group, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &group)
if err != nil {
return nil, err
}
return &group, nil
}
// DeleteGroup Deletes a Passbolt Group
func (c *Client) DeleteGroup(ctx context.Context, groupID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/groups/"+groupID+".json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}

26
healthcheck.go Normal file
View file

@ -0,0 +1,26 @@
package passbolt
import (
"context"
"encoding/json"
)
// PerformHealthCheck performs a Health Check
func (c *Client) PerformHealthCheck(ctx context.Context) (json.RawMessage, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/healthcheck.json", "v2", nil, nil)
if err != nil {
return nil, err
}
return msg.Body, nil
}
// GetHealthCheckStatus gets the Server Status
func (c *Client) GetHealthCheckStatus(ctx context.Context) (string, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/healthcheck/status.json", "v2", nil, nil)
if err != nil {
return "", err
}
return string(msg.Body), nil
}

33
helper/folder.go Normal file
View file

@ -0,0 +1,33 @@
package helper
import (
"context"
"github.com/speatzle/go-passbolt"
)
func CreateFolder(ctx context.Context, c *passbolt.Client, folderParentID, name string) (string, error) {
f, err := c.CreateFolder(ctx, passbolt.Folder{
Name: name,
FolderParentID: folderParentID,
})
return f.ID, err
}
func GetFolder(ctx context.Context, c *passbolt.Client, folderID string) (string, string, error) {
f, err := c.GetFolder(ctx, folderID)
return f.FolderParentID, f.Name, err
}
func UpdateFolder(ctx context.Context, c *passbolt.Client, folderID, name string) error {
_, err := c.UpdateFolder(ctx, folderID, passbolt.Folder{Name: name})
return err
}
func DeleteFolder(ctx context.Context, c *passbolt.Client, folderID string) error {
return c.DeleteFolder(ctx, folderID)
}
func MoveFolder(ctx context.Context, c *passbolt.Client, folderID, folderParentID string) error {
return c.MoveFolder(ctx, folderID, folderParentID)
}

208
helper/resources.go Normal file
View file

@ -0,0 +1,208 @@
package helper
import (
"context"
"encoding/json"
"fmt"
"github.com/speatzle/go-passbolt"
)
// CreateResource Creates a Resource where the Password and Description are Encrypted and Returns the Resources ID
func CreateResource(ctx context.Context, c *passbolt.Client, folderParentID, name, username, uri, password, description string) (string, error) {
types, err := c.GetResourceTypes(ctx, nil)
if err != nil {
return "", fmt.Errorf("Getting ResourceTypes: %w", err)
}
var rType *passbolt.ResourceType
for _, tmp := range types {
if tmp.Slug == "password-and-description" {
rType = &tmp
}
}
if rType == nil {
return "", fmt.Errorf("Cannot find Resource type password-and-description")
}
resource := passbolt.Resource{
ResourceTypeID: rType.ID,
FolderParentID: folderParentID,
Name: name,
Username: username,
URI: uri,
}
tmp := passbolt.SecretDataTypePasswordAndDescription{
Password: password,
Description: description,
}
secretData, err := json.Marshal(&tmp)
if err != nil {
return "", fmt.Errorf("Marshalling Secret Data: %w", err)
}
encSecretData, err := c.EncryptMessage(string(secretData))
if err != nil {
return "", fmt.Errorf("Encrypting Secret Data for User me: %w", err)
}
resource.Secrets = []passbolt.Secret{{Data: encSecretData}}
newresource, err := c.CreateResource(ctx, resource)
if err != nil {
return "", fmt.Errorf("Creating Resource: %w", err)
}
return newresource.ID, nil
}
// CreateResourceSimple Creates a Legacy Resource where only the Password is Encrypted and Returns the Resources ID
func CreateResourceSimple(ctx context.Context, c *passbolt.Client, folderParentID, name, username, uri, password, description string) (string, error) {
enc, err := c.EncryptMessage(password)
if err != nil {
return "", fmt.Errorf("Encrypting Password: %w", err)
}
res := passbolt.Resource{
Name: name,
URI: uri,
Username: username,
FolderParentID: folderParentID,
Description: description,
Secrets: []passbolt.Secret{
{Data: enc},
},
}
resource, err := c.CreateResource(ctx, res)
if err != nil {
return "", fmt.Errorf("Creating Resource: %w", err)
}
return resource.ID, nil
}
// GetResource Gets a Resource by ID
func GetResource(ctx context.Context, c *passbolt.Client, resourceID string) (folderParentID, name, username, uri, password, description string, err error) {
resource, err := c.GetResource(ctx, resourceID)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Getting Resource: %w", err)
}
rType, err := c.GetResourceType(ctx, resource.ResourceTypeID)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Getting ResourceType: %w", err)
}
secret, err := c.GetSecret(ctx, resource.ID)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Getting Resource Secret: %w", err)
}
var pw string
var desc string
switch rType.Slug {
case "password-string":
pw, err = c.DecryptMessage(secret.Data)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Decrypting Secret Data: %w", err)
}
desc = resource.Description
case "password-and-description":
rawSecretData, err := c.DecryptMessage(secret.Data)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Decrypting Secret Data: %w", err)
}
var secretData passbolt.SecretDataTypePasswordAndDescription
err = json.Unmarshal([]byte(rawSecretData), &secretData)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Parsing Decrypted Secret Data: %w", err)
}
pw = secretData.Password
desc = secretData.Description
default:
return "", "", "", "", "", "", fmt.Errorf("Unknown ResourceType: %v", rType.Slug)
}
return resource.FolderParentID, resource.Name, resource.Username, resource.URI, pw, desc, nil
}
// UpdateResource Updates all Fields.
// Note if you want to Change the FolderParentID please use the MoveResource Function
func UpdateResource(ctx context.Context, c *passbolt.Client, resourceID, name, username, uri, password, description string) error {
resource, err := c.GetResource(ctx, resourceID)
if err != nil {
return fmt.Errorf("Getting Resource: %w", err)
}
rType, err := c.GetResourceType(ctx, resource.ResourceTypeID)
if err != nil {
return fmt.Errorf("Getting ResourceType: %w", err)
}
opts := &passbolt.GetUsersOptions{
FilterHasAccess: resourceID,
}
users, err := c.GetUsers(ctx, opts)
if err != nil {
return fmt.Errorf("Getting Users: %w", err)
}
newResource := passbolt.Resource{
ID: resourceID,
// This needs to be specified or it will revert to a legacy password
ResourceTypeID: resource.ResourceTypeID,
Name: name,
Username: username,
URI: uri,
}
var secretData string
switch rType.Slug {
case "password-string":
newResource.Description = description
secretData = password
case "password-and-description":
tmp := passbolt.SecretDataTypePasswordAndDescription{
Password: password,
Description: description,
}
res, err := json.Marshal(&tmp)
if err != nil {
return fmt.Errorf("Marshalling Secret Data: %w", err)
}
secretData = string(res)
default:
return fmt.Errorf("Unknown ResourceType: %v", rType.Slug)
}
newResource.Secrets = []passbolt.Secret{}
for _, user := range users {
var encSecretData string
// if this is our user use our stored and verified public key instead
if user.ID == c.GetUserID() {
encSecretData, err = c.EncryptMessage(secretData)
if err != nil {
return fmt.Errorf("Encrypting Secret Data for User me: %w", err)
}
} else {
encSecretData, err = c.EncryptMessageWithPublicKey(user.GPGKey.ArmoredKey, secretData)
if err != nil {
return fmt.Errorf("Encrypting Secret Data for User %v: %w", user.ID, err)
}
}
newResource.Secrets = append(newResource.Secrets, passbolt.Secret{
UserID: user.ID,
Data: encSecretData,
})
}
_, err = c.UpdateResource(ctx, resourceID, newResource)
if err != nil {
return fmt.Errorf("Updating Resource: %w", err)
}
return nil
}
func DeleteResource(ctx context.Context, c *passbolt.Client, resourceID string) error {
return c.DeleteResource(ctx, resourceID)
}
func MoveResource(ctx context.Context, c *passbolt.Client, resourceID, folderParentID string) error {
return c.MoveResource(ctx, resourceID, folderParentID)
}

218
helper/share.go Normal file
View file

@ -0,0 +1,218 @@
package helper
import (
"context"
"fmt"
"github.com/speatzle/go-passbolt"
)
// ShareOperation defines how Resources are to be Shared With Users/Groups
type ShareOperation struct {
// Type of Permission: 1 = Read, 7 = can Update, 15 = Owner (Owner can also Share Resource)
// Note: Setting this to -1 Will delete this Permission if it already Exists, errors if this Permission Dosen't Already Exists
Type int
// ARO is what Type this should be Shared With (User, Group)
ARO string
// AROID is the ID of the User or Group(ARO) this should be Shared With
AROID string
}
// ShareResourceWithUsersAndGroups Shares a Resource With The Users and Groups with the Specified Permission Type,
// if the Resource has already been shared With the User/Group the Permission Type will be Adjusted/Deleted
func ShareResourceWithUsersAndGroups(ctx context.Context, c *passbolt.Client, resourceID string, Users []string, Groups []string, permissionType int) error {
changes := []ShareOperation{}
for _, userID := range Users {
changes = append(changes, ShareOperation{
Type: permissionType,
ARO: "User",
AROID: userID,
})
}
for _, groupID := range Groups {
changes = append(changes, ShareOperation{
Type: permissionType,
ARO: "Group",
AROID: groupID,
})
}
return ShareResource(ctx, c, resourceID, changes)
}
// ShareResource Shares a Resource as Specified in the Passed ShareOperation Struct Slice
func ShareResource(ctx context.Context, c *passbolt.Client, resourceID string, changes []ShareOperation) error {
oldPermissions, err := c.GetResourcePermissions(ctx, resourceID)
if err != nil {
return fmt.Errorf("Getting Resource Permissions: %w", err)
}
permissionChanges, err := GeneratePermissionChanges(oldPermissions, changes)
if err != nil {
return fmt.Errorf("Generating Resource Permission Changes: %w", err)
}
shareRequest := passbolt.ResourceShareRequest{Permissions: permissionChanges}
secret, err := c.GetSecret(ctx, resourceID)
if err != nil {
return fmt.Errorf("Get Resource: %w", err)
}
secretData, err := c.DecryptMessage(secret.Data)
if err != nil {
return fmt.Errorf("Decrypting Resource Secret: %w", err)
}
simulationResult, err := c.SimulateShareResource(ctx, resourceID, shareRequest)
if err != nil {
return fmt.Errorf("Simulate Share Resource: %w", err)
}
users, err := c.GetUsers(ctx, nil)
if err != nil {
return fmt.Errorf("Get Users: %w", err)
}
shareRequest.Secrets = []passbolt.Secret{}
for _, user := range simulationResult.Changes.Added {
pubkey, err := getPublicKeyByUserID(user.User.ID, users)
if err != nil {
return fmt.Errorf("Getting Public Key for User %v: %w", user.User.ID, err)
}
encSecretData, err := c.EncryptMessageWithPublicKey(pubkey, secretData)
if err != nil {
return fmt.Errorf("Encrypting Secret for User %v: %w", user.User.ID, err)
}
shareRequest.Secrets = append(shareRequest.Secrets, passbolt.Secret{
UserID: user.User.ID,
Data: encSecretData,
})
}
err = c.ShareResource(ctx, resourceID, shareRequest)
if err != nil {
return fmt.Errorf("Sharing Resource: %w", err)
}
return nil
}
// ShareFolderWithUsersAndGroups Shares a Folder With The Users and Groups with the Specified Type,
// if the Folder has already been shared With the User/Group the Permission Type will be Adjusted/Deleted.
// Note: Resources Permissions in the Folder are not Adjusted (Like the Extention does)
func ShareFolderWithUsersAndGroups(ctx context.Context, c *passbolt.Client, folderID string, Users []string, Groups []string, permissionType int) error {
changes := []ShareOperation{}
for _, userID := range Users {
changes = append(changes, ShareOperation{
Type: permissionType,
ARO: "User",
AROID: userID,
})
}
for _, groupID := range Groups {
changes = append(changes, ShareOperation{
Type: permissionType,
ARO: "Group",
AROID: groupID,
})
}
return ShareFolder(ctx, c, folderID, changes)
}
// ShareFolder Shares a Folder as Specified in the Passed ShareOperation Struct Slice.
// Note Resources Permissions in the Folder are not Adjusted
func ShareFolder(ctx context.Context, c *passbolt.Client, folderID string, changes []ShareOperation) error {
oldPermissions, err := c.GetFolderPermissions(ctx, folderID)
if err != nil {
return fmt.Errorf("Getting Folder Permissions: %w", err)
}
permissionChanges, err := GeneratePermissionChanges(oldPermissions, changes)
if err != nil {
return fmt.Errorf("Generating Folder Permission Changes: %w", err)
}
err = c.ShareFolder(ctx, folderID, permissionChanges)
if err != nil {
return fmt.Errorf("Sharing Folder: %w", err)
}
return nil
}
// GeneratePermissionChanges Generates the Permission Changes for a Resource/Folder nessesary for a single Share Operation
func GeneratePermissionChanges(oldPermissions []passbolt.Permission, changes []ShareOperation) ([]passbolt.Permission, error) {
// Check for Duplicate Users/Groups as that would break stuff
for i, changeA := range changes {
for j, changeB := range changes {
if i != j && changeA.AROID == changeB.AROID && changeA.ARO == changeB.ARO {
return nil, fmt.Errorf("Change %v and %v are Both About the same ARO %v ID: %v, there can only be once change per ARO", i, j, changeA.ARO, changeA.AROID)
}
}
}
// Get ACO and ACO ID from Existing Permissions
if len(oldPermissions) == 0 {
return nil, fmt.Errorf("There has to be atleast one Permission on a ACO")
}
ACO := oldPermissions[0].ACO
ACOID := oldPermissions[0].ACOForeignKey
permissionChanges := []passbolt.Permission{}
for _, change := range changes {
// Find Permission thats invloves the Same ARO as Requested in the change
var oldPermission *passbolt.Permission
for _, oldPerm := range oldPermissions {
if oldPerm.ARO == change.ARO && oldPerm.AROForeignKey == change.AROID {
oldPermission = &oldPerm
}
}
// Check Wheter Matching Permission Already Exists and needs to be adjusted or is a new one can be created
if oldPermission == nil {
if change.Type == 15 || change.Type == 7 || change.Type == 1 {
permissionChanges = append(permissionChanges, passbolt.Permission{
IsNew: true,
Type: change.Type,
ARO: change.ARO,
AROForeignKey: change.AROID,
ACO: ACO,
ACOForeignKey: ACOID,
})
} else if change.Type == -1 {
return nil, fmt.Errorf("Permission for %v %v Cannot be Deleted as No Matching Permission Exists", change.ARO, change.AROID)
} else {
return nil, fmt.Errorf("Unknown Permission Type: %v", change.Type)
}
} else {
tmp := passbolt.Permission{
ID: oldPermission.ID,
ARO: change.ARO,
AROForeignKey: change.AROID,
ACO: ACO,
ACOForeignKey: ACOID,
}
if change.Type == 15 || change.Type == 7 || change.Type == 1 {
if oldPermission.Type == change.Type {
return nil, fmt.Errorf("Permission for %v %v is already Type %v", change.ARO, change.AROID, change.Type)
}
tmp.Type = change.Type
} else if change.Type == -1 {
tmp.Delete = true
tmp.Type = oldPermission.Type
} else {
return nil, fmt.Errorf("Unknown Permission Type: %v", change.Type)
}
permissionChanges = append(permissionChanges, tmp)
}
}
return permissionChanges, nil
}
func getPublicKeyByUserID(userID string, Users []passbolt.User) (string, error) {
for _, user := range Users {
if user.ID == userID {
return user.GPGKey.ArmoredKey, nil
}
}
return "", fmt.Errorf("Cannot Find Key for user id %v", userID)
}

25
misc.go Normal file
View file

@ -0,0 +1,25 @@
package passbolt
import (
"crypto/rand"
"math/big"
)
func randStringBytesRmndr(length int) (string, error) {
result := ""
for {
if len(result) >= length {
return result, nil
}
num, err := rand.Int(rand.Reader, big.NewInt(int64(127)))
if err != nil {
return "", err
}
n := num.Int64()
// Make sure that the number/byte/letter is inside
// the range of printable ASCII characters (excluding space and DEL)
if n > 32 && n < 127 {
result += string(n)
}
}
}

50
permissions.go Normal file
View file

@ -0,0 +1,50 @@
package passbolt
import (
"context"
"encoding/json"
)
// Permission is a Permission
type Permission struct {
ID string `json:"id,omitempty"`
ACO string `json:"aco,omitempty"`
ARO string `json:"aro,omitempty"`
ACOForeignKey string `json:"aco_foreign_key,omitempty"`
AROForeignKey string `json:"aro_foreign_key,omitempty"`
Type int `json:"type,omitempty"`
Delete bool `json:"delete,omitempty"`
IsNew bool `json:"is_new,omitempty"`
Created *Time `json:"created,omitempty"`
Modified *Time `json:"modified,omitempty"`
}
// GetResourcePermissions gets a Resources Permissions
func (c *Client) GetResourcePermissions(ctx context.Context, resourceID string) ([]Permission, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/permissions/resource/"+resourceID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var permissions []Permission
err = json.Unmarshal(msg.Body, &permissions)
if err != nil {
return nil, err
}
return permissions, nil
}
// GetFolderPermissions gets a Folders Permissions
func (c *Client) GetFolderPermissions(ctx context.Context, folderID string) ([]Permission, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/permissions/folder/"+folderID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var permissions []Permission
err = json.Unmarshal(msg.Body, &permissions)
if err != nil {
return nil, err
}
return permissions, nil
}

49
resource_types.go Normal file
View file

@ -0,0 +1,49 @@
package passbolt
import (
"context"
"encoding/json"
)
//ResourceType is the Type of a Resource
type ResourceType struct {
ID string `json:"id,omitempty"`
Slug string `json:"slug,omitempty"`
Description string `json:"description,omitempty"`
Definition json.RawMessage `json:"definition,omitempty"`
Created *Time `json:"created,omitempty"`
Modified *Time `json:"modified,omitempty"`
}
type GetResourceTypesOptions struct {
}
// GetResourceTypes gets all Passbolt Resource Types
func (c *Client) GetResourceTypes(ctx context.Context, opts *GetResourceTypesOptions) ([]ResourceType, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/resource-types.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var types []ResourceType
err = json.Unmarshal(msg.Body, &types)
if err != nil {
return nil, err
}
return types, nil
}
// GetResourceType gets a Passbolt Type
func (c *Client) GetResourceType(ctx context.Context, typeID string) (*ResourceType, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/resource-types/"+typeID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var rType ResourceType
err = json.Unmarshal(msg.Body, &rType)
if err != nil {
return nil, err
}
return &rType, nil
}

135
resources.go Normal file
View file

@ -0,0 +1,135 @@
package passbolt
import (
"context"
"encoding/json"
)
// Resource is a Resource.
// Warning: Since Passbolt v3 some fields here may not be populated as they may be in the Secret depending on the ResourceType,
// for now the only Field like that is the Decription.
type Resource struct {
ID string `json:"id,omitempty"`
Created *Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
Creator *User `json:"creator,omitempty"`
Deleted bool `json:"deleted,omitempty"`
Description string `json:"description,omitempty"`
Favorite *Favorite `json:"favorite,omitempty"`
Modified *Time `json:"modified,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"`
Modifier *User `json:"modifier,omitempty"`
Name string `json:"name,omitempty"`
Permission *Permission `json:"permission,omitempty"`
URI string `json:"uri,omitempty"`
Username string `json:"username,omitempty"`
FolderParentID string `json:"folder_parent_id,omitempty"`
ResourceTypeID string `json:"resource_type_id,omitempty"`
Secrets []Secret `json:"secrets,omitempty"`
Tags []Tag `json:"tags,omitempty"`
}
// Tag is a Passbolt Password Tag
type Tag struct {
ID string `json:"id,omitempty"`
Slug string `json:"slug,omitempty"`
IsShared bool `json:"is_shared,omitempty"`
}
// GetResourcesOptions are all available query parameters
type GetResourcesOptions struct {
FilterIsFavorite bool `url:"filter[is-favorite],omitempty"`
FilterIsSharedWithMe bool `url:"filter[is-shared-with-me],omitempty"`
FilterIsSharedWithGroup string `url:"filter[is-shared-with-group],omitempty"`
FilterHasID string `url:"filter[has-id],omitempty"`
// Parent Folder id
FilterHasParent string `url:"filter[has-parent],omitempty"`
FilterHasTag string `url:"filter[has-tag],omitempty"`
ContainCreator bool `url:"contain[creator],omitempty"`
ContainFavorites bool `url:"contain[favorite],omitempty"`
ContainModifier bool `url:"contain[modifier],omitempty"`
ContainPermissions bool `url:"contain[permission],omitempty"`
ContainPermissionsUserProfile bool `url:"contain[permissions.user.profile],omitempty"`
ContainPermissionsGroup bool `url:"contain[permissions.group],omitempty"`
ContainSecret bool `url:"contain[secret],omitempty"`
ContainTags bool `url:"contain[tag],omitempty"`
}
// GetResources gets all Passbolt Resources
func (c *Client) GetResources(ctx context.Context, opts *GetResourcesOptions) ([]Resource, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/resources.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var resources []Resource
err = json.Unmarshal(msg.Body, &resources)
if err != nil {
return nil, err
}
return resources, nil
}
// CreateResource Creates a new Passbolt Resource
func (c *Client) CreateResource(ctx context.Context, resource Resource) (*Resource, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/resources.json", "v2", resource, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &resource)
if err != nil {
return nil, err
}
return &resource, nil
}
// GetResource gets a Passbolt Resource
func (c *Client) GetResource(ctx context.Context, resourceID string) (*Resource, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/resources/"+resourceID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var resource Resource
err = json.Unmarshal(msg.Body, &resource)
if err != nil {
return nil, err
}
return &resource, nil
}
// UpdateResource Updates a existing Passbolt Resource
func (c *Client) UpdateResource(ctx context.Context, resourceID string, resource Resource) (*Resource, error) {
msg, err := c.DoCustomRequest(ctx, "PUT", "/resources/"+resourceID+".json", "v2", resource, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &resource)
if err != nil {
return nil, err
}
return &resource, nil
}
// DeleteResource Deletes a Passbolt Resource
func (c *Client) DeleteResource(ctx context.Context, resourceID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/resources/"+resourceID+".json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}
// MoveResource Moves a Passbolt Resource
func (c *Client) MoveResource(ctx context.Context, resourceID, folderParentID string) error {
_, err := c.DoCustomRequest(ctx, "PUT", "/move/resource/"+resourceID+".json", "v2", Resource{
FolderParentID: folderParentID,
}, nil)
if err != nil {
return err
}
return nil
}

55
roles.go Normal file
View file

@ -0,0 +1,55 @@
package passbolt
import (
"context"
"encoding/json"
)
//Role is a Role
type Role struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Created *Time `json:"created,omitempty"`
Description string `json:"description,omitempty"`
Modified *Time `json:"modified,omitempty"`
Avatar Avatar `json:"avatar,omitempty"`
}
// Avatar is a Users Avatar
type Avatar struct {
ID string `json:"id,omitempty"`
UserID string `json:"user_id,omitempty"`
ForeignKey string `json:"foreign_key,omitempty"`
Model string `json:"model,omitempty"`
Filename string `json:"filename,omitempty"`
Filesize int `json:"filesize,omitempty"`
MimeType string `json:"mime_type,omitempty"`
Extension string `json:"extension,omitempty"`
Hash string `json:"hash,omitempty"`
Path string `json:"path,omitempty"`
Adapter string `json:"adapter,omitempty"`
Created *Time `json:"created,omitempty"`
Modified *Time `json:"modified,omitempty"`
URL *URL `json:"url,omitempty"`
}
// URL is a Passbolt URL
type URL struct {
Medium string `json:"medium,omitempty"`
Small string `json:"small,omitempty"`
}
// GetRoles gets all Passbolt Roles
func (c *Client) GetRoles(ctx context.Context) ([]Role, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/roles.json", "v2", nil, nil)
if err != nil {
return nil, err
}
var roles []Role
err = json.Unmarshal(msg.Body, &roles)
if err != nil {
return nil, err
}
return roles, nil
}

36
secrets.go Normal file
View file

@ -0,0 +1,36 @@
package passbolt
import (
"context"
"encoding/json"
)
// Secret is a Secret
type Secret struct {
ID string `json:"id,omitempty"`
UserID string `json:"user_id,omitempty"`
ResourceID string `json:"resource_id,omitempty"`
Data string `json:"data,omitempty"`
Created *Time `json:"created,omitempty"`
Modified *Time `json:"modified,omitempty"`
}
type SecretDataTypePasswordAndDescription struct {
Password string `json:"password"`
Description string `json:"description,omitempty"`
}
// GetSecret gets a Passbolt Secret
func (c *Client) GetSecret(ctx context.Context, resourceID string) (*Secret, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/secrets/resource/"+resourceID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var secret Secret
err = json.Unmarshal(msg.Body, &secret)
if err != nil {
return nil, err
}
return &secret, nil
}

93
share.go Normal file
View file

@ -0,0 +1,93 @@
package passbolt
import (
"context"
"encoding/json"
)
// ResourceShareRequest is a ResourceShareRequest
type ResourceShareRequest struct {
Permissions []Permission `json:"permissions,omitempty"`
Secrets []Secret `json:"secrets,omitempty"`
}
// ResourceShareSimulationResult is the Result of a Sharing Siumulation
type ResourceShareSimulationResult struct {
Changes ResourceShareSimulationChanges `json:"changes,omitempty"`
}
type ResourceShareSimulationChanges struct {
Added []ResourceShareSimulationChange `json:"added,omitempty"`
Removed []ResourceShareSimulationChange `json:"removed,omitempty"`
}
type ResourceShareSimulationChange struct {
User ResourceShareSimulationUser `json:"user,omitempty"`
}
type ResourceShareSimulationUser struct {
ID string `json:"id,omitempty"`
}
// ARO is a User or a Group
type ARO struct {
User
Group
}
// SearchAROsOptions are all available query parameters
type SearchAROsOptions struct {
FilterSearch string `url:"filter[search],omitempty"`
}
// SearchAROs gets all Passbolt AROs
func (c *Client) SearchAROs(ctx context.Context, opts SearchAROsOptions) ([]ARO, error) {
//set is_new to true in permission
msg, err := c.DoCustomRequest(ctx, "GET", "/share/search-aros.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var aros []ARO
err = json.Unmarshal(msg.Body, &aros)
if err != nil {
return nil, err
}
return aros, nil
}
// ShareResource Shares a Resource with AROs
func (c *Client) ShareResource(ctx context.Context, resourceID string, shareRequest ResourceShareRequest) error {
_, err := c.DoCustomRequest(ctx, "PUT", "/share/resource/"+resourceID+".json", "v2", shareRequest, nil)
if err != nil {
return err
}
return nil
}
// ShareFolder Shares a Folder with AROs
func (c *Client) ShareFolder(ctx context.Context, folderID string, permissions []Permission) error {
f := Folder{Permissions: permissions}
_, err := c.DoCustomRequest(ctx, "PUT", "/share/folder/"+folderID+".json", "v2", f, nil)
if err != nil {
return err
}
return nil
}
// SimulateShareResource Simulates Shareing a Resource with AROs
func (c *Client) SimulateShareResource(ctx context.Context, resourceID string, shareRequest ResourceShareRequest) (*ResourceShareSimulationResult, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/share/simulate/resource/"+resourceID+".json", "v2", shareRequest, nil)
if err != nil {
return nil, err
}
var res ResourceShareSimulationResult
err = json.Unmarshal(msg.Body, &res)
if err != nil {
return nil, err
}
return &res, nil
}

10
staticcheck.conf Normal file
View file

@ -0,0 +1,10 @@
checks = ["all", "-ST1005", "-ST1000", "-ST1003", "-ST1016"]
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS"]
dot_import_whitelist = []
http_status_code_whitelist = ["200", "400", "404", "500"]

29
time.go Normal file
View file

@ -0,0 +1,29 @@
package passbolt
import (
"strings"
"time"
)
// Time is here to unmarshall time correctly
type Time struct {
time.Time
}
// UnmarshalJSON Parses Passbolt *Time
func (t *Time) UnmarshalJSON(buf []byte) error {
if string(buf) == "null" {
return nil
}
tt, err := time.Parse(time.RFC3339, strings.Trim(string(buf), `"`))
if err != nil {
return err
}
t.Time = tt
return nil
}
// MarshalJSON Marshals Passbolt *Time
func (t Time) MarshalJSON() ([]byte, error) {
return []byte(`"` + t.Time.Format(time.RFC3339) + `"`), nil
}

124
users.go Normal file
View file

@ -0,0 +1,124 @@
package passbolt
import (
"context"
"encoding/json"
)
// User contains information about a passbolt User
type User struct {
ID string `json:"id,omitempty"`
Created *Time `json:"created,omitempty"`
Active bool `json:"active,omitempty"`
Deleted bool `json:"deleted,omitempty"`
Description string `json:"description,omitempty"`
Favorite *Favorite `json:"favorite,omitempty"`
Modified *Time `json:"modified,omitempty"`
Username string `json:"username,omitempty"`
RoleID string `json:"role_id,omitempty"`
Profile *Profile `json:"profile,omitempty"`
Role *Role `json:"role,omitempty"`
GPGKey *GPGKey `json:"gpgKey,omitempty"`
LastLoggedIn string `json:"last_logged_in,omitempty"`
}
// Profile is a Profile
type Profile struct {
ID string `json:"id,omitempty"`
UserID string `json:"user_id,omitempty"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Created *Time `json:"created,omitempty"`
Modified *Time `json:"modified,omitempty"`
}
// GetUsersOptions are all available query parameters
type GetUsersOptions struct {
FilterSearch string `url:"filter[search],omitempty"`
FilterHasGroup string `url:"filter[has-group],omitempty"`
FilterHasAccess string `url:"filter[has-access],omitempty"`
FilterIsAdmin bool `url:"filter[is-admin],omitempty"`
ContainLastLoggedIn bool `url:"contain[LastLoggedIn],omitempty"`
}
// GetUsers gets all Passbolt Users
func (c *Client) GetUsers(ctx context.Context, opts *GetUsersOptions) ([]User, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/users.json", "v2", nil, opts)
if err != nil {
return nil, err
}
var users []User
err = json.Unmarshal(msg.Body, &users)
if err != nil {
return nil, err
}
return users, nil
}
// CreateUser Creates a new Passbolt User
func (c *Client) CreateUser(ctx context.Context, user User) (*User, error) {
msg, err := c.DoCustomRequest(ctx, "POST", "/users.json", "v2", user, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &user)
if err != nil {
return nil, err
}
return &user, nil
}
// GetMe gets the currently logged in Passbolt User
func (c *Client) GetMe(ctx context.Context) (*User, error) {
return c.GetUser(ctx, "me")
}
// GetUser gets a Passbolt User
func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) {
msg, err := c.DoCustomRequest(ctx, "GET", "/users/"+userID+".json", "v2", nil, nil)
if err != nil {
return nil, err
}
var user User
err = json.Unmarshal(msg.Body, &user)
if err != nil {
return nil, err
}
return &user, nil
}
// UpdateUser Updates a existing Passbolt User
func (c *Client) UpdateUser(ctx context.Context, userID string, user User) (*User, error) {
msg, err := c.DoCustomRequest(ctx, "PUT", "/users/"+userID+".json", "v2", user, nil)
if err != nil {
return nil, err
}
err = json.Unmarshal(msg.Body, &user)
if err != nil {
return nil, err
}
return &user, nil
}
// DeleteUser Deletes a Passbolt User
func (c *Client) DeleteUser(ctx context.Context, userID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+".json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}
// DeleteUserDryrun Check if a Passbolt User is Deleteable
func (c *Client) DeleteUserDryrun(ctx context.Context, userID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+"/dry-run.json", "v2", nil, nil)
if err != nil {
return err
}
return nil
}