diff --git a/.github/workflows/.go.yml b/.github/workflows/.go.yml index bdb7a79..4611250 100644 --- a/.github/workflows/.go.yml +++ b/.github/workflows/.go.yml @@ -13,17 +13,17 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.23 - name: "Setup Passbolt" run: | git clone https://github.com/passbolt/passbolt_docker.git ../passbolt_docker cd ../passbolt_docker - docker-compose -f docker-compose/docker-compose-ce.yaml up -d + docker compose -f docker-compose/docker-compose-ce.yaml up -d docker ps -a - name: "Test" run: | - docker exec docker-compose_passbolt_1 sh -c '/usr/bin/wait-for.sh -t 30 localhost:443' - output=$(docker exec docker-compose_passbolt_1 sh -c 'su -m -c "/usr/share/php/passbolt/bin/cake \ + docker exec docker-compose-passbolt-1 sh -c '/usr/bin/wait-for.sh -t 30 localhost:443' + output=$(docker exec docker-compose-passbolt-1 sh -c 'su -m -c "/usr/share/php/passbolt/bin/cake \ passbolt register_user \ -u your@email.com \ -f yourname \ diff --git a/README.md b/README.md index ea15790..0ffc1fe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # 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 @@ -7,11 +8,11 @@ There also is a CLI Tool to interact with Passbolt using this module [here](http 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. @@ -19,12 +20,12 @@ PR's are welcome. But be gentle: if it's something bigger or fundamental: please Disclaimer: This project is community driven and not associated with Passbolt SA - # Install `go get github.com/passbolt/go-passbolt` # Examples + ## Login First, you will need to create a client and then log in on the server using the client: @@ -214,24 +215,25 @@ err = helper.UpdateUser( "lastname", // LastName ) ``` + Note: These helpers will only update fields that are not "". Helper update functions also exists for Folders. ## 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). The `permissionType` can be: -| Code | Meaning | -| --- | --- | -| `1` | "Read-only" | -| `7` | "Can update" | -| `15` | "Owner" | -| `-1` | Delete existing permission | +| Code | Meaning | +| ---- | -------------------------- | +| `1` | "Read-only" | +| `7` | "Can update" | +| `15` | "Owner" | +| `-1` | Delete existing permission | 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 You can setup a Account using a Invite Link like this: + ```go // Get the UserID and Token from the Invite Link userID, token, err := ParseInviteUrl(url) @@ -307,7 +310,7 @@ privkey, err := SetupAccount(ctx, rClient, userID, token, "password123") ## 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 // Setup the Verification @@ -327,18 +330,17 @@ if err != nil { ## 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 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 This example: -1. Creates a resource; +1. Creates a resource; 2. Searches for a user named "Test User"; 3. Checks that it's not itself; and, 4. Shares the password with the "Test User" if necessary: diff --git a/api/api.go b/api/api.go index 96a0f41..601a358 100644 --- a/api/api.go +++ b/api/api.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" ) // APIResponse is the Struct representation of a Json Response @@ -62,9 +63,9 @@ start: if res.Header.Status == "success" { return r, &res, nil } else if res.Header.Status == "error" { - if res.Header.Code == 403 && res.Header.URL == "/mfa/verify/error.json" { + if res.Header.Code == 403 && strings.HasSuffix(res.Header.URL, "/mfa/verify/error.json") { 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...:") } if c.MFACallback != nil { @@ -72,11 +73,11 @@ start: if err != nil { 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 goto start } 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)) diff --git a/api/client.go b/api/client.go index 150d0c7..e08609f 100644 --- a/api/client.go +++ b/api/client.go @@ -31,7 +31,7 @@ type Client struct { userPublicKey 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 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) diff --git a/api/mfa.go b/api/mfa.go index 113e3d3..ceffe22 100644 --- a/api/mfa.go +++ b/api/mfa.go @@ -1,6 +1,6 @@ package api -type MFAChallange struct { +type MFAChallenge struct { Provider MFAProviders `json:"providers,omitempty"` } @@ -8,6 +8,6 @@ type MFAProviders struct { TOTP string `json:"totp,omitempty"` } -type MFAChallangeResponse struct { +type MFAChallengeResponse struct { TOTP string `json:"totp,omitempty"` } diff --git a/api/resource_types.go b/api/resource_types.go index 5b3a753..3bdc1bc 100644 --- a/api/resource_types.go +++ b/api/resource_types.go @@ -6,7 +6,7 @@ import ( "fmt" ) -//ResourceType is the Type of a Resource +// ResourceType is the Type of a Resource type ResourceType struct { ID string `json:"id,omitempty"` Slug string `json:"slug,omitempty"` @@ -16,6 +16,11 @@ type ResourceType struct { 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 type GetResourceTypesOptions struct { } diff --git a/api/secrets.go b/api/secrets.go index 1f63b23..8bcad6c 100644 --- a/api/secrets.go +++ b/api/secrets.go @@ -22,6 +22,25 @@ type SecretDataTypePasswordAndDescription struct { 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 func (c *Client) GetSecret(ctx context.Context, resourceID string) (*Secret, error) { err := checkUUIDFormat(resourceID) diff --git a/api/verify.go b/api/verify.go index b8d42d4..89a15b8 100644 --- a/api/verify.go +++ b/api/verify.go @@ -33,7 +33,7 @@ func (c *Client) SetupServerVerification(ctx context.Context) (string, string, e token := "gpgauthv1.3.0|36|" + uuid.String() + "|gpgauthv1.3.0" encToken, err := c.EncryptMessageWithPublicKey(serverKey, token) if err != nil { - return "", "", fmt.Errorf("Encrypting Challange: %w", err) + return "", "", fmt.Errorf("Encrypting Challenge: %w", err) } err = c.VerifyServer(ctx, token, encToken) 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) 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 { diff --git a/go.mod b/go.mod index 6c552cb..64273dd 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,20 @@ module github.com/passbolt/go-passbolt -go 1.16 +go 1.23.0 require ( - github.com/ProtonMail/gopenpgp/v2 v2.4.2 + github.com/ProtonMail/gopenpgp/v2 v2.8.3 github.com/google/go-querystring v1.1.0 - github.com/google/uuid v1.3.0 - github.com/sirupsen/logrus v1.8.1 // indirect - golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect - golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect - golang.org/x/text v0.3.7 // indirect + github.com/google/uuid v1.6.0 + github.com/santhosh-tekuri/jsonschema v1.2.4 +) + +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 ) diff --git a/go.sum b/go.sum index 64e8ac1..24212a3 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +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-20220113124808-70ae35bab23f h1:J2FzIrXN82q5uyUraeJpLIm7U6PffRwje2ORho5yIik= -github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/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.4.2 h1:xPcQYAa3D3V2sDhJq0bYWwlWtxzTudxm1/XXHlSWcJo= -github.com/ProtonMail/gopenpgp/v2 v2.4.2/go.mod h1:0byYFEOo6x4F/1YqhN7Z6m015Cqnxllz3CGb5cjJueY= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= +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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -12,64 +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-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 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.3.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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= 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= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= -golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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-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-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-20210423082822-04245dca01da/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-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-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.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.6/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.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-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/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-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= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/helper/group.go b/helper/group.go index 329be41..fa70ca2 100644 --- a/helper/group.go +++ b/helper/group.go @@ -14,7 +14,7 @@ type GroupMembershipOperation struct { 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 { UserID string Username string diff --git a/helper/mfa.go b/helper/mfa.go index 0c834c2..6f553aa 100644 --- a/helper/mfa.go +++ b/helper/mfa.go @@ -14,12 +14,12 @@ import ( // 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) { c.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) { - challange := api.MFAChallange{} - err := json.Unmarshal(res.Body, &challange) + challenge := api.MFAChallenge{} + err := json.Unmarshal(res.Body, &challenge) 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") } 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 { return http.Cookie{}, fmt.Errorf("Error Generating MFA Code: %w", err) } - req := api.MFAChallangeResponse{ + req := api.MFAChallengeResponse{ TOTP: code, } var raw *http.Response raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil) if err != nil { 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 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("Failed MFA Challange 3 times: %w", err) + return http.Cookie{}, fmt.Errorf("Failed MFA Challenge 3 times: %w", err) } } diff --git a/helper/resources.go b/helper/resources.go index bdfd9ee..6a4f786 100644 --- a/helper/resources.go +++ b/helper/resources.go @@ -18,6 +18,7 @@ func CreateResource(ctx context.Context, c *api.Client, folderParentID, name, us for _, tmp := range types { if tmp.Slug == "password-and-description" { rType = &tmp + break } } 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) } + err = validateSecretData(rType, string(secretData)) + if err != nil { + return "", fmt.Errorf("Validating Secret Data: %w", err) + } + encSecretData, err := c.EncryptMessage(string(secretData)) if err != nil { 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 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: 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) } 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: 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{} for _, user := range users { var encSecretData string diff --git a/helper/setup.go b/helper/setup.go index f82c04b..7fdc006 100644 --- a/helper/setup.go +++ b/helper/setup.go @@ -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 - 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 { return "", fmt.Errorf("Generating Private Key: %w", err) } diff --git a/helper/setup_test.go b/helper/setup_test.go index 8ec3d2f..1d99e2f 100644 --- a/helper/setup_test.go +++ b/helper/setup_test.go @@ -31,6 +31,9 @@ func TestMain(m *testing.M) { panic(fmt.Errorf("Creating Registration Client: %w", err)) } + // Debug Output + rc.Debug = true + ctx := context.TODO() privkey, err := SetupAccount(ctx, rc, userID, token, "password123") @@ -43,6 +46,9 @@ func TestMain(m *testing.M) { panic(fmt.Errorf("Setup Client: %w", err)) } + // Debug Output + c.Debug = true + c.Login(ctx) if err != nil { panic(fmt.Errorf("Login Client: %w", err)) diff --git a/helper/share.go b/helper/share.go index f28dbbd..d9514b3 100644 --- a/helper/share.go +++ b/helper/share.go @@ -63,6 +63,22 @@ func ShareResource(ctx context.Context, c *api.Client, resourceID string, change 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) if err != nil { return fmt.Errorf("Simulate Share Resource: %w", err) diff --git a/helper/totp.go b/helper/totp.go index 42cf7b9..d32b9c2 100644 --- a/helper/totp.go +++ b/helper/totp.go @@ -33,6 +33,9 @@ func GenerateOTPCode(token string, when time.Time) (string, error) { // It should be uppercase always 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) if err != nil { return "", fmt.Errorf("Decoding token string: %w", err) diff --git a/helper/totp_test.go b/helper/totp_test.go new file mode 100644 index 0000000..aad32b6 --- /dev/null +++ b/helper/totp_test.go @@ -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) + } + } + }) + } +} diff --git a/helper/util.go b/helper/util.go index 78b1960..4d7a144 100644 --- a/helper/util.go +++ b/helper/util.go @@ -1,9 +1,13 @@ package helper import ( + "bytes" + "encoding/json" "fmt" + "strings" "github.com/passbolt/go-passbolt/api" + "github.com/santhosh-tekuri/jsonschema" ) 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) } + +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 +}