Compare commits

...

47 commits
v0.5.2 ... main

Author SHA1 Message Date
b919c3e09d
Merge pull request #25 from passbolt/update-dep
Some checks failed
Go / test (push) Has been cancelled
update deps
2025-03-04 16:55:47 +01:00
3762c5e278 update deps 2025-03-02 22:59:35 +01:00
26942f22d1
Merge pull request #24 from Nelwhix/extra-padding-fix
Some checks failed
Go / test (push) Has been cancelled
fix: correctly generates otp code from token with extra padding
2025-03-02 22:45:28 +01:00
Nelson Isioma
c1904ca20a fix: correctly generates otp code from token with extra padding 2025-02-23 13:47:41 +01:00
ddaa090bc7
Merge pull request #22 from passbolt/fix-deps
Some checks failed
Go / test (push) Has been cancelled
update deps
2024-08-13 11:58:48 +02:00
c86afac97c go mod tidy 2024-08-13 11:47:33 +02:00
2b3bb48385 update deps 2024-08-13 11:34:35 +02:00
83ba14250b
Merge pull request #20 from passbolt/fix_mfa_detection
Fix MFA detection with custom APP_BASE
2024-08-13 11:27:43 +02:00
390b7be866 Fix MFA detection with custom APP_BASE 2024-08-13 11:25:34 +02:00
aaf7213315
Merge pull request #21 from passbolt/fix-ci-docker
Fix ci docker compose command
2024-08-13 11:18:37 +02:00
1bbe7dc952 Update Docker Container Name 2024-08-13 11:16:30 +02:00
1499a80625 Update docker compose command 2024-08-13 11:11:42 +02:00
e13f484bcb Add Workaround for inconsistent API Response 2023-11-24 13:32:47 +01:00
360cc3748e Enable Debug Output for tests 2023-11-24 11:28:38 +01:00
a6a98a6887 fix ci 2023-11-24 11:18:19 +01:00
8dbb07720d Add totp and password-description-totp Support 2023-11-24 11:08:49 +01:00
605db2b047 Add Secret Json Schema Validation 2023-11-24 11:07:38 +01:00
adaffbce7e update deps 2023-11-24 11:03:35 +01:00
7316263056
Merge pull request #19 from passbolt/update-deps
update deps
2023-08-10 20:53:51 +02:00
876631e7c2 update deps 2023-08-10 20:40:04 +02:00
a9bd51e5da
Merge pull request #18 from passbolt/fix-create-resource-type
Fix Create Resource Type ID
2023-08-10 20:31:41 +02:00
ce38d65e45
Fix Create Resource Type ID
Fix determining the id for the resource type password-and-description.
2023-08-10 20:25:19 +02:00
e4537a8ca0
Merge pull request #17 from lenforiee/fix-spelling-mistakes
Fix spelling mistakes in the code
2023-04-19 22:17:41 +02:00
lenforiee
1b178b6634 containes -> contains 2023-04-19 21:25:44 +02:00
lenforiee
a86ae886f2 handeld -> handled 2023-04-19 21:24:53 +02:00
lenforiee
fd895a9d46 challange -> challenge 2023-04-19 21:24:12 +02:00
lenforiee
ced16f2479 infinit -> infinite 2023-04-19 21:17:57 +02:00
Samuel Lorch
1aaafcf66e
Merge pull request #15 from passbolt/fix-ci-keysize
update keysize in test
2022-12-30 17:16:34 +01:00
Samuel Lorch
8a5cbff839 update keysize in test 2022-12-30 17:14:14 +01:00
Samuel Lorch
5ce26cfbfc
Merge pull request #14 from passbolt/update-deps
update deps, update minimum go to 1.18
2022-12-30 17:02:57 +01:00
Samuel Lorch
4d482e6bf2 update deps, update minimum go to 1.18 2022-12-30 17:02:16 +01:00
Samuel Lorch
563c755168
Merge pull request #13 from passbolt/fix-FilterIsSharedWithGroup-type
fix Resource FilterIsSharedWithGroup Type
2022-04-13 11:16:30 +02:00
Samuel Lorch
9e181e6c83 fix Resource FilterIsSharedWithGroup Type 2022-04-13 11:14:32 +02:00
Samuel Lorch
df43c781ad
Merge pull request #11 from passbolt/fix-mfa-challange-detection
fix mfa challange detection
2022-04-07 16:19:52 +02:00
Samuel Lorch
1b30521b5b fix mfa challange detection
for servers where the default language is not English
2022-04-07 16:07:08 +02:00
Samuel Lorch
69221b3a24 update module import path for repository transfer 2022-02-22 16:38:42 +01:00
Samuel Lorch
47ec059232 update deps 2022-02-04 16:47:21 +01:00
Samuel Lorch
a3a55e2199 fix typo 2022-02-04 16:42:04 +01:00
Samuel Lorch
bd2460467d Add missing ResourceType for ContainResourceType 2022-02-04 16:41:44 +01:00
Samuel Lorch
9cc8cc8b02 update regex 2022-01-21 14:40:06 +01:00
Samuel Lorch
d18f38c066 actually fix cicd for real 2022-01-21 14:26:55 +01:00
Samuel Lorch
90b10d5570 fix cicd for real 2022-01-21 14:24:04 +01:00
Samuel Lorch
5bab492d89 fix cicd 2022-01-21 14:12:30 +01:00
Samuel Lorch
0a86e0c1e6 Update Deps 2022-01-21 13:49:28 +01:00
Samuel Lorch
f1122a019c add uuid check before insering into url 2022-01-21 13:46:26 +01:00
Samuel Lorch
f3fe6eb1c5 Update Group Helpers to use new Group Contains 2022-01-21 11:49:39 +01:00
Samuel Lorch
d84e7e7ad7 Added New GRoup Contains and Type 2022-01-21 11:48:25 +01:00
33 changed files with 561 additions and 157 deletions

View file

@ -13,17 +13,17 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: 1.17 go-version: 1.23
- name: "Setup Passbolt" - name: "Setup Passbolt"
run: | run: |
git clone https://github.com/passbolt/passbolt_docker.git ../passbolt_docker git clone https://github.com/passbolt/passbolt_docker.git ../passbolt_docker
cd ../passbolt_docker cd ../passbolt_docker
docker-compose up -d docker compose -f docker-compose/docker-compose-ce.yaml up -d
docker ps -a docker ps -a
- name: "Test" - name: "Test"
run: | run: |
docker exec passbolt_docker_passbolt_1 sh -c '/usr/bin/wait-for.sh -t 30 localhost:443' docker exec docker-compose-passbolt-1 sh -c '/usr/bin/wait-for.sh -t 30 localhost:443'
output=$(docker exec passbolt_docker_passbolt_1 sh -c 'su -m -c "/usr/share/php/passbolt/bin/cake \ output=$(docker exec docker-compose-passbolt-1 sh -c 'su -m -c "/usr/share/php/passbolt/bin/cake \
passbolt register_user \ passbolt register_user \
-u your@email.com \ -u your@email.com \
-f yourname \ -f yourname \

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 speatzle Copyright (c) 2021 Samuel Lorch
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,30 +1,31 @@
# go-passbolt # go-passbolt
[![Go Reference](https://pkg.go.dev/badge/github.com/speatzle/go-passbolt.svg)](https://pkg.go.dev/github.com/speatzle/go-passbolt)
[![Go Reference](https://pkg.go.dev/badge/github.com/passbolt/go-passbolt.svg)](https://pkg.go.dev/github.com/passbolt/go-passbolt)
A Go module to interact with [Passbolt](https://www.passbolt.com/), an open-source password manager for teams A Go module to interact with [Passbolt](https://www.passbolt.com/), an open-source password manager for teams
There also is a CLI Tool to interact with Passbolt using this module [here](https://github.com/speatzle/go-passbolt-cli). There also is a CLI Tool to interact with Passbolt using this module [here](https://github.com/passbolt/go-passbolt-cli).
This module tries to support the latest Passbolt Community/PRO server release, PRO Features such as folders are supported. Older versions of Passbolt such as v2 are unsupported (it's a password manager, please update it) This module tries to support the latest Passbolt Community/PRO server release, PRO Features such as folders are supported. Older versions of Passbolt such as v2 are unsupported (it's a password manager, please update it)
This module is divided into two packages: API and helper. This module is divided into two packages: API and helper.
In the API package, you will find everything to directly interact with the API. In the API package, you will find everything to directly interact with the API.
The helper package has simplified functions that use the API package to perform common but complicated tasks such as sharing a password. The helper package has simplified functions that use the API package to perform common but complicated tasks such as sharing a password.
To use the API package, please read the [Passbolt API docs](https://help.passbolt.com/api). Sadly the docs aren't complete so many things here have been found by looking at the source of Passbolt or through trial and error. If you have a question just ask. To use the API package, please read the [Passbolt API docs](https://help.passbolt.com/api). Sadly the docs aren't complete so many things here have been found by looking at the source of Passbolt or through trial and error. If you have a question just ask.
PR's are welcome. But be gentle: if it's something bigger or fundamental: please [create an issue](https://github.com/speatzle/go-passbolt/issues/new) and ask first. PR's are welcome. But be gentle: if it's something bigger or fundamental: please [create an issue](https://github.com/passbolt/go-passbolt/issues/new) and ask first.
Disclaimer: This project is community driven and not associated with Passbolt SA Disclaimer: This project is community driven and not associated with Passbolt SA
# Install # Install
`go get github.com/speatzle/go-passbolt` `go get github.com/passbolt/go-passbolt`
# Examples # Examples
## Login ## Login
First, you will need to create a client and then log in on the server using the client: First, you will need to create a client and then log in on the server using the client:
@ -36,7 +37,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
const address = "https://passbolt.example.com" const address = "https://passbolt.example.com"
@ -70,7 +71,7 @@ You can do this using the `client.CheckSession()` function.
## Create a Resource ## Create a Resource
Creating a resource using the helper package is simple. First, add `"github.com/speatzle/go-passbolt/helper"` to your imports. Creating a resource using the helper package is simple. First, add `"github.com/passbolt/go-passbolt/helper"` to your imports.
Then you can simply: Then you can simply:
@ -214,24 +215,25 @@ err = helper.UpdateUser(
"lastname", // LastName "lastname", // LastName
) )
``` ```
Note: These helpers will only update fields that are not "". Note: These helpers will only update fields that are not "".
Helper update functions also exists for Folders. Helper update functions also exists for Folders.
## Sharing ## Sharing
As sharing resources is very complicated there are multiple helper functions. As sharing resources is very complicated there are multiple helper functions.
During sharing you will encounter the [permission type](https://github.com/passbolt/passbolt_api/blob/858971516c5e61e1f1be37b007693f0869a70486/src/Model/Entity/Permission.php#L43-L45). During sharing you will encounter the [permission type](https://github.com/passbolt/passbolt_api/blob/858971516c5e61e1f1be37b007693f0869a70486/src/Model/Entity/Permission.php#L43-L45).
The `permissionType` can be: The `permissionType` can be:
| Code | Meaning | | Code | Meaning |
| --- | --- | | ---- | -------------------------- |
| `1` | "Read-only" | | `1` | "Read-only" |
| `7` | "Can update" | | `7` | "Can update" |
| `15` | "Owner" | | `15` | "Owner" |
| `-1` | Delete existing permission | | `-1` | Delete existing permission |
The `ShareResourceWithUsersAndGroups` function shares the resource with all provided users and groups with the given `permissionType`. The `ShareResourceWithUsersAndGroups` function shares the resource with all provided users and groups with the given `permissionType`.
@ -294,6 +296,7 @@ err := client.MoveFolder(ctx, "folder id", "parent folder id")
## Setup ## Setup
You can setup a Account using a Invite Link like this: You can setup a Account using a Invite Link like this:
```go ```go
// Get the UserID and Token from the Invite Link // Get the UserID and Token from the Invite Link
userID, token, err := ParseInviteUrl(url) userID, token, err := ParseInviteUrl(url)
@ -307,7 +310,7 @@ privkey, err := SetupAccount(ctx, rClient, userID, token, "password123")
## Verification ## Verification
You can Verify that the Server hasen't changed, for that you need to initially setup the Verification and save the returned values. Then you can Verify that the serverkey hasen't changed since you setup the Verification. Note this Only Works if the client is not logged in. You can Verify that the Server hasen't changed, for that you need to initially setup the Verification and save the returned values. Then you can Verify that the serverkey hasen't changed since you setup the Verification. Note this Only Works if the client is not logged in.
```go ```go
// Setup the Verification // Setup the Verification
@ -327,18 +330,17 @@ if err != nil {
## MFA ## MFA
go-passbolt now supports MFA! You can set it up using the Client's `MFACallback` function, it will provide everything you need to complete any MFA challanges. When your done you just need to return the new MFA Cookie (usually called passbolt_mfa). The helper package has a example implementation for a noninteractive TOTP Setup under helper/mfa.go in the function `AddMFACallbackTOTP`. go-passbolt now supports MFA! You can set it up using the Client's `MFACallback` function, it will provide everything you need to complete any MFA challenges. When your done you just need to return the new MFA Cookie (usually called passbolt_mfa). The helper package has a example implementation for a noninteractive TOTP Setup under helper/mfa.go in the function `AddMFACallbackTOTP`.
## Other ## Other
These examples are just the main use cases of these Modules, many more API calls are supported. Look at the [reference](https://pkg.go.dev/github.com/speatzle/go-passbolt) for more information. These examples are just the main use cases of these Modules, many more API calls are supported. Look at the [reference](https://pkg.go.dev/github.com/passbolt/go-passbolt) for more information.
## Full Example ## Full Example
This example: This example:
1. Creates a resource; 1. Creates a resource;
2. Searches for a user named "Test User"; 2. Searches for a user named "Test User";
3. Checks that it's not itself; and, 3. Checks that it's not itself; and,
4. Shares the password with the "Test User" if necessary: 4. Shares the password with the "Test User" if necessary:
@ -350,8 +352,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
"github.com/speatzle/go-passbolt/helper" "github.com/passbolt/go-passbolt/helper"
) )
const address = "https://passbolt.example.com" const address = "https://passbolt.example.com"

View file

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings"
) )
// APIResponse is the Struct representation of a Json Response // APIResponse is the Struct representation of a Json Response
@ -62,9 +63,9 @@ start:
if res.Header.Status == "success" { if res.Header.Status == "success" {
return r, &res, nil return r, &res, nil
} else if res.Header.Status == "error" { } else if res.Header.Status == "error" {
if res.Header.Code == 403 && res.Header.Message == "MFA authentication is required." { if res.Header.Code == 403 && strings.HasSuffix(res.Header.URL, "/mfa/verify/error.json") {
if !firstTime { if !firstTime {
// if we are here this probably means that the MFA callback is broken, to prevent a infinit loop lets error here // if we are here this probably means that the MFA callback is broken, to prevent a infinite loop lets error here
return r, &res, fmt.Errorf("Got MFA challenge twice in a row, is your MFA Callback broken? Bailing to prevent loop...:") return r, &res, fmt.Errorf("Got MFA challenge twice in a row, is your MFA Callback broken? Bailing to prevent loop...:")
} }
if c.MFACallback != nil { if c.MFACallback != nil {
@ -72,11 +73,11 @@ start:
if err != nil { if err != nil {
return r, &res, fmt.Errorf("MFA Callback: %w", err) return r, &res, fmt.Errorf("MFA Callback: %w", err)
} }
// ok, we got the MFA challange and the callback presumably handeld it so we can retry the original request // ok, we got the MFA challenge and the callback presumably handled it so we can retry the original request
firstTime = false firstTime = false
goto start goto start
} else { } else {
return r, &res, fmt.Errorf("Got MFA Challange but the MFA callback is not defined") return r, &res, fmt.Errorf("Got MFA Challenge but the MFA callback is not defined")
} }
} }
return r, &res, fmt.Errorf("%w: Message: %v, Body: %v", ErrAPIResponseErrorStatusCode, res.Header.Message, string(res.Body)) return r, &res, fmt.Errorf("%w: Message: %v, Body: %v", ErrAPIResponseErrorStatusCode, res.Header.Message, string(res.Body))

View file

@ -31,7 +31,7 @@ type Client struct {
userPublicKey string userPublicKey string
userID string userID string
// used for solving MFA challanges. You can block this to for example wait for user input. // used for solving MFA challenges. You can block this to for example wait for user input.
// You shouden't run any unrelated API Calls while you are in this callback. // You shouden't run any unrelated API Calls while you are in this callback.
// You need to Return the Cookie that Passbolt expects to verify you MFA, usually it is called passbolt_mfa // You need to Return the Cookie that Passbolt expects to verify you MFA, usually it is called passbolt_mfa
MFACallback func(ctx context.Context, c *Client, res *APIResponse) (http.Cookie, error) MFACallback func(ctx context.Context, c *Client, res *APIResponse) (http.Cookie, error)

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// Comment is a Comment // Comment is a Comment
@ -29,6 +30,10 @@ type GetCommentsOptions struct {
// GetComments gets all Passbolt Comments an The Specified Resource // GetComments gets all Passbolt Comments an The Specified Resource
func (c *Client) GetComments(ctx context.Context, resourceID string, opts *GetCommentsOptions) ([]Comment, error) { func (c *Client) GetComments(ctx context.Context, resourceID string, opts *GetCommentsOptions) ([]Comment, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/comments/resource/"+resourceID+".json", "v2", nil, opts) msg, err := c.DoCustomRequest(ctx, "GET", "/comments/resource/"+resourceID+".json", "v2", nil, opts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -44,6 +49,10 @@ func (c *Client) GetComments(ctx context.Context, resourceID string, opts *GetCo
// CreateComment Creates a new Passbolt Comment // CreateComment Creates a new Passbolt Comment
func (c *Client) CreateComment(ctx context.Context, resourceID string, comment Comment) (*Comment, error) { func (c *Client) CreateComment(ctx context.Context, resourceID string, comment Comment) (*Comment, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "POST", "/comments/resource/"+resourceID+".json", "v2", comment, nil) msg, err := c.DoCustomRequest(ctx, "POST", "/comments/resource/"+resourceID+".json", "v2", comment, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -58,6 +67,10 @@ func (c *Client) CreateComment(ctx context.Context, resourceID string, comment C
// UpdateComment Updates a existing Passbolt Comment // UpdateComment Updates a existing Passbolt Comment
func (c *Client) UpdateComment(ctx context.Context, commentID string, comment Comment) (*Comment, error) { func (c *Client) UpdateComment(ctx context.Context, commentID string, comment Comment) (*Comment, error) {
err := checkUUIDFormat(commentID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "PUT", "/comments/"+commentID+".json", "v2", comment, nil) msg, err := c.DoCustomRequest(ctx, "PUT", "/comments/"+commentID+".json", "v2", comment, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -72,7 +85,11 @@ func (c *Client) UpdateComment(ctx context.Context, commentID string, comment Co
// DeleteComment Deletes a Passbolt Comment // DeleteComment Deletes a Passbolt Comment
func (c *Client) DeleteComment(ctx context.Context, commentID string) error { func (c *Client) DeleteComment(ctx context.Context, commentID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/comments/"+commentID+".json", "v2", nil, nil) err := checkUUIDFormat(commentID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/comments/"+commentID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// Favorite is a Favorite // Favorite is a Favorite
@ -16,6 +17,10 @@ type Favorite struct {
// CreateFavorite Creates a new Passbolt Favorite for the given Resource ID // CreateFavorite Creates a new Passbolt Favorite for the given Resource ID
func (c *Client) CreateFavorite(ctx context.Context, resourceID string) (*Favorite, error) { func (c *Client) CreateFavorite(ctx context.Context, resourceID string) (*Favorite, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "POST", "/favorites/resource/"+resourceID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "POST", "/favorites/resource/"+resourceID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -31,7 +36,11 @@ func (c *Client) CreateFavorite(ctx context.Context, resourceID string) (*Favori
// DeleteFavorite Deletes a Passbolt Favorite // DeleteFavorite Deletes a Passbolt Favorite
func (c *Client) DeleteFavorite(ctx context.Context, favoriteID string) error { func (c *Client) DeleteFavorite(ctx context.Context, favoriteID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/favorites/"+favoriteID+".json", "v2", nil, nil) err := checkUUIDFormat(favoriteID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/favorites/"+favoriteID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// Folder is a Folder // Folder is a Folder
@ -83,6 +84,10 @@ func (c *Client) CreateFolder(ctx context.Context, folder Folder) (*Folder, erro
// GetFolder gets a Passbolt Folder // GetFolder gets a Passbolt Folder
func (c *Client) GetFolder(ctx context.Context, folderID string, opts *GetFolderOptions) (*Folder, error) { func (c *Client) GetFolder(ctx context.Context, folderID string, opts *GetFolderOptions) (*Folder, error) {
err := checkUUIDFormat(folderID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/folders/"+folderID+".json", "v2", nil, opts) msg, err := c.DoCustomRequest(ctx, "GET", "/folders/"+folderID+".json", "v2", nil, opts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -98,6 +103,10 @@ func (c *Client) GetFolder(ctx context.Context, folderID string, opts *GetFolder
// UpdateFolder Updates a existing Passbolt Folder // UpdateFolder Updates a existing Passbolt Folder
func (c *Client) UpdateFolder(ctx context.Context, folderID string, folder Folder) (*Folder, error) { func (c *Client) UpdateFolder(ctx context.Context, folderID string, folder Folder) (*Folder, error) {
err := checkUUIDFormat(folderID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "PUT", "/folders/"+folderID+".json", "v2", folder, nil) msg, err := c.DoCustomRequest(ctx, "PUT", "/folders/"+folderID+".json", "v2", folder, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -112,7 +121,11 @@ func (c *Client) UpdateFolder(ctx context.Context, folderID string, folder Folde
// DeleteFolder Deletes a Passbolt Folder // DeleteFolder Deletes a Passbolt Folder
func (c *Client) DeleteFolder(ctx context.Context, folderID string) error { func (c *Client) DeleteFolder(ctx context.Context, folderID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/folders/"+folderID+".json", "v2", nil, nil) err := checkUUIDFormat(folderID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/folders/"+folderID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }
@ -121,7 +134,11 @@ func (c *Client) DeleteFolder(ctx context.Context, folderID string) error {
// MoveFolder Moves a Passbolt Folder // MoveFolder Moves a Passbolt Folder
func (c *Client) MoveFolder(ctx context.Context, folderID, folderParentID string) error { func (c *Client) MoveFolder(ctx context.Context, folderID, folderParentID string) error {
_, err := c.DoCustomRequest(ctx, "PUT", "/move/folder/"+folderID+".json", "v2", Folder{ err := checkUUIDFormat(folderID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "PUT", "/move/folder/"+folderID+".json", "v2", Folder{
FolderParentID: folderParentID, FolderParentID: folderParentID,
}, nil) }, nil)
if err != nil { if err != nil {

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// GPGKey is a GPGKey // GPGKey is a GPGKey
@ -43,6 +44,10 @@ func (c *Client) GetGPGKeys(ctx context.Context, opts *GetGPGKeysOptions) ([]GPG
// GetGPGKey gets a Passbolt GPGKey // GetGPGKey gets a Passbolt GPGKey
func (c *Client) GetGPGKey(ctx context.Context, gpgkeyID string) (*GPGKey, error) { func (c *Client) GetGPGKey(ctx context.Context, gpgkeyID string) (*GPGKey, error) {
err := checkUUIDFormat(gpgkeyID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/gpgkeys/"+gpgkeyID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/gpgkeys/"+gpgkeyID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -3,18 +3,35 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
//Group is a Group //Group is a Group
type Group struct { type Group struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Created *Time `json:"created,omitempty"` Created *Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"` CreatedBy string `json:"created_by,omitempty"`
Deleted bool `json:"deleted,omitempty"` Deleted bool `json:"deleted,omitempty"`
Modified *Time `json:"modified,omitempty"` Modified *Time `json:"modified,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"` ModifiedBy string `json:"modified_by,omitempty"`
// This does not Contain Profile for Users Anymore...
GroupUsers []GroupMembership `json:"groups_users,omitempty"` GroupUsers []GroupMembership `json:"groups_users,omitempty"`
// This is new and undocumented but as all the data
Users []GroupUser `json:"users,omitempty"`
}
type GroupUser struct {
User
JoinData GroupJoinData `json:"_join_data,omitempty"`
}
type GroupJoinData struct {
ID string `json:"id,omitempty"`
GroupID string `json:"group_id,omitempty"`
UserID string `json:"user_id,omitempty"`
IsAdmin bool `json:"is_admin,omitempty"`
Created *Time `json:"created,omitempty"`
} }
type GroupMembership struct { type GroupMembership struct {
@ -38,11 +55,14 @@ type GetGroupsOptions struct {
FilterHasUsers []string `url:"filter[has_users],omitempty"` FilterHasUsers []string `url:"filter[has_users],omitempty"`
FilterHasManagers []string `url:"filter[has-managers],omitempty"` FilterHasManagers []string `url:"filter[has-managers],omitempty"`
ContainModifier bool `url:"contain[modifier],omitempty"` ContainModifier bool `url:"contain[modifier],omitempty"`
ContainModifierProfile bool `url:"contain[modifier.profile],omitempty"` ContainModifierProfile bool `url:"contain[modifier.profile],omitempty"`
ContainUser bool `url:"contain[user],omitempty"` ContainMyGroupUser bool `url:"contain[my_group_user],omitempty"`
ContainGroupUser bool `url:"contain[group_user],omitempty"` ContainUsers bool `url:"contain[users],omitempty"`
ContainMyGroupUser bool `url:"contain[my_group_user],omitempty"` ContainGroupsUsers bool `url:"contain[groups_users],omitempty"`
ContainGroupsUsersUser bool `url:"contain[groups_users.user],omitempty"`
ContainGroupsUsersUserProfile bool `url:"contain[groups_users.user.profile],omitempty"`
ContainGroupsUsersUserGPGKey bool `url:"contain[groups_users.user.gpgkey],omitempty"`
} }
// UpdateGroupDryRunResult is the Result of a Update Group DryRun // UpdateGroupDryRunResult is the Result of a Update Group DryRun
@ -105,6 +125,10 @@ func (c *Client) CreateGroup(ctx context.Context, group Group) (*Group, error) {
// GetGroup gets a Passbolt Group // GetGroup gets a Passbolt Group
func (c *Client) GetGroup(ctx context.Context, groupID string) (*Group, error) { func (c *Client) GetGroup(ctx context.Context, groupID string) (*Group, error) {
err := checkUUIDFormat(groupID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/groups/"+groupID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/groups/"+groupID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -120,6 +144,10 @@ func (c *Client) GetGroup(ctx context.Context, groupID string) (*Group, error) {
// UpdateGroup Updates a existing Passbolt Group // UpdateGroup Updates a existing Passbolt Group
func (c *Client) UpdateGroup(ctx context.Context, groupID string, update GroupUpdate) (*Group, error) { func (c *Client) UpdateGroup(ctx context.Context, groupID string, update GroupUpdate) (*Group, error) {
err := checkUUIDFormat(groupID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "PUT", "/groups/"+groupID+".json", "v2", update, nil) msg, err := c.DoCustomRequest(ctx, "PUT", "/groups/"+groupID+".json", "v2", update, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -134,6 +162,10 @@ func (c *Client) UpdateGroup(ctx context.Context, groupID string, update GroupUp
// UpdateGroupDryRun Checks that a Passbolt Group update passes validation // UpdateGroupDryRun Checks that a Passbolt Group update passes validation
func (c *Client) UpdateGroupDryRun(ctx context.Context, groupID string, update GroupUpdate) (*UpdateGroupDryRunResult, error) { func (c *Client) UpdateGroupDryRun(ctx context.Context, groupID string, update GroupUpdate) (*UpdateGroupDryRunResult, error) {
err := checkUUIDFormat(groupID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "PUT", "/groups/"+groupID+"/dry-run.json", "v2", update, nil) msg, err := c.DoCustomRequest(ctx, "PUT", "/groups/"+groupID+"/dry-run.json", "v2", update, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -148,7 +180,11 @@ func (c *Client) UpdateGroupDryRun(ctx context.Context, groupID string, update G
// DeleteGroup Deletes a Passbolt Group // DeleteGroup Deletes a Passbolt Group
func (c *Client) DeleteGroup(ctx context.Context, groupID string) error { func (c *Client) DeleteGroup(ctx context.Context, groupID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/groups/"+groupID+".json", "v2", nil, nil) err := checkUUIDFormat(groupID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/groups/"+groupID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,6 +1,6 @@
package api package api
type MFAChallange struct { type MFAChallenge struct {
Provider MFAProviders `json:"providers,omitempty"` Provider MFAProviders `json:"providers,omitempty"`
} }
@ -8,6 +8,6 @@ type MFAProviders struct {
TOTP string `json:"totp,omitempty"` TOTP string `json:"totp,omitempty"`
} }
type MFAChallangeResponse struct { type MFAChallengeResponse struct {
TOTP string `json:"totp,omitempty"` TOTP string `json:"totp,omitempty"`
} }

View file

@ -3,12 +3,15 @@ package api
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var isUUID = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
func randStringBytesRmndr(length int) string { func randStringBytesRmndr(length int) string {
b := make([]byte, length) b := make([]byte, length)
for i := range b { for i := range b {
@ -41,3 +44,10 @@ func checkAuthTokenFormat(authToken string) error {
} }
return nil return nil
} }
func checkUUIDFormat(data string) error {
if !isUUID.MatchString(data) {
return fmt.Errorf("UUID is not in the valid format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
}
return nil
}

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// Permission is a Permission // Permission is a Permission
@ -21,6 +22,10 @@ type Permission struct {
// GetResourcePermissions gets a Resources Permissions // GetResourcePermissions gets a Resources Permissions
func (c *Client) GetResourcePermissions(ctx context.Context, resourceID string) ([]Permission, error) { func (c *Client) GetResourcePermissions(ctx context.Context, resourceID string) ([]Permission, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/permissions/resource/"+resourceID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/permissions/resource/"+resourceID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -3,9 +3,10 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
//ResourceType is the Type of a Resource // ResourceType is the Type of a Resource
type ResourceType struct { type ResourceType struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Slug string `json:"slug,omitempty"` Slug string `json:"slug,omitempty"`
@ -15,6 +16,11 @@ type ResourceType struct {
Modified *Time `json:"modified,omitempty"` Modified *Time `json:"modified,omitempty"`
} }
type ResourceTypeSchema struct {
Resource json.RawMessage `json:"resource"`
Secret json.RawMessage `json:"secret"`
}
// GetResourceTypesOptions is a placeholder for future options // GetResourceTypesOptions is a placeholder for future options
type GetResourceTypesOptions struct { type GetResourceTypesOptions struct {
} }
@ -36,6 +42,10 @@ func (c *Client) GetResourceTypes(ctx context.Context, opts *GetResourceTypesOpt
// GetResourceType gets a Passbolt Type // GetResourceType gets a Passbolt Type
func (c *Client) GetResourceType(ctx context.Context, typeID string) (*ResourceType, error) { func (c *Client) GetResourceType(ctx context.Context, typeID string) (*ResourceType, error) {
err := checkUUIDFormat(typeID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/resource-types/"+typeID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/resource-types/"+typeID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -3,30 +3,32 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// Resource is a Resource. // 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, // 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. // for now the only Field like that is the Description.
type Resource struct { type Resource struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Created *Time `json:"created,omitempty"` Created *Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"` CreatedBy string `json:"created_by,omitempty"`
Creator *User `json:"creator,omitempty"` Creator *User `json:"creator,omitempty"`
Deleted bool `json:"deleted,omitempty"` Deleted bool `json:"deleted,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Favorite *Favorite `json:"favorite,omitempty"` Favorite *Favorite `json:"favorite,omitempty"`
Modified *Time `json:"modified,omitempty"` Modified *Time `json:"modified,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"` ModifiedBy string `json:"modified_by,omitempty"`
Modifier *User `json:"modifier,omitempty"` Modifier *User `json:"modifier,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Permission *Permission `json:"permission,omitempty"` Permission *Permission `json:"permission,omitempty"`
URI string `json:"uri,omitempty"` URI string `json:"uri,omitempty"`
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
FolderParentID string `json:"folder_parent_id,omitempty"` FolderParentID string `json:"folder_parent_id,omitempty"`
ResourceTypeID string `json:"resource_type_id,omitempty"` ResourceTypeID string `json:"resource_type_id,omitempty"`
Secrets []Secret `json:"secrets,omitempty"` ResourceType ResourceType `json:"resource_type,omitempty"`
Tags []Tag `json:"tags,omitempty"` Secrets []Secret `json:"secrets,omitempty"`
Tags []Tag `json:"tags,omitempty"`
} }
// Tag is a Passbolt Password Tag // Tag is a Passbolt Password Tag
@ -39,7 +41,7 @@ type Tag struct {
// GetResourcesOptions are all available query parameters // GetResourcesOptions are all available query parameters
type GetResourcesOptions struct { type GetResourcesOptions struct {
FilterIsFavorite bool `url:"filter[is-favorite],omitempty"` FilterIsFavorite bool `url:"filter[is-favorite],omitempty"`
FilterIsSharedWithGroup []string `url:"filter[is-shared-with-group][],omitempty"` FilterIsSharedWithGroup string `url:"filter[is-shared-with-group],omitempty"`
FilterIsOwnedByMe bool `url:"filter[is-owned-by-me],omitempty"` FilterIsOwnedByMe bool `url:"filter[is-owned-by-me],omitempty"`
FilterIsSharedWithMe bool `url:"filter[is-shared-with-me],omitempty"` FilterIsSharedWithMe bool `url:"filter[is-shared-with-me],omitempty"`
FilterHasID []string `url:"filter[has-id][],omitempty"` FilterHasID []string `url:"filter[has-id][],omitempty"`
@ -89,6 +91,10 @@ func (c *Client) CreateResource(ctx context.Context, resource Resource) (*Resour
// GetResource gets a Passbolt Resource // GetResource gets a Passbolt Resource
func (c *Client) GetResource(ctx context.Context, resourceID string) (*Resource, error) { func (c *Client) GetResource(ctx context.Context, resourceID string) (*Resource, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/resources/"+resourceID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/resources/"+resourceID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -104,6 +110,10 @@ func (c *Client) GetResource(ctx context.Context, resourceID string) (*Resource,
// UpdateResource Updates a existing Passbolt Resource // UpdateResource Updates a existing Passbolt Resource
func (c *Client) UpdateResource(ctx context.Context, resourceID string, resource Resource) (*Resource, error) { func (c *Client) UpdateResource(ctx context.Context, resourceID string, resource Resource) (*Resource, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "PUT", "/resources/"+resourceID+".json", "v2", resource, nil) msg, err := c.DoCustomRequest(ctx, "PUT", "/resources/"+resourceID+".json", "v2", resource, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -118,7 +128,11 @@ func (c *Client) UpdateResource(ctx context.Context, resourceID string, resource
// DeleteResource Deletes a Passbolt Resource // DeleteResource Deletes a Passbolt Resource
func (c *Client) DeleteResource(ctx context.Context, resourceID string) error { func (c *Client) DeleteResource(ctx context.Context, resourceID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/resources/"+resourceID+".json", "v2", nil, nil) err := checkUUIDFormat(resourceID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/resources/"+resourceID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }
@ -127,7 +141,11 @@ func (c *Client) DeleteResource(ctx context.Context, resourceID string) error {
// MoveResource Moves a Passbolt Resource // MoveResource Moves a Passbolt Resource
func (c *Client) MoveResource(ctx context.Context, resourceID, folderParentID string) error { func (c *Client) MoveResource(ctx context.Context, resourceID, folderParentID string) error {
_, err := c.DoCustomRequest(ctx, "PUT", "/move/resource/"+resourceID+".json", "v2", Resource{ err := checkUUIDFormat(resourceID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "PUT", "/move/resource/"+resourceID+".json", "v2", Resource{
FolderParentID: folderParentID, FolderParentID: folderParentID,
}, nil) }, nil)
if err != nil { if err != nil {

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// Secret is a Secret // Secret is a Secret
@ -21,8 +22,31 @@ type SecretDataTypePasswordAndDescription struct {
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
} }
type SecretDataTOTP struct {
Algorithm string `json:"algorithm"`
SecretKey string `json:"secret_key"`
Digits int `json:"digits"`
Period int `json:"period"`
}
// SecretDataTypeTOTP is the format a secret of resource type "totp" is stored in
type SecretDataTypeTOTP struct {
TOTP SecretDataTOTP `json:"totp"`
}
// SecretDataTypePasswordDescriptionTOTP is the format a secret of resource type "password-description-totp" is stored in
type SecretDataTypePasswordDescriptionTOTP struct {
Password string `json:"password"`
Description string `json:"description,omitempty"`
TOTP SecretDataTOTP `json:"totp"`
}
// GetSecret gets a Passbolt Secret // GetSecret gets a Passbolt Secret
func (c *Client) GetSecret(ctx context.Context, resourceID string) (*Secret, error) { func (c *Client) GetSecret(ctx context.Context, resourceID string) (*Secret, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/secrets/resource/"+resourceID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/secrets/resource/"+resourceID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
type SetupInstallResponse struct { type SetupInstallResponse struct {
@ -21,6 +22,14 @@ type SetupCompleteRequest struct {
// SetupInstall validates the userid and token used for Account setup, gives back the User Information // SetupInstall validates the userid and token used for Account setup, gives back the User Information
func (c *Client) SetupInstall(ctx context.Context, userID, token string) (*SetupInstallResponse, error) { func (c *Client) SetupInstall(ctx context.Context, userID, token string) (*SetupInstallResponse, error) {
err := checkUUIDFormat(userID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
err = checkUUIDFormat(token)
if err != nil {
return nil, fmt.Errorf("Checking Token format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/setup/install/"+userID+"/"+token+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/setup/install/"+userID+"/"+token+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -36,7 +45,11 @@ func (c *Client) SetupInstall(ctx context.Context, userID, token string) (*Setup
// SetupComplete Completes setup of a Passbolt Account // SetupComplete Completes setup of a Passbolt Account
func (c *Client) SetupComplete(ctx context.Context, userID string, request SetupCompleteRequest) error { func (c *Client) SetupComplete(ctx context.Context, userID string, request SetupCompleteRequest) error {
_, err := c.DoCustomRequest(ctx, "POST", "/setup/complete/"+userID+".json", "v2", request, nil) err := checkUUIDFormat(userID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "POST", "/setup/complete/"+userID+".json", "v2", request, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
// ResourceShareRequest is a ResourceShareRequest // ResourceShareRequest is a ResourceShareRequest
@ -61,7 +62,11 @@ func (c *Client) SearchAROs(ctx context.Context, opts SearchAROsOptions) ([]ARO,
// ShareResource Shares a Resource with AROs // ShareResource Shares a Resource with AROs
func (c *Client) ShareResource(ctx context.Context, resourceID string, shareRequest ResourceShareRequest) error { func (c *Client) ShareResource(ctx context.Context, resourceID string, shareRequest ResourceShareRequest) error {
_, err := c.DoCustomRequest(ctx, "PUT", "/share/resource/"+resourceID+".json", "v2", shareRequest, nil) err := checkUUIDFormat(resourceID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "PUT", "/share/resource/"+resourceID+".json", "v2", shareRequest, nil)
if err != nil { if err != nil {
return err return err
} }
@ -71,8 +76,12 @@ func (c *Client) ShareResource(ctx context.Context, resourceID string, shareRequ
// ShareFolder Shares a Folder with AROs // ShareFolder Shares a Folder with AROs
func (c *Client) ShareFolder(ctx context.Context, folderID string, permissions []Permission) error { func (c *Client) ShareFolder(ctx context.Context, folderID string, permissions []Permission) error {
err := checkUUIDFormat(folderID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
f := Folder{Permissions: permissions} f := Folder{Permissions: permissions}
_, err := c.DoCustomRequest(ctx, "PUT", "/share/folder/"+folderID+".json", "v2", f, nil) _, err = c.DoCustomRequest(ctx, "PUT", "/share/folder/"+folderID+".json", "v2", f, nil)
if err != nil { if err != nil {
return err return err
} }
@ -82,6 +91,10 @@ func (c *Client) ShareFolder(ctx context.Context, folderID string, permissions [
// SimulateShareResource Simulates Shareing a Resource with AROs // SimulateShareResource Simulates Shareing a Resource with AROs
func (c *Client) SimulateShareResource(ctx context.Context, resourceID string, shareRequest ResourceShareRequest) (*ResourceShareSimulationResult, error) { func (c *Client) SimulateShareResource(ctx context.Context, resourceID string, shareRequest ResourceShareRequest) (*ResourceShareSimulationResult, error) {
err := checkUUIDFormat(resourceID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "POST", "/share/simulate/resource/"+resourceID+".json", "v2", shareRequest, nil) msg, err := c.DoCustomRequest(ctx, "POST", "/share/simulate/resource/"+resourceID+".json", "v2", shareRequest, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
) )
const UserLocaleENUK = "en-UK" const UserLocaleENUK = "en-UK"
@ -81,6 +82,10 @@ func (c *Client) GetMe(ctx context.Context) (*User, error) {
// GetUser gets a Passbolt User // GetUser gets a Passbolt User
func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) { func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) {
err := checkUUIDFormat(userID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "GET", "/users/"+userID+".json", "v2", nil, nil) msg, err := c.DoCustomRequest(ctx, "GET", "/users/"+userID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -96,6 +101,10 @@ func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) {
// UpdateUser Updates a existing Passbolt User // UpdateUser Updates a existing Passbolt User
func (c *Client) UpdateUser(ctx context.Context, userID string, user User) (*User, error) { func (c *Client) UpdateUser(ctx context.Context, userID string, user User) (*User, error) {
err := checkUUIDFormat(userID)
if err != nil {
return nil, fmt.Errorf("Checking ID format: %w", err)
}
msg, err := c.DoCustomRequest(ctx, "PUT", "/users/"+userID+".json", "v2", user, nil) msg, err := c.DoCustomRequest(ctx, "PUT", "/users/"+userID+".json", "v2", user, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -110,7 +119,11 @@ func (c *Client) UpdateUser(ctx context.Context, userID string, user User) (*Use
// DeleteUser Deletes a Passbolt User // DeleteUser Deletes a Passbolt User
func (c *Client) DeleteUser(ctx context.Context, userID string) error { func (c *Client) DeleteUser(ctx context.Context, userID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+".json", "v2", nil, nil) err := checkUUIDFormat(userID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+".json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }
@ -119,7 +132,11 @@ func (c *Client) DeleteUser(ctx context.Context, userID string) error {
// DeleteUserDryrun Check if a Passbolt User is Deleteable // DeleteUserDryrun Check if a Passbolt User is Deleteable
func (c *Client) DeleteUserDryrun(ctx context.Context, userID string) error { func (c *Client) DeleteUserDryrun(ctx context.Context, userID string) error {
_, err := c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+"/dry-run.json", "v2", nil, nil) err := checkUUIDFormat(userID)
if err != nil {
return fmt.Errorf("Checking ID format: %w", err)
}
_, err = c.DoCustomRequest(ctx, "DELETE", "/users/"+userID+"/dry-run.json", "v2", nil, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -33,7 +33,7 @@ func (c *Client) SetupServerVerification(ctx context.Context) (string, string, e
token := "gpgauthv1.3.0|36|" + uuid.String() + "|gpgauthv1.3.0" token := "gpgauthv1.3.0|36|" + uuid.String() + "|gpgauthv1.3.0"
encToken, err := c.EncryptMessageWithPublicKey(serverKey, token) encToken, err := c.EncryptMessageWithPublicKey(serverKey, token)
if err != nil { if err != nil {
return "", "", fmt.Errorf("Encrypting Challange: %w", err) return "", "", fmt.Errorf("Encrypting Challenge: %w", err)
} }
err = c.VerifyServer(ctx, token, encToken) err = c.VerifyServer(ctx, token, encToken)
if err != nil { if err != nil {
@ -57,7 +57,7 @@ func (c *Client) VerifyServer(ctx context.Context, token, encToken string) error
} }
raw, _, err := c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/verify.json", "v2", data, nil) raw, _, err := c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/verify.json", "v2", data, nil)
if err != nil && !strings.Contains(err.Error(), "The authentication failed.") { if err != nil && !strings.Contains(err.Error(), "The authentication failed.") {
return fmt.Errorf("Sending Verification Challange: %w", err) return fmt.Errorf("Sending Verification Challenge: %w", err)
} }
if raw.Header.Get("X-GPGAuth-Verify-Response") != token { if raw.Header.Get("X-GPGAuth-Verify-Response") != token {

24
go.mod
View file

@ -1,14 +1,20 @@
module github.com/speatzle/go-passbolt module github.com/passbolt/go-passbolt
go 1.16 go 1.23.0
require ( require (
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect github.com/ProtonMail/gopenpgp/v2 v2.8.3
github.com/ProtonMail/gopenpgp/v2 v2.2.2
github.com/google/go-querystring v1.1.0 github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.6.0
github.com/sirupsen/logrus v1.8.1 // indirect github.com/santhosh-tekuri/jsonschema v1.2.4
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 require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
) )

110
go.sum
View file

@ -1,11 +1,21 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM= github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA=
github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/ProtonMail/gopenpgp/v2 v2.8.3 h1:1jHlELwCR00qovx2B50DkL/FjYwt/P91RnlsqeOp2Hs=
github.com/ProtonMail/gopenpgp/v2 v2.8.3/go.mod h1:LiuOTbnJit8w9ZzOoLscj0kmdALY7hfoCVh5Qlb0bcg=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -13,61 +23,71 @@ 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-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 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
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.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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/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-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-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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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-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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= 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/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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
// CreateFolder Creates a new Folder // CreateFolder Creates a new Folder

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
// GroupMembershipOperation creates/modifies/deletes a group membership // GroupMembershipOperation creates/modifies/deletes a group membership
@ -14,7 +14,7 @@ type GroupMembershipOperation struct {
Delete bool Delete bool
} }
// GroupMembership containes who and what kind of membership they have with a group // GroupMembership contains who and what kind of membership they have with a group
type GroupMembership struct { type GroupMembership struct {
UserID string UserID string
Username string Username string
@ -49,7 +49,9 @@ func CreateGroup(ctx context.Context, c *api.Client, name string, operations []G
func GetGroup(ctx context.Context, c *api.Client, groupID string) (string, []GroupMembership, error) { func GetGroup(ctx context.Context, c *api.Client, groupID string) (string, []GroupMembership, error) {
// for some reason the groups index api call does not give back the groups_users even though it is supposed to, so i have to do this... // for some reason the groups index api call does not give back the groups_users even though it is supposed to, so i have to do this...
groups, err := c.GetGroups(ctx, &api.GetGroupsOptions{ groups, err := c.GetGroups(ctx, &api.GetGroupsOptions{
ContainGroupUser: true, ContainGroupsUsers: true,
ContainGroupsUsersUser: true,
ContainGroupsUsersUserProfile: true,
}) })
if err != nil { if err != nil {
return "", nil, fmt.Errorf("Getting Groups: %w", err) return "", nil, fmt.Errorf("Getting Groups: %w", err)
@ -77,7 +79,7 @@ func GetGroup(ctx context.Context, c *api.Client, groupID string) (string, []Gro
func UpdateGroup(ctx context.Context, c *api.Client, groupID, name string, operations []GroupMembershipOperation) error { func UpdateGroup(ctx context.Context, c *api.Client, groupID, name string, operations []GroupMembershipOperation) error {
// for some reason the groups index api call does not give back the groups_users even though it is supposed to, so i have to do this... // for some reason the groups index api call does not give back the groups_users even though it is supposed to, so i have to do this...
groups, err := c.GetGroups(ctx, &api.GetGroupsOptions{ groups, err := c.GetGroups(ctx, &api.GetGroupsOptions{
ContainGroupUser: true, ContainGroupsUsers: true,
}) })
if err != nil { if err != nil {
return fmt.Errorf("Getting Groups: %w", err) return fmt.Errorf("Getting Groups: %w", err)

View file

@ -8,18 +8,18 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
// AddMFACallbackTOTP adds a MFA callback to the client that generates OTP Codes on demand using a Token with configurable retries and delay // AddMFACallbackTOTP adds a MFA callback to the client that generates OTP Codes on demand using a Token with configurable retries and delay
func AddMFACallbackTOTP(c *api.Client, retrys uint, retryDelay, offset time.Duration, token string) { func AddMFACallbackTOTP(c *api.Client, retrys uint, retryDelay, offset time.Duration, token string) {
c.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) { c.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
challange := api.MFAChallange{} challenge := api.MFAChallenge{}
err := json.Unmarshal(res.Body, &challange) err := json.Unmarshal(res.Body, &challenge)
if err != nil { if err != nil {
return http.Cookie{}, fmt.Errorf("Parsing MFA Challange") return http.Cookie{}, fmt.Errorf("Parsing MFA Challenge")
} }
if challange.Provider.TOTP == "" { if challenge.Provider.TOTP == "" {
return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider") return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider")
} }
for i := uint(0); i < retrys+1; i++ { for i := uint(0); i < retrys+1; i++ {
@ -28,14 +28,14 @@ func AddMFACallbackTOTP(c *api.Client, retrys uint, retryDelay, offset time.Dura
if err != nil { if err != nil {
return http.Cookie{}, fmt.Errorf("Error Generating MFA Code: %w", err) return http.Cookie{}, fmt.Errorf("Error Generating MFA Code: %w", err)
} }
req := api.MFAChallangeResponse{ req := api.MFAChallengeResponse{
TOTP: code, TOTP: code,
} }
var raw *http.Response var raw *http.Response
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil) raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
if err != nil { if err != nil {
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode { if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
return http.Cookie{}, fmt.Errorf("Doing MFA Challange Response: %w", err) return http.Cookie{}, fmt.Errorf("Doing MFA Challenge Response: %w", err)
} }
// MFA failed, so lets wait just let the loop try again // MFA failed, so lets wait just let the loop try again
time.Sleep(retryDelay) time.Sleep(retryDelay)
@ -49,6 +49,6 @@ func AddMFACallbackTOTP(c *api.Client, retrys uint, retryDelay, offset time.Dura
return http.Cookie{}, fmt.Errorf("Unable to find Passbolt MFA Cookie") return http.Cookie{}, fmt.Errorf("Unable to find Passbolt MFA Cookie")
} }
} }
return http.Cookie{}, fmt.Errorf("Failed MFA Challange 3 times: %w", err) return http.Cookie{}, fmt.Errorf("Failed MFA Challenge 3 times: %w", err)
} }
} }

View file

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
// CreateResource Creates a Resource where the Password and Description are Encrypted and Returns the Resources ID // CreateResource Creates a Resource where the Password and Description are Encrypted and Returns the Resources ID
@ -18,6 +18,7 @@ func CreateResource(ctx context.Context, c *api.Client, folderParentID, name, us
for _, tmp := range types { for _, tmp := range types {
if tmp.Slug == "password-and-description" { if tmp.Slug == "password-and-description" {
rType = &tmp rType = &tmp
break
} }
} }
if rType == nil { if rType == nil {
@ -41,6 +42,11 @@ func CreateResource(ctx context.Context, c *api.Client, folderParentID, name, us
return "", fmt.Errorf("Marshalling Secret Data: %w", err) return "", fmt.Errorf("Marshalling Secret Data: %w", err)
} }
err = validateSecretData(rType, string(secretData))
if err != nil {
return "", fmt.Errorf("Validating Secret Data: %w", err)
}
encSecretData, err := c.EncryptMessage(string(secretData)) encSecretData, err := c.EncryptMessage(string(secretData))
if err != nil { if err != nil {
return "", fmt.Errorf("Encrypting Secret Data for User me: %w", err) return "", fmt.Errorf("Encrypting Secret Data for User me: %w", err)
@ -122,6 +128,21 @@ func GetResourceFromData(c *api.Client, resource api.Resource, secret api.Secret
} }
pw = secretData.Password pw = secretData.Password
desc = secretData.Description desc = secretData.Description
case "password-description-totp":
rawSecretData, err := c.DecryptMessage(secret.Data)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Decrypting Secret Data: %w", err)
}
var secretData api.SecretDataTypePasswordDescriptionTOTP
err = json.Unmarshal([]byte(rawSecretData), &secretData)
if err != nil {
return "", "", "", "", "", "", fmt.Errorf("Parsing Decrypted Secret Data: %w", err)
}
pw = secretData.Password
desc = secretData.Description
case "totp":
// nothing fits into the interface in this case
default: default:
return "", "", "", "", "", "", fmt.Errorf("Unknown ResourceType: %v", rType.Slug) return "", "", "", "", "", "", fmt.Errorf("Unknown ResourceType: %v", rType.Slug)
} }
@ -218,10 +239,62 @@ func UpdateResource(ctx context.Context, c *api.Client, resourceID, name, userna
return fmt.Errorf("Marshalling Secret Data: %w", err) return fmt.Errorf("Marshalling Secret Data: %w", err)
} }
secretData = string(res) secretData = string(res)
case "password-description-totp":
secret, err := c.GetSecret(ctx, resourceID)
if err != nil {
return fmt.Errorf("Getting Secret: %w", err)
}
oldSecretData, err := c.DecryptMessage(secret.Data)
if err != nil {
return fmt.Errorf("Decrypting Secret: %w", err)
}
var oldSecret api.SecretDataTypePasswordDescriptionTOTP
err = json.Unmarshal([]byte(oldSecretData), &secretData)
if err != nil {
return fmt.Errorf("Parsing Decrypted Secret Data: %w", err)
}
if password != "" {
oldSecret.Password = password
}
if description != "" {
oldSecret.Description = description
}
res, err := json.Marshal(&oldSecret)
if err != nil {
return fmt.Errorf("Marshalling Secret Data: %w", err)
}
secretData = string(res)
case "totp":
secret, err := c.GetSecret(ctx, resourceID)
if err != nil {
return fmt.Errorf("Getting Secret: %w", err)
}
oldSecretData, err := c.DecryptMessage(secret.Data)
if err != nil {
return fmt.Errorf("Decrypting Secret: %w", err)
}
var oldSecret api.SecretDataTypeTOTP
err = json.Unmarshal([]byte(oldSecretData), &secretData)
if err != nil {
return fmt.Errorf("Parsing Decrypted Secret Data: %w", err)
}
// since we don't have totp parameters we don't do anything
res, err := json.Marshal(&oldSecret)
if err != nil {
return fmt.Errorf("Marshalling Secret Data: %w", err)
}
secretData = string(res)
default: default:
return fmt.Errorf("Unknown ResourceType: %v", rType.Slug) return fmt.Errorf("Unknown ResourceType: %v", rType.Slug)
} }
err = validateSecretData(rType, secretData)
if err != nil {
return fmt.Errorf("Validating Secret Data: %w", err)
}
newResource.Secrets = []api.Secret{} newResource.Secrets = []api.Secret{}
for _, user := range users { for _, user := range users {
var encSecretData string var encSecretData string

View file

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
"github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/gopenpgp/v2/helper" "github.com/ProtonMail/gopenpgp/v2/helper"
@ -31,7 +31,7 @@ func SetupAccount(ctx context.Context, c *api.Client, userID, token, password st
keyName := install.Profile.FirstName + " " + install.Profile.LastName + " " + install.Username keyName := install.Profile.FirstName + " " + install.Profile.LastName + " " + install.Username
privateKey, err := helper.GenerateKey(keyName, install.Username, []byte(password), "rsa", 2048) privateKey, err := helper.GenerateKey(keyName, install.Username, []byte(password), "rsa", 4096)
if err != nil { if err != nil {
return "", fmt.Errorf("Generating Private Key: %w", err) return "", fmt.Errorf("Generating Private Key: %w", err)
} }

View file

@ -8,7 +8,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
var client *api.Client var client *api.Client
@ -31,6 +31,9 @@ func TestMain(m *testing.M) {
panic(fmt.Errorf("Creating Registration Client: %w", err)) panic(fmt.Errorf("Creating Registration Client: %w", err))
} }
// Debug Output
rc.Debug = true
ctx := context.TODO() ctx := context.TODO()
privkey, err := SetupAccount(ctx, rc, userID, token, "password123") privkey, err := SetupAccount(ctx, rc, userID, token, "password123")
@ -43,6 +46,9 @@ func TestMain(m *testing.M) {
panic(fmt.Errorf("Setup Client: %w", err)) panic(fmt.Errorf("Setup Client: %w", err))
} }
// Debug Output
c.Debug = true
c.Login(ctx) c.Login(ctx)
if err != nil { if err != nil {
panic(fmt.Errorf("Login Client: %w", err)) panic(fmt.Errorf("Login Client: %w", err))

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
// ShareOperation defines how Resources are to be Shared With Users/Groups // ShareOperation defines how Resources are to be Shared With Users/Groups
@ -63,6 +63,22 @@ func ShareResource(ctx context.Context, c *api.Client, resourceID string, change
return fmt.Errorf("Decrypting Resource Secret: %w", err) return fmt.Errorf("Decrypting Resource Secret: %w", err)
} }
// Secret Validation
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)
}
err = validateSecretData(rType, secretData)
if err != nil {
return fmt.Errorf("Validating Secret Data: %w", err)
}
simulationResult, err := c.SimulateShareResource(ctx, resourceID, shareRequest) simulationResult, err := c.SimulateShareResource(ctx, resourceID, shareRequest)
if err != nil { if err != nil {
return fmt.Errorf("Simulate Share Resource: %w", err) return fmt.Errorf("Simulate Share Resource: %w", err)

View file

@ -33,6 +33,9 @@ func GenerateOTPCode(token string, when time.Time) (string, error) {
// It should be uppercase always // It should be uppercase always
token = strings.ToUpper(token) token = strings.ToUpper(token)
// Remove all the extra "=" padding at the end
token = strings.TrimRight(token, "=")
secretBytes, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(token) secretBytes, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(token)
if err != nil { if err != nil {
return "", fmt.Errorf("Decoding token string: %w", err) return "", fmt.Errorf("Decoding token string: %w", err)

36
helper/totp_test.go Normal file
View file

@ -0,0 +1,36 @@
package helper
import (
"testing"
"time"
)
var testCases = []struct {
description string
token string
expectErr bool
}{
{"generates otpcode from token with padding", "PGWXXL7B66MMSRBAWSKEKIYD3P675KRJ===", false},
{"generates otpcode from token without padding", "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP", false},
{"invalid token format", "INVALIDTOKEN123", true},
}
func TestGenerateOTPCode(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
code, err := GenerateOTPCode(tc.token, time.Now())
if tc.expectErr {
if err == nil {
t.Errorf("Expected error for input '%s', but got none", tc.token)
}
} else {
if err != nil {
t.Errorf("GenerateOTPCode returned an error: %s", err.Error())
} else if len(code) != 6 {
t.Errorf("Expected 6-digit OTP, got: %s", code)
}
}
})
}
}

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
) )
// CreateUser Creates a new User // CreateUser Creates a new User

View file

@ -1,9 +1,13 @@
package helper package helper
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"strings"
"github.com/speatzle/go-passbolt/api" "github.com/passbolt/go-passbolt/api"
"github.com/santhosh-tekuri/jsonschema"
) )
func getPublicKeyByUserID(userID string, Users []api.User) (string, error) { func getPublicKeyByUserID(userID string, Users []api.User) (string, error) {
@ -32,3 +36,44 @@ func getSecretByResourceID(secrets []api.Secret, resourceID string) (*api.Secret
} }
return nil, fmt.Errorf("Cannot Find Secret for id %v", resourceID) return nil, fmt.Errorf("Cannot Find Secret for id %v", resourceID)
} }
func validateSecretData(rType *api.ResourceType, secretData string) error {
var schemaDefinition api.ResourceTypeSchema
err := json.Unmarshal([]byte(rType.Definition), &schemaDefinition)
if err != nil {
// Workaround for inconsistant API Responses where sometime the Schema is embedded directly and sometimes it's escaped as a string
if err.Error() == "json: cannot unmarshal string into Go value of type api.ResourceTypeSchema" {
var tmp string
err = json.Unmarshal([]byte(rType.Definition), &tmp)
if err != nil {
return fmt.Errorf("Workaround Unmarshal Json Schema String: %w", err)
}
err = json.Unmarshal([]byte(tmp), &schemaDefinition)
if err != nil {
return fmt.Errorf("Workaround Unmarshal Json Schema: %w", err)
}
} else {
return fmt.Errorf("Unmarshal Json Schema: %w", err)
}
}
comp := jsonschema.NewCompiler()
err = comp.AddResource("secret.json", bytes.NewReader(schemaDefinition.Secret))
if err != nil {
return fmt.Errorf("Adding Json Schema: %w", err)
}
schema, err := comp.Compile("secret.json")
if err != nil {
return fmt.Errorf("Compiling Json Schema: %w", err)
}
err = schema.Validate(strings.NewReader(secretData))
if err != nil {
return fmt.Errorf("Validating Secret Data: %w", err)
}
return nil
}